4 * This module is an SMTP and ESMTP implementation for the Citadel system.
5 * It is compliant with all of the following:
7 * RFC 821 - Simple Mail Transfer Protocol
8 * RFC 876 - Survey of SMTP Implementations
9 * RFC 1047 - Duplicate messages and SMTP
10 * RFC 1854 - command pipelining
11 * RFC 1869 - Extended Simple Mail Transfer Protocol
12 * RFC 1870 - SMTP Service Extension for Message Size Declaration
13 * RFC 1893 - Enhanced Mail System Status Codes
14 * RFC 2033 - Local Mail Transfer Protocol
15 * RFC 2034 - SMTP Service Extension for Returning Enhanced Error Codes
16 * RFC 2197 - SMTP Service Extension for Command Pipelining
17 * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
18 * RFC 2554 - SMTP Service Extension for Authentication
19 * RFC 2821 - Simple Mail Transfer Protocol
20 * RFC 2822 - Internet Message Format
21 * RFC 2920 - SMTP Service Extension for Command Pipelining
33 #include <sys/types.h>
36 #if TIME_WITH_SYS_TIME
37 # include <sys/time.h>
41 # include <sys/time.h>
51 #include <sys/socket.h>
52 #include <netinet/in.h>
53 #include <arpa/inet.h>
56 #include "sysdep_decls.h"
57 #include "citserver.h"
61 #include "serv_extensions.h"
68 #include "internet_addressing.h"
71 #include "clientsocket.h"
72 #include "locate_host.h"
73 #include "citadel_dirs.h"
76 #include "serv_crypto.h"
85 struct citsmtp { /* Information about the current session */
88 struct ctdluser vrfy_buffer;
93 int number_of_recipients;
95 int message_originated_locally;
101 enum { /* Command states for login authentication */
107 enum { /* Delivery modes */
112 #define SMTP CC->SMTP
113 #define SMTP_RECPS CC->SMTP_RECPS
114 #define SMTP_ROOMS CC->SMTP_ROOMS
117 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
121 /*****************************************************************************/
122 /* SMTP SERVER (INBOUND) STUFF */
123 /*****************************************************************************/
127 * Here's where our SMTP session begins its happy day.
129 void smtp_greeting(void) {
131 strcpy(CC->cs_clientname, "SMTP session");
132 CC->internal_pgm = 1;
133 CC->cs_flags |= CS_STEALTH;
134 SMTP = malloc(sizeof(struct citsmtp));
135 SMTP_RECPS = malloc(SIZ);
136 SMTP_ROOMS = malloc(SIZ);
137 memset(SMTP, 0, sizeof(struct citsmtp));
138 memset(SMTP_RECPS, 0, SIZ);
139 memset(SMTP_ROOMS, 0, SIZ);
141 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
146 * SMTPS is just like SMTP, except it goes crypto right away.
149 void smtps_greeting(void) {
150 CtdlStartTLS(NULL, NULL, NULL);
157 * SMTP MSA port requires authentication.
159 void smtp_msa_greeting(void) {
166 * LMTP is like SMTP but with some extra bonus footage added.
168 void lmtp_greeting(void) {
175 * We also have an unfiltered LMTP socket that bypasses spam filters.
177 void lmtp_unfiltered_greeting(void) {
180 SMTP->is_unfiltered = 1;
185 * Login greeting common to all auth methods
187 void smtp_auth_greeting(void) {
188 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
189 lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
190 CC->internal_pgm = 0;
191 CC->cs_flags &= ~CS_STEALTH;
196 * Implement HELO and EHLO commands.
198 * which_command: 0=HELO, 1=EHLO, 2=LHLO
200 void smtp_hello(char *argbuf, int which_command) {
202 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
204 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
205 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
209 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
210 cprintf("500 LHLO is only allowed when running LMTP\r\n");
214 if (which_command == 0) {
215 cprintf("250 Hello %s (%s [%s])\r\n",
222 if (which_command == 1) {
223 cprintf("250-Hello %s (%s [%s])\r\n",
230 cprintf("250-Greetings and joyous salutations.\r\n");
232 cprintf("250-HELP\r\n");
233 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
237 /* Only offer the PIPELINING command if TLS is inactive,
238 * because of flow control issues. Also, avoid offering TLS
239 * if TLS is already active. Finally, we only offer TLS on
240 * the SMTP-MSA port, not on the SMTP-MTA port, due to
241 * questionable reliability of TLS in certain sending MTA's.
243 if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
244 cprintf("250-PIPELINING\r\n");
245 cprintf("250-STARTTLS\r\n");
248 #else /* HAVE_OPENSSL */
250 /* Non SSL enabled server, so always offer PIPELINING. */
251 cprintf("250-PIPELINING\r\n");
253 #endif /* HAVE_OPENSSL */
255 cprintf("250-AUTH LOGIN PLAIN\r\n");
256 cprintf("250-AUTH=LOGIN PLAIN\r\n");
258 cprintf("250 ENHANCEDSTATUSCODES\r\n");
265 * Implement HELP command.
267 void smtp_help(void) {
268 cprintf("214-Commands accepted:\r\n");
269 cprintf("214- DATA\r\n");
270 cprintf("214- EHLO\r\n");
271 cprintf("214- EXPN\r\n");
272 cprintf("214- HELO\r\n");
273 cprintf("214- HELP\r\n");
274 cprintf("214- MAIL\r\n");
275 cprintf("214- NOOP\r\n");
276 cprintf("214- QUIT\r\n");
277 cprintf("214- RCPT\r\n");
278 cprintf("214- RSET\r\n");
279 cprintf("214- VRFY\r\n");
287 void smtp_get_user(char *argbuf) {
291 CtdlDecodeBase64(username, argbuf, SIZ);
292 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", username); */
293 if (CtdlLoginExistingUser(username) == login_ok) {
294 CtdlEncodeBase64(buf, "Password:", 9);
295 cprintf("334 %s\r\n", buf);
296 SMTP->command_state = smtp_password;
299 cprintf("500 5.7.0 No such user.\r\n");
300 SMTP->command_state = smtp_command;
308 void smtp_get_pass(char *argbuf) {
311 CtdlDecodeBase64(password, argbuf, SIZ);
312 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", password); */
313 if (CtdlTryPassword(password) == pass_ok) {
314 smtp_auth_greeting();
317 cprintf("535 5.7.0 Authentication failed.\r\n");
319 SMTP->command_state = smtp_command;
326 void smtp_auth(char *argbuf) {
327 char username_prompt[64];
329 char encoded_authstring[1024];
330 char decoded_authstring[1024];
336 cprintf("504 5.7.4 Already logged in.\r\n");
340 extract_token(method, argbuf, 0, ' ', sizeof method);
342 if (!strncasecmp(method, "login", 5) ) {
343 if (strlen(argbuf) >= 7) {
344 smtp_get_user(&argbuf[6]);
347 CtdlEncodeBase64(username_prompt, "Username:", 9);
348 cprintf("334 %s\r\n", username_prompt);
349 SMTP->command_state = smtp_user;
354 if (!strncasecmp(method, "plain", 5) ) {
355 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
356 CtdlDecodeBase64(decoded_authstring,
358 strlen(encoded_authstring) );
359 safestrncpy(ident, decoded_authstring, sizeof ident);
360 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
361 safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
363 if (CtdlLoginExistingUser(user) == login_ok) {
364 if (CtdlTryPassword(pass) == pass_ok) {
365 smtp_auth_greeting();
369 cprintf("504 5.7.4 Authentication failed.\r\n");
372 if (strncasecmp(method, "login", 5) ) {
373 cprintf("504 5.7.4 Unknown authentication method.\r\n");
381 * Back end for smtp_vrfy() command
383 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
385 if (!fuzzy_match(us, SMTP->vrfy_match)) {
387 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
393 * Implements the VRFY (verify user name) command.
394 * Performs fuzzy match on full user names.
396 void smtp_vrfy(char *argbuf) {
397 SMTP->vrfy_count = 0;
398 strcpy(SMTP->vrfy_match, argbuf);
399 ForEachUser(smtp_vrfy_backend, NULL);
401 if (SMTP->vrfy_count < 1) {
402 cprintf("550 5.1.1 String does not match anything.\r\n");
404 else if (SMTP->vrfy_count == 1) {
405 cprintf("250 %s <cit%ld@%s>\r\n",
406 SMTP->vrfy_buffer.fullname,
407 SMTP->vrfy_buffer.usernum,
410 else if (SMTP->vrfy_count > 1) {
411 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
420 * Back end for smtp_expn() command
422 void smtp_expn_backend(struct ctdluser *us, void *data) {
424 if (!fuzzy_match(us, SMTP->vrfy_match)) {
426 if (SMTP->vrfy_count >= 1) {
427 cprintf("250-%s <cit%ld@%s>\r\n",
428 SMTP->vrfy_buffer.fullname,
429 SMTP->vrfy_buffer.usernum,
434 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
440 * Implements the EXPN (expand user name) command.
441 * Performs fuzzy match on full user names.
443 void smtp_expn(char *argbuf) {
444 SMTP->vrfy_count = 0;
445 strcpy(SMTP->vrfy_match, argbuf);
446 ForEachUser(smtp_expn_backend, NULL);
448 if (SMTP->vrfy_count < 1) {
449 cprintf("550 5.1.1 String does not match anything.\r\n");
451 else if (SMTP->vrfy_count >= 1) {
452 cprintf("250 %s <cit%ld@%s>\r\n",
453 SMTP->vrfy_buffer.fullname,
454 SMTP->vrfy_buffer.usernum,
461 * Implements the RSET (reset state) command.
462 * Currently this just zeroes out the state buffer. If pointers to data
463 * allocated with malloc() are ever placed in the state buffer, we have to
464 * be sure to free() them first!
466 * Set do_response to nonzero to output the SMTP RSET response code.
468 void smtp_rset(int do_response) {
473 * Our entire SMTP state is discarded when a RSET command is issued,
474 * but we need to preserve this one little piece of information, so
475 * we save it for later.
477 is_lmtp = SMTP->is_lmtp;
478 is_unfiltered = SMTP->is_unfiltered;
480 memset(SMTP, 0, sizeof(struct citsmtp));
483 * It is somewhat ambiguous whether we want to log out when a RSET
484 * command is issued. Here's the code to do it. It is commented out
485 * because some clients (such as Pine) issue RSET commands before
486 * each message, but still expect to be logged in.
488 * if (CC->logged_in) {
494 * Reinstate this little piece of information we saved (see above).
496 SMTP->is_lmtp = is_lmtp;
497 SMTP->is_unfiltered = is_unfiltered;
500 cprintf("250 2.0.0 Zap!\r\n");
505 * Clear out the portions of the state buffer that need to be cleared out
506 * after the DATA command finishes.
508 void smtp_data_clear(void) {
509 strcpy(SMTP->from, "");
510 strcpy(SMTP->recipients, "");
511 SMTP->number_of_recipients = 0;
512 SMTP->delivery_mode = 0;
513 SMTP->message_originated_locally = 0;
519 * Implements the "MAIL From:" command
521 void smtp_mail(char *argbuf) {
526 if (strlen(SMTP->from) != 0) {
527 cprintf("503 5.1.0 Only one sender permitted\r\n");
531 if (strncasecmp(argbuf, "From:", 5)) {
532 cprintf("501 5.1.7 Syntax error\r\n");
536 strcpy(SMTP->from, &argbuf[5]);
538 if (haschar(SMTP->from, '<') > 0) {
539 stripallbut(SMTP->from, '<', '>');
542 /* We used to reject empty sender names, until it was brought to our
543 * attention that RFC1123 5.2.9 requires that this be allowed. So now
544 * we allow it, but replace the empty string with a fake
545 * address so we don't have to contend with the empty string causing
546 * other code to fail when it's expecting something there.
548 if (strlen(SMTP->from) == 0) {
549 strcpy(SMTP->from, "someone@somewhere.org");
552 /* If this SMTP connection is from a logged-in user, force the 'from'
553 * to be the user's Internet e-mail address as Citadel knows it.
556 safestrncpy(SMTP->from, CC->cs_inet_email, sizeof SMTP->from);
557 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
558 SMTP->message_originated_locally = 1;
562 else if (SMTP->is_lmtp) {
563 /* Bypass forgery checking for LMTP */
566 /* Otherwise, make sure outsiders aren't trying to forge mail from
567 * this system (unless, of course, c_allow_spoofing is enabled)
569 else if (config.c_allow_spoofing == 0) {
570 process_rfc822_addr(SMTP->from, user, node, name);
571 if (CtdlHostAlias(node) != hostalias_nomatch) {
573 "You must log in to send mail from %s\r\n",
575 strcpy(SMTP->from, "");
580 cprintf("250 2.0.0 Sender ok\r\n");
586 * Implements the "RCPT To:" command
588 void smtp_rcpt(char *argbuf) {
590 char message_to_spammer[SIZ];
591 struct recptypes *valid = NULL;
593 if (strlen(SMTP->from) == 0) {
594 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
598 if (strncasecmp(argbuf, "To:", 3)) {
599 cprintf("501 5.1.7 Syntax error\r\n");
603 if ( (SMTP->is_msa) && (!CC->logged_in) ) {
605 "You must log in to send mail on this port.\r\n");
606 strcpy(SMTP->from, "");
610 strcpy(recp, &argbuf[3]);
612 stripallbut(recp, '<', '>');
614 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
615 cprintf("452 4.5.3 Too many recipients\r\n");
620 if ( (!CC->logged_in)
621 && (!SMTP->is_lmtp) ) {
622 if (rbl_check(message_to_spammer)) {
623 cprintf("550 %s\r\n", message_to_spammer);
624 /* no need to free(valid), it's not allocated yet */
629 valid = validate_recipients(recp);
630 if (valid->num_error != 0) {
631 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
636 if (valid->num_internet > 0) {
638 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
639 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
646 if (valid->num_internet > 0) {
647 if ( (SMTP->message_originated_locally == 0)
648 && (SMTP->is_lmtp == 0) ) {
649 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
655 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
656 if (strlen(SMTP->recipients) > 0) {
657 strcat(SMTP->recipients, ",");
659 strcat(SMTP->recipients, recp);
660 SMTP->number_of_recipients += 1;
667 * Implements the DATA command
669 void smtp_data(void) {
671 struct CtdlMessage *msg;
674 struct recptypes *valid;
679 if (strlen(SMTP->from) == 0) {
680 cprintf("503 5.5.1 Need MAIL command first.\r\n");
684 if (SMTP->number_of_recipients < 1) {
685 cprintf("503 5.5.1 Need RCPT command first.\r\n");
689 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
691 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
694 if (body != NULL) snprintf(body, 4096,
695 "Received: from %s (%s [%s])\n"
703 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
706 "Unable to save message: internal error.\r\n");
710 lprintf(CTDL_DEBUG, "Converting message...\n");
711 msg = convert_internet_message(body);
713 /* If the user is locally authenticated, FORCE the From: header to
714 * show up as the real sender. Yes, this violates the RFC standard,
715 * but IT MAKES SENSE. If you prefer strict RFC adherence over
716 * common sense, you can disable this in the configuration.
718 * We also set the "message room name" ('O' field) to MAILROOM
719 * (which is Mail> on most systems) to prevent it from getting set
720 * to something ugly like "0000058008.Sent Items>" when the message
721 * is read with a Citadel client.
723 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
724 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
725 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
726 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
727 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
728 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
729 msg->cm_fields['A'] = strdup(CC->user.fullname);
730 msg->cm_fields['N'] = strdup(config.c_nodename);
731 msg->cm_fields['H'] = strdup(config.c_humannode);
732 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
733 msg->cm_fields['O'] = strdup(MAILROOM);
736 /* Submit the message into the Citadel system. */
737 valid = validate_recipients(SMTP->recipients);
739 /* If there are modules that want to scan this message before final
740 * submission (such as virus checkers or spam filters), call them now
741 * and give them an opportunity to reject the message.
743 if (SMTP->is_unfiltered) {
747 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
750 if (scan_errors > 0) { /* We don't want this message! */
752 if (msg->cm_fields['0'] == NULL) {
753 msg->cm_fields['0'] = strdup(
754 "5.7.1 Message rejected by filter");
757 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
760 else { /* Ok, we'll accept this message. */
761 msgnum = CtdlSubmitMsg(msg, valid, "");
763 sprintf(result, "250 2.0.0 Message accepted.\r\n");
766 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
770 /* For SMTP and ESTMP, just print the result message. For LMTP, we
771 * have to print one result message for each recipient. Since there
772 * is nothing in Citadel which would cause different recipients to
773 * have different results, we can get away with just spitting out the
774 * same message once for each recipient.
777 for (i=0; i<SMTP->number_of_recipients; ++i) {
778 cprintf("%s", result);
782 cprintf("%s", result);
785 /* Write something to the syslog (which may or may not be where the
786 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
789 syslog((LOG_MAIL | LOG_INFO),
790 "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
793 SMTP->number_of_recipients,
801 CtdlFreeMessage(msg);
803 smtp_data_clear(); /* clear out the buffers now */
808 * implements the STARTTLS command (Citadel API version)
811 void smtp_starttls(void)
813 char ok_response[SIZ];
814 char nosup_response[SIZ];
815 char error_response[SIZ];
818 "200 2.0.0 Begin TLS negotiation now\r\n");
819 sprintf(nosup_response,
820 "554 5.7.3 TLS not supported here\r\n");
821 sprintf(error_response,
822 "554 5.7.3 Internal error\r\n");
823 CtdlStartTLS(ok_response, nosup_response, error_response);
831 * Main command loop for SMTP sessions.
833 void smtp_command_loop(void) {
837 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
838 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
839 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
843 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
844 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
846 if (SMTP->command_state == smtp_user) {
847 smtp_get_user(cmdbuf);
850 else if (SMTP->command_state == smtp_password) {
851 smtp_get_pass(cmdbuf);
854 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
855 smtp_auth(&cmdbuf[5]);
858 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
862 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
863 smtp_expn(&cmdbuf[5]);
866 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
867 smtp_hello(&cmdbuf[5], 0);
870 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
871 smtp_hello(&cmdbuf[5], 1);
874 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
875 smtp_hello(&cmdbuf[5], 2);
878 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
882 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
883 smtp_mail(&cmdbuf[5]);
886 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
887 cprintf("250 NOOP\r\n");
890 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
891 cprintf("221 Goodbye...\r\n");
896 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
897 smtp_rcpt(&cmdbuf[5]);
900 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
904 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
908 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
909 smtp_vrfy(&cmdbuf[5]);
913 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
922 /*****************************************************************************/
923 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
924 /*****************************************************************************/
931 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
934 void smtp_try(const char *key, const char *addr, int *status,
935 char *dsn, size_t n, long msgnum)
942 char user[1024], node[1024], name[1024];
955 /* Parse out the host portion of the recipient address */
956 process_rfc822_addr(addr, user, node, name);
958 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
961 /* Load the message out of the database */
962 CC->redirect_buffer = malloc(SIZ);
963 CC->redirect_len = 0;
964 CC->redirect_alloc = SIZ;
965 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
966 msgtext = CC->redirect_buffer;
967 msg_size = CC->redirect_len;
968 CC->redirect_buffer = NULL;
969 CC->redirect_len = 0;
970 CC->redirect_alloc = 0;
972 /* Extract something to send later in the 'MAIL From:' command */
973 strcpy(mailfrom, "");
977 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
980 if (!strncasecmp(buf, "From:", 5)) {
981 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
983 for (i=0; i<strlen(mailfrom); ++i) {
984 if (!isprint(mailfrom[i])) {
985 strcpy(&mailfrom[i], &mailfrom[i+1]);
990 /* Strip out parenthesized names */
993 for (i=0; i<strlen(mailfrom); ++i) {
994 if (mailfrom[i] == '(') lp = i;
995 if (mailfrom[i] == ')') rp = i;
997 if ((lp>0)&&(rp>lp)) {
998 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
1001 /* Prefer brokketized names */
1004 for (i=0; i<strlen(mailfrom); ++i) {
1005 if (mailfrom[i] == '<') lp = i;
1006 if (mailfrom[i] == '>') rp = i;
1008 if ( (lp>=0) && (rp>lp) ) {
1010 strcpy(mailfrom, &mailfrom[lp]);
1015 } while (scan_done == 0);
1016 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
1017 stripallbut(mailfrom, '<', '>');
1019 /* Figure out what mail exchanger host we have to connect to */
1020 num_mxhosts = getmx(mxhosts, node);
1021 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
1022 if (num_mxhosts < 1) {
1024 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1029 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1030 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1031 strcpy(mx_user, "");
1032 strcpy(mx_pass, "");
1033 if (num_tokens(buf, '@') > 1) {
1034 extract_token(mx_user, buf, 0, '@', sizeof mx_user);
1035 if (num_tokens(mx_user, ':') > 1) {
1036 extract_token(mx_pass, mx_user, 1, ':', sizeof mx_pass);
1037 remove_token(mx_user, 1, ':');
1039 remove_token(buf, 0, '@');
1041 extract_token(mx_host, buf, 0, ':', sizeof mx_host);
1042 extract_token(mx_port, buf, 1, ':', sizeof mx_port);
1044 strcpy(mx_port, "25");
1046 lprintf(CTDL_DEBUG, "FIXME user<%s> pass<%s> host<%s> port<%s>\n",
1047 mx_user, mx_pass, mx_host, mx_port);
1048 lprintf(CTDL_DEBUG, "Trying %s : %s ...\n", mx_host, mx_port);
1049 sock = sock_connect(mx_host, mx_port, "tcp");
1050 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1051 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
1052 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
1056 *status = 4; /* dsn is already filled in */
1060 /* Process the SMTP greeting from the server */
1061 if (ml_sock_gets(sock, buf) < 0) {
1063 strcpy(dsn, "Connection broken during SMTP conversation");
1066 lprintf(CTDL_DEBUG, "<%s\n", buf);
1067 if (buf[0] != '2') {
1068 if (buf[0] == '4') {
1070 safestrncpy(dsn, &buf[4], 1023);
1075 safestrncpy(dsn, &buf[4], 1023);
1080 /* At this point we know we are talking to a real SMTP server */
1082 /* Do a HELO command */
1083 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1084 lprintf(CTDL_DEBUG, ">%s", buf);
1085 sock_write(sock, buf, strlen(buf));
1086 if (ml_sock_gets(sock, buf) < 0) {
1088 strcpy(dsn, "Connection broken during SMTP HELO");
1091 lprintf(CTDL_DEBUG, "<%s\n", buf);
1092 if (buf[0] != '2') {
1093 if (buf[0] == '4') {
1095 safestrncpy(dsn, &buf[4], 1023);
1100 safestrncpy(dsn, &buf[4], 1023);
1105 /* Do an AUTH command if necessary */
1106 if (strlen(mx_user) > 0) {
1107 sprintf(buf, "%s%c%s%c%s%c", mx_user, 0, mx_user, 0, mx_pass, 0);
1108 CtdlEncodeBase64(mailfrom, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 3);
1109 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", mailfrom);
1110 lprintf(CTDL_DEBUG, ">%s", buf);
1111 sock_write(sock, buf, strlen(buf));
1112 if (ml_sock_gets(sock, buf) < 0) {
1114 strcpy(dsn, "Connection broken during SMTP AUTH");
1117 lprintf(CTDL_DEBUG, "<%s\n", buf);
1118 if (buf[0] != '2') {
1119 if (buf[0] == '4') {
1121 safestrncpy(dsn, &buf[4], 1023);
1126 safestrncpy(dsn, &buf[4], 1023);
1132 /* previous command succeeded, now try the MAIL From: command */
1133 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1134 lprintf(CTDL_DEBUG, ">%s", buf);
1135 sock_write(sock, buf, strlen(buf));
1136 if (ml_sock_gets(sock, buf) < 0) {
1138 strcpy(dsn, "Connection broken during SMTP MAIL");
1141 lprintf(CTDL_DEBUG, "<%s\n", buf);
1142 if (buf[0] != '2') {
1143 if (buf[0] == '4') {
1145 safestrncpy(dsn, &buf[4], 1023);
1150 safestrncpy(dsn, &buf[4], 1023);
1155 /* MAIL succeeded, now try the RCPT To: command */
1156 snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
1157 lprintf(CTDL_DEBUG, ">%s", buf);
1158 sock_write(sock, buf, strlen(buf));
1159 if (ml_sock_gets(sock, buf) < 0) {
1161 strcpy(dsn, "Connection broken during SMTP RCPT");
1164 lprintf(CTDL_DEBUG, "<%s\n", buf);
1165 if (buf[0] != '2') {
1166 if (buf[0] == '4') {
1168 safestrncpy(dsn, &buf[4], 1023);
1173 safestrncpy(dsn, &buf[4], 1023);
1178 /* RCPT succeeded, now try the DATA command */
1179 lprintf(CTDL_DEBUG, ">DATA\n");
1180 sock_write(sock, "DATA\r\n", 6);
1181 if (ml_sock_gets(sock, buf) < 0) {
1183 strcpy(dsn, "Connection broken during SMTP DATA");
1186 lprintf(CTDL_DEBUG, "<%s\n", buf);
1187 if (buf[0] != '3') {
1188 if (buf[0] == '4') {
1190 safestrncpy(dsn, &buf[4], 1023);
1195 safestrncpy(dsn, &buf[4], 1023);
1200 /* If we reach this point, the server is expecting data */
1201 sock_write(sock, msgtext, msg_size);
1202 if (msgtext[msg_size-1] != 10) {
1203 lprintf(CTDL_WARNING, "Possible problem: message did not "
1204 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1208 sock_write(sock, ".\r\n", 3);
1209 if (ml_sock_gets(sock, buf) < 0) {
1211 strcpy(dsn, "Connection broken during SMTP message transmit");
1214 lprintf(CTDL_DEBUG, "%s\n", buf);
1215 if (buf[0] != '2') {
1216 if (buf[0] == '4') {
1218 safestrncpy(dsn, &buf[4], 1023);
1223 safestrncpy(dsn, &buf[4], 1023);
1229 safestrncpy(dsn, &buf[4], 1023);
1232 lprintf(CTDL_DEBUG, ">QUIT\n");
1233 sock_write(sock, "QUIT\r\n", 6);
1234 ml_sock_gets(sock, buf);
1235 lprintf(CTDL_DEBUG, "<%s\n", buf);
1236 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1239 bail: free(msgtext);
1242 /* Write something to the syslog (which may or may not be where the
1243 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1245 if (enable_syslog) {
1246 syslog((LOG_MAIL | LOG_INFO),
1247 "%ld: to=<%s>, relay=%s, stat=%s",
1261 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1262 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1263 * a "bounce" message (delivery status notification).
1265 void smtp_do_bounce(char *instr) {
1273 char bounceto[1024];
1274 int num_bounces = 0;
1275 int bounce_this = 0;
1276 long bounce_msgid = (-1);
1277 time_t submitted = 0L;
1278 struct CtdlMessage *bmsg = NULL;
1280 struct recptypes *valid;
1281 int successful_bounce = 0;
1283 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1284 strcpy(bounceto, "");
1286 lines = num_tokens(instr, '\n');
1289 /* See if it's time to give up on delivery of this message */
1290 for (i=0; i<lines; ++i) {
1291 extract_token(buf, instr, i, '\n', sizeof buf);
1292 extract_token(key, buf, 0, '|', sizeof key);
1293 extract_token(addr, buf, 1, '|', sizeof addr);
1294 if (!strcasecmp(key, "submitted")) {
1295 submitted = atol(addr);
1299 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1305 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1306 if (bmsg == NULL) return;
1307 memset(bmsg, 0, sizeof(struct CtdlMessage));
1309 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1310 bmsg->cm_anon_type = MES_NORMAL;
1311 bmsg->cm_format_type = 1;
1312 bmsg->cm_fields['A'] = strdup("Citadel");
1313 bmsg->cm_fields['O'] = strdup(MAILROOM);
1314 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1315 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1317 if (give_up) bmsg->cm_fields['M'] = strdup(
1318 "A message you sent could not be delivered to some or all of its recipients\n"
1319 "due to prolonged unavailability of its destination(s).\n"
1320 "Giving up on the following addresses:\n\n"
1323 else bmsg->cm_fields['M'] = strdup(
1324 "A message you sent could not be delivered to some or all of its recipients.\n"
1325 "The following addresses were undeliverable:\n\n"
1329 * Now go through the instructions checking for stuff.
1331 for (i=0; i<lines; ++i) {
1332 extract_token(buf, instr, i, '\n', sizeof buf);
1333 extract_token(key, buf, 0, '|', sizeof key);
1334 extract_token(addr, buf, 1, '|', sizeof addr);
1335 status = extract_int(buf, 2);
1336 extract_token(dsn, buf, 3, '|', sizeof dsn);
1339 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1340 key, addr, status, dsn);
1342 if (!strcasecmp(key, "bounceto")) {
1343 strcpy(bounceto, addr);
1347 (!strcasecmp(key, "local"))
1348 || (!strcasecmp(key, "remote"))
1349 || (!strcasecmp(key, "ignet"))
1350 || (!strcasecmp(key, "room"))
1352 if (status == 5) bounce_this = 1;
1353 if (give_up) bounce_this = 1;
1359 if (bmsg->cm_fields['M'] == NULL) {
1360 lprintf(CTDL_ERR, "ERROR ... M field is null "
1361 "(%s:%d)\n", __FILE__, __LINE__);
1364 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1365 strlen(bmsg->cm_fields['M']) + 1024 );
1366 strcat(bmsg->cm_fields['M'], addr);
1367 strcat(bmsg->cm_fields['M'], ": ");
1368 strcat(bmsg->cm_fields['M'], dsn);
1369 strcat(bmsg->cm_fields['M'], "\n");
1371 remove_token(instr, i, '\n');
1377 /* Deliver the bounce if there's anything worth mentioning */
1378 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1379 if (num_bounces > 0) {
1381 /* First try the user who sent the message */
1382 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1383 if (strlen(bounceto) == 0) {
1384 lprintf(CTDL_ERR, "No bounce address specified\n");
1385 bounce_msgid = (-1L);
1388 /* Can we deliver the bounce to the original sender? */
1389 valid = validate_recipients(bounceto);
1390 if (valid != NULL) {
1391 if (valid->num_error == 0) {
1392 CtdlSubmitMsg(bmsg, valid, "");
1393 successful_bounce = 1;
1397 /* If not, post it in the Aide> room */
1398 if (successful_bounce == 0) {
1399 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1402 /* Free up the memory we used */
1403 if (valid != NULL) {
1408 CtdlFreeMessage(bmsg);
1409 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1414 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1415 * set of delivery instructions for completed deliveries and remove them.
1417 * It returns the number of incomplete deliveries remaining.
1419 int smtp_purge_completed_deliveries(char *instr) {
1430 lines = num_tokens(instr, '\n');
1431 for (i=0; i<lines; ++i) {
1432 extract_token(buf, instr, i, '\n', sizeof buf);
1433 extract_token(key, buf, 0, '|', sizeof key);
1434 extract_token(addr, buf, 1, '|', sizeof addr);
1435 status = extract_int(buf, 2);
1436 extract_token(dsn, buf, 3, '|', sizeof dsn);
1441 (!strcasecmp(key, "local"))
1442 || (!strcasecmp(key, "remote"))
1443 || (!strcasecmp(key, "ignet"))
1444 || (!strcasecmp(key, "room"))
1446 if (status == 2) completed = 1;
1451 remove_token(instr, i, '\n');
1464 * Called by smtp_do_queue() to handle an individual message.
1466 void smtp_do_procmsg(long msgnum, void *userdata) {
1467 struct CtdlMessage *msg;
1469 char *results = NULL;
1477 long text_msgid = (-1);
1478 int incomplete_deliveries_remaining;
1479 time_t attempted = 0L;
1480 time_t last_attempted = 0L;
1481 time_t retry = SMTP_RETRY_INTERVAL;
1483 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1485 msg = CtdlFetchMessage(msgnum, 1);
1487 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1491 instr = strdup(msg->cm_fields['M']);
1492 CtdlFreeMessage(msg);
1494 /* Strip out the headers amd any other non-instruction line */
1495 lines = num_tokens(instr, '\n');
1496 for (i=0; i<lines; ++i) {
1497 extract_token(buf, instr, i, '\n', sizeof buf);
1498 if (num_tokens(buf, '|') < 2) {
1499 remove_token(instr, i, '\n');
1505 /* Learn the message ID and find out about recent delivery attempts */
1506 lines = num_tokens(instr, '\n');
1507 for (i=0; i<lines; ++i) {
1508 extract_token(buf, instr, i, '\n', sizeof buf);
1509 extract_token(key, buf, 0, '|', sizeof key);
1510 if (!strcasecmp(key, "msgid")) {
1511 text_msgid = extract_long(buf, 1);
1513 if (!strcasecmp(key, "retry")) {
1514 /* double the retry interval after each attempt */
1515 retry = extract_long(buf, 1) * 2L;
1516 if (retry > SMTP_RETRY_MAX) {
1517 retry = SMTP_RETRY_MAX;
1519 remove_token(instr, i, '\n');
1521 if (!strcasecmp(key, "attempted")) {
1522 attempted = extract_long(buf, 1);
1523 if (attempted > last_attempted)
1524 last_attempted = attempted;
1529 * Postpone delivery if we've already tried recently.
1531 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1532 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1539 * Bail out if there's no actual message associated with this
1541 if (text_msgid < 0L) {
1542 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1547 /* Plow through the instructions looking for 'remote' directives and
1548 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1549 * were experienced and it's time to try again)
1551 lines = num_tokens(instr, '\n');
1552 for (i=0; i<lines; ++i) {
1553 extract_token(buf, instr, i, '\n', sizeof buf);
1554 extract_token(key, buf, 0, '|', sizeof key);
1555 extract_token(addr, buf, 1, '|', sizeof addr);
1556 status = extract_int(buf, 2);
1557 extract_token(dsn, buf, 3, '|', sizeof dsn);
1558 if ( (!strcasecmp(key, "remote"))
1559 && ((status==0)||(status==3)||(status==4)) ) {
1561 /* Remove this "remote" instruction from the set,
1562 * but replace the set's final newline if
1563 * remove_token() stripped it. It has to be there.
1565 remove_token(instr, i, '\n');
1566 if (instr[strlen(instr)-1] != '\n') {
1567 strcat(instr, "\n");
1572 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1573 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1575 if (results == NULL) {
1576 results = malloc(1024);
1577 memset(results, 0, 1024);
1580 results = realloc(results,
1581 strlen(results) + 1024);
1583 snprintf(&results[strlen(results)], 1024,
1585 key, addr, status, dsn);
1590 if (results != NULL) {
1591 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1592 strcat(instr, results);
1597 /* Generate 'bounce' messages */
1598 smtp_do_bounce(instr);
1600 /* Go through the delivery list, deleting completed deliveries */
1601 incomplete_deliveries_remaining =
1602 smtp_purge_completed_deliveries(instr);
1606 * No delivery instructions remain, so delete both the instructions
1607 * message and the message message.
1609 if (incomplete_deliveries_remaining <= 0) {
1610 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "", 0);
1611 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "", 0);
1616 * Uncompleted delivery instructions remain, so delete the old
1617 * instructions and replace with the updated ones.
1619 if (incomplete_deliveries_remaining > 0) {
1620 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "", 0);
1621 msg = malloc(sizeof(struct CtdlMessage));
1622 memset(msg, 0, sizeof(struct CtdlMessage));
1623 msg->cm_magic = CTDLMESSAGE_MAGIC;
1624 msg->cm_anon_type = MES_NORMAL;
1625 msg->cm_format_type = FMT_RFC822;
1626 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1627 snprintf(msg->cm_fields['M'],
1629 "Content-type: %s\n\n%s\n"
1632 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1633 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1634 CtdlFreeMessage(msg);
1645 * Run through the queue sending out messages.
1647 void smtp_do_queue(void) {
1648 static int doing_queue = 0;
1651 * This is a simple concurrency check to make sure only one queue run
1652 * is done at a time. We could do this with a mutex, but since we
1653 * don't really require extremely fine granularity here, we'll do it
1654 * with a static variable instead.
1656 if (doing_queue) return;
1660 * Go ahead and run the queue
1662 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1664 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1665 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1668 CtdlForEachMessage(MSGS_ALL, 0L,
1669 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1671 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1678 /*****************************************************************************/
1679 /* SMTP UTILITY COMMANDS */
1680 /*****************************************************************************/
1682 void cmd_smtp(char *argbuf) {
1689 if (CtdlAccessCheck(ac_aide)) return;
1691 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1693 if (!strcasecmp(cmd, "mx")) {
1694 extract_token(node, argbuf, 1, '|', sizeof node);
1695 num_mxhosts = getmx(buf, node);
1696 cprintf("%d %d MX hosts listed for %s\n",
1697 LISTING_FOLLOWS, num_mxhosts, node);
1698 for (i=0; i<num_mxhosts; ++i) {
1699 extract_token(node, buf, i, '|', sizeof node);
1700 cprintf("%s\n", node);
1706 else if (!strcasecmp(cmd, "runqueue")) {
1708 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1713 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1720 * Initialize the SMTP outbound queue
1722 void smtp_init_spoolout(void) {
1723 struct ctdlroom qrbuf;
1726 * Create the room. This will silently fail if the room already
1727 * exists, and that's perfectly ok, because we want it to exist.
1729 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1732 * Make sure it's set to be a "system room" so it doesn't show up
1733 * in the <K>nown rooms list for Aides.
1735 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1736 qrbuf.QRflags2 |= QR2_SYSTEM;
1744 /*****************************************************************************/
1745 /* MODULE INITIALIZATION STUFF */
1746 /*****************************************************************************/
1748 * This cleanup function blows away the temporary memory used by
1751 void smtp_cleanup_function(void) {
1753 /* Don't do this stuff if this is not an SMTP session! */
1754 if (CC->h_command_function != smtp_command_loop) return;
1756 lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1766 char *serv_smtp_init(void)
1769 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1776 CtdlRegisterServiceHook(config.c_smtps_port,
1783 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1789 CtdlRegisterServiceHook(0, /* local LMTP */
1795 CtdlRegisterServiceHook(0, /* local LMTP */
1796 file_lmtp_unfiltered_socket,
1797 lmtp_unfiltered_greeting,
1801 smtp_init_spoolout();
1802 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1803 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1804 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");