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 2476 - Message Submission
18 * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
19 * RFC 2554 - SMTP Service Extension for Authentication
20 * RFC 2821 - Simple Mail Transfer Protocol
21 * RFC 2822 - Internet Message Format
22 * RFC 2920 - SMTP Service Extension for Command Pipelining
34 #include <sys/types.h>
37 #if TIME_WITH_SYS_TIME
38 # include <sys/time.h>
42 # include <sys/time.h>
52 #include <sys/socket.h>
53 #include <netinet/in.h>
54 #include <arpa/inet.h>
57 #include "sysdep_decls.h"
58 #include "citserver.h"
62 #include "serv_extensions.h"
69 #include "internet_addressing.h"
72 #include "clientsocket.h"
73 #include "locate_host.h"
74 #include "citadel_dirs.h"
77 #include "serv_crypto.h"
86 struct citsmtp { /* Information about the current session */
89 struct ctdluser vrfy_buffer;
94 int number_of_recipients;
96 int message_originated_locally;
102 enum { /* Command states for login authentication */
109 enum { /* Delivery modes */
114 #define SMTP CC->SMTP
115 #define SMTP_RECPS CC->SMTP_RECPS
116 #define SMTP_ROOMS CC->SMTP_ROOMS
119 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
123 /*****************************************************************************/
124 /* SMTP SERVER (INBOUND) STUFF */
125 /*****************************************************************************/
129 * Here's where our SMTP session begins its happy day.
131 void smtp_greeting(void) {
133 strcpy(CC->cs_clientname, "SMTP session");
134 CC->internal_pgm = 1;
135 CC->cs_flags |= CS_STEALTH;
136 SMTP = malloc(sizeof(struct citsmtp));
137 SMTP_RECPS = malloc(SIZ);
138 SMTP_ROOMS = malloc(SIZ);
139 memset(SMTP, 0, sizeof(struct citsmtp));
140 memset(SMTP_RECPS, 0, SIZ);
141 memset(SMTP_ROOMS, 0, SIZ);
143 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
148 * SMTPS is just like SMTP, except it goes crypto right away.
151 void smtps_greeting(void) {
152 CtdlStartTLS(NULL, NULL, NULL);
159 * SMTP MSA port requires authentication.
161 void smtp_msa_greeting(void) {
168 * LMTP is like SMTP but with some extra bonus footage added.
170 void lmtp_greeting(void) {
177 * We also have an unfiltered LMTP socket that bypasses spam filters.
179 void lmtp_unfiltered_greeting(void) {
182 SMTP->is_unfiltered = 1;
187 * Login greeting common to all auth methods
189 void smtp_auth_greeting(void) {
190 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
191 lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
192 CC->internal_pgm = 0;
193 CC->cs_flags &= ~CS_STEALTH;
198 * Implement HELO and EHLO commands.
200 * which_command: 0=HELO, 1=EHLO, 2=LHLO
202 void smtp_hello(char *argbuf, int which_command) {
204 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
206 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
207 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
211 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
212 cprintf("500 LHLO is only allowed when running LMTP\r\n");
216 if (which_command == 0) {
217 cprintf("250 Hello %s (%s [%s])\r\n",
224 if (which_command == 1) {
225 cprintf("250-Hello %s (%s [%s])\r\n",
232 cprintf("250-Greetings and joyous salutations.\r\n");
234 cprintf("250-HELP\r\n");
235 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
239 /* Only offer the PIPELINING command if TLS is inactive,
240 * because of flow control issues. Also, avoid offering TLS
241 * if TLS is already active. Finally, we only offer TLS on
242 * the SMTP-MSA port, not on the SMTP-MTA port, due to
243 * questionable reliability of TLS in certain sending MTA's.
245 if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
246 cprintf("250-PIPELINING\r\n");
247 cprintf("250-STARTTLS\r\n");
250 #else /* HAVE_OPENSSL */
252 /* Non SSL enabled server, so always offer PIPELINING. */
253 cprintf("250-PIPELINING\r\n");
255 #endif /* HAVE_OPENSSL */
257 cprintf("250-AUTH LOGIN PLAIN\r\n");
258 cprintf("250-AUTH=LOGIN PLAIN\r\n");
260 cprintf("250 ENHANCEDSTATUSCODES\r\n");
267 * Implement HELP command.
269 void smtp_help(void) {
270 cprintf("214-Commands accepted:\r\n");
271 cprintf("214- DATA\r\n");
272 cprintf("214- EHLO\r\n");
273 cprintf("214- EXPN\r\n");
274 cprintf("214- HELO\r\n");
275 cprintf("214- HELP\r\n");
276 cprintf("214- MAIL\r\n");
277 cprintf("214- NOOP\r\n");
278 cprintf("214- QUIT\r\n");
279 cprintf("214- RCPT\r\n");
280 cprintf("214- RSET\r\n");
281 cprintf("214- VRFY\r\n");
289 void smtp_get_user(char *argbuf) {
293 CtdlDecodeBase64(username, argbuf, SIZ);
294 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", username); */
295 if (CtdlLoginExistingUser(username) == login_ok) {
296 CtdlEncodeBase64(buf, "Password:", 9);
297 cprintf("334 %s\r\n", buf);
298 SMTP->command_state = smtp_password;
301 cprintf("500 5.7.0 No such user.\r\n");
302 SMTP->command_state = smtp_command;
310 void smtp_get_pass(char *argbuf) {
313 CtdlDecodeBase64(password, argbuf, SIZ);
314 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", password); */
315 if (CtdlTryPassword(password) == pass_ok) {
316 smtp_auth_greeting();
319 cprintf("535 5.7.0 Authentication failed.\r\n");
321 SMTP->command_state = smtp_command;
326 * Back end for PLAIN auth method (either inline or multistate)
328 void smtp_try_plain(char *encoded_authstring) {
329 char decoded_authstring[1024];
334 CtdlDecodeBase64(decoded_authstring,
336 strlen(encoded_authstring) );
337 safestrncpy(ident, decoded_authstring, sizeof ident);
338 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
339 safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
341 SMTP->command_state = smtp_command;
342 if (CtdlLoginExistingUser(user) == login_ok) {
343 if (CtdlTryPassword(pass) == pass_ok) {
344 smtp_auth_greeting();
348 cprintf("504 5.7.4 Authentication failed.\r\n");
353 * Attempt to perform authenticated SMTP
355 void smtp_auth(char *argbuf) {
356 char username_prompt[64];
358 char encoded_authstring[1024];
361 cprintf("504 5.7.4 Already logged in.\r\n");
365 extract_token(method, argbuf, 0, ' ', sizeof method);
367 if (!strncasecmp(method, "login", 5) ) {
368 if (strlen(argbuf) >= 7) {
369 smtp_get_user(&argbuf[6]);
372 CtdlEncodeBase64(username_prompt, "Username:", 9);
373 cprintf("334 %s\r\n", username_prompt);
374 SMTP->command_state = smtp_user;
379 if (!strncasecmp(method, "plain", 5) ) {
380 if (num_tokens(argbuf, ' ') < 2) {
382 SMTP->command_state = smtp_plain;
386 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
388 smtp_try_plain(encoded_authstring);
392 if (strncasecmp(method, "login", 5) ) {
393 cprintf("504 5.7.4 Unknown authentication method.\r\n");
401 * Back end for smtp_vrfy() command
403 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
405 if (!fuzzy_match(us, SMTP->vrfy_match)) {
407 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
413 * Implements the VRFY (verify user name) command.
414 * Performs fuzzy match on full user names.
416 void smtp_vrfy(char *argbuf) {
417 SMTP->vrfy_count = 0;
418 strcpy(SMTP->vrfy_match, argbuf);
419 ForEachUser(smtp_vrfy_backend, NULL);
421 if (SMTP->vrfy_count < 1) {
422 cprintf("550 5.1.1 String does not match anything.\r\n");
424 else if (SMTP->vrfy_count == 1) {
425 cprintf("250 %s <cit%ld@%s>\r\n",
426 SMTP->vrfy_buffer.fullname,
427 SMTP->vrfy_buffer.usernum,
430 else if (SMTP->vrfy_count > 1) {
431 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
440 * Back end for smtp_expn() command
442 void smtp_expn_backend(struct ctdluser *us, void *data) {
444 if (!fuzzy_match(us, SMTP->vrfy_match)) {
446 if (SMTP->vrfy_count >= 1) {
447 cprintf("250-%s <cit%ld@%s>\r\n",
448 SMTP->vrfy_buffer.fullname,
449 SMTP->vrfy_buffer.usernum,
454 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
460 * Implements the EXPN (expand user name) command.
461 * Performs fuzzy match on full user names.
463 void smtp_expn(char *argbuf) {
464 SMTP->vrfy_count = 0;
465 strcpy(SMTP->vrfy_match, argbuf);
466 ForEachUser(smtp_expn_backend, NULL);
468 if (SMTP->vrfy_count < 1) {
469 cprintf("550 5.1.1 String does not match anything.\r\n");
471 else if (SMTP->vrfy_count >= 1) {
472 cprintf("250 %s <cit%ld@%s>\r\n",
473 SMTP->vrfy_buffer.fullname,
474 SMTP->vrfy_buffer.usernum,
481 * Implements the RSET (reset state) command.
482 * Currently this just zeroes out the state buffer. If pointers to data
483 * allocated with malloc() are ever placed in the state buffer, we have to
484 * be sure to free() them first!
486 * Set do_response to nonzero to output the SMTP RSET response code.
488 void smtp_rset(int do_response) {
493 * Our entire SMTP state is discarded when a RSET command is issued,
494 * but we need to preserve this one little piece of information, so
495 * we save it for later.
497 is_lmtp = SMTP->is_lmtp;
498 is_unfiltered = SMTP->is_unfiltered;
500 memset(SMTP, 0, sizeof(struct citsmtp));
503 * It is somewhat ambiguous whether we want to log out when a RSET
504 * command is issued. Here's the code to do it. It is commented out
505 * because some clients (such as Pine) issue RSET commands before
506 * each message, but still expect to be logged in.
508 * if (CC->logged_in) {
514 * Reinstate this little piece of information we saved (see above).
516 SMTP->is_lmtp = is_lmtp;
517 SMTP->is_unfiltered = is_unfiltered;
520 cprintf("250 2.0.0 Zap!\r\n");
525 * Clear out the portions of the state buffer that need to be cleared out
526 * after the DATA command finishes.
528 void smtp_data_clear(void) {
529 strcpy(SMTP->from, "");
530 strcpy(SMTP->recipients, "");
531 SMTP->number_of_recipients = 0;
532 SMTP->delivery_mode = 0;
533 SMTP->message_originated_locally = 0;
539 * Implements the "MAIL From:" command
541 void smtp_mail(char *argbuf) {
546 if (strlen(SMTP->from) != 0) {
547 cprintf("503 5.1.0 Only one sender permitted\r\n");
551 if (strncasecmp(argbuf, "From:", 5)) {
552 cprintf("501 5.1.7 Syntax error\r\n");
556 strcpy(SMTP->from, &argbuf[5]);
558 if (haschar(SMTP->from, '<') > 0) {
559 stripallbut(SMTP->from, '<', '>');
562 /* We used to reject empty sender names, until it was brought to our
563 * attention that RFC1123 5.2.9 requires that this be allowed. So now
564 * we allow it, but replace the empty string with a fake
565 * address so we don't have to contend with the empty string causing
566 * other code to fail when it's expecting something there.
568 if (strlen(SMTP->from) == 0) {
569 strcpy(SMTP->from, "someone@somewhere.org");
572 /* If this SMTP connection is from a logged-in user, force the 'from'
573 * to be the user's Internet e-mail address as Citadel knows it.
576 safestrncpy(SMTP->from, CC->cs_inet_email, sizeof SMTP->from);
577 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
578 SMTP->message_originated_locally = 1;
582 else if (SMTP->is_lmtp) {
583 /* Bypass forgery checking for LMTP */
586 /* Otherwise, make sure outsiders aren't trying to forge mail from
587 * this system (unless, of course, c_allow_spoofing is enabled)
589 else if (config.c_allow_spoofing == 0) {
590 process_rfc822_addr(SMTP->from, user, node, name);
591 if (CtdlHostAlias(node) != hostalias_nomatch) {
593 "You must log in to send mail from %s\r\n",
595 strcpy(SMTP->from, "");
600 cprintf("250 2.0.0 Sender ok\r\n");
606 * Implements the "RCPT To:" command
608 void smtp_rcpt(char *argbuf) {
610 char message_to_spammer[SIZ];
611 struct recptypes *valid = NULL;
613 if (strlen(SMTP->from) == 0) {
614 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
618 if (strncasecmp(argbuf, "To:", 3)) {
619 cprintf("501 5.1.7 Syntax error\r\n");
623 if ( (SMTP->is_msa) && (!CC->logged_in) ) {
625 "You must log in to send mail on this port.\r\n");
626 strcpy(SMTP->from, "");
630 strcpy(recp, &argbuf[3]);
632 stripallbut(recp, '<', '>');
634 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
635 cprintf("452 4.5.3 Too many recipients\r\n");
640 if ( (!CC->logged_in)
641 && (!SMTP->is_lmtp) ) {
642 if (rbl_check(message_to_spammer)) {
643 cprintf("550 %s\r\n", message_to_spammer);
644 /* no need to free(valid), it's not allocated yet */
649 valid = validate_recipients(recp);
650 if (valid->num_error != 0) {
651 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
656 if (valid->num_internet > 0) {
658 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
659 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
666 if (valid->num_internet > 0) {
667 if ( (SMTP->message_originated_locally == 0)
668 && (SMTP->is_lmtp == 0) ) {
669 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
675 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
676 if (strlen(SMTP->recipients) > 0) {
677 strcat(SMTP->recipients, ",");
679 strcat(SMTP->recipients, recp);
680 SMTP->number_of_recipients += 1;
687 * Implements the DATA command
689 void smtp_data(void) {
691 struct CtdlMessage *msg;
694 struct recptypes *valid;
699 if (strlen(SMTP->from) == 0) {
700 cprintf("503 5.5.1 Need MAIL command first.\r\n");
704 if (SMTP->number_of_recipients < 1) {
705 cprintf("503 5.5.1 Need RCPT command first.\r\n");
709 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
711 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
714 if (body != NULL) snprintf(body, 4096,
715 "Received: from %s (%s [%s])\n"
723 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
726 "Unable to save message: internal error.\r\n");
730 lprintf(CTDL_DEBUG, "Converting message...\n");
731 msg = convert_internet_message(body);
733 /* If the user is locally authenticated, FORCE the From: header to
734 * show up as the real sender. Yes, this violates the RFC standard,
735 * but IT MAKES SENSE. If you prefer strict RFC adherence over
736 * common sense, you can disable this in the configuration.
738 * We also set the "message room name" ('O' field) to MAILROOM
739 * (which is Mail> on most systems) to prevent it from getting set
740 * to something ugly like "0000058008.Sent Items>" when the message
741 * is read with a Citadel client.
743 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
744 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
745 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
746 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
747 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
748 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
749 msg->cm_fields['A'] = strdup(CC->user.fullname);
750 msg->cm_fields['N'] = strdup(config.c_nodename);
751 msg->cm_fields['H'] = strdup(config.c_humannode);
752 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
753 msg->cm_fields['O'] = strdup(MAILROOM);
756 /* Submit the message into the Citadel system. */
757 valid = validate_recipients(SMTP->recipients);
759 /* If there are modules that want to scan this message before final
760 * submission (such as virus checkers or spam filters), call them now
761 * and give them an opportunity to reject the message.
763 if (SMTP->is_unfiltered) {
767 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
770 if (scan_errors > 0) { /* We don't want this message! */
772 if (msg->cm_fields['0'] == NULL) {
773 msg->cm_fields['0'] = strdup(
774 "5.7.1 Message rejected by filter");
777 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
780 else { /* Ok, we'll accept this message. */
781 msgnum = CtdlSubmitMsg(msg, valid, "");
783 sprintf(result, "250 2.0.0 Message accepted.\r\n");
786 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
790 /* For SMTP and ESTMP, just print the result message. For LMTP, we
791 * have to print one result message for each recipient. Since there
792 * is nothing in Citadel which would cause different recipients to
793 * have different results, we can get away with just spitting out the
794 * same message once for each recipient.
797 for (i=0; i<SMTP->number_of_recipients; ++i) {
798 cprintf("%s", result);
802 cprintf("%s", result);
805 /* Write something to the syslog (which may or may not be where the
806 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
809 syslog((LOG_MAIL | LOG_INFO),
810 "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
813 SMTP->number_of_recipients,
821 CtdlFreeMessage(msg);
823 smtp_data_clear(); /* clear out the buffers now */
828 * implements the STARTTLS command (Citadel API version)
831 void smtp_starttls(void)
833 char ok_response[SIZ];
834 char nosup_response[SIZ];
835 char error_response[SIZ];
838 "200 2.0.0 Begin TLS negotiation now\r\n");
839 sprintf(nosup_response,
840 "554 5.7.3 TLS not supported here\r\n");
841 sprintf(error_response,
842 "554 5.7.3 Internal error\r\n");
843 CtdlStartTLS(ok_response, nosup_response, error_response);
851 * Main command loop for SMTP sessions.
853 void smtp_command_loop(void) {
857 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
858 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
859 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
863 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
864 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
866 if (SMTP->command_state == smtp_user) {
867 smtp_get_user(cmdbuf);
870 else if (SMTP->command_state == smtp_password) {
871 smtp_get_pass(cmdbuf);
874 else if (SMTP->command_state == smtp_plain) {
875 smtp_try_plain(cmdbuf);
878 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
879 smtp_auth(&cmdbuf[5]);
882 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
886 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
887 smtp_expn(&cmdbuf[5]);
890 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
891 smtp_hello(&cmdbuf[5], 0);
894 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
895 smtp_hello(&cmdbuf[5], 1);
898 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
899 smtp_hello(&cmdbuf[5], 2);
902 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
906 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
907 smtp_mail(&cmdbuf[5]);
910 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
911 cprintf("250 NOOP\r\n");
914 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
915 cprintf("221 Goodbye...\r\n");
920 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
921 smtp_rcpt(&cmdbuf[5]);
924 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
928 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
932 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
933 smtp_vrfy(&cmdbuf[5]);
937 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
946 /*****************************************************************************/
947 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
948 /*****************************************************************************/
955 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
958 void smtp_try(const char *key, const char *addr, int *status,
959 char *dsn, size_t n, long msgnum)
966 char user[1024], node[1024], name[1024];
979 /* Parse out the host portion of the recipient address */
980 process_rfc822_addr(addr, user, node, name);
982 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
985 /* Load the message out of the database */
986 CC->redirect_buffer = malloc(SIZ);
987 CC->redirect_len = 0;
988 CC->redirect_alloc = SIZ;
989 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
990 msgtext = CC->redirect_buffer;
991 msg_size = CC->redirect_len;
992 CC->redirect_buffer = NULL;
993 CC->redirect_len = 0;
994 CC->redirect_alloc = 0;
996 /* Extract something to send later in the 'MAIL From:' command */
997 strcpy(mailfrom, "");
1001 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
1004 if (!strncasecmp(buf, "From:", 5)) {
1005 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
1007 for (i=0; i<strlen(mailfrom); ++i) {
1008 if (!isprint(mailfrom[i])) {
1009 strcpy(&mailfrom[i], &mailfrom[i+1]);
1014 /* Strip out parenthesized names */
1017 for (i=0; i<strlen(mailfrom); ++i) {
1018 if (mailfrom[i] == '(') lp = i;
1019 if (mailfrom[i] == ')') rp = i;
1021 if ((lp>0)&&(rp>lp)) {
1022 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
1025 /* Prefer brokketized names */
1028 for (i=0; i<strlen(mailfrom); ++i) {
1029 if (mailfrom[i] == '<') lp = i;
1030 if (mailfrom[i] == '>') rp = i;
1032 if ( (lp>=0) && (rp>lp) ) {
1034 strcpy(mailfrom, &mailfrom[lp]);
1039 } while (scan_done == 0);
1040 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
1041 stripallbut(mailfrom, '<', '>');
1043 /* Figure out what mail exchanger host we have to connect to */
1044 num_mxhosts = getmx(mxhosts, node);
1045 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
1046 if (num_mxhosts < 1) {
1048 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1053 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1054 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1055 strcpy(mx_user, "");
1056 strcpy(mx_pass, "");
1057 if (num_tokens(buf, '@') > 1) {
1058 extract_token(mx_user, buf, 0, '@', sizeof mx_user);
1059 if (num_tokens(mx_user, ':') > 1) {
1060 extract_token(mx_pass, mx_user, 1, ':', sizeof mx_pass);
1061 remove_token(mx_user, 1, ':');
1063 remove_token(buf, 0, '@');
1065 extract_token(mx_host, buf, 0, ':', sizeof mx_host);
1066 extract_token(mx_port, buf, 1, ':', sizeof mx_port);
1068 strcpy(mx_port, "25");
1070 lprintf(CTDL_DEBUG, "FIXME user<%s> pass<%s> host<%s> port<%s>\n",
1071 mx_user, mx_pass, mx_host, mx_port);
1072 lprintf(CTDL_DEBUG, "Trying %s : %s ...\n", mx_host, mx_port);
1073 sock = sock_connect(mx_host, mx_port, "tcp");
1074 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1075 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
1076 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
1080 *status = 4; /* dsn is already filled in */
1084 /* Process the SMTP greeting from the server */
1085 if (ml_sock_gets(sock, buf) < 0) {
1087 strcpy(dsn, "Connection broken during SMTP conversation");
1090 lprintf(CTDL_DEBUG, "<%s\n", buf);
1091 if (buf[0] != '2') {
1092 if (buf[0] == '4') {
1094 safestrncpy(dsn, &buf[4], 1023);
1099 safestrncpy(dsn, &buf[4], 1023);
1104 /* At this point we know we are talking to a real SMTP server */
1106 /* Do a EHLO command. If it fails, try the HELO command. */
1107 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1108 lprintf(CTDL_DEBUG, ">%s", buf);
1109 sock_write(sock, buf, strlen(buf));
1110 if (ml_sock_gets(sock, buf) < 0) {
1112 strcpy(dsn, "Connection broken during SMTP HELO");
1115 lprintf(CTDL_DEBUG, "<%s\n", buf);
1116 if (buf[0] != '2') {
1117 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1118 lprintf(CTDL_DEBUG, ">%s", buf);
1119 sock_write(sock, buf, strlen(buf));
1120 if (ml_sock_gets(sock, buf) < 0) {
1122 strcpy(dsn, "Connection broken during SMTP HELO");
1126 if (buf[0] != '2') {
1127 if (buf[0] == '4') {
1129 safestrncpy(dsn, &buf[4], 1023);
1134 safestrncpy(dsn, &buf[4], 1023);
1139 /* Do an AUTH command if necessary */
1140 if (strlen(mx_user) > 0) {
1141 sprintf(buf, "%s%c%s%c%s%c", mx_user, 0, mx_user, 0, mx_pass, 0);
1142 CtdlEncodeBase64(mailfrom, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 3);
1143 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", mailfrom);
1144 lprintf(CTDL_DEBUG, ">%s", buf);
1145 sock_write(sock, buf, strlen(buf));
1146 if (ml_sock_gets(sock, buf) < 0) {
1148 strcpy(dsn, "Connection broken during SMTP AUTH");
1151 lprintf(CTDL_DEBUG, "<%s\n", buf);
1152 if (buf[0] != '2') {
1153 if (buf[0] == '4') {
1155 safestrncpy(dsn, &buf[4], 1023);
1160 safestrncpy(dsn, &buf[4], 1023);
1166 /* previous command succeeded, now try the MAIL From: command */
1167 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1168 lprintf(CTDL_DEBUG, ">%s", buf);
1169 sock_write(sock, buf, strlen(buf));
1170 if (ml_sock_gets(sock, buf) < 0) {
1172 strcpy(dsn, "Connection broken during SMTP MAIL");
1175 lprintf(CTDL_DEBUG, "<%s\n", buf);
1176 if (buf[0] != '2') {
1177 if (buf[0] == '4') {
1179 safestrncpy(dsn, &buf[4], 1023);
1184 safestrncpy(dsn, &buf[4], 1023);
1189 /* MAIL succeeded, now try the RCPT To: command */
1190 snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
1191 lprintf(CTDL_DEBUG, ">%s", buf);
1192 sock_write(sock, buf, strlen(buf));
1193 if (ml_sock_gets(sock, buf) < 0) {
1195 strcpy(dsn, "Connection broken during SMTP RCPT");
1198 lprintf(CTDL_DEBUG, "<%s\n", buf);
1199 if (buf[0] != '2') {
1200 if (buf[0] == '4') {
1202 safestrncpy(dsn, &buf[4], 1023);
1207 safestrncpy(dsn, &buf[4], 1023);
1212 /* RCPT succeeded, now try the DATA command */
1213 lprintf(CTDL_DEBUG, ">DATA\n");
1214 sock_write(sock, "DATA\r\n", 6);
1215 if (ml_sock_gets(sock, buf) < 0) {
1217 strcpy(dsn, "Connection broken during SMTP DATA");
1220 lprintf(CTDL_DEBUG, "<%s\n", buf);
1221 if (buf[0] != '3') {
1222 if (buf[0] == '4') {
1224 safestrncpy(dsn, &buf[4], 1023);
1229 safestrncpy(dsn, &buf[4], 1023);
1234 /* If we reach this point, the server is expecting data */
1235 sock_write(sock, msgtext, msg_size);
1236 if (msgtext[msg_size-1] != 10) {
1237 lprintf(CTDL_WARNING, "Possible problem: message did not "
1238 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1242 sock_write(sock, ".\r\n", 3);
1243 if (ml_sock_gets(sock, buf) < 0) {
1245 strcpy(dsn, "Connection broken during SMTP message transmit");
1248 lprintf(CTDL_DEBUG, "%s\n", buf);
1249 if (buf[0] != '2') {
1250 if (buf[0] == '4') {
1252 safestrncpy(dsn, &buf[4], 1023);
1257 safestrncpy(dsn, &buf[4], 1023);
1263 safestrncpy(dsn, &buf[4], 1023);
1266 lprintf(CTDL_DEBUG, ">QUIT\n");
1267 sock_write(sock, "QUIT\r\n", 6);
1268 ml_sock_gets(sock, buf);
1269 lprintf(CTDL_DEBUG, "<%s\n", buf);
1270 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1273 bail: free(msgtext);
1276 /* Write something to the syslog (which may or may not be where the
1277 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1279 if (enable_syslog) {
1280 syslog((LOG_MAIL | LOG_INFO),
1281 "%ld: to=<%s>, relay=%s, stat=%s",
1295 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1296 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1297 * a "bounce" message (delivery status notification).
1299 void smtp_do_bounce(char *instr) {
1307 char bounceto[1024];
1308 int num_bounces = 0;
1309 int bounce_this = 0;
1310 long bounce_msgid = (-1);
1311 time_t submitted = 0L;
1312 struct CtdlMessage *bmsg = NULL;
1314 struct recptypes *valid;
1315 int successful_bounce = 0;
1317 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1318 strcpy(bounceto, "");
1320 lines = num_tokens(instr, '\n');
1323 /* See if it's time to give up on delivery of this message */
1324 for (i=0; i<lines; ++i) {
1325 extract_token(buf, instr, i, '\n', sizeof buf);
1326 extract_token(key, buf, 0, '|', sizeof key);
1327 extract_token(addr, buf, 1, '|', sizeof addr);
1328 if (!strcasecmp(key, "submitted")) {
1329 submitted = atol(addr);
1333 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1337 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1338 if (bmsg == NULL) return;
1339 memset(bmsg, 0, sizeof(struct CtdlMessage));
1341 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1342 bmsg->cm_anon_type = MES_NORMAL;
1343 bmsg->cm_format_type = 1;
1344 bmsg->cm_fields['A'] = strdup("Citadel");
1345 bmsg->cm_fields['O'] = strdup(MAILROOM);
1346 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1347 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1349 if (give_up) bmsg->cm_fields['M'] = strdup(
1350 "A message you sent could not be delivered to some or all of its recipients\n"
1351 "due to prolonged unavailability of its destination(s).\n"
1352 "Giving up on the following addresses:\n\n"
1355 else bmsg->cm_fields['M'] = strdup(
1356 "A message you sent could not be delivered to some or all of its recipients.\n"
1357 "The following addresses were undeliverable:\n\n"
1361 * Now go through the instructions checking for stuff.
1363 for (i=0; i<lines; ++i) {
1364 extract_token(buf, instr, i, '\n', sizeof buf);
1365 extract_token(key, buf, 0, '|', sizeof key);
1366 extract_token(addr, buf, 1, '|', sizeof addr);
1367 status = extract_int(buf, 2);
1368 extract_token(dsn, buf, 3, '|', sizeof dsn);
1371 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1372 key, addr, status, dsn);
1374 if (!strcasecmp(key, "bounceto")) {
1375 strcpy(bounceto, addr);
1379 (!strcasecmp(key, "local"))
1380 || (!strcasecmp(key, "remote"))
1381 || (!strcasecmp(key, "ignet"))
1382 || (!strcasecmp(key, "room"))
1384 if (status == 5) bounce_this = 1;
1385 if (give_up) bounce_this = 1;
1391 if (bmsg->cm_fields['M'] == NULL) {
1392 lprintf(CTDL_ERR, "ERROR ... M field is null "
1393 "(%s:%d)\n", __FILE__, __LINE__);
1396 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1397 strlen(bmsg->cm_fields['M']) + 1024 );
1398 strcat(bmsg->cm_fields['M'], addr);
1399 strcat(bmsg->cm_fields['M'], ": ");
1400 strcat(bmsg->cm_fields['M'], dsn);
1401 strcat(bmsg->cm_fields['M'], "\n");
1403 remove_token(instr, i, '\n');
1409 /* Deliver the bounce if there's anything worth mentioning */
1410 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1411 if (num_bounces > 0) {
1413 /* First try the user who sent the message */
1414 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1415 if (strlen(bounceto) == 0) {
1416 lprintf(CTDL_ERR, "No bounce address specified\n");
1417 bounce_msgid = (-1L);
1420 /* Can we deliver the bounce to the original sender? */
1421 valid = validate_recipients(bounceto);
1422 if (valid != NULL) {
1423 if (valid->num_error == 0) {
1424 CtdlSubmitMsg(bmsg, valid, "");
1425 successful_bounce = 1;
1429 /* If not, post it in the Aide> room */
1430 if (successful_bounce == 0) {
1431 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1434 /* Free up the memory we used */
1435 if (valid != NULL) {
1440 CtdlFreeMessage(bmsg);
1441 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1446 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1447 * set of delivery instructions for completed deliveries and remove them.
1449 * It returns the number of incomplete deliveries remaining.
1451 int smtp_purge_completed_deliveries(char *instr) {
1462 lines = num_tokens(instr, '\n');
1463 for (i=0; i<lines; ++i) {
1464 extract_token(buf, instr, i, '\n', sizeof buf);
1465 extract_token(key, buf, 0, '|', sizeof key);
1466 extract_token(addr, buf, 1, '|', sizeof addr);
1467 status = extract_int(buf, 2);
1468 extract_token(dsn, buf, 3, '|', sizeof dsn);
1473 (!strcasecmp(key, "local"))
1474 || (!strcasecmp(key, "remote"))
1475 || (!strcasecmp(key, "ignet"))
1476 || (!strcasecmp(key, "room"))
1478 if (status == 2) completed = 1;
1483 remove_token(instr, i, '\n');
1496 * Called by smtp_do_queue() to handle an individual message.
1498 void smtp_do_procmsg(long msgnum, void *userdata) {
1499 struct CtdlMessage *msg;
1501 char *results = NULL;
1509 long text_msgid = (-1);
1510 int incomplete_deliveries_remaining;
1511 time_t attempted = 0L;
1512 time_t last_attempted = 0L;
1513 time_t retry = SMTP_RETRY_INTERVAL;
1515 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1517 msg = CtdlFetchMessage(msgnum, 1);
1519 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1523 instr = strdup(msg->cm_fields['M']);
1524 CtdlFreeMessage(msg);
1526 /* Strip out the headers amd any other non-instruction line */
1527 lines = num_tokens(instr, '\n');
1528 for (i=0; i<lines; ++i) {
1529 extract_token(buf, instr, i, '\n', sizeof buf);
1530 if (num_tokens(buf, '|') < 2) {
1531 remove_token(instr, i, '\n');
1537 /* Learn the message ID and find out about recent delivery attempts */
1538 lines = num_tokens(instr, '\n');
1539 for (i=0; i<lines; ++i) {
1540 extract_token(buf, instr, i, '\n', sizeof buf);
1541 extract_token(key, buf, 0, '|', sizeof key);
1542 if (!strcasecmp(key, "msgid")) {
1543 text_msgid = extract_long(buf, 1);
1545 if (!strcasecmp(key, "retry")) {
1546 /* double the retry interval after each attempt */
1547 retry = extract_long(buf, 1) * 2L;
1548 if (retry > SMTP_RETRY_MAX) {
1549 retry = SMTP_RETRY_MAX;
1551 remove_token(instr, i, '\n');
1553 if (!strcasecmp(key, "attempted")) {
1554 attempted = extract_long(buf, 1);
1555 if (attempted > last_attempted)
1556 last_attempted = attempted;
1561 * Postpone delivery if we've already tried recently.
1563 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1564 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1571 * Bail out if there's no actual message associated with this
1573 if (text_msgid < 0L) {
1574 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1579 /* Plow through the instructions looking for 'remote' directives and
1580 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1581 * were experienced and it's time to try again)
1583 lines = num_tokens(instr, '\n');
1584 for (i=0; i<lines; ++i) {
1585 extract_token(buf, instr, i, '\n', sizeof buf);
1586 extract_token(key, buf, 0, '|', sizeof key);
1587 extract_token(addr, buf, 1, '|', sizeof addr);
1588 status = extract_int(buf, 2);
1589 extract_token(dsn, buf, 3, '|', sizeof dsn);
1590 if ( (!strcasecmp(key, "remote"))
1591 && ((status==0)||(status==3)||(status==4)) ) {
1593 /* Remove this "remote" instruction from the set,
1594 * but replace the set's final newline if
1595 * remove_token() stripped it. It has to be there.
1597 remove_token(instr, i, '\n');
1598 if (instr[strlen(instr)-1] != '\n') {
1599 strcat(instr, "\n");
1604 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1605 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1607 if (results == NULL) {
1608 results = malloc(1024);
1609 memset(results, 0, 1024);
1612 results = realloc(results,
1613 strlen(results) + 1024);
1615 snprintf(&results[strlen(results)], 1024,
1617 key, addr, status, dsn);
1622 if (results != NULL) {
1623 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1624 strcat(instr, results);
1629 /* Generate 'bounce' messages */
1630 smtp_do_bounce(instr);
1632 /* Go through the delivery list, deleting completed deliveries */
1633 incomplete_deliveries_remaining =
1634 smtp_purge_completed_deliveries(instr);
1638 * No delivery instructions remain, so delete both the instructions
1639 * message and the message message.
1641 if (incomplete_deliveries_remaining <= 0) {
1643 delmsgs[0] = msgnum;
1644 delmsgs[1] = text_msgid;
1645 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "", 0);
1649 * Uncompleted delivery instructions remain, so delete the old
1650 * instructions and replace with the updated ones.
1652 if (incomplete_deliveries_remaining > 0) {
1653 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "", 0);
1654 msg = malloc(sizeof(struct CtdlMessage));
1655 memset(msg, 0, sizeof(struct CtdlMessage));
1656 msg->cm_magic = CTDLMESSAGE_MAGIC;
1657 msg->cm_anon_type = MES_NORMAL;
1658 msg->cm_format_type = FMT_RFC822;
1659 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1660 snprintf(msg->cm_fields['M'],
1662 "Content-type: %s\n\n%s\n"
1665 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1666 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1667 CtdlFreeMessage(msg);
1678 * Run through the queue sending out messages.
1680 void smtp_do_queue(void) {
1681 static int doing_queue = 0;
1684 * This is a simple concurrency check to make sure only one queue run
1685 * is done at a time. We could do this with a mutex, but since we
1686 * don't really require extremely fine granularity here, we'll do it
1687 * with a static variable instead.
1689 if (doing_queue) return;
1693 * Go ahead and run the queue
1695 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1697 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1698 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1701 CtdlForEachMessage(MSGS_ALL, 0L, NULL,
1702 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1704 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1711 /*****************************************************************************/
1712 /* SMTP UTILITY COMMANDS */
1713 /*****************************************************************************/
1715 void cmd_smtp(char *argbuf) {
1722 if (CtdlAccessCheck(ac_aide)) return;
1724 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1726 if (!strcasecmp(cmd, "mx")) {
1727 extract_token(node, argbuf, 1, '|', sizeof node);
1728 num_mxhosts = getmx(buf, node);
1729 cprintf("%d %d MX hosts listed for %s\n",
1730 LISTING_FOLLOWS, num_mxhosts, node);
1731 for (i=0; i<num_mxhosts; ++i) {
1732 extract_token(node, buf, i, '|', sizeof node);
1733 cprintf("%s\n", node);
1739 else if (!strcasecmp(cmd, "runqueue")) {
1741 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1746 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1753 * Initialize the SMTP outbound queue
1755 void smtp_init_spoolout(void) {
1756 struct ctdlroom qrbuf;
1759 * Create the room. This will silently fail if the room already
1760 * exists, and that's perfectly ok, because we want it to exist.
1762 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1765 * Make sure it's set to be a "system room" so it doesn't show up
1766 * in the <K>nown rooms list for Aides.
1768 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1769 qrbuf.QRflags2 |= QR2_SYSTEM;
1777 /*****************************************************************************/
1778 /* MODULE INITIALIZATION STUFF */
1779 /*****************************************************************************/
1781 * This cleanup function blows away the temporary memory used by
1784 void smtp_cleanup_function(void) {
1786 /* Don't do this stuff if this is not an SMTP session! */
1787 if (CC->h_command_function != smtp_command_loop) return;
1789 lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1799 char *serv_smtp_init(void)
1802 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1809 CtdlRegisterServiceHook(config.c_smtps_port,
1816 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1822 CtdlRegisterServiceHook(0, /* local LMTP */
1828 CtdlRegisterServiceHook(0, /* local LMTP */
1829 file_lmtp_unfiltered_socket,
1830 lmtp_unfiltered_greeting,
1834 smtp_init_spoolout();
1835 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1836 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1837 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");