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];
953 /* Parse out the host portion of the recipient address */
954 process_rfc822_addr(addr, user, node, name);
956 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
959 /* Load the message out of the database */
960 CC->redirect_buffer = malloc(SIZ);
961 CC->redirect_len = 0;
962 CC->redirect_alloc = SIZ;
963 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
964 msgtext = CC->redirect_buffer;
965 msg_size = CC->redirect_len;
966 CC->redirect_buffer = NULL;
967 CC->redirect_len = 0;
968 CC->redirect_alloc = 0;
970 /* Extract something to send later in the 'MAIL From:' command */
971 strcpy(mailfrom, "");
975 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
978 if (!strncasecmp(buf, "From:", 5)) {
979 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
981 for (i=0; i<strlen(mailfrom); ++i) {
982 if (!isprint(mailfrom[i])) {
983 strcpy(&mailfrom[i], &mailfrom[i+1]);
988 /* Strip out parenthesized names */
991 for (i=0; i<strlen(mailfrom); ++i) {
992 if (mailfrom[i] == '(') lp = i;
993 if (mailfrom[i] == ')') rp = i;
995 if ((lp>0)&&(rp>lp)) {
996 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
999 /* Prefer brokketized names */
1002 for (i=0; i<strlen(mailfrom); ++i) {
1003 if (mailfrom[i] == '<') lp = i;
1004 if (mailfrom[i] == '>') rp = i;
1006 if ( (lp>=0) && (rp>lp) ) {
1008 strcpy(mailfrom, &mailfrom[lp]);
1013 } while (scan_done == 0);
1014 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
1015 stripallbut(mailfrom, '<', '>');
1017 /* Figure out what mail exchanger host we have to connect to */
1018 num_mxhosts = getmx(mxhosts, node);
1019 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
1020 if (num_mxhosts < 1) {
1022 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1027 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1028 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1029 extract_token(mx_host, buf, 0, ':', sizeof mx_host);
1030 extract_token(mx_port, buf, 1, ':', sizeof mx_port);
1032 strcpy(mx_port, "25");
1034 lprintf(CTDL_DEBUG, "Trying %s : %s ...\n", mx_host, mx_port);
1035 sock = sock_connect(mx_host, mx_port, "tcp");
1036 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1037 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
1038 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
1042 *status = 4; /* dsn is already filled in */
1046 /* Process the SMTP greeting from the server */
1047 if (ml_sock_gets(sock, buf) < 0) {
1049 strcpy(dsn, "Connection broken during SMTP conversation");
1052 lprintf(CTDL_DEBUG, "<%s\n", buf);
1053 if (buf[0] != '2') {
1054 if (buf[0] == '4') {
1056 safestrncpy(dsn, &buf[4], 1023);
1061 safestrncpy(dsn, &buf[4], 1023);
1066 /* At this point we know we are talking to a real SMTP server */
1068 /* Do a HELO command */
1069 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1070 lprintf(CTDL_DEBUG, ">%s", buf);
1071 sock_write(sock, buf, strlen(buf));
1072 if (ml_sock_gets(sock, buf) < 0) {
1074 strcpy(dsn, "Connection broken during SMTP HELO");
1077 lprintf(CTDL_DEBUG, "<%s\n", buf);
1078 if (buf[0] != '2') {
1079 if (buf[0] == '4') {
1081 safestrncpy(dsn, &buf[4], 1023);
1086 safestrncpy(dsn, &buf[4], 1023);
1091 /* HELO succeeded, now try the MAIL From: command */
1092 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1093 lprintf(CTDL_DEBUG, ">%s", buf);
1094 sock_write(sock, buf, strlen(buf));
1095 if (ml_sock_gets(sock, buf) < 0) {
1097 strcpy(dsn, "Connection broken during SMTP MAIL");
1100 lprintf(CTDL_DEBUG, "<%s\n", buf);
1101 if (buf[0] != '2') {
1102 if (buf[0] == '4') {
1104 safestrncpy(dsn, &buf[4], 1023);
1109 safestrncpy(dsn, &buf[4], 1023);
1114 /* MAIL succeeded, now try the RCPT To: command */
1115 snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
1116 lprintf(CTDL_DEBUG, ">%s", buf);
1117 sock_write(sock, buf, strlen(buf));
1118 if (ml_sock_gets(sock, buf) < 0) {
1120 strcpy(dsn, "Connection broken during SMTP RCPT");
1123 lprintf(CTDL_DEBUG, "<%s\n", buf);
1124 if (buf[0] != '2') {
1125 if (buf[0] == '4') {
1127 safestrncpy(dsn, &buf[4], 1023);
1132 safestrncpy(dsn, &buf[4], 1023);
1137 /* RCPT succeeded, now try the DATA command */
1138 lprintf(CTDL_DEBUG, ">DATA\n");
1139 sock_write(sock, "DATA\r\n", 6);
1140 if (ml_sock_gets(sock, buf) < 0) {
1142 strcpy(dsn, "Connection broken during SMTP DATA");
1145 lprintf(CTDL_DEBUG, "<%s\n", buf);
1146 if (buf[0] != '3') {
1147 if (buf[0] == '4') {
1149 safestrncpy(dsn, &buf[4], 1023);
1154 safestrncpy(dsn, &buf[4], 1023);
1159 /* If we reach this point, the server is expecting data */
1160 sock_write(sock, msgtext, msg_size);
1161 if (msgtext[msg_size-1] != 10) {
1162 lprintf(CTDL_WARNING, "Possible problem: message did not "
1163 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1167 sock_write(sock, ".\r\n", 3);
1168 if (ml_sock_gets(sock, buf) < 0) {
1170 strcpy(dsn, "Connection broken during SMTP message transmit");
1173 lprintf(CTDL_DEBUG, "%s\n", buf);
1174 if (buf[0] != '2') {
1175 if (buf[0] == '4') {
1177 safestrncpy(dsn, &buf[4], 1023);
1182 safestrncpy(dsn, &buf[4], 1023);
1188 safestrncpy(dsn, &buf[4], 1023);
1191 lprintf(CTDL_DEBUG, ">QUIT\n");
1192 sock_write(sock, "QUIT\r\n", 6);
1193 ml_sock_gets(sock, buf);
1194 lprintf(CTDL_DEBUG, "<%s\n", buf);
1195 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1198 bail: free(msgtext);
1201 /* Write something to the syslog (which may or may not be where the
1202 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1204 if (enable_syslog) {
1205 syslog((LOG_MAIL | LOG_INFO),
1206 "%ld: to=<%s>, relay=%s, stat=%s",
1220 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1221 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1222 * a "bounce" message (delivery status notification).
1224 void smtp_do_bounce(char *instr) {
1232 char bounceto[1024];
1233 int num_bounces = 0;
1234 int bounce_this = 0;
1235 long bounce_msgid = (-1);
1236 time_t submitted = 0L;
1237 struct CtdlMessage *bmsg = NULL;
1239 struct recptypes *valid;
1240 int successful_bounce = 0;
1242 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1243 strcpy(bounceto, "");
1245 lines = num_tokens(instr, '\n');
1248 /* See if it's time to give up on delivery of this message */
1249 for (i=0; i<lines; ++i) {
1250 extract_token(buf, instr, i, '\n', sizeof buf);
1251 extract_token(key, buf, 0, '|', sizeof key);
1252 extract_token(addr, buf, 1, '|', sizeof addr);
1253 if (!strcasecmp(key, "submitted")) {
1254 submitted = atol(addr);
1258 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1264 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1265 if (bmsg == NULL) return;
1266 memset(bmsg, 0, sizeof(struct CtdlMessage));
1268 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1269 bmsg->cm_anon_type = MES_NORMAL;
1270 bmsg->cm_format_type = 1;
1271 bmsg->cm_fields['A'] = strdup("Citadel");
1272 bmsg->cm_fields['O'] = strdup(MAILROOM);
1273 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1274 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1276 if (give_up) bmsg->cm_fields['M'] = strdup(
1277 "A message you sent could not be delivered to some or all of its recipients\n"
1278 "due to prolonged unavailability of its destination(s).\n"
1279 "Giving up on the following addresses:\n\n"
1282 else bmsg->cm_fields['M'] = strdup(
1283 "A message you sent could not be delivered to some or all of its recipients.\n"
1284 "The following addresses were undeliverable:\n\n"
1288 * Now go through the instructions checking for stuff.
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 status = extract_int(buf, 2);
1295 extract_token(dsn, buf, 3, '|', sizeof dsn);
1298 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1299 key, addr, status, dsn);
1301 if (!strcasecmp(key, "bounceto")) {
1302 strcpy(bounceto, addr);
1306 (!strcasecmp(key, "local"))
1307 || (!strcasecmp(key, "remote"))
1308 || (!strcasecmp(key, "ignet"))
1309 || (!strcasecmp(key, "room"))
1311 if (status == 5) bounce_this = 1;
1312 if (give_up) bounce_this = 1;
1318 if (bmsg->cm_fields['M'] == NULL) {
1319 lprintf(CTDL_ERR, "ERROR ... M field is null "
1320 "(%s:%d)\n", __FILE__, __LINE__);
1323 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1324 strlen(bmsg->cm_fields['M']) + 1024 );
1325 strcat(bmsg->cm_fields['M'], addr);
1326 strcat(bmsg->cm_fields['M'], ": ");
1327 strcat(bmsg->cm_fields['M'], dsn);
1328 strcat(bmsg->cm_fields['M'], "\n");
1330 remove_token(instr, i, '\n');
1336 /* Deliver the bounce if there's anything worth mentioning */
1337 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1338 if (num_bounces > 0) {
1340 /* First try the user who sent the message */
1341 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1342 if (strlen(bounceto) == 0) {
1343 lprintf(CTDL_ERR, "No bounce address specified\n");
1344 bounce_msgid = (-1L);
1347 /* Can we deliver the bounce to the original sender? */
1348 valid = validate_recipients(bounceto);
1349 if (valid != NULL) {
1350 if (valid->num_error == 0) {
1351 CtdlSubmitMsg(bmsg, valid, "");
1352 successful_bounce = 1;
1356 /* If not, post it in the Aide> room */
1357 if (successful_bounce == 0) {
1358 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1361 /* Free up the memory we used */
1362 if (valid != NULL) {
1367 CtdlFreeMessage(bmsg);
1368 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1373 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1374 * set of delivery instructions for completed deliveries and remove them.
1376 * It returns the number of incomplete deliveries remaining.
1378 int smtp_purge_completed_deliveries(char *instr) {
1389 lines = num_tokens(instr, '\n');
1390 for (i=0; i<lines; ++i) {
1391 extract_token(buf, instr, i, '\n', sizeof buf);
1392 extract_token(key, buf, 0, '|', sizeof key);
1393 extract_token(addr, buf, 1, '|', sizeof addr);
1394 status = extract_int(buf, 2);
1395 extract_token(dsn, buf, 3, '|', sizeof dsn);
1400 (!strcasecmp(key, "local"))
1401 || (!strcasecmp(key, "remote"))
1402 || (!strcasecmp(key, "ignet"))
1403 || (!strcasecmp(key, "room"))
1405 if (status == 2) completed = 1;
1410 remove_token(instr, i, '\n');
1423 * Called by smtp_do_queue() to handle an individual message.
1425 void smtp_do_procmsg(long msgnum, void *userdata) {
1426 struct CtdlMessage *msg;
1428 char *results = NULL;
1436 long text_msgid = (-1);
1437 int incomplete_deliveries_remaining;
1438 time_t attempted = 0L;
1439 time_t last_attempted = 0L;
1440 time_t retry = SMTP_RETRY_INTERVAL;
1442 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1444 msg = CtdlFetchMessage(msgnum, 1);
1446 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1450 instr = strdup(msg->cm_fields['M']);
1451 CtdlFreeMessage(msg);
1453 /* Strip out the headers amd any other non-instruction line */
1454 lines = num_tokens(instr, '\n');
1455 for (i=0; i<lines; ++i) {
1456 extract_token(buf, instr, i, '\n', sizeof buf);
1457 if (num_tokens(buf, '|') < 2) {
1458 remove_token(instr, i, '\n');
1464 /* Learn the message ID and find out about recent delivery attempts */
1465 lines = num_tokens(instr, '\n');
1466 for (i=0; i<lines; ++i) {
1467 extract_token(buf, instr, i, '\n', sizeof buf);
1468 extract_token(key, buf, 0, '|', sizeof key);
1469 if (!strcasecmp(key, "msgid")) {
1470 text_msgid = extract_long(buf, 1);
1472 if (!strcasecmp(key, "retry")) {
1473 /* double the retry interval after each attempt */
1474 retry = extract_long(buf, 1) * 2L;
1475 if (retry > SMTP_RETRY_MAX) {
1476 retry = SMTP_RETRY_MAX;
1478 remove_token(instr, i, '\n');
1480 if (!strcasecmp(key, "attempted")) {
1481 attempted = extract_long(buf, 1);
1482 if (attempted > last_attempted)
1483 last_attempted = attempted;
1488 * Postpone delivery if we've already tried recently.
1490 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1491 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1498 * Bail out if there's no actual message associated with this
1500 if (text_msgid < 0L) {
1501 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1506 /* Plow through the instructions looking for 'remote' directives and
1507 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1508 * were experienced and it's time to try again)
1510 lines = num_tokens(instr, '\n');
1511 for (i=0; i<lines; ++i) {
1512 extract_token(buf, instr, i, '\n', sizeof buf);
1513 extract_token(key, buf, 0, '|', sizeof key);
1514 extract_token(addr, buf, 1, '|', sizeof addr);
1515 status = extract_int(buf, 2);
1516 extract_token(dsn, buf, 3, '|', sizeof dsn);
1517 if ( (!strcasecmp(key, "remote"))
1518 && ((status==0)||(status==3)||(status==4)) ) {
1520 /* Remove this "remote" instruction from the set,
1521 * but replace the set's final newline if
1522 * remove_token() stripped it. It has to be there.
1524 remove_token(instr, i, '\n');
1525 if (instr[strlen(instr)-1] != '\n') {
1526 strcat(instr, "\n");
1531 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1532 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1534 if (results == NULL) {
1535 results = malloc(1024);
1536 memset(results, 0, 1024);
1539 results = realloc(results,
1540 strlen(results) + 1024);
1542 snprintf(&results[strlen(results)], 1024,
1544 key, addr, status, dsn);
1549 if (results != NULL) {
1550 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1551 strcat(instr, results);
1556 /* Generate 'bounce' messages */
1557 smtp_do_bounce(instr);
1559 /* Go through the delivery list, deleting completed deliveries */
1560 incomplete_deliveries_remaining =
1561 smtp_purge_completed_deliveries(instr);
1565 * No delivery instructions remain, so delete both the instructions
1566 * message and the message message.
1568 if (incomplete_deliveries_remaining <= 0) {
1569 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "", 0);
1570 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "", 0);
1575 * Uncompleted delivery instructions remain, so delete the old
1576 * instructions and replace with the updated ones.
1578 if (incomplete_deliveries_remaining > 0) {
1579 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "", 0);
1580 msg = malloc(sizeof(struct CtdlMessage));
1581 memset(msg, 0, sizeof(struct CtdlMessage));
1582 msg->cm_magic = CTDLMESSAGE_MAGIC;
1583 msg->cm_anon_type = MES_NORMAL;
1584 msg->cm_format_type = FMT_RFC822;
1585 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1586 snprintf(msg->cm_fields['M'],
1588 "Content-type: %s\n\n%s\n"
1591 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1592 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1593 CtdlFreeMessage(msg);
1604 * Run through the queue sending out messages.
1606 void smtp_do_queue(void) {
1607 static int doing_queue = 0;
1610 * This is a simple concurrency check to make sure only one queue run
1611 * is done at a time. We could do this with a mutex, but since we
1612 * don't really require extremely fine granularity here, we'll do it
1613 * with a static variable instead.
1615 if (doing_queue) return;
1619 * Go ahead and run the queue
1621 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1623 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1624 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1627 CtdlForEachMessage(MSGS_ALL, 0L,
1628 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1630 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1637 /*****************************************************************************/
1638 /* SMTP UTILITY COMMANDS */
1639 /*****************************************************************************/
1641 void cmd_smtp(char *argbuf) {
1648 if (CtdlAccessCheck(ac_aide)) return;
1650 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1652 if (!strcasecmp(cmd, "mx")) {
1653 extract_token(node, argbuf, 1, '|', sizeof node);
1654 num_mxhosts = getmx(buf, node);
1655 cprintf("%d %d MX hosts listed for %s\n",
1656 LISTING_FOLLOWS, num_mxhosts, node);
1657 for (i=0; i<num_mxhosts; ++i) {
1658 extract_token(node, buf, i, '|', sizeof node);
1659 cprintf("%s\n", node);
1665 else if (!strcasecmp(cmd, "runqueue")) {
1667 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1672 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1679 * Initialize the SMTP outbound queue
1681 void smtp_init_spoolout(void) {
1682 struct ctdlroom qrbuf;
1685 * Create the room. This will silently fail if the room already
1686 * exists, and that's perfectly ok, because we want it to exist.
1688 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1691 * Make sure it's set to be a "system room" so it doesn't show up
1692 * in the <K>nown rooms list for Aides.
1694 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1695 qrbuf.QRflags2 |= QR2_SYSTEM;
1703 /*****************************************************************************/
1704 /* MODULE INITIALIZATION STUFF */
1705 /*****************************************************************************/
1707 * This cleanup function blows away the temporary memory used by
1710 void smtp_cleanup_function(void) {
1712 /* Don't do this stuff if this is not an SMTP session! */
1713 if (CC->h_command_function != smtp_command_loop) return;
1715 lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1725 char *serv_smtp_init(void)
1728 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1735 CtdlRegisterServiceHook(config.c_smtps_port,
1742 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1748 CtdlRegisterServiceHook(0, /* local LMTP */
1754 CtdlRegisterServiceHook(0, /* local LMTP */
1755 file_lmtp_unfiltered_socket,
1756 lmtp_unfiltered_greeting,
1760 smtp_init_spoolout();
1761 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1762 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1763 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");