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 */
108 enum { /* Delivery modes */
113 #define SMTP CC->SMTP
114 #define SMTP_RECPS CC->SMTP_RECPS
115 #define SMTP_ROOMS CC->SMTP_ROOMS
118 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
122 /*****************************************************************************/
123 /* SMTP SERVER (INBOUND) STUFF */
124 /*****************************************************************************/
128 * Here's where our SMTP session begins its happy day.
130 void smtp_greeting(void) {
132 strcpy(CC->cs_clientname, "SMTP session");
133 CC->internal_pgm = 1;
134 CC->cs_flags |= CS_STEALTH;
135 SMTP = malloc(sizeof(struct citsmtp));
136 SMTP_RECPS = malloc(SIZ);
137 SMTP_ROOMS = malloc(SIZ);
138 memset(SMTP, 0, sizeof(struct citsmtp));
139 memset(SMTP_RECPS, 0, SIZ);
140 memset(SMTP_ROOMS, 0, SIZ);
142 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
147 * SMTPS is just like SMTP, except it goes crypto right away.
150 void smtps_greeting(void) {
151 CtdlStartTLS(NULL, NULL, NULL);
158 * SMTP MSA port requires authentication.
160 void smtp_msa_greeting(void) {
167 * LMTP is like SMTP but with some extra bonus footage added.
169 void lmtp_greeting(void) {
176 * We also have an unfiltered LMTP socket that bypasses spam filters.
178 void lmtp_unfiltered_greeting(void) {
181 SMTP->is_unfiltered = 1;
186 * Login greeting common to all auth methods
188 void smtp_auth_greeting(void) {
189 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
190 lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
191 CC->internal_pgm = 0;
192 CC->cs_flags &= ~CS_STEALTH;
197 * Implement HELO and EHLO commands.
199 * which_command: 0=HELO, 1=EHLO, 2=LHLO
201 void smtp_hello(char *argbuf, int which_command) {
203 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
205 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
206 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
210 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
211 cprintf("500 LHLO is only allowed when running LMTP\r\n");
215 if (which_command == 0) {
216 cprintf("250 Hello %s (%s [%s])\r\n",
223 if (which_command == 1) {
224 cprintf("250-Hello %s (%s [%s])\r\n",
231 cprintf("250-Greetings and joyous salutations.\r\n");
233 cprintf("250-HELP\r\n");
234 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
238 /* Only offer the PIPELINING command if TLS is inactive,
239 * because of flow control issues. Also, avoid offering TLS
240 * if TLS is already active. Finally, we only offer TLS on
241 * the SMTP-MSA port, not on the SMTP-MTA port, due to
242 * questionable reliability of TLS in certain sending MTA's.
244 if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
245 cprintf("250-PIPELINING\r\n");
246 cprintf("250-STARTTLS\r\n");
249 #else /* HAVE_OPENSSL */
251 /* Non SSL enabled server, so always offer PIPELINING. */
252 cprintf("250-PIPELINING\r\n");
254 #endif /* HAVE_OPENSSL */
256 cprintf("250-AUTH LOGIN PLAIN\r\n");
257 cprintf("250-AUTH=LOGIN PLAIN\r\n");
259 cprintf("250 ENHANCEDSTATUSCODES\r\n");
266 * Implement HELP command.
268 void smtp_help(void) {
269 cprintf("214-Commands accepted:\r\n");
270 cprintf("214- DATA\r\n");
271 cprintf("214- EHLO\r\n");
272 cprintf("214- EXPN\r\n");
273 cprintf("214- HELO\r\n");
274 cprintf("214- HELP\r\n");
275 cprintf("214- MAIL\r\n");
276 cprintf("214- NOOP\r\n");
277 cprintf("214- QUIT\r\n");
278 cprintf("214- RCPT\r\n");
279 cprintf("214- RSET\r\n");
280 cprintf("214- VRFY\r\n");
288 void smtp_get_user(char *argbuf) {
292 CtdlDecodeBase64(username, argbuf, SIZ);
293 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", username); */
294 if (CtdlLoginExistingUser(username) == login_ok) {
295 CtdlEncodeBase64(buf, "Password:", 9);
296 cprintf("334 %s\r\n", buf);
297 SMTP->command_state = smtp_password;
300 cprintf("500 5.7.0 No such user.\r\n");
301 SMTP->command_state = smtp_command;
309 void smtp_get_pass(char *argbuf) {
312 CtdlDecodeBase64(password, argbuf, SIZ);
313 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", password); */
314 if (CtdlTryPassword(password) == pass_ok) {
315 smtp_auth_greeting();
318 cprintf("535 5.7.0 Authentication failed.\r\n");
320 SMTP->command_state = smtp_command;
327 void smtp_auth(char *argbuf) {
328 char username_prompt[64];
330 char encoded_authstring[1024];
331 char decoded_authstring[1024];
337 cprintf("504 5.7.4 Already logged in.\r\n");
341 extract_token(method, argbuf, 0, ' ', sizeof method);
343 if (!strncasecmp(method, "login", 5) ) {
344 if (strlen(argbuf) >= 7) {
345 smtp_get_user(&argbuf[6]);
348 CtdlEncodeBase64(username_prompt, "Username:", 9);
349 cprintf("334 %s\r\n", username_prompt);
350 SMTP->command_state = smtp_user;
355 if (!strncasecmp(method, "plain", 5) ) {
356 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
357 CtdlDecodeBase64(decoded_authstring,
359 strlen(encoded_authstring) );
360 safestrncpy(ident, decoded_authstring, sizeof ident);
361 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
362 safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
364 if (CtdlLoginExistingUser(user) == login_ok) {
365 if (CtdlTryPassword(pass) == pass_ok) {
366 smtp_auth_greeting();
370 cprintf("504 5.7.4 Authentication failed.\r\n");
373 if (strncasecmp(method, "login", 5) ) {
374 cprintf("504 5.7.4 Unknown authentication method.\r\n");
382 * Back end for smtp_vrfy() command
384 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
386 if (!fuzzy_match(us, SMTP->vrfy_match)) {
388 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
394 * Implements the VRFY (verify user name) command.
395 * Performs fuzzy match on full user names.
397 void smtp_vrfy(char *argbuf) {
398 SMTP->vrfy_count = 0;
399 strcpy(SMTP->vrfy_match, argbuf);
400 ForEachUser(smtp_vrfy_backend, NULL);
402 if (SMTP->vrfy_count < 1) {
403 cprintf("550 5.1.1 String does not match anything.\r\n");
405 else if (SMTP->vrfy_count == 1) {
406 cprintf("250 %s <cit%ld@%s>\r\n",
407 SMTP->vrfy_buffer.fullname,
408 SMTP->vrfy_buffer.usernum,
411 else if (SMTP->vrfy_count > 1) {
412 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
421 * Back end for smtp_expn() command
423 void smtp_expn_backend(struct ctdluser *us, void *data) {
425 if (!fuzzy_match(us, SMTP->vrfy_match)) {
427 if (SMTP->vrfy_count >= 1) {
428 cprintf("250-%s <cit%ld@%s>\r\n",
429 SMTP->vrfy_buffer.fullname,
430 SMTP->vrfy_buffer.usernum,
435 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
441 * Implements the EXPN (expand user name) command.
442 * Performs fuzzy match on full user names.
444 void smtp_expn(char *argbuf) {
445 SMTP->vrfy_count = 0;
446 strcpy(SMTP->vrfy_match, argbuf);
447 ForEachUser(smtp_expn_backend, NULL);
449 if (SMTP->vrfy_count < 1) {
450 cprintf("550 5.1.1 String does not match anything.\r\n");
452 else if (SMTP->vrfy_count >= 1) {
453 cprintf("250 %s <cit%ld@%s>\r\n",
454 SMTP->vrfy_buffer.fullname,
455 SMTP->vrfy_buffer.usernum,
462 * Implements the RSET (reset state) command.
463 * Currently this just zeroes out the state buffer. If pointers to data
464 * allocated with malloc() are ever placed in the state buffer, we have to
465 * be sure to free() them first!
467 * Set do_response to nonzero to output the SMTP RSET response code.
469 void smtp_rset(int do_response) {
474 * Our entire SMTP state is discarded when a RSET command is issued,
475 * but we need to preserve this one little piece of information, so
476 * we save it for later.
478 is_lmtp = SMTP->is_lmtp;
479 is_unfiltered = SMTP->is_unfiltered;
481 memset(SMTP, 0, sizeof(struct citsmtp));
484 * It is somewhat ambiguous whether we want to log out when a RSET
485 * command is issued. Here's the code to do it. It is commented out
486 * because some clients (such as Pine) issue RSET commands before
487 * each message, but still expect to be logged in.
489 * if (CC->logged_in) {
495 * Reinstate this little piece of information we saved (see above).
497 SMTP->is_lmtp = is_lmtp;
498 SMTP->is_unfiltered = is_unfiltered;
501 cprintf("250 2.0.0 Zap!\r\n");
506 * Clear out the portions of the state buffer that need to be cleared out
507 * after the DATA command finishes.
509 void smtp_data_clear(void) {
510 strcpy(SMTP->from, "");
511 strcpy(SMTP->recipients, "");
512 SMTP->number_of_recipients = 0;
513 SMTP->delivery_mode = 0;
514 SMTP->message_originated_locally = 0;
520 * Implements the "MAIL From:" command
522 void smtp_mail(char *argbuf) {
527 if (strlen(SMTP->from) != 0) {
528 cprintf("503 5.1.0 Only one sender permitted\r\n");
532 if (strncasecmp(argbuf, "From:", 5)) {
533 cprintf("501 5.1.7 Syntax error\r\n");
537 strcpy(SMTP->from, &argbuf[5]);
539 if (haschar(SMTP->from, '<') > 0) {
540 stripallbut(SMTP->from, '<', '>');
543 /* We used to reject empty sender names, until it was brought to our
544 * attention that RFC1123 5.2.9 requires that this be allowed. So now
545 * we allow it, but replace the empty string with a fake
546 * address so we don't have to contend with the empty string causing
547 * other code to fail when it's expecting something there.
549 if (strlen(SMTP->from) == 0) {
550 strcpy(SMTP->from, "someone@somewhere.org");
553 /* If this SMTP connection is from a logged-in user, force the 'from'
554 * to be the user's Internet e-mail address as Citadel knows it.
557 safestrncpy(SMTP->from, CC->cs_inet_email, sizeof SMTP->from);
558 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
559 SMTP->message_originated_locally = 1;
563 else if (SMTP->is_lmtp) {
564 /* Bypass forgery checking for LMTP */
567 /* Otherwise, make sure outsiders aren't trying to forge mail from
568 * this system (unless, of course, c_allow_spoofing is enabled)
570 else if (config.c_allow_spoofing == 0) {
571 process_rfc822_addr(SMTP->from, user, node, name);
572 if (CtdlHostAlias(node) != hostalias_nomatch) {
574 "You must log in to send mail from %s\r\n",
576 strcpy(SMTP->from, "");
581 cprintf("250 2.0.0 Sender ok\r\n");
587 * Implements the "RCPT To:" command
589 void smtp_rcpt(char *argbuf) {
591 char message_to_spammer[SIZ];
592 struct recptypes *valid = NULL;
594 if (strlen(SMTP->from) == 0) {
595 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
599 if (strncasecmp(argbuf, "To:", 3)) {
600 cprintf("501 5.1.7 Syntax error\r\n");
604 if ( (SMTP->is_msa) && (!CC->logged_in) ) {
606 "You must log in to send mail on this port.\r\n");
607 strcpy(SMTP->from, "");
611 strcpy(recp, &argbuf[3]);
613 stripallbut(recp, '<', '>');
615 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
616 cprintf("452 4.5.3 Too many recipients\r\n");
621 if ( (!CC->logged_in)
622 && (!SMTP->is_lmtp) ) {
623 if (rbl_check(message_to_spammer)) {
624 cprintf("550 %s\r\n", message_to_spammer);
625 /* no need to free(valid), it's not allocated yet */
630 valid = validate_recipients(recp);
631 if (valid->num_error != 0) {
632 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
637 if (valid->num_internet > 0) {
639 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
640 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
647 if (valid->num_internet > 0) {
648 if ( (SMTP->message_originated_locally == 0)
649 && (SMTP->is_lmtp == 0) ) {
650 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
656 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
657 if (strlen(SMTP->recipients) > 0) {
658 strcat(SMTP->recipients, ",");
660 strcat(SMTP->recipients, recp);
661 SMTP->number_of_recipients += 1;
668 * Implements the DATA command
670 void smtp_data(void) {
672 struct CtdlMessage *msg;
675 struct recptypes *valid;
680 if (strlen(SMTP->from) == 0) {
681 cprintf("503 5.5.1 Need MAIL command first.\r\n");
685 if (SMTP->number_of_recipients < 1) {
686 cprintf("503 5.5.1 Need RCPT command first.\r\n");
690 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
692 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
695 if (body != NULL) snprintf(body, 4096,
696 "Received: from %s (%s [%s])\n"
704 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
707 "Unable to save message: internal error.\r\n");
711 lprintf(CTDL_DEBUG, "Converting message...\n");
712 msg = convert_internet_message(body);
714 /* If the user is locally authenticated, FORCE the From: header to
715 * show up as the real sender. Yes, this violates the RFC standard,
716 * but IT MAKES SENSE. If you prefer strict RFC adherence over
717 * common sense, you can disable this in the configuration.
719 * We also set the "message room name" ('O' field) to MAILROOM
720 * (which is Mail> on most systems) to prevent it from getting set
721 * to something ugly like "0000058008.Sent Items>" when the message
722 * is read with a Citadel client.
724 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
725 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
726 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
727 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
728 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
729 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
730 msg->cm_fields['A'] = strdup(CC->user.fullname);
731 msg->cm_fields['N'] = strdup(config.c_nodename);
732 msg->cm_fields['H'] = strdup(config.c_humannode);
733 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
734 msg->cm_fields['O'] = strdup(MAILROOM);
737 /* Submit the message into the Citadel system. */
738 valid = validate_recipients(SMTP->recipients);
740 /* If there are modules that want to scan this message before final
741 * submission (such as virus checkers or spam filters), call them now
742 * and give them an opportunity to reject the message.
744 if (SMTP->is_unfiltered) {
748 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
751 if (scan_errors > 0) { /* We don't want this message! */
753 if (msg->cm_fields['0'] == NULL) {
754 msg->cm_fields['0'] = strdup(
755 "5.7.1 Message rejected by filter");
758 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
761 else { /* Ok, we'll accept this message. */
762 msgnum = CtdlSubmitMsg(msg, valid, "");
764 sprintf(result, "250 2.0.0 Message accepted.\r\n");
767 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
771 /* For SMTP and ESTMP, just print the result message. For LMTP, we
772 * have to print one result message for each recipient. Since there
773 * is nothing in Citadel which would cause different recipients to
774 * have different results, we can get away with just spitting out the
775 * same message once for each recipient.
778 for (i=0; i<SMTP->number_of_recipients; ++i) {
779 cprintf("%s", result);
783 cprintf("%s", result);
786 /* Write something to the syslog (which may or may not be where the
787 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
790 syslog((LOG_MAIL | LOG_INFO),
791 "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
794 SMTP->number_of_recipients,
802 CtdlFreeMessage(msg);
804 smtp_data_clear(); /* clear out the buffers now */
809 * implements the STARTTLS command (Citadel API version)
812 void smtp_starttls(void)
814 char ok_response[SIZ];
815 char nosup_response[SIZ];
816 char error_response[SIZ];
819 "200 2.0.0 Begin TLS negotiation now\r\n");
820 sprintf(nosup_response,
821 "554 5.7.3 TLS not supported here\r\n");
822 sprintf(error_response,
823 "554 5.7.3 Internal error\r\n");
824 CtdlStartTLS(ok_response, nosup_response, error_response);
832 * Main command loop for SMTP sessions.
834 void smtp_command_loop(void) {
838 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
839 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
840 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
844 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
845 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
847 if (SMTP->command_state == smtp_user) {
848 smtp_get_user(cmdbuf);
851 else if (SMTP->command_state == smtp_password) {
852 smtp_get_pass(cmdbuf);
855 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
856 smtp_auth(&cmdbuf[5]);
859 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
863 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
864 smtp_expn(&cmdbuf[5]);
867 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
868 smtp_hello(&cmdbuf[5], 0);
871 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
872 smtp_hello(&cmdbuf[5], 1);
875 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
876 smtp_hello(&cmdbuf[5], 2);
879 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
883 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
884 smtp_mail(&cmdbuf[5]);
887 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
888 cprintf("250 NOOP\r\n");
891 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
892 cprintf("221 Goodbye...\r\n");
897 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
898 smtp_rcpt(&cmdbuf[5]);
901 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
905 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
909 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
910 smtp_vrfy(&cmdbuf[5]);
914 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
923 /*****************************************************************************/
924 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
925 /*****************************************************************************/
932 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
935 void smtp_try(const char *key, const char *addr, int *status,
936 char *dsn, size_t n, long msgnum)
943 char user[1024], node[1024], name[1024];
956 /* Parse out the host portion of the recipient address */
957 process_rfc822_addr(addr, user, node, name);
959 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
962 /* Load the message out of the database */
963 CC->redirect_buffer = malloc(SIZ);
964 CC->redirect_len = 0;
965 CC->redirect_alloc = SIZ;
966 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
967 msgtext = CC->redirect_buffer;
968 msg_size = CC->redirect_len;
969 CC->redirect_buffer = NULL;
970 CC->redirect_len = 0;
971 CC->redirect_alloc = 0;
973 /* Extract something to send later in the 'MAIL From:' command */
974 strcpy(mailfrom, "");
978 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
981 if (!strncasecmp(buf, "From:", 5)) {
982 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
984 for (i=0; i<strlen(mailfrom); ++i) {
985 if (!isprint(mailfrom[i])) {
986 strcpy(&mailfrom[i], &mailfrom[i+1]);
991 /* Strip out parenthesized names */
994 for (i=0; i<strlen(mailfrom); ++i) {
995 if (mailfrom[i] == '(') lp = i;
996 if (mailfrom[i] == ')') rp = i;
998 if ((lp>0)&&(rp>lp)) {
999 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
1002 /* Prefer brokketized names */
1005 for (i=0; i<strlen(mailfrom); ++i) {
1006 if (mailfrom[i] == '<') lp = i;
1007 if (mailfrom[i] == '>') rp = i;
1009 if ( (lp>=0) && (rp>lp) ) {
1011 strcpy(mailfrom, &mailfrom[lp]);
1016 } while (scan_done == 0);
1017 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
1018 stripallbut(mailfrom, '<', '>');
1020 /* Figure out what mail exchanger host we have to connect to */
1021 num_mxhosts = getmx(mxhosts, node);
1022 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
1023 if (num_mxhosts < 1) {
1025 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1030 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1031 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1032 strcpy(mx_user, "");
1033 strcpy(mx_pass, "");
1034 if (num_tokens(buf, '@') > 1) {
1035 extract_token(mx_user, buf, 0, '@', sizeof mx_user);
1036 if (num_tokens(mx_user, ':') > 1) {
1037 extract_token(mx_pass, mx_user, 1, ':', sizeof mx_pass);
1038 remove_token(mx_user, 1, ':');
1040 remove_token(buf, 0, '@');
1042 extract_token(mx_host, buf, 0, ':', sizeof mx_host);
1043 extract_token(mx_port, buf, 1, ':', sizeof mx_port);
1045 strcpy(mx_port, "25");
1047 lprintf(CTDL_DEBUG, "FIXME user<%s> pass<%s> host<%s> port<%s>\n",
1048 mx_user, mx_pass, mx_host, mx_port);
1049 lprintf(CTDL_DEBUG, "Trying %s : %s ...\n", mx_host, mx_port);
1050 sock = sock_connect(mx_host, mx_port, "tcp");
1051 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1052 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
1053 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
1057 *status = 4; /* dsn is already filled in */
1061 /* Process the SMTP greeting from the server */
1062 if (ml_sock_gets(sock, buf) < 0) {
1064 strcpy(dsn, "Connection broken during SMTP conversation");
1067 lprintf(CTDL_DEBUG, "<%s\n", buf);
1068 if (buf[0] != '2') {
1069 if (buf[0] == '4') {
1071 safestrncpy(dsn, &buf[4], 1023);
1076 safestrncpy(dsn, &buf[4], 1023);
1081 /* At this point we know we are talking to a real SMTP server */
1083 /* Do a EHLO command. If it fails, try the HELO command. */
1084 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1085 lprintf(CTDL_DEBUG, ">%s", buf);
1086 sock_write(sock, buf, strlen(buf));
1087 if (ml_sock_gets(sock, buf) < 0) {
1089 strcpy(dsn, "Connection broken during SMTP HELO");
1092 lprintf(CTDL_DEBUG, "<%s\n", buf);
1093 if (buf[0] != '2') {
1094 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1095 lprintf(CTDL_DEBUG, ">%s", buf);
1096 sock_write(sock, buf, strlen(buf));
1097 if (ml_sock_gets(sock, buf) < 0) {
1099 strcpy(dsn, "Connection broken during SMTP HELO");
1103 if (buf[0] != '2') {
1104 if (buf[0] == '4') {
1106 safestrncpy(dsn, &buf[4], 1023);
1111 safestrncpy(dsn, &buf[4], 1023);
1116 /* Do an AUTH command if necessary */
1117 if (strlen(mx_user) > 0) {
1118 sprintf(buf, "%s%c%s%c%s%c", mx_user, 0, mx_user, 0, mx_pass, 0);
1119 CtdlEncodeBase64(mailfrom, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 3);
1120 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", mailfrom);
1121 lprintf(CTDL_DEBUG, ">%s", buf);
1122 sock_write(sock, buf, strlen(buf));
1123 if (ml_sock_gets(sock, buf) < 0) {
1125 strcpy(dsn, "Connection broken during SMTP AUTH");
1128 lprintf(CTDL_DEBUG, "<%s\n", buf);
1129 if (buf[0] != '2') {
1130 if (buf[0] == '4') {
1132 safestrncpy(dsn, &buf[4], 1023);
1137 safestrncpy(dsn, &buf[4], 1023);
1143 /* previous command succeeded, now try the MAIL From: command */
1144 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1145 lprintf(CTDL_DEBUG, ">%s", buf);
1146 sock_write(sock, buf, strlen(buf));
1147 if (ml_sock_gets(sock, buf) < 0) {
1149 strcpy(dsn, "Connection broken during SMTP MAIL");
1152 lprintf(CTDL_DEBUG, "<%s\n", buf);
1153 if (buf[0] != '2') {
1154 if (buf[0] == '4') {
1156 safestrncpy(dsn, &buf[4], 1023);
1161 safestrncpy(dsn, &buf[4], 1023);
1166 /* MAIL succeeded, now try the RCPT To: command */
1167 snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
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 RCPT");
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 /* RCPT succeeded, now try the DATA command */
1190 lprintf(CTDL_DEBUG, ">DATA\n");
1191 sock_write(sock, "DATA\r\n", 6);
1192 if (ml_sock_gets(sock, buf) < 0) {
1194 strcpy(dsn, "Connection broken during SMTP DATA");
1197 lprintf(CTDL_DEBUG, "<%s\n", buf);
1198 if (buf[0] != '3') {
1199 if (buf[0] == '4') {
1201 safestrncpy(dsn, &buf[4], 1023);
1206 safestrncpy(dsn, &buf[4], 1023);
1211 /* If we reach this point, the server is expecting data */
1212 sock_write(sock, msgtext, msg_size);
1213 if (msgtext[msg_size-1] != 10) {
1214 lprintf(CTDL_WARNING, "Possible problem: message did not "
1215 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1219 sock_write(sock, ".\r\n", 3);
1220 if (ml_sock_gets(sock, buf) < 0) {
1222 strcpy(dsn, "Connection broken during SMTP message transmit");
1225 lprintf(CTDL_DEBUG, "%s\n", buf);
1226 if (buf[0] != '2') {
1227 if (buf[0] == '4') {
1229 safestrncpy(dsn, &buf[4], 1023);
1234 safestrncpy(dsn, &buf[4], 1023);
1240 safestrncpy(dsn, &buf[4], 1023);
1243 lprintf(CTDL_DEBUG, ">QUIT\n");
1244 sock_write(sock, "QUIT\r\n", 6);
1245 ml_sock_gets(sock, buf);
1246 lprintf(CTDL_DEBUG, "<%s\n", buf);
1247 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1250 bail: free(msgtext);
1253 /* Write something to the syslog (which may or may not be where the
1254 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1256 if (enable_syslog) {
1257 syslog((LOG_MAIL | LOG_INFO),
1258 "%ld: to=<%s>, relay=%s, stat=%s",
1272 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1273 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1274 * a "bounce" message (delivery status notification).
1276 void smtp_do_bounce(char *instr) {
1284 char bounceto[1024];
1285 int num_bounces = 0;
1286 int bounce_this = 0;
1287 long bounce_msgid = (-1);
1288 time_t submitted = 0L;
1289 struct CtdlMessage *bmsg = NULL;
1291 struct recptypes *valid;
1292 int successful_bounce = 0;
1294 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1295 strcpy(bounceto, "");
1297 lines = num_tokens(instr, '\n');
1300 /* See if it's time to give up on delivery of this message */
1301 for (i=0; i<lines; ++i) {
1302 extract_token(buf, instr, i, '\n', sizeof buf);
1303 extract_token(key, buf, 0, '|', sizeof key);
1304 extract_token(addr, buf, 1, '|', sizeof addr);
1305 if (!strcasecmp(key, "submitted")) {
1306 submitted = atol(addr);
1310 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1316 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1317 if (bmsg == NULL) return;
1318 memset(bmsg, 0, sizeof(struct CtdlMessage));
1320 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1321 bmsg->cm_anon_type = MES_NORMAL;
1322 bmsg->cm_format_type = 1;
1323 bmsg->cm_fields['A'] = strdup("Citadel");
1324 bmsg->cm_fields['O'] = strdup(MAILROOM);
1325 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1326 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1328 if (give_up) bmsg->cm_fields['M'] = strdup(
1329 "A message you sent could not be delivered to some or all of its recipients\n"
1330 "due to prolonged unavailability of its destination(s).\n"
1331 "Giving up on the following addresses:\n\n"
1334 else bmsg->cm_fields['M'] = strdup(
1335 "A message you sent could not be delivered to some or all of its recipients.\n"
1336 "The following addresses were undeliverable:\n\n"
1340 * Now go through the instructions checking for stuff.
1342 for (i=0; i<lines; ++i) {
1343 extract_token(buf, instr, i, '\n', sizeof buf);
1344 extract_token(key, buf, 0, '|', sizeof key);
1345 extract_token(addr, buf, 1, '|', sizeof addr);
1346 status = extract_int(buf, 2);
1347 extract_token(dsn, buf, 3, '|', sizeof dsn);
1350 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1351 key, addr, status, dsn);
1353 if (!strcasecmp(key, "bounceto")) {
1354 strcpy(bounceto, addr);
1358 (!strcasecmp(key, "local"))
1359 || (!strcasecmp(key, "remote"))
1360 || (!strcasecmp(key, "ignet"))
1361 || (!strcasecmp(key, "room"))
1363 if (status == 5) bounce_this = 1;
1364 if (give_up) bounce_this = 1;
1370 if (bmsg->cm_fields['M'] == NULL) {
1371 lprintf(CTDL_ERR, "ERROR ... M field is null "
1372 "(%s:%d)\n", __FILE__, __LINE__);
1375 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1376 strlen(bmsg->cm_fields['M']) + 1024 );
1377 strcat(bmsg->cm_fields['M'], addr);
1378 strcat(bmsg->cm_fields['M'], ": ");
1379 strcat(bmsg->cm_fields['M'], dsn);
1380 strcat(bmsg->cm_fields['M'], "\n");
1382 remove_token(instr, i, '\n');
1388 /* Deliver the bounce if there's anything worth mentioning */
1389 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1390 if (num_bounces > 0) {
1392 /* First try the user who sent the message */
1393 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1394 if (strlen(bounceto) == 0) {
1395 lprintf(CTDL_ERR, "No bounce address specified\n");
1396 bounce_msgid = (-1L);
1399 /* Can we deliver the bounce to the original sender? */
1400 valid = validate_recipients(bounceto);
1401 if (valid != NULL) {
1402 if (valid->num_error == 0) {
1403 CtdlSubmitMsg(bmsg, valid, "");
1404 successful_bounce = 1;
1408 /* If not, post it in the Aide> room */
1409 if (successful_bounce == 0) {
1410 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1413 /* Free up the memory we used */
1414 if (valid != NULL) {
1419 CtdlFreeMessage(bmsg);
1420 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1425 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1426 * set of delivery instructions for completed deliveries and remove them.
1428 * It returns the number of incomplete deliveries remaining.
1430 int smtp_purge_completed_deliveries(char *instr) {
1441 lines = num_tokens(instr, '\n');
1442 for (i=0; i<lines; ++i) {
1443 extract_token(buf, instr, i, '\n', sizeof buf);
1444 extract_token(key, buf, 0, '|', sizeof key);
1445 extract_token(addr, buf, 1, '|', sizeof addr);
1446 status = extract_int(buf, 2);
1447 extract_token(dsn, buf, 3, '|', sizeof dsn);
1452 (!strcasecmp(key, "local"))
1453 || (!strcasecmp(key, "remote"))
1454 || (!strcasecmp(key, "ignet"))
1455 || (!strcasecmp(key, "room"))
1457 if (status == 2) completed = 1;
1462 remove_token(instr, i, '\n');
1475 * Called by smtp_do_queue() to handle an individual message.
1477 void smtp_do_procmsg(long msgnum, void *userdata) {
1478 struct CtdlMessage *msg;
1480 char *results = NULL;
1488 long text_msgid = (-1);
1489 int incomplete_deliveries_remaining;
1490 time_t attempted = 0L;
1491 time_t last_attempted = 0L;
1492 time_t retry = SMTP_RETRY_INTERVAL;
1494 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1496 msg = CtdlFetchMessage(msgnum, 1);
1498 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1502 instr = strdup(msg->cm_fields['M']);
1503 CtdlFreeMessage(msg);
1505 /* Strip out the headers amd any other non-instruction line */
1506 lines = num_tokens(instr, '\n');
1507 for (i=0; i<lines; ++i) {
1508 extract_token(buf, instr, i, '\n', sizeof buf);
1509 if (num_tokens(buf, '|') < 2) {
1510 remove_token(instr, i, '\n');
1516 /* Learn the message ID and find out about recent delivery attempts */
1517 lines = num_tokens(instr, '\n');
1518 for (i=0; i<lines; ++i) {
1519 extract_token(buf, instr, i, '\n', sizeof buf);
1520 extract_token(key, buf, 0, '|', sizeof key);
1521 if (!strcasecmp(key, "msgid")) {
1522 text_msgid = extract_long(buf, 1);
1524 if (!strcasecmp(key, "retry")) {
1525 /* double the retry interval after each attempt */
1526 retry = extract_long(buf, 1) * 2L;
1527 if (retry > SMTP_RETRY_MAX) {
1528 retry = SMTP_RETRY_MAX;
1530 remove_token(instr, i, '\n');
1532 if (!strcasecmp(key, "attempted")) {
1533 attempted = extract_long(buf, 1);
1534 if (attempted > last_attempted)
1535 last_attempted = attempted;
1540 * Postpone delivery if we've already tried recently.
1542 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1543 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1550 * Bail out if there's no actual message associated with this
1552 if (text_msgid < 0L) {
1553 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1558 /* Plow through the instructions looking for 'remote' directives and
1559 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1560 * were experienced and it's time to try again)
1562 lines = num_tokens(instr, '\n');
1563 for (i=0; i<lines; ++i) {
1564 extract_token(buf, instr, i, '\n', sizeof buf);
1565 extract_token(key, buf, 0, '|', sizeof key);
1566 extract_token(addr, buf, 1, '|', sizeof addr);
1567 status = extract_int(buf, 2);
1568 extract_token(dsn, buf, 3, '|', sizeof dsn);
1569 if ( (!strcasecmp(key, "remote"))
1570 && ((status==0)||(status==3)||(status==4)) ) {
1572 /* Remove this "remote" instruction from the set,
1573 * but replace the set's final newline if
1574 * remove_token() stripped it. It has to be there.
1576 remove_token(instr, i, '\n');
1577 if (instr[strlen(instr)-1] != '\n') {
1578 strcat(instr, "\n");
1583 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1584 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1586 if (results == NULL) {
1587 results = malloc(1024);
1588 memset(results, 0, 1024);
1591 results = realloc(results,
1592 strlen(results) + 1024);
1594 snprintf(&results[strlen(results)], 1024,
1596 key, addr, status, dsn);
1601 if (results != NULL) {
1602 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1603 strcat(instr, results);
1608 /* Generate 'bounce' messages */
1609 smtp_do_bounce(instr);
1611 /* Go through the delivery list, deleting completed deliveries */
1612 incomplete_deliveries_remaining =
1613 smtp_purge_completed_deliveries(instr);
1617 * No delivery instructions remain, so delete both the instructions
1618 * message and the message message.
1620 if (incomplete_deliveries_remaining <= 0) {
1621 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "", 0);
1622 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "", 0);
1627 * Uncompleted delivery instructions remain, so delete the old
1628 * instructions and replace with the updated ones.
1630 if (incomplete_deliveries_remaining > 0) {
1631 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "", 0);
1632 msg = malloc(sizeof(struct CtdlMessage));
1633 memset(msg, 0, sizeof(struct CtdlMessage));
1634 msg->cm_magic = CTDLMESSAGE_MAGIC;
1635 msg->cm_anon_type = MES_NORMAL;
1636 msg->cm_format_type = FMT_RFC822;
1637 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1638 snprintf(msg->cm_fields['M'],
1640 "Content-type: %s\n\n%s\n"
1643 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1644 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1645 CtdlFreeMessage(msg);
1656 * Run through the queue sending out messages.
1658 void smtp_do_queue(void) {
1659 static int doing_queue = 0;
1662 * This is a simple concurrency check to make sure only one queue run
1663 * is done at a time. We could do this with a mutex, but since we
1664 * don't really require extremely fine granularity here, we'll do it
1665 * with a static variable instead.
1667 if (doing_queue) return;
1671 * Go ahead and run the queue
1673 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1675 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1676 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1679 CtdlForEachMessage(MSGS_ALL, 0L,
1680 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1682 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1689 /*****************************************************************************/
1690 /* SMTP UTILITY COMMANDS */
1691 /*****************************************************************************/
1693 void cmd_smtp(char *argbuf) {
1700 if (CtdlAccessCheck(ac_aide)) return;
1702 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1704 if (!strcasecmp(cmd, "mx")) {
1705 extract_token(node, argbuf, 1, '|', sizeof node);
1706 num_mxhosts = getmx(buf, node);
1707 cprintf("%d %d MX hosts listed for %s\n",
1708 LISTING_FOLLOWS, num_mxhosts, node);
1709 for (i=0; i<num_mxhosts; ++i) {
1710 extract_token(node, buf, i, '|', sizeof node);
1711 cprintf("%s\n", node);
1717 else if (!strcasecmp(cmd, "runqueue")) {
1719 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1724 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1731 * Initialize the SMTP outbound queue
1733 void smtp_init_spoolout(void) {
1734 struct ctdlroom qrbuf;
1737 * Create the room. This will silently fail if the room already
1738 * exists, and that's perfectly ok, because we want it to exist.
1740 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1743 * Make sure it's set to be a "system room" so it doesn't show up
1744 * in the <K>nown rooms list for Aides.
1746 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1747 qrbuf.QRflags2 |= QR2_SYSTEM;
1755 /*****************************************************************************/
1756 /* MODULE INITIALIZATION STUFF */
1757 /*****************************************************************************/
1759 * This cleanup function blows away the temporary memory used by
1762 void smtp_cleanup_function(void) {
1764 /* Don't do this stuff if this is not an SMTP session! */
1765 if (CC->h_command_function != smtp_command_loop) return;
1767 lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1777 char *serv_smtp_init(void)
1780 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1787 CtdlRegisterServiceHook(config.c_smtps_port,
1794 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1800 CtdlRegisterServiceHook(0, /* local LMTP */
1806 CtdlRegisterServiceHook(0, /* local LMTP */
1807 file_lmtp_unfiltered_socket,
1808 lmtp_unfiltered_greeting,
1812 smtp_init_spoolout();
1813 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1814 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1815 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");