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 2487 - SMTP Service Extension for Secure SMTP over TLS
18 * RFC 2554 - SMTP Service Extension for Authentication
19 * RFC 2821 - Simple Mail Transfer Protocol
20 * RFC 2822 - Internet Message Format
21 * RFC 2920 - SMTP Service Extension for Command Pipelining
33 #include <sys/types.h>
35 #if TIME_WITH_SYS_TIME
36 # include <sys/time.h>
40 # include <sys/time.h>
50 #include <sys/socket.h>
51 #include <netinet/in.h>
52 #include <arpa/inet.h>
55 #include "sysdep_decls.h"
56 #include "citserver.h"
60 #include "serv_extensions.h"
67 #include "internet_addressing.h"
70 #include "clientsocket.h"
71 #include "locate_host.h"
74 #include "serv_crypto.h"
83 struct citsmtp { /* Information about the current session */
86 struct ctdluser vrfy_buffer;
91 int number_of_recipients;
93 int message_originated_locally;
97 enum { /* Command states for login authentication */
103 enum { /* Delivery modes */
108 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
109 #define SMTP_RECPS ((char *)CtdlGetUserData(SYM_SMTP_RECPS))
110 #define SMTP_ROOMS ((char *)CtdlGetUserData(SYM_SMTP_ROOMS))
113 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
117 /*****************************************************************************/
118 /* SMTP SERVER (INBOUND) STUFF */
119 /*****************************************************************************/
125 * Here's where our SMTP session begins its happy day.
127 void smtp_greeting(void) {
129 strcpy(CC->cs_clientname, "SMTP session");
130 CC->internal_pgm = 1;
131 CC->cs_flags |= CS_STEALTH;
132 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
133 CtdlAllocUserData(SYM_SMTP_RECPS, SIZ);
134 CtdlAllocUserData(SYM_SMTP_ROOMS, SIZ);
135 snprintf(SMTP_RECPS, SIZ, "%s", "");
136 snprintf(SMTP_ROOMS, SIZ, "%s", "");
138 cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
142 * LMTP is like SMTP but with some extra bonus footage added.
144 void lmtp_greeting(void) {
151 * Implement HELO and EHLO commands.
153 * which_command: 0=HELO, 1=EHLO, 2=LHLO
155 void smtp_hello(char *argbuf, int which_command) {
157 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
159 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
160 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
164 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
165 cprintf("500 LHLO is only allowed when running LMTP\r\n");
169 if (which_command == 0) {
170 cprintf("250 Hello %s (%s [%s])\r\n",
177 if (which_command == 1) {
178 cprintf("250-Hello %s (%s [%s])\r\n",
185 cprintf("250-Greetings and joyous salutations.\r\n");
187 cprintf("250-HELP\r\n");
188 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
189 cprintf("250-PIPELINING\r\n");
190 cprintf("250-AUTH=LOGIN\r\n");
192 cprintf("250-STARTTLS\r\n");
194 cprintf("250 ENHANCEDSTATUSCODES\r\n");
201 * Implement HELP command.
203 void smtp_help(void) {
204 cprintf("214-Commands accepted:\r\n");
205 cprintf("214- DATA\r\n");
206 cprintf("214- EHLO\r\n");
207 cprintf("214- EXPN\r\n");
208 cprintf("214- HELO\r\n");
209 cprintf("214- HELP\r\n");
210 cprintf("214- MAIL\r\n");
211 cprintf("214- NOOP\r\n");
212 cprintf("214- QUIT\r\n");
213 cprintf("214- RCPT\r\n");
214 cprintf("214- RSET\r\n");
215 cprintf("214- VRFY\r\n");
223 void smtp_get_user(char *argbuf) {
227 CtdlDecodeBase64(username, argbuf, SIZ);
228 lprintf(9, "Trying <%s>\n", username);
229 if (CtdlLoginExistingUser(username) == login_ok) {
230 CtdlEncodeBase64(buf, "Password:", 9);
231 cprintf("334 %s\r\n", buf);
232 SMTP->command_state = smtp_password;
235 cprintf("500 5.7.0 No such user.\r\n");
236 SMTP->command_state = smtp_command;
244 void smtp_get_pass(char *argbuf) {
247 CtdlDecodeBase64(password, argbuf, SIZ);
248 lprintf(9, "Trying <%s>\n", password);
249 if (CtdlTryPassword(password) == pass_ok) {
250 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
251 lprintf(9, "SMTP authenticated login successful\n");
252 CC->internal_pgm = 0;
253 CC->cs_flags &= ~CS_STEALTH;
256 cprintf("535 5.7.0 Authentication failed.\r\n");
258 SMTP->command_state = smtp_command;
265 void smtp_auth(char *argbuf) {
268 if (strncasecmp(argbuf, "login", 5) ) {
269 cprintf("504 5.7.4 We only support LOGIN authentication.\r\n");
273 if (strlen(argbuf) >= 7) {
274 smtp_get_user(&argbuf[6]);
278 CtdlEncodeBase64(buf, "Username:", 9);
279 cprintf("334 %s\r\n", buf);
280 SMTP->command_state = smtp_user;
286 * Back end for smtp_vrfy() command
288 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
290 if (!fuzzy_match(us, SMTP->vrfy_match)) {
292 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
298 * Implements the VRFY (verify user name) command.
299 * Performs fuzzy match on full user names.
301 void smtp_vrfy(char *argbuf) {
302 SMTP->vrfy_count = 0;
303 strcpy(SMTP->vrfy_match, argbuf);
304 ForEachUser(smtp_vrfy_backend, NULL);
306 if (SMTP->vrfy_count < 1) {
307 cprintf("550 5.1.1 String does not match anything.\r\n");
309 else if (SMTP->vrfy_count == 1) {
310 cprintf("250 %s <cit%ld@%s>\r\n",
311 SMTP->vrfy_buffer.fullname,
312 SMTP->vrfy_buffer.usernum,
315 else if (SMTP->vrfy_count > 1) {
316 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
325 * Back end for smtp_expn() command
327 void smtp_expn_backend(struct ctdluser *us, void *data) {
329 if (!fuzzy_match(us, SMTP->vrfy_match)) {
331 if (SMTP->vrfy_count >= 1) {
332 cprintf("250-%s <cit%ld@%s>\r\n",
333 SMTP->vrfy_buffer.fullname,
334 SMTP->vrfy_buffer.usernum,
339 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
345 * Implements the EXPN (expand user name) command.
346 * Performs fuzzy match on full user names.
348 void smtp_expn(char *argbuf) {
349 SMTP->vrfy_count = 0;
350 strcpy(SMTP->vrfy_match, argbuf);
351 ForEachUser(smtp_expn_backend, NULL);
353 if (SMTP->vrfy_count < 1) {
354 cprintf("550 5.1.1 String does not match anything.\r\n");
356 else if (SMTP->vrfy_count >= 1) {
357 cprintf("250 %s <cit%ld@%s>\r\n",
358 SMTP->vrfy_buffer.fullname,
359 SMTP->vrfy_buffer.usernum,
366 * Implements the RSET (reset state) command.
367 * Currently this just zeroes out the state buffer. If pointers to data
368 * allocated with mallok() are ever placed in the state buffer, we have to
369 * be sure to phree() them first!
371 * Set do_response to nonzero to output the SMTP RSET response code.
373 void smtp_rset(int do_response) {
377 * Our entire SMTP state is discarded when a RSET command is issued,
378 * but we need to preserve this one little piece of information, so
379 * we save it for later.
381 is_lmtp = SMTP->is_lmtp;
383 memset(SMTP, 0, sizeof(struct citsmtp));
386 * It is somewhat ambiguous whether we want to log out when a RSET
387 * command is issued. Here's the code to do it. It is commented out
388 * because some clients (such as Pine) issue RSET commands before
389 * each message, but still expect to be logged in.
391 * if (CC->logged_in) {
397 * Reinstate this little piece of information we saved (see above).
399 SMTP->is_lmtp = is_lmtp;
402 cprintf("250 2.0.0 Zap!\r\n");
407 * Clear out the portions of the state buffer that need to be cleared out
408 * after the DATA command finishes.
410 void smtp_data_clear(void) {
411 strcpy(SMTP->from, "");
412 strcpy(SMTP->recipients, "");
413 SMTP->number_of_recipients = 0;
414 SMTP->delivery_mode = 0;
415 SMTP->message_originated_locally = 0;
421 * Implements the "MAIL From:" command
423 void smtp_mail(char *argbuf) {
428 if (strlen(SMTP->from) != 0) {
429 cprintf("503 5.1.0 Only one sender permitted\r\n");
433 if (strncasecmp(argbuf, "From:", 5)) {
434 cprintf("501 5.1.7 Syntax error\r\n");
438 strcpy(SMTP->from, &argbuf[5]);
440 stripallbut(SMTP->from, '<', '>');
442 /* We used to reject empty sender names, until it was brought to our
443 * attention that RFC1123 5.2.9 requires that this be allowed. So now
444 * we allow it, but replace the empty string with a fake
445 * address so we don't have to contend with the empty string causing
446 * other code to fail when it's expecting something there.
448 if (strlen(SMTP->from) == 0) {
449 strcpy(SMTP->from, "someone@somewhere.org");
452 /* If this SMTP connection is from a logged-in user, force the 'from'
453 * to be the user's Internet e-mail address as Citadel knows it.
456 strcpy(SMTP->from, CC->cs_inet_email);
457 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
458 SMTP->message_originated_locally = 1;
462 else if (SMTP->is_lmtp) {
463 /* Bypass forgery checking for LMTP */
466 /* Otherwise, make sure outsiders aren't trying to forge mail from
470 process_rfc822_addr(SMTP->from, user, node, name);
471 if (CtdlHostAlias(node) != hostalias_nomatch) {
473 "You must log in to send mail from %s\r\n",
475 strcpy(SMTP->from, "");
480 cprintf("250 2.0.0 Sender ok\r\n");
486 * Implements the "RCPT To:" command
488 void smtp_rcpt(char *argbuf) {
490 char message_to_spammer[SIZ];
491 struct recptypes *valid = NULL;
493 if (strlen(SMTP->from) == 0) {
494 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
498 if (strncasecmp(argbuf, "To:", 3)) {
499 cprintf("501 5.1.7 Syntax error\r\n");
503 strcpy(recp, &argbuf[3]);
505 stripallbut(recp, '<', '>');
507 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
508 cprintf("452 4.5.3 Too many recipients\r\n");
513 if ( (!CC->logged_in)
514 && (!SMTP->is_lmtp) ) {
515 if (rbl_check(message_to_spammer)) {
516 cprintf("550 %s\r\n", message_to_spammer);
517 /* no need to phree(valid), it's not allocated yet */
522 valid = validate_recipients(recp);
523 if (valid->num_error > 0) {
524 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
529 if (valid->num_internet > 0) {
530 if ( (SMTP->message_originated_locally == 0)
531 && (SMTP->is_lmtp == 0) ) {
532 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
538 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
539 if (strlen(SMTP->recipients) > 0) {
540 strcat(SMTP->recipients, ",");
542 strcat(SMTP->recipients, recp);
543 SMTP->number_of_recipients += 1;
550 * Implements the DATA command
552 void smtp_data(void) {
554 struct CtdlMessage *msg;
557 struct recptypes *valid;
562 if (strlen(SMTP->from) == 0) {
563 cprintf("503 5.5.1 Need MAIL command first.\r\n");
567 if (SMTP->number_of_recipients < 1) {
568 cprintf("503 5.5.1 Need RCPT command first.\r\n");
572 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
574 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
577 if (body != NULL) snprintf(body, 4096,
578 "Received: from %s (%s [%s])\n"
586 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
589 "Unable to save message: internal error.\r\n");
593 lprintf(9, "Converting message...\n");
594 msg = convert_internet_message(body);
596 /* If the user is locally authenticated, FORCE the From: header to
597 * show up as the real sender. Yes, this violates the RFC standard,
598 * but IT MAKES SENSE. If you prefer strict RFC adherence over
599 * common sense, you can disable this in the configuration.
601 * We also set the "message room name" ('O' field) to MAILROOM
602 * (which is Mail> on most systems) to prevent it from getting set
603 * to something ugly like "0000058008.Sent Items>" when the message
604 * is read with a Citadel client.
606 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
607 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
608 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
609 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
610 if (msg->cm_fields['F'] != NULL) phree(msg->cm_fields['F']);
611 if (msg->cm_fields['O'] != NULL) phree(msg->cm_fields['O']);
612 msg->cm_fields['A'] = strdoop(CC->user.fullname);
613 msg->cm_fields['N'] = strdoop(config.c_nodename);
614 msg->cm_fields['H'] = strdoop(config.c_humannode);
615 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
616 msg->cm_fields['O'] = strdoop(MAILROOM);
619 /* Submit the message into the Citadel system. */
620 valid = validate_recipients(SMTP->recipients);
622 /* If there are modules that want to scan this message before final
623 * submission (such as virus checkers or spam filters), call them now
624 * and give them an opportunity to reject the message.
626 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
628 if (scan_errors > 0) { /* We don't want this message! */
630 if (msg->cm_fields['0'] == NULL) {
631 msg->cm_fields['0'] = strdoop(
632 "5.7.1 Message rejected by filter");
635 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
638 else { /* Ok, we'll accept this message. */
639 msgnum = CtdlSubmitMsg(msg, valid, "");
641 sprintf(result, "250 2.0.0 Message accepted.\r\n");
644 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
648 /* For SMTP and ESTMP, just print the result message. For LMTP, we
649 * have to print one result message for each recipient. Since there
650 * is nothing in Citadel which would cause different recipients to
651 * have different results, we can get away with just spitting out the
652 * same message once for each recipient.
655 for (i=0; i<SMTP->number_of_recipients; ++i) {
656 cprintf("%s", result);
660 cprintf("%s", result);
663 CtdlFreeMessage(msg);
665 smtp_data_clear(); /* clear out the buffers now */
670 * implements the STARTTLS command (Citadel API version)
673 void smtp_starttls(void)
675 char ok_response[SIZ];
676 char nosup_response[SIZ];
677 char error_response[SIZ];
680 "200 2.0.0 Begin TLS negotiation now\r\n");
681 sprintf(nosup_response,
682 "554 5.7.3 TLS not supported here\r\n");
683 sprintf(error_response,
684 "554 5.7.3 Internal error\r\n");
685 CtdlStartTLS(ok_response, nosup_response, error_response);
693 * Main command loop for SMTP sessions.
695 void smtp_command_loop(void) {
699 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
700 if (client_gets(cmdbuf) < 1) {
701 lprintf(3, "SMTP socket is broken. Ending session.\n");
705 lprintf(5, "SMTP: %s\n", cmdbuf);
706 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
708 if (SMTP->command_state == smtp_user) {
709 smtp_get_user(cmdbuf);
712 else if (SMTP->command_state == smtp_password) {
713 smtp_get_pass(cmdbuf);
716 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
717 smtp_auth(&cmdbuf[5]);
720 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
724 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
725 smtp_expn(&cmdbuf[5]);
728 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
729 smtp_hello(&cmdbuf[5], 0);
732 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
733 smtp_hello(&cmdbuf[5], 1);
736 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
737 smtp_hello(&cmdbuf[5], 2);
740 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
744 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
745 smtp_mail(&cmdbuf[5]);
748 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
749 cprintf("250 NOOP\r\n");
752 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
753 cprintf("221 Goodbye...\r\n");
758 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
759 smtp_rcpt(&cmdbuf[5]);
762 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
766 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
770 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
771 smtp_vrfy(&cmdbuf[5]);
775 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
783 /*****************************************************************************/
784 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
785 /*****************************************************************************/
792 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
795 void smtp_try(const char *key, const char *addr, int *status,
796 char *dsn, size_t n, long msgnum)
803 char user[SIZ], node[SIZ], name[SIZ];
809 size_t blocksize = 0;
812 /* Parse out the host portion of the recipient address */
813 process_rfc822_addr(addr, user, node, name);
815 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
818 /* Load the message out of the database into a temp file */
820 if (msg_fp == NULL) {
822 snprintf(dsn, n, "Error creating temporary file");
826 CtdlRedirectOutput(msg_fp, -1);
827 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
828 CtdlRedirectOutput(NULL, -1);
829 fseek(msg_fp, 0L, SEEK_END);
830 msg_size = ftell(msg_fp);
834 /* Extract something to send later in the 'MAIL From:' command */
835 strcpy(mailfrom, "");
839 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
840 if (!strncasecmp(buf, "From:", 5)) {
841 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
843 for (i=0; i<strlen(mailfrom); ++i) {
844 if (!isprint(mailfrom[i])) {
845 strcpy(&mailfrom[i], &mailfrom[i+1]);
850 /* Strip out parenthesized names */
853 for (i=0; i<strlen(mailfrom); ++i) {
854 if (mailfrom[i] == '(') lp = i;
855 if (mailfrom[i] == ')') rp = i;
857 if ((lp>0)&&(rp>lp)) {
858 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
861 /* Prefer brokketized names */
864 for (i=0; i<strlen(mailfrom); ++i) {
865 if (mailfrom[i] == '<') lp = i;
866 if (mailfrom[i] == '>') rp = i;
868 if ( (lp>=0) && (rp>lp) ) {
870 strcpy(mailfrom, &mailfrom[lp]);
875 } while (scan_done == 0);
876 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
878 /* Figure out what mail exchanger host we have to connect to */
879 num_mxhosts = getmx(mxhosts, node);
880 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
881 if (num_mxhosts < 1) {
883 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
888 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
889 extract(buf, mxhosts, mx);
890 lprintf(9, "Trying <%s>\n", buf);
891 sock = sock_connect(buf, "25", "tcp");
892 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
893 if (sock >= 0) lprintf(9, "Connected!\n");
894 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
898 *status = 4; /* dsn is already filled in */
902 /* Process the SMTP greeting from the server */
903 if (ml_sock_gets(sock, buf) < 0) {
905 strcpy(dsn, "Connection broken during SMTP conversation");
908 lprintf(9, "<%s\n", buf);
912 safestrncpy(dsn, &buf[4], 1023);
917 safestrncpy(dsn, &buf[4], 1023);
922 /* At this point we know we are talking to a real SMTP server */
924 /* Do a HELO command */
925 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
926 lprintf(9, ">%s", buf);
927 sock_write(sock, buf, strlen(buf));
928 if (ml_sock_gets(sock, buf) < 0) {
930 strcpy(dsn, "Connection broken during SMTP HELO");
933 lprintf(9, "<%s\n", buf);
937 safestrncpy(dsn, &buf[4], 1023);
942 safestrncpy(dsn, &buf[4], 1023);
948 /* HELO succeeded, now try the MAIL From: command */
949 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
950 lprintf(9, ">%s", buf);
951 sock_write(sock, buf, strlen(buf));
952 if (ml_sock_gets(sock, buf) < 0) {
954 strcpy(dsn, "Connection broken during SMTP MAIL");
957 lprintf(9, "<%s\n", buf);
961 safestrncpy(dsn, &buf[4], 1023);
966 safestrncpy(dsn, &buf[4], 1023);
972 /* MAIL succeeded, now try the RCPT To: command */
973 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
974 lprintf(9, ">%s", buf);
975 sock_write(sock, buf, strlen(buf));
976 if (ml_sock_gets(sock, buf) < 0) {
978 strcpy(dsn, "Connection broken during SMTP RCPT");
981 lprintf(9, "<%s\n", buf);
985 safestrncpy(dsn, &buf[4], 1023);
990 safestrncpy(dsn, &buf[4], 1023);
996 /* RCPT succeeded, now try the DATA command */
997 lprintf(9, ">DATA\n");
998 sock_write(sock, "DATA\r\n", 6);
999 if (ml_sock_gets(sock, buf) < 0) {
1001 strcpy(dsn, "Connection broken during SMTP DATA");
1004 lprintf(9, "<%s\n", buf);
1005 if (buf[0] != '3') {
1006 if (buf[0] == '4') {
1008 safestrncpy(dsn, &buf[4], 1023);
1013 safestrncpy(dsn, &buf[4], 1023);
1018 /* If we reach this point, the server is expecting data */
1020 while (msg_size > 0) {
1021 blocksize = sizeof(buf);
1022 if (blocksize > msg_size) blocksize = msg_size;
1023 fread(buf, blocksize, 1, msg_fp);
1024 sock_write(sock, buf, blocksize);
1025 msg_size -= blocksize;
1027 if (buf[blocksize-1] != 10) {
1028 lprintf(5, "Possible problem: message did not correctly "
1029 "terminate. (expecting 0x10, got 0x%02x)\n",
1033 sock_write(sock, ".\r\n", 3);
1034 if (ml_sock_gets(sock, buf) < 0) {
1036 strcpy(dsn, "Connection broken during SMTP message transmit");
1039 lprintf(9, "%s\n", buf);
1040 if (buf[0] != '2') {
1041 if (buf[0] == '4') {
1043 safestrncpy(dsn, &buf[4], 1023);
1048 safestrncpy(dsn, &buf[4], 1023);
1054 safestrncpy(dsn, &buf[4], 1023);
1057 lprintf(9, ">QUIT\n");
1058 sock_write(sock, "QUIT\r\n", 6);
1059 ml_sock_gets(sock, buf);
1060 lprintf(9, "<%s\n", buf);
1062 bail: if (msg_fp != NULL) fclose(msg_fp);
1070 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1071 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1072 * a "bounce" message (delivery status notification).
1074 void smtp_do_bounce(char *instr) {
1082 char bounceto[1024];
1083 int num_bounces = 0;
1084 int bounce_this = 0;
1085 long bounce_msgid = (-1);
1086 time_t submitted = 0L;
1087 struct CtdlMessage *bmsg = NULL;
1089 struct recptypes *valid;
1090 int successful_bounce = 0;
1092 lprintf(9, "smtp_do_bounce() called\n");
1093 strcpy(bounceto, "");
1095 lines = num_tokens(instr, '\n');
1098 /* See if it's time to give up on delivery of this message */
1099 for (i=0; i<lines; ++i) {
1100 extract_token(buf, instr, i, '\n');
1101 extract(key, buf, 0);
1102 extract(addr, buf, 1);
1103 if (!strcasecmp(key, "submitted")) {
1104 submitted = atol(addr);
1108 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1114 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1115 if (bmsg == NULL) return;
1116 memset(bmsg, 0, sizeof(struct CtdlMessage));
1118 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1119 bmsg->cm_anon_type = MES_NORMAL;
1120 bmsg->cm_format_type = 1;
1121 bmsg->cm_fields['A'] = strdoop("Citadel");
1122 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1123 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1125 if (give_up) bmsg->cm_fields['M'] = strdoop(
1126 "A message you sent could not be delivered to some or all of its recipients\n"
1127 "due to prolonged unavailability of its destination(s).\n"
1128 "Giving up on the following addresses:\n\n"
1131 else bmsg->cm_fields['M'] = strdoop(
1132 "A message you sent could not be delivered to some or all of its recipients.\n"
1133 "The following addresses were undeliverable:\n\n"
1137 * Now go through the instructions checking for stuff.
1139 for (i=0; i<lines; ++i) {
1140 extract_token(buf, instr, i, '\n');
1141 extract(key, buf, 0);
1142 extract(addr, buf, 1);
1143 status = extract_int(buf, 2);
1144 extract(dsn, buf, 3);
1147 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1148 key, addr, status, dsn);
1150 if (!strcasecmp(key, "bounceto")) {
1151 strcpy(bounceto, addr);
1155 (!strcasecmp(key, "local"))
1156 || (!strcasecmp(key, "remote"))
1157 || (!strcasecmp(key, "ignet"))
1158 || (!strcasecmp(key, "room"))
1160 if (status == 5) bounce_this = 1;
1161 if (give_up) bounce_this = 1;
1167 if (bmsg->cm_fields['M'] == NULL) {
1168 lprintf(2, "ERROR ... M field is null "
1169 "(%s:%d)\n", __FILE__, __LINE__);
1172 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1173 strlen(bmsg->cm_fields['M']) + 1024 );
1174 strcat(bmsg->cm_fields['M'], addr);
1175 strcat(bmsg->cm_fields['M'], ": ");
1176 strcat(bmsg->cm_fields['M'], dsn);
1177 strcat(bmsg->cm_fields['M'], "\n");
1179 remove_token(instr, i, '\n');
1185 /* Deliver the bounce if there's anything worth mentioning */
1186 lprintf(9, "num_bounces = %d\n", num_bounces);
1187 if (num_bounces > 0) {
1189 /* First try the user who sent the message */
1190 lprintf(9, "bounce to user? <%s>\n", bounceto);
1191 if (strlen(bounceto) == 0) {
1192 lprintf(7, "No bounce address specified\n");
1193 bounce_msgid = (-1L);
1196 /* Can we deliver the bounce to the original sender? */
1197 valid = validate_recipients(bounceto);
1198 if (valid != NULL) {
1199 if (valid->num_error == 0) {
1200 CtdlSubmitMsg(bmsg, valid, "");
1201 successful_bounce = 1;
1205 /* If not, post it in the Aide> room */
1206 if (successful_bounce == 0) {
1207 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1210 /* Free up the memory we used */
1211 if (valid != NULL) {
1216 CtdlFreeMessage(bmsg);
1217 lprintf(9, "Done processing bounces\n");
1222 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1223 * set of delivery instructions for completed deliveries and remove them.
1225 * It returns the number of incomplete deliveries remaining.
1227 int smtp_purge_completed_deliveries(char *instr) {
1238 lines = num_tokens(instr, '\n');
1239 for (i=0; i<lines; ++i) {
1240 extract_token(buf, instr, i, '\n');
1241 extract(key, buf, 0);
1242 extract(addr, buf, 1);
1243 status = extract_int(buf, 2);
1244 extract(dsn, buf, 3);
1249 (!strcasecmp(key, "local"))
1250 || (!strcasecmp(key, "remote"))
1251 || (!strcasecmp(key, "ignet"))
1252 || (!strcasecmp(key, "room"))
1254 if (status == 2) completed = 1;
1259 remove_token(instr, i, '\n');
1272 * Called by smtp_do_queue() to handle an individual message.
1274 void smtp_do_procmsg(long msgnum, void *userdata) {
1275 struct CtdlMessage *msg;
1277 char *results = NULL;
1285 long text_msgid = (-1);
1286 int incomplete_deliveries_remaining;
1287 time_t attempted = 0L;
1288 time_t last_attempted = 0L;
1289 time_t retry = SMTP_RETRY_INTERVAL;
1291 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1293 msg = CtdlFetchMessage(msgnum);
1295 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1299 instr = strdoop(msg->cm_fields['M']);
1300 CtdlFreeMessage(msg);
1302 /* Strip out the headers amd any other non-instruction line */
1303 lines = num_tokens(instr, '\n');
1304 for (i=0; i<lines; ++i) {
1305 extract_token(buf, instr, i, '\n');
1306 if (num_tokens(buf, '|') < 2) {
1307 remove_token(instr, i, '\n');
1313 /* Learn the message ID and find out about recent delivery attempts */
1314 lines = num_tokens(instr, '\n');
1315 for (i=0; i<lines; ++i) {
1316 extract_token(buf, instr, i, '\n');
1317 extract(key, buf, 0);
1318 if (!strcasecmp(key, "msgid")) {
1319 text_msgid = extract_long(buf, 1);
1321 if (!strcasecmp(key, "retry")) {
1322 /* double the retry interval after each attempt */
1323 retry = extract_long(buf, 1) * 2L;
1324 if (retry > SMTP_RETRY_MAX) {
1325 retry = SMTP_RETRY_MAX;
1327 remove_token(instr, i, '\n');
1329 if (!strcasecmp(key, "attempted")) {
1330 attempted = extract_long(buf, 1);
1331 if (attempted > last_attempted)
1332 last_attempted = attempted;
1337 * Postpone delivery if we've already tried recently.
1339 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1340 lprintf(7, "Retry time not yet reached.\n");
1347 * Bail out if there's no actual message associated with this
1349 if (text_msgid < 0L) {
1350 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1355 /* Plow through the instructions looking for 'remote' directives and
1356 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1357 * were experienced and it's time to try again)
1359 lines = num_tokens(instr, '\n');
1360 for (i=0; i<lines; ++i) {
1361 extract_token(buf, instr, i, '\n');
1362 extract(key, buf, 0);
1363 extract(addr, buf, 1);
1364 status = extract_int(buf, 2);
1365 extract(dsn, buf, 3);
1366 if ( (!strcasecmp(key, "remote"))
1367 && ((status==0)||(status==3)||(status==4)) ) {
1369 /* Remove this "remote" instruction from the set,
1370 * but replace the set's final newline if
1371 * remove_token() stripped it. It has to be there.
1373 remove_token(instr, i, '\n');
1374 if (instr[strlen(instr)-1] != '\n') {
1375 strcat(instr, "\n");
1380 lprintf(9, "SMTP: Trying <%s>\n", addr);
1381 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1383 if (results == NULL) {
1384 results = mallok(1024);
1385 memset(results, 0, 1024);
1388 results = reallok(results,
1389 strlen(results) + 1024);
1391 snprintf(&results[strlen(results)], 1024,
1393 key, addr, status, dsn);
1398 if (results != NULL) {
1399 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1400 strcat(instr, results);
1405 /* Generate 'bounce' messages */
1406 smtp_do_bounce(instr);
1408 /* Go through the delivery list, deleting completed deliveries */
1409 incomplete_deliveries_remaining =
1410 smtp_purge_completed_deliveries(instr);
1414 * No delivery instructions remain, so delete both the instructions
1415 * message and the message message.
1417 if (incomplete_deliveries_remaining <= 0) {
1418 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1419 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1424 * Uncompleted delivery instructions remain, so delete the old
1425 * instructions and replace with the updated ones.
1427 if (incomplete_deliveries_remaining > 0) {
1428 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1429 msg = mallok(sizeof(struct CtdlMessage));
1430 memset(msg, 0, sizeof(struct CtdlMessage));
1431 msg->cm_magic = CTDLMESSAGE_MAGIC;
1432 msg->cm_anon_type = MES_NORMAL;
1433 msg->cm_format_type = FMT_RFC822;
1434 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1435 snprintf(msg->cm_fields['M'],
1437 "Content-type: %s\n\n%s\n"
1440 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1442 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1443 CtdlFreeMessage(msg);
1453 * Run through the queue sending out messages.
1455 void smtp_do_queue(void) {
1456 static int doing_queue = 0;
1459 * This is a simple concurrency check to make sure only one queue run
1460 * is done at a time. We could do this with a mutex, but since we
1461 * don't really require extremely fine granularity here, we'll do it
1462 * with a static variable instead.
1464 if (doing_queue) return;
1468 * Go ahead and run the queue
1470 lprintf(7, "SMTP: processing outbound queue\n");
1472 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1473 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1476 CtdlForEachMessage(MSGS_ALL, 0L,
1477 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1479 lprintf(7, "SMTP: queue run completed\n");
1486 /*****************************************************************************/
1487 /* SMTP UTILITY COMMANDS */
1488 /*****************************************************************************/
1490 void cmd_smtp(char *argbuf) {
1497 if (CtdlAccessCheck(ac_aide)) return;
1499 extract(cmd, argbuf, 0);
1501 if (!strcasecmp(cmd, "mx")) {
1502 extract(node, argbuf, 1);
1503 num_mxhosts = getmx(buf, node);
1504 cprintf("%d %d MX hosts listed for %s\n",
1505 LISTING_FOLLOWS, num_mxhosts, node);
1506 for (i=0; i<num_mxhosts; ++i) {
1507 extract(node, buf, i);
1508 cprintf("%s\n", node);
1514 else if (!strcasecmp(cmd, "runqueue")) {
1516 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1521 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1528 * Initialize the SMTP outbound queue
1530 void smtp_init_spoolout(void) {
1531 struct ctdlroom qrbuf;
1534 * Create the room. This will silently fail if the room already
1535 * exists, and that's perfectly ok, because we want it to exist.
1537 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1540 * Make sure it's set to be a "system room" so it doesn't show up
1541 * in the <K>nown rooms list for Aides.
1543 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1544 qrbuf.QRflags2 |= QR2_SYSTEM;
1552 /*****************************************************************************/
1553 /* MODULE INITIALIZATION STUFF */
1554 /*****************************************************************************/
1557 char *serv_smtp_init(void)
1559 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1564 CtdlRegisterServiceHook(0, /* ...and locally */
1569 smtp_init_spoolout();
1570 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1571 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");