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>
35 #if TIME_WITH_SYS_TIME
36 # include <sys/time.h>
40 # include <sys/time.h>
50 #include <sys/socket.h>
51 #include <netinet/in.h>
52 #include <arpa/inet.h>
55 #include "sysdep_decls.h"
56 #include "citserver.h"
60 #include "serv_extensions.h"
67 #include "internet_addressing.h"
70 #include "clientsocket.h"
71 #include "locate_host.h"
74 #include "serv_crypto.h"
83 struct citsmtp { /* Information about the current session */
86 struct ctdluser vrfy_buffer;
91 int number_of_recipients;
93 int message_originated_locally;
99 enum { /* Command states for login authentication */
105 enum { /* Delivery modes */
110 #define SMTP CC->SMTP
111 #define SMTP_RECPS CC->SMTP_RECPS
112 #define SMTP_ROOMS CC->SMTP_ROOMS
115 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
119 /*****************************************************************************/
120 /* SMTP SERVER (INBOUND) STUFF */
121 /*****************************************************************************/
125 * Here's where our SMTP session begins its happy day.
127 void smtp_greeting(void) {
129 strcpy(CC->cs_clientname, "SMTP session");
130 CC->internal_pgm = 1;
131 CC->cs_flags |= CS_STEALTH;
132 SMTP = malloc(sizeof(struct citsmtp));
133 SMTP_RECPS = malloc(SIZ);
134 SMTP_ROOMS = malloc(SIZ);
135 memset(SMTP, 0, sizeof(struct citsmtp));
136 memset(SMTP_RECPS, 0, SIZ);
137 memset(SMTP_ROOMS, 0, SIZ);
139 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
144 * SMTPS is just like SMTP, except it goes crypto right away.
147 void smtps_greeting(void) {
148 CtdlStartTLS(NULL, NULL, NULL);
155 * SMTP MSA port requires authentication.
157 void smtp_msa_greeting(void) {
164 * LMTP is like SMTP but with some extra bonus footage added.
166 void lmtp_greeting(void) {
173 * We also have an unfiltered LMTP socket that bypasses spam filters.
175 void lmtp_unfiltered_greeting(void) {
178 SMTP->is_unfiltered = 1;
183 * Login greeting common to all auth methods
185 void smtp_auth_greeting(void) {
186 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
187 lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
188 CC->internal_pgm = 0;
189 CC->cs_flags &= ~CS_STEALTH;
194 * Implement HELO and EHLO commands.
196 * which_command: 0=HELO, 1=EHLO, 2=LHLO
198 void smtp_hello(char *argbuf, int which_command) {
200 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
202 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
203 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
207 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
208 cprintf("500 LHLO is only allowed when running LMTP\r\n");
212 if (which_command == 0) {
213 cprintf("250 Hello %s (%s [%s])\r\n",
220 if (which_command == 1) {
221 cprintf("250-Hello %s (%s [%s])\r\n",
228 cprintf("250-Greetings and joyous salutations.\r\n");
230 cprintf("250-HELP\r\n");
231 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
235 /* Only offer the PIPELINING command if TLS is inactive,
236 * because of flow control issues. Also, avoid offering TLS
237 * if TLS is already active. Finally, we only offer TLS on
238 * the SMTP-MSA port, not on the SMTP-MTA port, due to
239 * questionable reliability of TLS in certain sending MTA's.
241 if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
242 cprintf("250-PIPELINING\r\n");
243 cprintf("250-STARTTLS\r\n");
246 #else /* HAVE_OPENSSL */
248 /* Non SSL enabled server, so always offer PIPELINING. */
249 cprintf("250-PIPELINING\r\n");
251 #endif /* HAVE_OPENSSL */
253 cprintf("250-AUTH LOGIN PLAIN\r\n");
254 cprintf("250-AUTH=LOGIN PLAIN\r\n");
256 cprintf("250 ENHANCEDSTATUSCODES\r\n");
263 * Implement HELP command.
265 void smtp_help(void) {
266 cprintf("214-Commands accepted:\r\n");
267 cprintf("214- DATA\r\n");
268 cprintf("214- EHLO\r\n");
269 cprintf("214- EXPN\r\n");
270 cprintf("214- HELO\r\n");
271 cprintf("214- HELP\r\n");
272 cprintf("214- MAIL\r\n");
273 cprintf("214- NOOP\r\n");
274 cprintf("214- QUIT\r\n");
275 cprintf("214- RCPT\r\n");
276 cprintf("214- RSET\r\n");
277 cprintf("214- VRFY\r\n");
285 void smtp_get_user(char *argbuf) {
289 CtdlDecodeBase64(username, argbuf, SIZ);
290 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", username); */
291 if (CtdlLoginExistingUser(username) == login_ok) {
292 CtdlEncodeBase64(buf, "Password:", 9);
293 cprintf("334 %s\r\n", buf);
294 SMTP->command_state = smtp_password;
297 cprintf("500 5.7.0 No such user.\r\n");
298 SMTP->command_state = smtp_command;
306 void smtp_get_pass(char *argbuf) {
309 CtdlDecodeBase64(password, argbuf, SIZ);
310 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", password); */
311 if (CtdlTryPassword(password) == pass_ok) {
312 smtp_auth_greeting();
315 cprintf("535 5.7.0 Authentication failed.\r\n");
317 SMTP->command_state = smtp_command;
324 void smtp_auth(char *argbuf) {
325 char username_prompt[64];
327 char encoded_authstring[1024];
328 char decoded_authstring[1024];
334 cprintf("504 5.7.4 Already logged in.\r\n");
338 extract_token(method, argbuf, 0, ' ', sizeof method);
340 if (!strncasecmp(method, "login", 5) ) {
341 if (strlen(argbuf) >= 7) {
342 smtp_get_user(&argbuf[6]);
345 CtdlEncodeBase64(username_prompt, "Username:", 9);
346 cprintf("334 %s\r\n", username_prompt);
347 SMTP->command_state = smtp_user;
352 if (!strncasecmp(method, "plain", 5) ) {
353 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
354 CtdlDecodeBase64(decoded_authstring,
356 strlen(encoded_authstring) );
357 safestrncpy(ident, decoded_authstring, sizeof ident);
358 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
359 safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
361 if (CtdlLoginExistingUser(user) == login_ok) {
362 if (CtdlTryPassword(pass) == pass_ok) {
363 smtp_auth_greeting();
367 cprintf("504 5.7.4 Authentication failed.\r\n");
370 if (strncasecmp(method, "login", 5) ) {
371 cprintf("504 5.7.4 Unknown authentication method.\r\n");
379 * Back end for smtp_vrfy() command
381 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
383 if (!fuzzy_match(us, SMTP->vrfy_match)) {
385 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
391 * Implements the VRFY (verify user name) command.
392 * Performs fuzzy match on full user names.
394 void smtp_vrfy(char *argbuf) {
395 SMTP->vrfy_count = 0;
396 strcpy(SMTP->vrfy_match, argbuf);
397 ForEachUser(smtp_vrfy_backend, NULL);
399 if (SMTP->vrfy_count < 1) {
400 cprintf("550 5.1.1 String does not match anything.\r\n");
402 else if (SMTP->vrfy_count == 1) {
403 cprintf("250 %s <cit%ld@%s>\r\n",
404 SMTP->vrfy_buffer.fullname,
405 SMTP->vrfy_buffer.usernum,
408 else if (SMTP->vrfy_count > 1) {
409 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
418 * Back end for smtp_expn() command
420 void smtp_expn_backend(struct ctdluser *us, void *data) {
422 if (!fuzzy_match(us, SMTP->vrfy_match)) {
424 if (SMTP->vrfy_count >= 1) {
425 cprintf("250-%s <cit%ld@%s>\r\n",
426 SMTP->vrfy_buffer.fullname,
427 SMTP->vrfy_buffer.usernum,
432 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
438 * Implements the EXPN (expand user name) command.
439 * Performs fuzzy match on full user names.
441 void smtp_expn(char *argbuf) {
442 SMTP->vrfy_count = 0;
443 strcpy(SMTP->vrfy_match, argbuf);
444 ForEachUser(smtp_expn_backend, NULL);
446 if (SMTP->vrfy_count < 1) {
447 cprintf("550 5.1.1 String does not match anything.\r\n");
449 else if (SMTP->vrfy_count >= 1) {
450 cprintf("250 %s <cit%ld@%s>\r\n",
451 SMTP->vrfy_buffer.fullname,
452 SMTP->vrfy_buffer.usernum,
459 * Implements the RSET (reset state) command.
460 * Currently this just zeroes out the state buffer. If pointers to data
461 * allocated with malloc() are ever placed in the state buffer, we have to
462 * be sure to free() them first!
464 * Set do_response to nonzero to output the SMTP RSET response code.
466 void smtp_rset(int do_response) {
471 * Our entire SMTP state is discarded when a RSET command is issued,
472 * but we need to preserve this one little piece of information, so
473 * we save it for later.
475 is_lmtp = SMTP->is_lmtp;
476 is_unfiltered = SMTP->is_unfiltered;
478 memset(SMTP, 0, sizeof(struct citsmtp));
481 * It is somewhat ambiguous whether we want to log out when a RSET
482 * command is issued. Here's the code to do it. It is commented out
483 * because some clients (such as Pine) issue RSET commands before
484 * each message, but still expect to be logged in.
486 * if (CC->logged_in) {
492 * Reinstate this little piece of information we saved (see above).
494 SMTP->is_lmtp = is_lmtp;
495 SMTP->is_unfiltered = is_unfiltered;
498 cprintf("250 2.0.0 Zap!\r\n");
503 * Clear out the portions of the state buffer that need to be cleared out
504 * after the DATA command finishes.
506 void smtp_data_clear(void) {
507 strcpy(SMTP->from, "");
508 strcpy(SMTP->recipients, "");
509 SMTP->number_of_recipients = 0;
510 SMTP->delivery_mode = 0;
511 SMTP->message_originated_locally = 0;
517 * Implements the "MAIL From:" command
519 void smtp_mail(char *argbuf) {
524 if (strlen(SMTP->from) != 0) {
525 cprintf("503 5.1.0 Only one sender permitted\r\n");
529 if (strncasecmp(argbuf, "From:", 5)) {
530 cprintf("501 5.1.7 Syntax error\r\n");
534 strcpy(SMTP->from, &argbuf[5]);
536 if (haschar(SMTP->from, '<') > 0) {
537 stripallbut(SMTP->from, '<', '>');
540 /* We used to reject empty sender names, until it was brought to our
541 * attention that RFC1123 5.2.9 requires that this be allowed. So now
542 * we allow it, but replace the empty string with a fake
543 * address so we don't have to contend with the empty string causing
544 * other code to fail when it's expecting something there.
546 if (strlen(SMTP->from) == 0) {
547 strcpy(SMTP->from, "someone@somewhere.org");
550 /* If this SMTP connection is from a logged-in user, force the 'from'
551 * to be the user's Internet e-mail address as Citadel knows it.
554 safestrncpy(SMTP->from, CC->cs_inet_email, sizeof SMTP->from);
555 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
556 SMTP->message_originated_locally = 1;
560 else if (SMTP->is_lmtp) {
561 /* Bypass forgery checking for LMTP */
564 /* Otherwise, make sure outsiders aren't trying to forge mail from
565 * this system (unless, of course, c_allow_spoofing is enabled)
567 else if (config.c_allow_spoofing == 0) {
568 process_rfc822_addr(SMTP->from, user, node, name);
569 if (CtdlHostAlias(node) != hostalias_nomatch) {
571 "You must log in to send mail from %s\r\n",
573 strcpy(SMTP->from, "");
578 cprintf("250 2.0.0 Sender ok\r\n");
584 * Implements the "RCPT To:" command
586 void smtp_rcpt(char *argbuf) {
588 char message_to_spammer[SIZ];
589 struct recptypes *valid = NULL;
591 if (strlen(SMTP->from) == 0) {
592 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
596 if (strncasecmp(argbuf, "To:", 3)) {
597 cprintf("501 5.1.7 Syntax error\r\n");
601 if ( (SMTP->is_msa) && (!CC->logged_in) ) {
603 "You must log in to send mail on this port.\r\n");
604 strcpy(SMTP->from, "");
608 strcpy(recp, &argbuf[3]);
610 stripallbut(recp, '<', '>');
612 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
613 cprintf("452 4.5.3 Too many recipients\r\n");
618 if ( (!CC->logged_in)
619 && (!SMTP->is_lmtp) ) {
620 if (rbl_check(message_to_spammer)) {
621 cprintf("550 %s\r\n", message_to_spammer);
622 /* no need to free(valid), it's not allocated yet */
627 valid = validate_recipients(recp);
628 if (valid->num_error != 0) {
629 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
634 if (valid->num_internet > 0) {
636 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
637 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
644 if (valid->num_internet > 0) {
645 if ( (SMTP->message_originated_locally == 0)
646 && (SMTP->is_lmtp == 0) ) {
647 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
653 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
654 if (strlen(SMTP->recipients) > 0) {
655 strcat(SMTP->recipients, ",");
657 strcat(SMTP->recipients, recp);
658 SMTP->number_of_recipients += 1;
665 * Implements the DATA command
667 void smtp_data(void) {
669 struct CtdlMessage *msg;
672 struct recptypes *valid;
677 if (strlen(SMTP->from) == 0) {
678 cprintf("503 5.5.1 Need MAIL command first.\r\n");
682 if (SMTP->number_of_recipients < 1) {
683 cprintf("503 5.5.1 Need RCPT command first.\r\n");
687 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
689 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
692 if (body != NULL) snprintf(body, 4096,
693 "Received: from %s (%s [%s])\n"
701 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
704 "Unable to save message: internal error.\r\n");
708 lprintf(CTDL_DEBUG, "Converting message...\n");
709 msg = convert_internet_message(body);
711 /* If the user is locally authenticated, FORCE the From: header to
712 * show up as the real sender. Yes, this violates the RFC standard,
713 * but IT MAKES SENSE. If you prefer strict RFC adherence over
714 * common sense, you can disable this in the configuration.
716 * We also set the "message room name" ('O' field) to MAILROOM
717 * (which is Mail> on most systems) to prevent it from getting set
718 * to something ugly like "0000058008.Sent Items>" when the message
719 * is read with a Citadel client.
721 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
722 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
723 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
724 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
725 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
726 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
727 msg->cm_fields['A'] = strdup(CC->user.fullname);
728 msg->cm_fields['N'] = strdup(config.c_nodename);
729 msg->cm_fields['H'] = strdup(config.c_humannode);
730 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
731 msg->cm_fields['O'] = strdup(MAILROOM);
734 /* Submit the message into the Citadel system. */
735 valid = validate_recipients(SMTP->recipients);
737 /* If there are modules that want to scan this message before final
738 * submission (such as virus checkers or spam filters), call them now
739 * and give them an opportunity to reject the message.
741 if (SMTP->is_unfiltered) {
745 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
748 if (scan_errors > 0) { /* We don't want this message! */
750 if (msg->cm_fields['0'] == NULL) {
751 msg->cm_fields['0'] = strdup(
752 "5.7.1 Message rejected by filter");
755 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
758 else { /* Ok, we'll accept this message. */
759 msgnum = CtdlSubmitMsg(msg, valid, "");
761 sprintf(result, "250 2.0.0 Message accepted.\r\n");
764 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
768 /* For SMTP and ESTMP, just print the result message. For LMTP, we
769 * have to print one result message for each recipient. Since there
770 * is nothing in Citadel which would cause different recipients to
771 * have different results, we can get away with just spitting out the
772 * same message once for each recipient.
775 for (i=0; i<SMTP->number_of_recipients; ++i) {
776 cprintf("%s", result);
780 cprintf("%s", result);
783 CtdlFreeMessage(msg);
785 smtp_data_clear(); /* clear out the buffers now */
790 * implements the STARTTLS command (Citadel API version)
793 void smtp_starttls(void)
795 char ok_response[SIZ];
796 char nosup_response[SIZ];
797 char error_response[SIZ];
800 "200 2.0.0 Begin TLS negotiation now\r\n");
801 sprintf(nosup_response,
802 "554 5.7.3 TLS not supported here\r\n");
803 sprintf(error_response,
804 "554 5.7.3 Internal error\r\n");
805 CtdlStartTLS(ok_response, nosup_response, error_response);
813 * Main command loop for SMTP sessions.
815 void smtp_command_loop(void) {
819 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
820 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
821 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
825 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
826 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
828 if (SMTP->command_state == smtp_user) {
829 smtp_get_user(cmdbuf);
832 else if (SMTP->command_state == smtp_password) {
833 smtp_get_pass(cmdbuf);
836 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
837 smtp_auth(&cmdbuf[5]);
840 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
844 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
845 smtp_expn(&cmdbuf[5]);
848 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
849 smtp_hello(&cmdbuf[5], 0);
852 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
853 smtp_hello(&cmdbuf[5], 1);
856 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
857 smtp_hello(&cmdbuf[5], 2);
860 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
864 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
865 smtp_mail(&cmdbuf[5]);
868 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
869 cprintf("250 NOOP\r\n");
872 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
873 cprintf("221 Goodbye...\r\n");
878 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
879 smtp_rcpt(&cmdbuf[5]);
882 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
886 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
890 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
891 smtp_vrfy(&cmdbuf[5]);
895 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
904 /*****************************************************************************/
905 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
906 /*****************************************************************************/
913 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
916 void smtp_try(const char *key, const char *addr, int *status,
917 char *dsn, size_t n, long msgnum)
924 char user[SIZ], node[SIZ], name[SIZ];
933 /* Parse out the host portion of the recipient address */
934 process_rfc822_addr(addr, user, node, name);
936 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
939 /* Load the message out of the database */
940 CC->redirect_buffer = malloc(SIZ);
941 CC->redirect_len = 0;
942 CC->redirect_alloc = SIZ;
943 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
944 msgtext = CC->redirect_buffer;
945 msg_size = CC->redirect_len;
946 CC->redirect_buffer = NULL;
947 CC->redirect_len = 0;
948 CC->redirect_alloc = 0;
950 /* Extract something to send later in the 'MAIL From:' command */
951 strcpy(mailfrom, "");
955 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
958 if (!strncasecmp(buf, "From:", 5)) {
959 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
961 for (i=0; i<strlen(mailfrom); ++i) {
962 if (!isprint(mailfrom[i])) {
963 strcpy(&mailfrom[i], &mailfrom[i+1]);
968 /* Strip out parenthesized names */
971 for (i=0; i<strlen(mailfrom); ++i) {
972 if (mailfrom[i] == '(') lp = i;
973 if (mailfrom[i] == ')') rp = i;
975 if ((lp>0)&&(rp>lp)) {
976 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
979 /* Prefer brokketized names */
982 for (i=0; i<strlen(mailfrom); ++i) {
983 if (mailfrom[i] == '<') lp = i;
984 if (mailfrom[i] == '>') rp = i;
986 if ( (lp>=0) && (rp>lp) ) {
988 strcpy(mailfrom, &mailfrom[lp]);
993 } while (scan_done == 0);
994 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
995 stripallbut(mailfrom, '<', '>');
997 /* Figure out what mail exchanger host we have to connect to */
998 num_mxhosts = getmx(mxhosts, node);
999 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
1000 if (num_mxhosts < 1) {
1002 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1007 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1008 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1009 lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
1010 sock = sock_connect(buf, "25", "tcp");
1011 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1012 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
1013 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
1017 *status = 4; /* dsn is already filled in */
1021 /* Process the SMTP greeting from the server */
1022 if (ml_sock_gets(sock, buf) < 0) {
1024 strcpy(dsn, "Connection broken during SMTP conversation");
1027 lprintf(CTDL_DEBUG, "<%s\n", buf);
1028 if (buf[0] != '2') {
1029 if (buf[0] == '4') {
1031 safestrncpy(dsn, &buf[4], 1023);
1036 safestrncpy(dsn, &buf[4], 1023);
1041 /* At this point we know we are talking to a real SMTP server */
1043 /* Do a HELO command */
1044 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1045 lprintf(CTDL_DEBUG, ">%s", buf);
1046 sock_write(sock, buf, strlen(buf));
1047 if (ml_sock_gets(sock, buf) < 0) {
1049 strcpy(dsn, "Connection broken during SMTP HELO");
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 /* HELO succeeded, now try the MAIL From: command */
1067 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1068 lprintf(CTDL_DEBUG, ">%s", buf);
1069 sock_write(sock, buf, strlen(buf));
1070 if (ml_sock_gets(sock, buf) < 0) {
1072 strcpy(dsn, "Connection broken during SMTP MAIL");
1075 lprintf(CTDL_DEBUG, "<%s\n", buf);
1076 if (buf[0] != '2') {
1077 if (buf[0] == '4') {
1079 safestrncpy(dsn, &buf[4], 1023);
1084 safestrncpy(dsn, &buf[4], 1023);
1089 /* MAIL succeeded, now try the RCPT To: command */
1090 snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
1091 lprintf(CTDL_DEBUG, ">%s", buf);
1092 sock_write(sock, buf, strlen(buf));
1093 if (ml_sock_gets(sock, buf) < 0) {
1095 strcpy(dsn, "Connection broken during SMTP RCPT");
1098 lprintf(CTDL_DEBUG, "<%s\n", buf);
1099 if (buf[0] != '2') {
1100 if (buf[0] == '4') {
1102 safestrncpy(dsn, &buf[4], 1023);
1107 safestrncpy(dsn, &buf[4], 1023);
1112 /* RCPT succeeded, now try the DATA command */
1113 lprintf(CTDL_DEBUG, ">DATA\n");
1114 sock_write(sock, "DATA\r\n", 6);
1115 if (ml_sock_gets(sock, buf) < 0) {
1117 strcpy(dsn, "Connection broken during SMTP DATA");
1120 lprintf(CTDL_DEBUG, "<%s\n", buf);
1121 if (buf[0] != '3') {
1122 if (buf[0] == '4') {
1124 safestrncpy(dsn, &buf[4], 1023);
1129 safestrncpy(dsn, &buf[4], 1023);
1134 /* If we reach this point, the server is expecting data */
1135 sock_write(sock, msgtext, msg_size);
1136 if (msgtext[msg_size-1] != 10) {
1137 lprintf(CTDL_WARNING, "Possible problem: message did not "
1138 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1142 sock_write(sock, ".\r\n", 3);
1143 if (ml_sock_gets(sock, buf) < 0) {
1145 strcpy(dsn, "Connection broken during SMTP message transmit");
1148 lprintf(CTDL_DEBUG, "%s\n", buf);
1149 if (buf[0] != '2') {
1150 if (buf[0] == '4') {
1152 safestrncpy(dsn, &buf[4], 1023);
1157 safestrncpy(dsn, &buf[4], 1023);
1163 safestrncpy(dsn, &buf[4], 1023);
1166 lprintf(CTDL_DEBUG, ">QUIT\n");
1167 sock_write(sock, "QUIT\r\n", 6);
1168 ml_sock_gets(sock, buf);
1169 lprintf(CTDL_DEBUG, "<%s\n", buf);
1170 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1173 bail: free(msgtext);
1181 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1182 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1183 * a "bounce" message (delivery status notification).
1185 void smtp_do_bounce(char *instr) {
1193 char bounceto[1024];
1194 int num_bounces = 0;
1195 int bounce_this = 0;
1196 long bounce_msgid = (-1);
1197 time_t submitted = 0L;
1198 struct CtdlMessage *bmsg = NULL;
1200 struct recptypes *valid;
1201 int successful_bounce = 0;
1203 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1204 strcpy(bounceto, "");
1206 lines = num_tokens(instr, '\n');
1209 /* See if it's time to give up on delivery of this message */
1210 for (i=0; i<lines; ++i) {
1211 extract_token(buf, instr, i, '\n', sizeof buf);
1212 extract_token(key, buf, 0, '|', sizeof key);
1213 extract_token(addr, buf, 1, '|', sizeof addr);
1214 if (!strcasecmp(key, "submitted")) {
1215 submitted = atol(addr);
1219 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1225 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1226 if (bmsg == NULL) return;
1227 memset(bmsg, 0, sizeof(struct CtdlMessage));
1229 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1230 bmsg->cm_anon_type = MES_NORMAL;
1231 bmsg->cm_format_type = 1;
1232 bmsg->cm_fields['A'] = strdup("Citadel");
1233 bmsg->cm_fields['O'] = strdup(MAILROOM);
1234 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1235 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1237 if (give_up) bmsg->cm_fields['M'] = strdup(
1238 "A message you sent could not be delivered to some or all of its recipients\n"
1239 "due to prolonged unavailability of its destination(s).\n"
1240 "Giving up on the following addresses:\n\n"
1243 else bmsg->cm_fields['M'] = strdup(
1244 "A message you sent could not be delivered to some or all of its recipients.\n"
1245 "The following addresses were undeliverable:\n\n"
1249 * Now go through the instructions checking for stuff.
1251 for (i=0; i<lines; ++i) {
1252 extract_token(buf, instr, i, '\n', sizeof buf);
1253 extract_token(key, buf, 0, '|', sizeof key);
1254 extract_token(addr, buf, 1, '|', sizeof addr);
1255 status = extract_int(buf, 2);
1256 extract_token(dsn, buf, 3, '|', sizeof dsn);
1259 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1260 key, addr, status, dsn);
1262 if (!strcasecmp(key, "bounceto")) {
1263 strcpy(bounceto, addr);
1267 (!strcasecmp(key, "local"))
1268 || (!strcasecmp(key, "remote"))
1269 || (!strcasecmp(key, "ignet"))
1270 || (!strcasecmp(key, "room"))
1272 if (status == 5) bounce_this = 1;
1273 if (give_up) bounce_this = 1;
1279 if (bmsg->cm_fields['M'] == NULL) {
1280 lprintf(CTDL_ERR, "ERROR ... M field is null "
1281 "(%s:%d)\n", __FILE__, __LINE__);
1284 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1285 strlen(bmsg->cm_fields['M']) + 1024 );
1286 strcat(bmsg->cm_fields['M'], addr);
1287 strcat(bmsg->cm_fields['M'], ": ");
1288 strcat(bmsg->cm_fields['M'], dsn);
1289 strcat(bmsg->cm_fields['M'], "\n");
1291 remove_token(instr, i, '\n');
1297 /* Deliver the bounce if there's anything worth mentioning */
1298 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1299 if (num_bounces > 0) {
1301 /* First try the user who sent the message */
1302 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1303 if (strlen(bounceto) == 0) {
1304 lprintf(CTDL_ERR, "No bounce address specified\n");
1305 bounce_msgid = (-1L);
1308 /* Can we deliver the bounce to the original sender? */
1309 valid = validate_recipients(bounceto);
1310 if (valid != NULL) {
1311 if (valid->num_error == 0) {
1312 CtdlSubmitMsg(bmsg, valid, "");
1313 successful_bounce = 1;
1317 /* If not, post it in the Aide> room */
1318 if (successful_bounce == 0) {
1319 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1322 /* Free up the memory we used */
1323 if (valid != NULL) {
1328 CtdlFreeMessage(bmsg);
1329 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1334 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1335 * set of delivery instructions for completed deliveries and remove them.
1337 * It returns the number of incomplete deliveries remaining.
1339 int smtp_purge_completed_deliveries(char *instr) {
1350 lines = num_tokens(instr, '\n');
1351 for (i=0; i<lines; ++i) {
1352 extract_token(buf, instr, i, '\n', sizeof buf);
1353 extract_token(key, buf, 0, '|', sizeof key);
1354 extract_token(addr, buf, 1, '|', sizeof addr);
1355 status = extract_int(buf, 2);
1356 extract_token(dsn, buf, 3, '|', sizeof dsn);
1361 (!strcasecmp(key, "local"))
1362 || (!strcasecmp(key, "remote"))
1363 || (!strcasecmp(key, "ignet"))
1364 || (!strcasecmp(key, "room"))
1366 if (status == 2) completed = 1;
1371 remove_token(instr, i, '\n');
1384 * Called by smtp_do_queue() to handle an individual message.
1386 void smtp_do_procmsg(long msgnum, void *userdata) {
1387 struct CtdlMessage *msg;
1389 char *results = NULL;
1397 long text_msgid = (-1);
1398 int incomplete_deliveries_remaining;
1399 time_t attempted = 0L;
1400 time_t last_attempted = 0L;
1401 time_t retry = SMTP_RETRY_INTERVAL;
1403 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1405 msg = CtdlFetchMessage(msgnum, 1);
1407 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1411 instr = strdup(msg->cm_fields['M']);
1412 CtdlFreeMessage(msg);
1414 /* Strip out the headers amd any other non-instruction line */
1415 lines = num_tokens(instr, '\n');
1416 for (i=0; i<lines; ++i) {
1417 extract_token(buf, instr, i, '\n', sizeof buf);
1418 if (num_tokens(buf, '|') < 2) {
1419 remove_token(instr, i, '\n');
1425 /* Learn the message ID and find out about recent delivery attempts */
1426 lines = num_tokens(instr, '\n');
1427 for (i=0; i<lines; ++i) {
1428 extract_token(buf, instr, i, '\n', sizeof buf);
1429 extract_token(key, buf, 0, '|', sizeof key);
1430 if (!strcasecmp(key, "msgid")) {
1431 text_msgid = extract_long(buf, 1);
1433 if (!strcasecmp(key, "retry")) {
1434 /* double the retry interval after each attempt */
1435 retry = extract_long(buf, 1) * 2L;
1436 if (retry > SMTP_RETRY_MAX) {
1437 retry = SMTP_RETRY_MAX;
1439 remove_token(instr, i, '\n');
1441 if (!strcasecmp(key, "attempted")) {
1442 attempted = extract_long(buf, 1);
1443 if (attempted > last_attempted)
1444 last_attempted = attempted;
1449 * Postpone delivery if we've already tried recently.
1451 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1452 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1459 * Bail out if there's no actual message associated with this
1461 if (text_msgid < 0L) {
1462 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1467 /* Plow through the instructions looking for 'remote' directives and
1468 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1469 * were experienced and it's time to try again)
1471 lines = num_tokens(instr, '\n');
1472 for (i=0; i<lines; ++i) {
1473 extract_token(buf, instr, i, '\n', sizeof buf);
1474 extract_token(key, buf, 0, '|', sizeof key);
1475 extract_token(addr, buf, 1, '|', sizeof addr);
1476 status = extract_int(buf, 2);
1477 extract_token(dsn, buf, 3, '|', sizeof dsn);
1478 if ( (!strcasecmp(key, "remote"))
1479 && ((status==0)||(status==3)||(status==4)) ) {
1481 /* Remove this "remote" instruction from the set,
1482 * but replace the set's final newline if
1483 * remove_token() stripped it. It has to be there.
1485 remove_token(instr, i, '\n');
1486 if (instr[strlen(instr)-1] != '\n') {
1487 strcat(instr, "\n");
1492 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1493 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1495 if (results == NULL) {
1496 results = malloc(1024);
1497 memset(results, 0, 1024);
1500 results = realloc(results,
1501 strlen(results) + 1024);
1503 snprintf(&results[strlen(results)], 1024,
1505 key, addr, status, dsn);
1510 if (results != NULL) {
1511 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1512 strcat(instr, results);
1517 /* Generate 'bounce' messages */
1518 smtp_do_bounce(instr);
1520 /* Go through the delivery list, deleting completed deliveries */
1521 incomplete_deliveries_remaining =
1522 smtp_purge_completed_deliveries(instr);
1526 * No delivery instructions remain, so delete both the instructions
1527 * message and the message message.
1529 if (incomplete_deliveries_remaining <= 0) {
1530 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "", 0);
1531 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "", 0);
1536 * Uncompleted delivery instructions remain, so delete the old
1537 * instructions and replace with the updated ones.
1539 if (incomplete_deliveries_remaining > 0) {
1540 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "", 0);
1541 msg = malloc(sizeof(struct CtdlMessage));
1542 memset(msg, 0, sizeof(struct CtdlMessage));
1543 msg->cm_magic = CTDLMESSAGE_MAGIC;
1544 msg->cm_anon_type = MES_NORMAL;
1545 msg->cm_format_type = FMT_RFC822;
1546 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1547 snprintf(msg->cm_fields['M'],
1549 "Content-type: %s\n\n%s\n"
1552 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1553 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1554 CtdlFreeMessage(msg);
1565 * Run through the queue sending out messages.
1567 void smtp_do_queue(void) {
1568 static int doing_queue = 0;
1571 * This is a simple concurrency check to make sure only one queue run
1572 * is done at a time. We could do this with a mutex, but since we
1573 * don't really require extremely fine granularity here, we'll do it
1574 * with a static variable instead.
1576 if (doing_queue) return;
1580 * Go ahead and run the queue
1582 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1584 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1585 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1588 CtdlForEachMessage(MSGS_ALL, 0L,
1589 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1591 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1598 /*****************************************************************************/
1599 /* SMTP UTILITY COMMANDS */
1600 /*****************************************************************************/
1602 void cmd_smtp(char *argbuf) {
1609 if (CtdlAccessCheck(ac_aide)) return;
1611 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1613 if (!strcasecmp(cmd, "mx")) {
1614 extract_token(node, argbuf, 1, '|', sizeof node);
1615 num_mxhosts = getmx(buf, node);
1616 cprintf("%d %d MX hosts listed for %s\n",
1617 LISTING_FOLLOWS, num_mxhosts, node);
1618 for (i=0; i<num_mxhosts; ++i) {
1619 extract_token(node, buf, i, '|', sizeof node);
1620 cprintf("%s\n", node);
1626 else if (!strcasecmp(cmd, "runqueue")) {
1628 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1633 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1640 * Initialize the SMTP outbound queue
1642 void smtp_init_spoolout(void) {
1643 struct ctdlroom qrbuf;
1646 * Create the room. This will silently fail if the room already
1647 * exists, and that's perfectly ok, because we want it to exist.
1649 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1652 * Make sure it's set to be a "system room" so it doesn't show up
1653 * in the <K>nown rooms list for Aides.
1655 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1656 qrbuf.QRflags2 |= QR2_SYSTEM;
1664 /*****************************************************************************/
1665 /* MODULE INITIALIZATION STUFF */
1666 /*****************************************************************************/
1668 * This cleanup function blows away the temporary memory used by
1671 void smtp_cleanup_function(void) {
1673 /* Don't do this stuff if this is not an SMTP session! */
1674 if (CC->h_command_function != smtp_command_loop) return;
1676 lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1686 char *serv_smtp_init(void)
1688 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1695 CtdlRegisterServiceHook(config.c_smtps_port,
1702 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1708 CtdlRegisterServiceHook(0, /* local LMTP */
1709 #ifndef HAVE_RUN_DIR
1719 CtdlRegisterServiceHook(0, /* local LMTP */
1720 #ifndef HAVE_RUN_DIR
1725 "/lmtp-unfiltered.socket",
1726 lmtp_unfiltered_greeting,
1730 smtp_init_spoolout();
1731 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1732 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1733 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");