4 * This module is an SMTP and ESMTP implementation for the Citadel system.
5 * It is compliant with all of the following:
7 * RFC 821 - Simple Mail Transfer Protocol
8 * RFC 876 - Survey of SMTP Implementations
9 * RFC 1047 - Duplicate messages and SMTP
10 * RFC 1854 - command pipelining
11 * RFC 1869 - Extended Simple Mail Transfer Protocol
12 * RFC 1870 - SMTP Service Extension for Message Size Declaration
13 * RFC 1893 - Enhanced Mail System Status Codes
14 * RFC 2033 - Local Mail Transfer Protocol
15 * RFC 2034 - SMTP Service Extension for Returning Enhanced Error Codes
16 * RFC 2197 - SMTP Service Extension for Command Pipelining
17 * RFC 2554 - SMTP Service Extension for Authentication
18 * RFC 2821 - Simple Mail Transfer Protocol
19 * RFC 2822 - Internet Message Format
20 * RFC 2920 - SMTP Service Extension for Command Pipelining
32 #include <sys/types.h>
34 #if TIME_WITH_SYS_TIME
35 # include <sys/time.h>
39 # include <sys/time.h>
49 #include <sys/socket.h>
50 #include <netinet/in.h>
51 #include <arpa/inet.h>
54 #include "sysdep_decls.h"
55 #include "citserver.h"
59 #include "serv_extensions.h"
66 #include "internet_addressing.h"
69 #include "clientsocket.h"
70 #include "locate_host.h"
77 struct citsmtp { /* Information about the current session */
80 struct ctdluser vrfy_buffer;
85 int number_of_recipients;
87 int message_originated_locally;
91 enum { /* Command states for login authentication */
97 enum { /* Delivery modes */
102 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
103 #define SMTP_RECPS ((char *)CtdlGetUserData(SYM_SMTP_RECPS))
104 #define SMTP_ROOMS ((char *)CtdlGetUserData(SYM_SMTP_ROOMS))
107 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
111 /*****************************************************************************/
112 /* SMTP SERVER (INBOUND) STUFF */
113 /*****************************************************************************/
119 * Here's where our SMTP session begins its happy day.
121 void smtp_greeting(void) {
123 strcpy(CC->cs_clientname, "SMTP session");
124 CC->internal_pgm = 1;
125 CC->cs_flags |= CS_STEALTH;
126 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
127 CtdlAllocUserData(SYM_SMTP_RECPS, SIZ);
128 CtdlAllocUserData(SYM_SMTP_ROOMS, SIZ);
129 snprintf(SMTP_RECPS, SIZ, "%s", "");
130 snprintf(SMTP_ROOMS, SIZ, "%s", "");
132 cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
136 * LMTP is like SMTP but with some extra bonus footage added.
138 void lmtp_greeting(void) {
145 * Implement HELO and EHLO commands.
147 * which_command: 0=HELO, 1=EHLO, 2=LHLO
149 void smtp_hello(char *argbuf, int which_command) {
151 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
153 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
154 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
158 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
159 cprintf("500 LHLO is only allowed when running LMTP\r\n");
163 if (which_command == 0) {
164 cprintf("250 Greetings and joyous salutations.\r\n");
167 cprintf("250-Greetings and joyous salutations.\r\n");
168 cprintf("250-HELP\r\n");
169 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
170 cprintf("250-PIPELINING\r\n");
171 cprintf("250-AUTH=LOGIN\r\n");
172 cprintf("250 ENHANCEDSTATUSCODES\r\n");
178 * Implement HELP command.
180 void smtp_help(void) {
181 cprintf("214-Commands accepted:\r\n");
182 cprintf("214- DATA\r\n");
183 cprintf("214- EHLO\r\n");
184 cprintf("214- EXPN\r\n");
185 cprintf("214- HELO\r\n");
186 cprintf("214- HELP\r\n");
187 cprintf("214- MAIL\r\n");
188 cprintf("214- NOOP\r\n");
189 cprintf("214- QUIT\r\n");
190 cprintf("214- RCPT\r\n");
191 cprintf("214- RSET\r\n");
192 cprintf("214- VRFY\r\n");
200 void smtp_get_user(char *argbuf) {
204 CtdlDecodeBase64(username, argbuf, SIZ);
205 lprintf(9, "Trying <%s>\n", username);
206 if (CtdlLoginExistingUser(username) == login_ok) {
207 CtdlEncodeBase64(buf, "Password:", 9);
208 cprintf("334 %s\r\n", buf);
209 SMTP->command_state = smtp_password;
212 cprintf("500 5.7.0 No such user.\r\n");
213 SMTP->command_state = smtp_command;
221 void smtp_get_pass(char *argbuf) {
224 CtdlDecodeBase64(password, argbuf, SIZ);
225 lprintf(9, "Trying <%s>\n", password);
226 if (CtdlTryPassword(password) == pass_ok) {
227 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
228 lprintf(9, "SMTP authenticated login successful\n");
229 CC->internal_pgm = 0;
230 CC->cs_flags &= ~CS_STEALTH;
233 cprintf("535 5.7.0 Authentication failed.\r\n");
235 SMTP->command_state = smtp_command;
242 void smtp_auth(char *argbuf) {
245 if (strncasecmp(argbuf, "login", 5) ) {
246 cprintf("504 5.7.4 We only support LOGIN authentication.\r\n");
250 if (strlen(argbuf) >= 7) {
251 smtp_get_user(&argbuf[6]);
255 CtdlEncodeBase64(buf, "Username:", 9);
256 cprintf("334 %s\r\n", buf);
257 SMTP->command_state = smtp_user;
263 * Back end for smtp_vrfy() command
265 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
267 if (!fuzzy_match(us, SMTP->vrfy_match)) {
269 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
275 * Implements the VRFY (verify user name) command.
276 * Performs fuzzy match on full user names.
278 void smtp_vrfy(char *argbuf) {
279 SMTP->vrfy_count = 0;
280 strcpy(SMTP->vrfy_match, argbuf);
281 ForEachUser(smtp_vrfy_backend, NULL);
283 if (SMTP->vrfy_count < 1) {
284 cprintf("550 5.1.1 String does not match anything.\r\n");
286 else if (SMTP->vrfy_count == 1) {
287 cprintf("250 %s <cit%ld@%s>\r\n",
288 SMTP->vrfy_buffer.fullname,
289 SMTP->vrfy_buffer.usernum,
292 else if (SMTP->vrfy_count > 1) {
293 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
302 * Back end for smtp_expn() command
304 void smtp_expn_backend(struct ctdluser *us, void *data) {
306 if (!fuzzy_match(us, SMTP->vrfy_match)) {
308 if (SMTP->vrfy_count >= 1) {
309 cprintf("250-%s <cit%ld@%s>\r\n",
310 SMTP->vrfy_buffer.fullname,
311 SMTP->vrfy_buffer.usernum,
316 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
322 * Implements the EXPN (expand user name) command.
323 * Performs fuzzy match on full user names.
325 void smtp_expn(char *argbuf) {
326 SMTP->vrfy_count = 0;
327 strcpy(SMTP->vrfy_match, argbuf);
328 ForEachUser(smtp_expn_backend, NULL);
330 if (SMTP->vrfy_count < 1) {
331 cprintf("550 5.1.1 String does not match anything.\r\n");
333 else if (SMTP->vrfy_count >= 1) {
334 cprintf("250 %s <cit%ld@%s>\r\n",
335 SMTP->vrfy_buffer.fullname,
336 SMTP->vrfy_buffer.usernum,
343 * Implements the RSET (reset state) command.
344 * Currently this just zeroes out the state buffer. If pointers to data
345 * allocated with mallok() are ever placed in the state buffer, we have to
346 * be sure to phree() them first!
348 void smtp_rset(void) {
352 * Our entire SMTP state is discarded when a RSET command is issued,
353 * but we need to preserve this one little piece of information, so
354 * we save it for later.
356 is_lmtp = SMTP->is_lmtp;
358 memset(SMTP, 0, sizeof(struct citsmtp));
361 * It is somewhat ambiguous whether we want to log out when a RSET
362 * command is issued. Here's the code to do it. It is commented out
363 * because some clients (such as Pine) issue RSET commands before
364 * each message, but still expect to be logged in.
366 * if (CC->logged_in) {
372 * Reinstate this little piece of information we saved (see above).
374 SMTP->is_lmtp = is_lmtp;
376 cprintf("250 2.0.0 Zap!\r\n");
380 * Clear out the portions of the state buffer that need to be cleared out
381 * after the DATA command finishes.
383 void smtp_data_clear(void) {
384 strcpy(SMTP->from, "");
385 strcpy(SMTP->recipients, "");
386 SMTP->number_of_recipients = 0;
387 SMTP->delivery_mode = 0;
388 SMTP->message_originated_locally = 0;
394 * Implements the "MAIL From:" command
396 void smtp_mail(char *argbuf) {
401 if (strlen(SMTP->from) != 0) {
402 cprintf("503 5.1.0 Only one sender permitted\r\n");
406 if (strncasecmp(argbuf, "From:", 5)) {
407 cprintf("501 5.1.7 Syntax error\r\n");
411 strcpy(SMTP->from, &argbuf[5]);
413 stripallbut(SMTP->from, '<', '>');
415 /* We used to reject empty sender names, until it was brought to our
416 * attention that RFC1123 5.2.9 requires that this be allowed. So now
417 * we allow it, but replace the empty string with a fake
418 * address so we don't have to contend with the empty string causing
419 * other code to fail when it's expecting something there.
421 if (strlen(SMTP->from) == 0) {
422 strcpy(SMTP->from, "someone@somewhere.org");
425 /* If this SMTP connection is from a logged-in user, force the 'from'
426 * to be the user's Internet e-mail address as Citadel knows it.
429 strcpy(SMTP->from, CC->cs_inet_email);
430 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
431 SMTP->message_originated_locally = 1;
435 else if (SMTP->is_lmtp) {
436 /* Bypass forgery checking for LMTP */
439 /* Otherwise, make sure outsiders aren't trying to forge mail from
443 process_rfc822_addr(SMTP->from, user, node, name);
444 if (CtdlHostAlias(node) != hostalias_nomatch) {
446 "You must log in to send mail from %s\r\n",
448 strcpy(SMTP->from, "");
453 cprintf("250 2.0.0 Sender ok\r\n");
459 * Implements the "RCPT To:" command
461 void smtp_rcpt(char *argbuf) {
463 char message_to_spammer[SIZ];
464 struct recptypes *valid = NULL;
466 if (strlen(SMTP->from) == 0) {
467 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
471 if (strncasecmp(argbuf, "To:", 3)) {
472 cprintf("501 5.1.7 Syntax error\r\n");
476 strcpy(recp, &argbuf[3]);
478 stripallbut(recp, '<', '>');
480 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
481 cprintf("452 4.5.3 Too many recipients\r\n");
486 if ( (!CC->logged_in)
487 && (!SMTP->is_lmtp) ) {
488 if (rbl_check(message_to_spammer)) {
489 cprintf("550 %s\r\n", message_to_spammer);
490 /* no need to phree(valid), it's not allocated yet */
495 valid = validate_recipients(recp);
496 if (valid->num_error > 0) {
497 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
502 if (valid->num_internet > 0) {
503 if ( (SMTP->message_originated_locally == 0)
504 && (SMTP->is_lmtp == 0) ) {
505 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
511 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
512 if (strlen(SMTP->recipients) > 0) {
513 strcat(SMTP->recipients, ",");
515 strcat(SMTP->recipients, recp);
516 SMTP->number_of_recipients += 1;
523 * Implements the DATA command
525 void smtp_data(void) {
527 struct CtdlMessage *msg;
530 struct recptypes *valid;
535 if (strlen(SMTP->from) == 0) {
536 cprintf("503 5.5.1 Need MAIL command first.\r\n");
540 if (SMTP->number_of_recipients < 1) {
541 cprintf("503 5.5.1 Need RCPT command first.\r\n");
545 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
547 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
550 if (body != NULL) snprintf(body, 4096,
551 "Received: from %s (%s [%s])\n"
559 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
562 "Unable to save message: internal error.\r\n");
566 lprintf(9, "Converting message...\n");
567 msg = convert_internet_message(body);
569 /* If the user is locally authenticated, FORCE the From: header to
570 * show up as the real sender. Yes, this violates the RFC standard,
571 * but IT MAKES SENSE. If you prefer strict RFC adherence over
572 * common sense, you can disable this in the configuration.
574 * We also set the "message room name" ('O' field) to MAILROOM
575 * (which is Mail> on most systems) to prevent it from getting set
576 * to something ugly like "0000058008.Sent Items>" when the message
577 * is read with a Citadel client.
579 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
580 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
581 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
582 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
583 if (msg->cm_fields['F'] != NULL) phree(msg->cm_fields['F']);
584 if (msg->cm_fields['O'] != NULL) phree(msg->cm_fields['O']);
585 msg->cm_fields['A'] = strdoop(CC->user.fullname);
586 msg->cm_fields['N'] = strdoop(config.c_nodename);
587 msg->cm_fields['H'] = strdoop(config.c_humannode);
588 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
589 msg->cm_fields['O'] = strdoop(MAILROOM);
592 /* Submit the message into the Citadel system. */
593 valid = validate_recipients(SMTP->recipients);
595 /* If there are modules that want to scan this message before final
596 * submission (such as virus checkers or spam filters), call them now
597 * and give them an opportunity to reject the message.
599 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
601 if (scan_errors > 0) { /* We don't want this message! */
603 if (msg->cm_fields['0'] == NULL) {
604 msg->cm_fields['0'] = strdoop(
605 "5.7.1 Message rejected by filter");
608 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
611 else { /* Ok, we'll accept this message. */
612 msgnum = CtdlSubmitMsg(msg, valid, "");
614 sprintf(result, "250 2.0.0 Message accepted.\r\n");
617 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
621 /* For SMTP and ESTMP, just print the result message. For LMTP, we
622 * have to print one result message for each recipient. Since there
623 * is nothing in Citadel which would cause different recipients to
624 * have different results, we can get away with just spitting out the
625 * same message once for each recipient.
628 for (i=0; i<SMTP->number_of_recipients; ++i) {
629 cprintf("%s", result);
633 cprintf("%s", result);
636 CtdlFreeMessage(msg);
638 smtp_data_clear(); /* clear out the buffers now */
645 * Main command loop for SMTP sessions.
647 void smtp_command_loop(void) {
651 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
652 if (client_gets(cmdbuf) < 1) {
653 lprintf(3, "SMTP socket is broken. Ending session.\n");
657 lprintf(5, "SMTP: %s\n", cmdbuf);
658 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
660 lprintf(9, "CC->logged_in = %d\n", CC->logged_in);
662 if (SMTP->command_state == smtp_user) {
663 smtp_get_user(cmdbuf);
666 else if (SMTP->command_state == smtp_password) {
667 smtp_get_pass(cmdbuf);
670 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
671 smtp_auth(&cmdbuf[5]);
674 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
678 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
679 smtp_expn(&cmdbuf[5]);
682 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
683 smtp_hello(&cmdbuf[5], 0);
686 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
687 smtp_hello(&cmdbuf[5], 1);
690 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
691 smtp_hello(&cmdbuf[5], 2);
694 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
698 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
699 smtp_mail(&cmdbuf[5]);
702 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
703 cprintf("250 NOOP\r\n");
706 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
707 cprintf("221 Goodbye...\r\n");
712 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
713 smtp_rcpt(&cmdbuf[5]);
716 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
720 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
721 smtp_vrfy(&cmdbuf[5]);
725 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
733 /*****************************************************************************/
734 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
735 /*****************************************************************************/
742 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
745 void smtp_try(const char *key, const char *addr, int *status,
746 char *dsn, size_t n, long msgnum)
753 char user[SIZ], node[SIZ], name[SIZ];
759 size_t blocksize = 0;
762 /* Parse out the host portion of the recipient address */
763 process_rfc822_addr(addr, user, node, name);
765 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
768 /* Load the message out of the database into a temp file */
770 if (msg_fp == NULL) {
772 snprintf(dsn, n, "Error creating temporary file");
776 CtdlRedirectOutput(msg_fp, -1);
777 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
778 CtdlRedirectOutput(NULL, -1);
779 fseek(msg_fp, 0L, SEEK_END);
780 msg_size = ftell(msg_fp);
784 /* Extract something to send later in the 'MAIL From:' command */
785 strcpy(mailfrom, "");
789 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
790 if (!strncasecmp(buf, "From:", 5)) {
791 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
793 for (i=0; i<strlen(mailfrom); ++i) {
794 if (!isprint(mailfrom[i])) {
795 strcpy(&mailfrom[i], &mailfrom[i+1]);
800 /* Strip out parenthesized names */
803 for (i=0; i<strlen(mailfrom); ++i) {
804 if (mailfrom[i] == '(') lp = i;
805 if (mailfrom[i] == ')') rp = i;
807 if ((lp>0)&&(rp>lp)) {
808 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
811 /* Prefer brokketized names */
814 for (i=0; i<strlen(mailfrom); ++i) {
815 if (mailfrom[i] == '<') lp = i;
816 if (mailfrom[i] == '>') rp = i;
818 if ( (lp>=0) && (rp>lp) ) {
820 strcpy(mailfrom, &mailfrom[lp]);
825 } while (scan_done == 0);
826 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
828 /* Figure out what mail exchanger host we have to connect to */
829 num_mxhosts = getmx(mxhosts, node);
830 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
831 if (num_mxhosts < 1) {
833 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
838 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
839 extract(buf, mxhosts, mx);
840 lprintf(9, "Trying <%s>\n", buf);
841 sock = sock_connect(buf, "25", "tcp");
842 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
843 if (sock >= 0) lprintf(9, "Connected!\n");
844 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
848 *status = 4; /* dsn is already filled in */
852 /* Process the SMTP greeting from the server */
853 if (ml_sock_gets(sock, buf) < 0) {
855 strcpy(dsn, "Connection broken during SMTP conversation");
858 lprintf(9, "<%s\n", buf);
862 safestrncpy(dsn, &buf[4], 1023);
867 safestrncpy(dsn, &buf[4], 1023);
872 /* At this point we know we are talking to a real SMTP server */
874 /* Do a HELO command */
875 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
876 lprintf(9, ">%s", buf);
877 sock_write(sock, buf, strlen(buf));
878 if (ml_sock_gets(sock, buf) < 0) {
880 strcpy(dsn, "Connection broken during SMTP HELO");
883 lprintf(9, "<%s\n", buf);
887 safestrncpy(dsn, &buf[4], 1023);
892 safestrncpy(dsn, &buf[4], 1023);
898 /* HELO succeeded, now try the MAIL From: command */
899 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
900 lprintf(9, ">%s", buf);
901 sock_write(sock, buf, strlen(buf));
902 if (ml_sock_gets(sock, buf) < 0) {
904 strcpy(dsn, "Connection broken during SMTP MAIL");
907 lprintf(9, "<%s\n", buf);
911 safestrncpy(dsn, &buf[4], 1023);
916 safestrncpy(dsn, &buf[4], 1023);
922 /* MAIL succeeded, now try the RCPT To: command */
923 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
924 lprintf(9, ">%s", buf);
925 sock_write(sock, buf, strlen(buf));
926 if (ml_sock_gets(sock, buf) < 0) {
928 strcpy(dsn, "Connection broken during SMTP RCPT");
931 lprintf(9, "<%s\n", buf);
935 safestrncpy(dsn, &buf[4], 1023);
940 safestrncpy(dsn, &buf[4], 1023);
946 /* RCPT succeeded, now try the DATA command */
947 lprintf(9, ">DATA\n");
948 sock_write(sock, "DATA\r\n", 6);
949 if (ml_sock_gets(sock, buf) < 0) {
951 strcpy(dsn, "Connection broken during SMTP DATA");
954 lprintf(9, "<%s\n", buf);
958 safestrncpy(dsn, &buf[4], 1023);
963 safestrncpy(dsn, &buf[4], 1023);
968 /* If we reach this point, the server is expecting data */
970 while (msg_size > 0) {
971 blocksize = sizeof(buf);
972 if (blocksize > msg_size) blocksize = msg_size;
973 fread(buf, blocksize, 1, msg_fp);
974 sock_write(sock, buf, blocksize);
975 msg_size -= blocksize;
977 if (buf[blocksize-1] != 10) {
978 lprintf(5, "Possible problem: message did not correctly "
979 "terminate. (expecting 0x10, got 0x%02x)\n",
983 sock_write(sock, ".\r\n", 3);
984 if (ml_sock_gets(sock, buf) < 0) {
986 strcpy(dsn, "Connection broken during SMTP message transmit");
989 lprintf(9, "%s\n", buf);
993 safestrncpy(dsn, &buf[4], 1023);
998 safestrncpy(dsn, &buf[4], 1023);
1004 safestrncpy(dsn, &buf[4], 1023);
1007 lprintf(9, ">QUIT\n");
1008 sock_write(sock, "QUIT\r\n", 6);
1009 ml_sock_gets(sock, buf);
1010 lprintf(9, "<%s\n", buf);
1012 bail: if (msg_fp != NULL) fclose(msg_fp);
1020 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1021 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1022 * a "bounce" message (delivery status notification).
1024 void smtp_do_bounce(char *instr) {
1032 char bounceto[1024];
1033 int num_bounces = 0;
1034 int bounce_this = 0;
1035 long bounce_msgid = (-1);
1036 time_t submitted = 0L;
1037 struct CtdlMessage *bmsg = NULL;
1039 struct recptypes *valid;
1040 int successful_bounce = 0;
1042 lprintf(9, "smtp_do_bounce() called\n");
1043 strcpy(bounceto, "");
1045 lines = num_tokens(instr, '\n');
1048 /* See if it's time to give up on delivery of this message */
1049 for (i=0; i<lines; ++i) {
1050 extract_token(buf, instr, i, '\n');
1051 extract(key, buf, 0);
1052 extract(addr, buf, 1);
1053 if (!strcasecmp(key, "submitted")) {
1054 submitted = atol(addr);
1058 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1064 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1065 if (bmsg == NULL) return;
1066 memset(bmsg, 0, sizeof(struct CtdlMessage));
1068 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1069 bmsg->cm_anon_type = MES_NORMAL;
1070 bmsg->cm_format_type = 1;
1071 bmsg->cm_fields['A'] = strdoop("Citadel");
1072 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1073 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1075 if (give_up) bmsg->cm_fields['M'] = strdoop(
1076 "A message you sent could not be delivered to some or all of its recipients\n"
1077 "due to prolonged unavailability of its destination(s).\n"
1078 "Giving up on the following addresses:\n\n"
1081 else bmsg->cm_fields['M'] = strdoop(
1082 "A message you sent could not be delivered to some or all of its recipients.\n"
1083 "The following addresses were undeliverable:\n\n"
1087 * Now go through the instructions checking for stuff.
1089 for (i=0; i<lines; ++i) {
1090 extract_token(buf, instr, i, '\n');
1091 extract(key, buf, 0);
1092 extract(addr, buf, 1);
1093 status = extract_int(buf, 2);
1094 extract(dsn, buf, 3);
1097 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1098 key, addr, status, dsn);
1100 if (!strcasecmp(key, "bounceto")) {
1101 strcpy(bounceto, addr);
1105 (!strcasecmp(key, "local"))
1106 || (!strcasecmp(key, "remote"))
1107 || (!strcasecmp(key, "ignet"))
1108 || (!strcasecmp(key, "room"))
1110 if (status == 5) bounce_this = 1;
1111 if (give_up) bounce_this = 1;
1117 if (bmsg->cm_fields['M'] == NULL) {
1118 lprintf(2, "ERROR ... M field is null "
1119 "(%s:%d)\n", __FILE__, __LINE__);
1122 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1123 strlen(bmsg->cm_fields['M']) + 1024 );
1124 strcat(bmsg->cm_fields['M'], addr);
1125 strcat(bmsg->cm_fields['M'], ": ");
1126 strcat(bmsg->cm_fields['M'], dsn);
1127 strcat(bmsg->cm_fields['M'], "\n");
1129 remove_token(instr, i, '\n');
1135 /* Deliver the bounce if there's anything worth mentioning */
1136 lprintf(9, "num_bounces = %d\n", num_bounces);
1137 if (num_bounces > 0) {
1139 /* First try the user who sent the message */
1140 lprintf(9, "bounce to user? <%s>\n", bounceto);
1141 if (strlen(bounceto) == 0) {
1142 lprintf(7, "No bounce address specified\n");
1143 bounce_msgid = (-1L);
1146 /* Can we deliver the bounce to the original sender? */
1147 valid = validate_recipients(bounceto);
1148 if (valid != NULL) {
1149 if (valid->num_error == 0) {
1150 CtdlSubmitMsg(bmsg, valid, "");
1151 successful_bounce = 1;
1155 /* If not, post it in the Aide> room */
1156 if (successful_bounce == 0) {
1157 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1160 /* Free up the memory we used */
1161 if (valid != NULL) {
1166 CtdlFreeMessage(bmsg);
1167 lprintf(9, "Done processing bounces\n");
1172 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1173 * set of delivery instructions for completed deliveries and remove them.
1175 * It returns the number of incomplete deliveries remaining.
1177 int smtp_purge_completed_deliveries(char *instr) {
1188 lines = num_tokens(instr, '\n');
1189 for (i=0; i<lines; ++i) {
1190 extract_token(buf, instr, i, '\n');
1191 extract(key, buf, 0);
1192 extract(addr, buf, 1);
1193 status = extract_int(buf, 2);
1194 extract(dsn, buf, 3);
1199 (!strcasecmp(key, "local"))
1200 || (!strcasecmp(key, "remote"))
1201 || (!strcasecmp(key, "ignet"))
1202 || (!strcasecmp(key, "room"))
1204 if (status == 2) completed = 1;
1209 remove_token(instr, i, '\n');
1222 * Called by smtp_do_queue() to handle an individual message.
1224 void smtp_do_procmsg(long msgnum, void *userdata) {
1225 struct CtdlMessage *msg;
1227 char *results = NULL;
1235 long text_msgid = (-1);
1236 int incomplete_deliveries_remaining;
1237 time_t attempted = 0L;
1238 time_t last_attempted = 0L;
1239 time_t retry = SMTP_RETRY_INTERVAL;
1241 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1243 msg = CtdlFetchMessage(msgnum);
1245 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1249 instr = strdoop(msg->cm_fields['M']);
1250 CtdlFreeMessage(msg);
1252 /* Strip out the headers amd any other non-instruction line */
1253 lines = num_tokens(instr, '\n');
1254 for (i=0; i<lines; ++i) {
1255 extract_token(buf, instr, i, '\n');
1256 if (num_tokens(buf, '|') < 2) {
1257 remove_token(instr, i, '\n');
1263 /* Learn the message ID and find out about recent delivery attempts */
1264 lines = num_tokens(instr, '\n');
1265 for (i=0; i<lines; ++i) {
1266 extract_token(buf, instr, i, '\n');
1267 extract(key, buf, 0);
1268 if (!strcasecmp(key, "msgid")) {
1269 text_msgid = extract_long(buf, 1);
1271 if (!strcasecmp(key, "retry")) {
1272 /* double the retry interval after each attempt */
1273 retry = extract_long(buf, 1) * 2L;
1274 if (retry > SMTP_RETRY_MAX) {
1275 retry = SMTP_RETRY_MAX;
1277 remove_token(instr, i, '\n');
1279 if (!strcasecmp(key, "attempted")) {
1280 attempted = extract_long(buf, 1);
1281 if (attempted > last_attempted)
1282 last_attempted = attempted;
1287 * Postpone delivery if we've already tried recently.
1289 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1290 lprintf(7, "Retry time not yet reached.\n");
1297 * Bail out if there's no actual message associated with this
1299 if (text_msgid < 0L) {
1300 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1305 /* Plow through the instructions looking for 'remote' directives and
1306 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1307 * were experienced and it's time to try again)
1309 lines = num_tokens(instr, '\n');
1310 for (i=0; i<lines; ++i) {
1311 extract_token(buf, instr, i, '\n');
1312 extract(key, buf, 0);
1313 extract(addr, buf, 1);
1314 status = extract_int(buf, 2);
1315 extract(dsn, buf, 3);
1316 if ( (!strcasecmp(key, "remote"))
1317 && ((status==0)||(status==3)||(status==4)) ) {
1319 /* Remove this "remote" instruction from the set,
1320 * but replace the set's final newline if
1321 * remove_token() stripped it. It has to be there.
1323 remove_token(instr, i, '\n');
1324 if (instr[strlen(instr)-1] != '\n') {
1325 strcat(instr, "\n");
1330 lprintf(9, "SMTP: Trying <%s>\n", addr);
1331 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1333 if (results == NULL) {
1334 results = mallok(1024);
1335 memset(results, 0, 1024);
1338 results = reallok(results,
1339 strlen(results) + 1024);
1341 snprintf(&results[strlen(results)], 1024,
1343 key, addr, status, dsn);
1348 if (results != NULL) {
1349 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1350 strcat(instr, results);
1355 /* Generate 'bounce' messages */
1356 smtp_do_bounce(instr);
1358 /* Go through the delivery list, deleting completed deliveries */
1359 incomplete_deliveries_remaining =
1360 smtp_purge_completed_deliveries(instr);
1364 * No delivery instructions remain, so delete both the instructions
1365 * message and the message message.
1367 if (incomplete_deliveries_remaining <= 0) {
1368 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1369 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1374 * Uncompleted delivery instructions remain, so delete the old
1375 * instructions and replace with the updated ones.
1377 if (incomplete_deliveries_remaining > 0) {
1378 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1379 msg = mallok(sizeof(struct CtdlMessage));
1380 memset(msg, 0, sizeof(struct CtdlMessage));
1381 msg->cm_magic = CTDLMESSAGE_MAGIC;
1382 msg->cm_anon_type = MES_NORMAL;
1383 msg->cm_format_type = FMT_RFC822;
1384 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1385 snprintf(msg->cm_fields['M'],
1387 "Content-type: %s\n\n%s\n"
1390 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1392 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1393 CtdlFreeMessage(msg);
1403 * Run through the queue sending out messages.
1405 void smtp_do_queue(void) {
1406 static int doing_queue = 0;
1409 * This is a simple concurrency check to make sure only one queue run
1410 * is done at a time. We could do this with a mutex, but since we
1411 * don't really require extremely fine granularity here, we'll do it
1412 * with a static variable instead.
1414 if (doing_queue) return;
1418 * Go ahead and run the queue
1420 lprintf(7, "SMTP: processing outbound queue\n");
1422 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1423 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1426 CtdlForEachMessage(MSGS_ALL, 0L,
1427 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1429 lprintf(7, "SMTP: queue run completed\n");
1436 /*****************************************************************************/
1437 /* SMTP UTILITY COMMANDS */
1438 /*****************************************************************************/
1440 void cmd_smtp(char *argbuf) {
1447 if (CtdlAccessCheck(ac_aide)) return;
1449 extract(cmd, argbuf, 0);
1451 if (!strcasecmp(cmd, "mx")) {
1452 extract(node, argbuf, 1);
1453 num_mxhosts = getmx(buf, node);
1454 cprintf("%d %d MX hosts listed for %s\n",
1455 LISTING_FOLLOWS, num_mxhosts, node);
1456 for (i=0; i<num_mxhosts; ++i) {
1457 extract(node, buf, i);
1458 cprintf("%s\n", node);
1464 else if (!strcasecmp(cmd, "runqueue")) {
1466 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1471 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1478 * Initialize the SMTP outbound queue
1480 void smtp_init_spoolout(void) {
1481 struct ctdlroom qrbuf;
1484 * Create the room. This will silently fail if the room already
1485 * exists, and that's perfectly ok, because we want it to exist.
1487 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1490 * Make sure it's set to be a "system room" so it doesn't show up
1491 * in the <K>nown rooms list for Aides.
1493 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1494 qrbuf.QRflags2 |= QR2_SYSTEM;
1502 /*****************************************************************************/
1503 /* MODULE INITIALIZATION STUFF */
1504 /*****************************************************************************/
1507 char *serv_smtp_init(void)
1509 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1514 CtdlRegisterServiceHook(0, /* ...and locally */
1519 smtp_init_spoolout();
1520 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1521 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");