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 HELO command */
1084 snprintf(buf, sizeof buf, "HELO %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 if (buf[0] == '4') {
1096 safestrncpy(dsn, &buf[4], 1023);
1101 safestrncpy(dsn, &buf[4], 1023);
1106 /* Do an AUTH command if necessary */
1107 if (strlen(mx_user) > 0) {
1108 sprintf(buf, "%s%c%s%c%s%c", mx_user, 0, mx_user, 0, mx_pass, 0);
1109 CtdlEncodeBase64(mailfrom, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 3);
1110 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", mailfrom);
1111 lprintf(CTDL_DEBUG, ">%s", buf);
1112 sock_write(sock, buf, strlen(buf));
1113 if (ml_sock_gets(sock, buf) < 0) {
1115 strcpy(dsn, "Connection broken during SMTP AUTH");
1118 lprintf(CTDL_DEBUG, "<%s\n", buf);
1119 if (buf[0] != '2') {
1120 if (buf[0] == '4') {
1122 safestrncpy(dsn, &buf[4], 1023);
1127 safestrncpy(dsn, &buf[4], 1023);
1133 /* previous command succeeded, now try the MAIL From: command */
1134 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1135 lprintf(CTDL_DEBUG, ">%s", buf);
1136 sock_write(sock, buf, strlen(buf));
1137 if (ml_sock_gets(sock, buf) < 0) {
1139 strcpy(dsn, "Connection broken during SMTP MAIL");
1142 lprintf(CTDL_DEBUG, "<%s\n", buf);
1143 if (buf[0] != '2') {
1144 if (buf[0] == '4') {
1146 safestrncpy(dsn, &buf[4], 1023);
1151 safestrncpy(dsn, &buf[4], 1023);
1156 /* MAIL succeeded, now try the RCPT To: command */
1157 snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
1158 lprintf(CTDL_DEBUG, ">%s", buf);
1159 sock_write(sock, buf, strlen(buf));
1160 if (ml_sock_gets(sock, buf) < 0) {
1162 strcpy(dsn, "Connection broken during SMTP RCPT");
1165 lprintf(CTDL_DEBUG, "<%s\n", buf);
1166 if (buf[0] != '2') {
1167 if (buf[0] == '4') {
1169 safestrncpy(dsn, &buf[4], 1023);
1174 safestrncpy(dsn, &buf[4], 1023);
1179 /* RCPT succeeded, now try the DATA command */
1180 lprintf(CTDL_DEBUG, ">DATA\n");
1181 sock_write(sock, "DATA\r\n", 6);
1182 if (ml_sock_gets(sock, buf) < 0) {
1184 strcpy(dsn, "Connection broken during SMTP DATA");
1187 lprintf(CTDL_DEBUG, "<%s\n", buf);
1188 if (buf[0] != '3') {
1189 if (buf[0] == '4') {
1191 safestrncpy(dsn, &buf[4], 1023);
1196 safestrncpy(dsn, &buf[4], 1023);
1201 /* If we reach this point, the server is expecting data */
1202 sock_write(sock, msgtext, msg_size);
1203 if (msgtext[msg_size-1] != 10) {
1204 lprintf(CTDL_WARNING, "Possible problem: message did not "
1205 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1209 sock_write(sock, ".\r\n", 3);
1210 if (ml_sock_gets(sock, buf) < 0) {
1212 strcpy(dsn, "Connection broken during SMTP message transmit");
1215 lprintf(CTDL_DEBUG, "%s\n", buf);
1216 if (buf[0] != '2') {
1217 if (buf[0] == '4') {
1219 safestrncpy(dsn, &buf[4], 1023);
1224 safestrncpy(dsn, &buf[4], 1023);
1230 safestrncpy(dsn, &buf[4], 1023);
1233 lprintf(CTDL_DEBUG, ">QUIT\n");
1234 sock_write(sock, "QUIT\r\n", 6);
1235 ml_sock_gets(sock, buf);
1236 lprintf(CTDL_DEBUG, "<%s\n", buf);
1237 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1240 bail: free(msgtext);
1243 /* Write something to the syslog (which may or may not be where the
1244 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1246 if (enable_syslog) {
1247 syslog((LOG_MAIL | LOG_INFO),
1248 "%ld: to=<%s>, relay=%s, stat=%s",
1262 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1263 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1264 * a "bounce" message (delivery status notification).
1266 void smtp_do_bounce(char *instr) {
1274 char bounceto[1024];
1275 int num_bounces = 0;
1276 int bounce_this = 0;
1277 long bounce_msgid = (-1);
1278 time_t submitted = 0L;
1279 struct CtdlMessage *bmsg = NULL;
1281 struct recptypes *valid;
1282 int successful_bounce = 0;
1284 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1285 strcpy(bounceto, "");
1287 lines = num_tokens(instr, '\n');
1290 /* See if it's time to give up on delivery of this message */
1291 for (i=0; i<lines; ++i) {
1292 extract_token(buf, instr, i, '\n', sizeof buf);
1293 extract_token(key, buf, 0, '|', sizeof key);
1294 extract_token(addr, buf, 1, '|', sizeof addr);
1295 if (!strcasecmp(key, "submitted")) {
1296 submitted = atol(addr);
1300 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1306 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1307 if (bmsg == NULL) return;
1308 memset(bmsg, 0, sizeof(struct CtdlMessage));
1310 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1311 bmsg->cm_anon_type = MES_NORMAL;
1312 bmsg->cm_format_type = 1;
1313 bmsg->cm_fields['A'] = strdup("Citadel");
1314 bmsg->cm_fields['O'] = strdup(MAILROOM);
1315 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1316 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1318 if (give_up) bmsg->cm_fields['M'] = strdup(
1319 "A message you sent could not be delivered to some or all of its recipients\n"
1320 "due to prolonged unavailability of its destination(s).\n"
1321 "Giving up on the following addresses:\n\n"
1324 else bmsg->cm_fields['M'] = strdup(
1325 "A message you sent could not be delivered to some or all of its recipients.\n"
1326 "The following addresses were undeliverable:\n\n"
1330 * Now go through the instructions checking for stuff.
1332 for (i=0; i<lines; ++i) {
1333 extract_token(buf, instr, i, '\n', sizeof buf);
1334 extract_token(key, buf, 0, '|', sizeof key);
1335 extract_token(addr, buf, 1, '|', sizeof addr);
1336 status = extract_int(buf, 2);
1337 extract_token(dsn, buf, 3, '|', sizeof dsn);
1340 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1341 key, addr, status, dsn);
1343 if (!strcasecmp(key, "bounceto")) {
1344 strcpy(bounceto, addr);
1348 (!strcasecmp(key, "local"))
1349 || (!strcasecmp(key, "remote"))
1350 || (!strcasecmp(key, "ignet"))
1351 || (!strcasecmp(key, "room"))
1353 if (status == 5) bounce_this = 1;
1354 if (give_up) bounce_this = 1;
1360 if (bmsg->cm_fields['M'] == NULL) {
1361 lprintf(CTDL_ERR, "ERROR ... M field is null "
1362 "(%s:%d)\n", __FILE__, __LINE__);
1365 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1366 strlen(bmsg->cm_fields['M']) + 1024 );
1367 strcat(bmsg->cm_fields['M'], addr);
1368 strcat(bmsg->cm_fields['M'], ": ");
1369 strcat(bmsg->cm_fields['M'], dsn);
1370 strcat(bmsg->cm_fields['M'], "\n");
1372 remove_token(instr, i, '\n');
1378 /* Deliver the bounce if there's anything worth mentioning */
1379 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1380 if (num_bounces > 0) {
1382 /* First try the user who sent the message */
1383 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1384 if (strlen(bounceto) == 0) {
1385 lprintf(CTDL_ERR, "No bounce address specified\n");
1386 bounce_msgid = (-1L);
1389 /* Can we deliver the bounce to the original sender? */
1390 valid = validate_recipients(bounceto);
1391 if (valid != NULL) {
1392 if (valid->num_error == 0) {
1393 CtdlSubmitMsg(bmsg, valid, "");
1394 successful_bounce = 1;
1398 /* If not, post it in the Aide> room */
1399 if (successful_bounce == 0) {
1400 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1403 /* Free up the memory we used */
1404 if (valid != NULL) {
1409 CtdlFreeMessage(bmsg);
1410 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1415 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1416 * set of delivery instructions for completed deliveries and remove them.
1418 * It returns the number of incomplete deliveries remaining.
1420 int smtp_purge_completed_deliveries(char *instr) {
1431 lines = num_tokens(instr, '\n');
1432 for (i=0; i<lines; ++i) {
1433 extract_token(buf, instr, i, '\n', sizeof buf);
1434 extract_token(key, buf, 0, '|', sizeof key);
1435 extract_token(addr, buf, 1, '|', sizeof addr);
1436 status = extract_int(buf, 2);
1437 extract_token(dsn, buf, 3, '|', sizeof dsn);
1442 (!strcasecmp(key, "local"))
1443 || (!strcasecmp(key, "remote"))
1444 || (!strcasecmp(key, "ignet"))
1445 || (!strcasecmp(key, "room"))
1447 if (status == 2) completed = 1;
1452 remove_token(instr, i, '\n');
1465 * Called by smtp_do_queue() to handle an individual message.
1467 void smtp_do_procmsg(long msgnum, void *userdata) {
1468 struct CtdlMessage *msg;
1470 char *results = NULL;
1478 long text_msgid = (-1);
1479 int incomplete_deliveries_remaining;
1480 time_t attempted = 0L;
1481 time_t last_attempted = 0L;
1482 time_t retry = SMTP_RETRY_INTERVAL;
1484 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1486 msg = CtdlFetchMessage(msgnum, 1);
1488 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1492 instr = strdup(msg->cm_fields['M']);
1493 CtdlFreeMessage(msg);
1495 /* Strip out the headers amd any other non-instruction line */
1496 lines = num_tokens(instr, '\n');
1497 for (i=0; i<lines; ++i) {
1498 extract_token(buf, instr, i, '\n', sizeof buf);
1499 if (num_tokens(buf, '|') < 2) {
1500 remove_token(instr, i, '\n');
1506 /* Learn the message ID and find out about recent delivery attempts */
1507 lines = num_tokens(instr, '\n');
1508 for (i=0; i<lines; ++i) {
1509 extract_token(buf, instr, i, '\n', sizeof buf);
1510 extract_token(key, buf, 0, '|', sizeof key);
1511 if (!strcasecmp(key, "msgid")) {
1512 text_msgid = extract_long(buf, 1);
1514 if (!strcasecmp(key, "retry")) {
1515 /* double the retry interval after each attempt */
1516 retry = extract_long(buf, 1) * 2L;
1517 if (retry > SMTP_RETRY_MAX) {
1518 retry = SMTP_RETRY_MAX;
1520 remove_token(instr, i, '\n');
1522 if (!strcasecmp(key, "attempted")) {
1523 attempted = extract_long(buf, 1);
1524 if (attempted > last_attempted)
1525 last_attempted = attempted;
1530 * Postpone delivery if we've already tried recently.
1532 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1533 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1540 * Bail out if there's no actual message associated with this
1542 if (text_msgid < 0L) {
1543 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1548 /* Plow through the instructions looking for 'remote' directives and
1549 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1550 * were experienced and it's time to try again)
1552 lines = num_tokens(instr, '\n');
1553 for (i=0; i<lines; ++i) {
1554 extract_token(buf, instr, i, '\n', sizeof buf);
1555 extract_token(key, buf, 0, '|', sizeof key);
1556 extract_token(addr, buf, 1, '|', sizeof addr);
1557 status = extract_int(buf, 2);
1558 extract_token(dsn, buf, 3, '|', sizeof dsn);
1559 if ( (!strcasecmp(key, "remote"))
1560 && ((status==0)||(status==3)||(status==4)) ) {
1562 /* Remove this "remote" instruction from the set,
1563 * but replace the set's final newline if
1564 * remove_token() stripped it. It has to be there.
1566 remove_token(instr, i, '\n');
1567 if (instr[strlen(instr)-1] != '\n') {
1568 strcat(instr, "\n");
1573 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1574 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1576 if (results == NULL) {
1577 results = malloc(1024);
1578 memset(results, 0, 1024);
1581 results = realloc(results,
1582 strlen(results) + 1024);
1584 snprintf(&results[strlen(results)], 1024,
1586 key, addr, status, dsn);
1591 if (results != NULL) {
1592 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1593 strcat(instr, results);
1598 /* Generate 'bounce' messages */
1599 smtp_do_bounce(instr);
1601 /* Go through the delivery list, deleting completed deliveries */
1602 incomplete_deliveries_remaining =
1603 smtp_purge_completed_deliveries(instr);
1607 * No delivery instructions remain, so delete both the instructions
1608 * message and the message message.
1610 if (incomplete_deliveries_remaining <= 0) {
1611 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "", 0);
1612 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "", 0);
1617 * Uncompleted delivery instructions remain, so delete the old
1618 * instructions and replace with the updated ones.
1620 if (incomplete_deliveries_remaining > 0) {
1621 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "", 0);
1622 msg = malloc(sizeof(struct CtdlMessage));
1623 memset(msg, 0, sizeof(struct CtdlMessage));
1624 msg->cm_magic = CTDLMESSAGE_MAGIC;
1625 msg->cm_anon_type = MES_NORMAL;
1626 msg->cm_format_type = FMT_RFC822;
1627 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1628 snprintf(msg->cm_fields['M'],
1630 "Content-type: %s\n\n%s\n"
1633 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1634 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1635 CtdlFreeMessage(msg);
1646 * Run through the queue sending out messages.
1648 void smtp_do_queue(void) {
1649 static int doing_queue = 0;
1652 * This is a simple concurrency check to make sure only one queue run
1653 * is done at a time. We could do this with a mutex, but since we
1654 * don't really require extremely fine granularity here, we'll do it
1655 * with a static variable instead.
1657 if (doing_queue) return;
1661 * Go ahead and run the queue
1663 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1665 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1666 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1669 CtdlForEachMessage(MSGS_ALL, 0L,
1670 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1672 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1679 /*****************************************************************************/
1680 /* SMTP UTILITY COMMANDS */
1681 /*****************************************************************************/
1683 void cmd_smtp(char *argbuf) {
1690 if (CtdlAccessCheck(ac_aide)) return;
1692 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1694 if (!strcasecmp(cmd, "mx")) {
1695 extract_token(node, argbuf, 1, '|', sizeof node);
1696 num_mxhosts = getmx(buf, node);
1697 cprintf("%d %d MX hosts listed for %s\n",
1698 LISTING_FOLLOWS, num_mxhosts, node);
1699 for (i=0; i<num_mxhosts; ++i) {
1700 extract_token(node, buf, i, '|', sizeof node);
1701 cprintf("%s\n", node);
1707 else if (!strcasecmp(cmd, "runqueue")) {
1709 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1714 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1721 * Initialize the SMTP outbound queue
1723 void smtp_init_spoolout(void) {
1724 struct ctdlroom qrbuf;
1727 * Create the room. This will silently fail if the room already
1728 * exists, and that's perfectly ok, because we want it to exist.
1730 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1733 * Make sure it's set to be a "system room" so it doesn't show up
1734 * in the <K>nown rooms list for Aides.
1736 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1737 qrbuf.QRflags2 |= QR2_SYSTEM;
1745 /*****************************************************************************/
1746 /* MODULE INITIALIZATION STUFF */
1747 /*****************************************************************************/
1749 * This cleanup function blows away the temporary memory used by
1752 void smtp_cleanup_function(void) {
1754 /* Don't do this stuff if this is not an SMTP session! */
1755 if (CC->h_command_function != smtp_command_loop) return;
1757 lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1767 char *serv_smtp_init(void)
1770 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1777 CtdlRegisterServiceHook(config.c_smtps_port,
1784 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1790 CtdlRegisterServiceHook(0, /* local LMTP */
1796 CtdlRegisterServiceHook(0, /* local LMTP */
1797 file_lmtp_unfiltered_socket,
1798 lmtp_unfiltered_greeting,
1802 smtp_init_spoolout();
1803 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1804 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1805 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");