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;
98 enum { /* Command states for login authentication */
104 enum { /* Delivery modes */
109 #define SMTP CC->SMTP
110 #define SMTP_RECPS CC->SMTP_RECPS
111 #define SMTP_ROOMS CC->SMTP_ROOMS
114 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
118 /*****************************************************************************/
119 /* SMTP SERVER (INBOUND) STUFF */
120 /*****************************************************************************/
124 * Here's where our SMTP session begins its happy day.
126 void smtp_greeting(void) {
128 strcpy(CC->cs_clientname, "SMTP session");
129 CC->internal_pgm = 1;
130 CC->cs_flags |= CS_STEALTH;
131 SMTP = malloc(sizeof(struct citsmtp));
132 SMTP_RECPS = malloc(SIZ);
133 SMTP_ROOMS = malloc(SIZ);
134 memset(SMTP, 0, sizeof(struct citsmtp));
135 memset(SMTP_RECPS, 0, SIZ);
136 memset(SMTP_ROOMS, 0, SIZ);
138 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
143 * SMTPS is just like SMTP, except it goes crypto right away.
146 void smtps_greeting(void) {
147 CtdlStartTLS(NULL, NULL, NULL);
154 * SMTP MSA port requires authentication.
156 void smtp_msa_greeting(void) {
163 * LMTP is like SMTP but with some extra bonus footage added.
165 void lmtp_greeting(void) {
172 * Login greeting common to all auth methods
174 void smtp_auth_greeting(void) {
175 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
176 lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
177 CC->internal_pgm = 0;
178 CC->cs_flags &= ~CS_STEALTH;
183 * Implement HELO and EHLO commands.
185 * which_command: 0=HELO, 1=EHLO, 2=LHLO
187 void smtp_hello(char *argbuf, int which_command) {
189 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
191 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
192 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
196 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
197 cprintf("500 LHLO is only allowed when running LMTP\r\n");
201 if (which_command == 0) {
202 cprintf("250 Hello %s (%s [%s])\r\n",
209 if (which_command == 1) {
210 cprintf("250-Hello %s (%s [%s])\r\n",
217 cprintf("250-Greetings and joyous salutations.\r\n");
219 cprintf("250-HELP\r\n");
220 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
224 /* Only offer the PIPELINING command if TLS is inactive,
225 * because of flow control issues. Also, avoid offering TLS
226 * if TLS is already active. Finally, we only offer TLS on
227 * the SMTP-MSA port, not on the SMTP-MTA port, due to
228 * questionable reliability of TLS in certain sending MTA's.
230 if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
231 cprintf("250-PIPELINING\r\n");
232 cprintf("250-STARTTLS\r\n");
235 #else /* HAVE_OPENSSL */
237 /* Non SSL enabled server, so always offer PIPELINING. */
238 cprintf("250-PIPELINING\r\n");
240 #endif /* HAVE_OPENSSL */
242 cprintf("250-AUTH LOGIN PLAIN\r\n");
243 cprintf("250-AUTH=LOGIN PLAIN\r\n");
245 cprintf("250 ENHANCEDSTATUSCODES\r\n");
252 * Implement HELP command.
254 void smtp_help(void) {
255 cprintf("214-Commands accepted:\r\n");
256 cprintf("214- DATA\r\n");
257 cprintf("214- EHLO\r\n");
258 cprintf("214- EXPN\r\n");
259 cprintf("214- HELO\r\n");
260 cprintf("214- HELP\r\n");
261 cprintf("214- MAIL\r\n");
262 cprintf("214- NOOP\r\n");
263 cprintf("214- QUIT\r\n");
264 cprintf("214- RCPT\r\n");
265 cprintf("214- RSET\r\n");
266 cprintf("214- VRFY\r\n");
274 void smtp_get_user(char *argbuf) {
278 CtdlDecodeBase64(username, argbuf, SIZ);
279 lprintf(CTDL_DEBUG, "Trying <%s>\n", username);
280 if (CtdlLoginExistingUser(username) == login_ok) {
281 CtdlEncodeBase64(buf, "Password:", 9);
282 cprintf("334 %s\r\n", buf);
283 SMTP->command_state = smtp_password;
286 cprintf("500 5.7.0 No such user.\r\n");
287 SMTP->command_state = smtp_command;
295 void smtp_get_pass(char *argbuf) {
298 CtdlDecodeBase64(password, argbuf, SIZ);
299 lprintf(CTDL_DEBUG, "Trying <%s>\n", password);
300 if (CtdlTryPassword(password) == pass_ok) {
301 smtp_auth_greeting();
304 cprintf("535 5.7.0 Authentication failed.\r\n");
306 SMTP->command_state = smtp_command;
313 void smtp_auth(char *argbuf) {
314 char username_prompt[64];
316 char encoded_authstring[1024];
317 char decoded_authstring[1024];
323 cprintf("504 5.7.4 Already logged in.\r\n");
327 extract_token(method, argbuf, 0, ' ', sizeof method);
329 if (!strncasecmp(method, "login", 5) ) {
330 if (strlen(argbuf) >= 7) {
331 smtp_get_user(&argbuf[6]);
334 CtdlEncodeBase64(username_prompt, "Username:", 9);
335 cprintf("334 %s\r\n", username_prompt);
336 SMTP->command_state = smtp_user;
341 if (!strncasecmp(method, "plain", 5) ) {
342 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
343 CtdlDecodeBase64(decoded_authstring,
345 strlen(encoded_authstring) );
346 safestrncpy(ident, decoded_authstring, sizeof ident);
347 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
348 safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
350 if (CtdlLoginExistingUser(user) == login_ok) {
351 if (CtdlTryPassword(pass) == pass_ok) {
352 smtp_auth_greeting();
356 cprintf("504 5.7.4 Authentication failed.\r\n");
359 if (strncasecmp(method, "login", 5) ) {
360 cprintf("504 5.7.4 Unknown authentication method.\r\n");
368 * Back end for smtp_vrfy() command
370 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
372 if (!fuzzy_match(us, SMTP->vrfy_match)) {
374 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
380 * Implements the VRFY (verify user name) command.
381 * Performs fuzzy match on full user names.
383 void smtp_vrfy(char *argbuf) {
384 SMTP->vrfy_count = 0;
385 strcpy(SMTP->vrfy_match, argbuf);
386 ForEachUser(smtp_vrfy_backend, NULL);
388 if (SMTP->vrfy_count < 1) {
389 cprintf("550 5.1.1 String does not match anything.\r\n");
391 else if (SMTP->vrfy_count == 1) {
392 cprintf("250 %s <cit%ld@%s>\r\n",
393 SMTP->vrfy_buffer.fullname,
394 SMTP->vrfy_buffer.usernum,
397 else if (SMTP->vrfy_count > 1) {
398 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
407 * Back end for smtp_expn() command
409 void smtp_expn_backend(struct ctdluser *us, void *data) {
411 if (!fuzzy_match(us, SMTP->vrfy_match)) {
413 if (SMTP->vrfy_count >= 1) {
414 cprintf("250-%s <cit%ld@%s>\r\n",
415 SMTP->vrfy_buffer.fullname,
416 SMTP->vrfy_buffer.usernum,
421 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
427 * Implements the EXPN (expand user name) command.
428 * Performs fuzzy match on full user names.
430 void smtp_expn(char *argbuf) {
431 SMTP->vrfy_count = 0;
432 strcpy(SMTP->vrfy_match, argbuf);
433 ForEachUser(smtp_expn_backend, NULL);
435 if (SMTP->vrfy_count < 1) {
436 cprintf("550 5.1.1 String does not match anything.\r\n");
438 else if (SMTP->vrfy_count >= 1) {
439 cprintf("250 %s <cit%ld@%s>\r\n",
440 SMTP->vrfy_buffer.fullname,
441 SMTP->vrfy_buffer.usernum,
448 * Implements the RSET (reset state) command.
449 * Currently this just zeroes out the state buffer. If pointers to data
450 * allocated with malloc() are ever placed in the state buffer, we have to
451 * be sure to free() them first!
453 * Set do_response to nonzero to output the SMTP RSET response code.
455 void smtp_rset(int do_response) {
459 * Our entire SMTP state is discarded when a RSET command is issued,
460 * but we need to preserve this one little piece of information, so
461 * we save it for later.
463 is_lmtp = SMTP->is_lmtp;
465 memset(SMTP, 0, sizeof(struct citsmtp));
468 * It is somewhat ambiguous whether we want to log out when a RSET
469 * command is issued. Here's the code to do it. It is commented out
470 * because some clients (such as Pine) issue RSET commands before
471 * each message, but still expect to be logged in.
473 * if (CC->logged_in) {
479 * Reinstate this little piece of information we saved (see above).
481 SMTP->is_lmtp = is_lmtp;
484 cprintf("250 2.0.0 Zap!\r\n");
489 * Clear out the portions of the state buffer that need to be cleared out
490 * after the DATA command finishes.
492 void smtp_data_clear(void) {
493 strcpy(SMTP->from, "");
494 strcpy(SMTP->recipients, "");
495 SMTP->number_of_recipients = 0;
496 SMTP->delivery_mode = 0;
497 SMTP->message_originated_locally = 0;
503 * Implements the "MAIL From:" command
505 void smtp_mail(char *argbuf) {
510 if (strlen(SMTP->from) != 0) {
511 cprintf("503 5.1.0 Only one sender permitted\r\n");
515 if (strncasecmp(argbuf, "From:", 5)) {
516 cprintf("501 5.1.7 Syntax error\r\n");
520 strcpy(SMTP->from, &argbuf[5]);
522 if (haschar(SMTP->from, '<') > 0) {
523 stripallbut(SMTP->from, '<', '>');
526 /* We used to reject empty sender names, until it was brought to our
527 * attention that RFC1123 5.2.9 requires that this be allowed. So now
528 * we allow it, but replace the empty string with a fake
529 * address so we don't have to contend with the empty string causing
530 * other code to fail when it's expecting something there.
532 if (strlen(SMTP->from) == 0) {
533 strcpy(SMTP->from, "someone@somewhere.org");
536 /* If this SMTP connection is from a logged-in user, force the 'from'
537 * to be the user's Internet e-mail address as Citadel knows it.
540 strcpy(SMTP->from, CC->cs_inet_email);
541 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
542 SMTP->message_originated_locally = 1;
546 else if (SMTP->is_lmtp) {
547 /* Bypass forgery checking for LMTP */
550 /* Otherwise, make sure outsiders aren't trying to forge mail from
554 process_rfc822_addr(SMTP->from, user, node, name);
555 if (CtdlHostAlias(node) != hostalias_nomatch) {
557 "You must log in to send mail from %s\r\n",
559 strcpy(SMTP->from, "");
564 cprintf("250 2.0.0 Sender ok\r\n");
570 * Implements the "RCPT To:" command
572 void smtp_rcpt(char *argbuf) {
574 char message_to_spammer[SIZ];
575 struct recptypes *valid = NULL;
577 if (strlen(SMTP->from) == 0) {
578 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
582 if (strncasecmp(argbuf, "To:", 3)) {
583 cprintf("501 5.1.7 Syntax error\r\n");
587 if ( (SMTP->is_msa) && (!CC->logged_in) ) {
589 "You must log in to send mail on this port.\r\n");
590 strcpy(SMTP->from, "");
594 strcpy(recp, &argbuf[3]);
596 stripallbut(recp, '<', '>');
598 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
599 cprintf("452 4.5.3 Too many recipients\r\n");
604 if ( (!CC->logged_in)
605 && (!SMTP->is_lmtp) ) {
606 if (rbl_check(message_to_spammer)) {
607 cprintf("550 %s\r\n", message_to_spammer);
608 /* no need to free(valid), it's not allocated yet */
613 valid = validate_recipients(recp);
614 if (valid->num_error > 0) {
615 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
620 if (valid->num_internet > 0) {
622 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
623 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
630 if (valid->num_internet > 0) {
631 if ( (SMTP->message_originated_locally == 0)
632 && (SMTP->is_lmtp == 0) ) {
633 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
639 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
640 if (strlen(SMTP->recipients) > 0) {
641 strcat(SMTP->recipients, ",");
643 strcat(SMTP->recipients, recp);
644 SMTP->number_of_recipients += 1;
651 * Implements the DATA command
653 void smtp_data(void) {
655 struct CtdlMessage *msg;
658 struct recptypes *valid;
663 if (strlen(SMTP->from) == 0) {
664 cprintf("503 5.5.1 Need MAIL command first.\r\n");
668 if (SMTP->number_of_recipients < 1) {
669 cprintf("503 5.5.1 Need RCPT command first.\r\n");
673 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
675 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
678 if (body != NULL) snprintf(body, 4096,
679 "Received: from %s (%s [%s])\n"
687 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
690 "Unable to save message: internal error.\r\n");
694 lprintf(CTDL_DEBUG, "Converting message...\n");
695 msg = convert_internet_message(body);
697 /* If the user is locally authenticated, FORCE the From: header to
698 * show up as the real sender. Yes, this violates the RFC standard,
699 * but IT MAKES SENSE. If you prefer strict RFC adherence over
700 * common sense, you can disable this in the configuration.
702 * We also set the "message room name" ('O' field) to MAILROOM
703 * (which is Mail> on most systems) to prevent it from getting set
704 * to something ugly like "0000058008.Sent Items>" when the message
705 * is read with a Citadel client.
707 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
708 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
709 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
710 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
711 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
712 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
713 msg->cm_fields['A'] = strdup(CC->user.fullname);
714 msg->cm_fields['N'] = strdup(config.c_nodename);
715 msg->cm_fields['H'] = strdup(config.c_humannode);
716 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
717 msg->cm_fields['O'] = strdup(MAILROOM);
720 /* Submit the message into the Citadel system. */
721 valid = validate_recipients(SMTP->recipients);
723 /* If there are modules that want to scan this message before final
724 * submission (such as virus checkers or spam filters), call them now
725 * and give them an opportunity to reject the message.
727 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
729 if (scan_errors > 0) { /* We don't want this message! */
731 if (msg->cm_fields['0'] == NULL) {
732 msg->cm_fields['0'] = strdup(
733 "5.7.1 Message rejected by filter");
736 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
739 else { /* Ok, we'll accept this message. */
740 msgnum = CtdlSubmitMsg(msg, valid, "");
742 sprintf(result, "250 2.0.0 Message accepted.\r\n");
745 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
749 /* For SMTP and ESTMP, just print the result message. For LMTP, we
750 * have to print one result message for each recipient. Since there
751 * is nothing in Citadel which would cause different recipients to
752 * have different results, we can get away with just spitting out the
753 * same message once for each recipient.
756 for (i=0; i<SMTP->number_of_recipients; ++i) {
757 cprintf("%s", result);
761 cprintf("%s", result);
764 CtdlFreeMessage(msg);
766 smtp_data_clear(); /* clear out the buffers now */
771 * implements the STARTTLS command (Citadel API version)
774 void smtp_starttls(void)
776 char ok_response[SIZ];
777 char nosup_response[SIZ];
778 char error_response[SIZ];
781 "200 2.0.0 Begin TLS negotiation now\r\n");
782 sprintf(nosup_response,
783 "554 5.7.3 TLS not supported here\r\n");
784 sprintf(error_response,
785 "554 5.7.3 Internal error\r\n");
786 CtdlStartTLS(ok_response, nosup_response, error_response);
794 * Main command loop for SMTP sessions.
796 void smtp_command_loop(void) {
800 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
801 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
802 lprintf(CTDL_CRIT, "SMTP socket is broken. Ending session.\n");
806 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
807 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
809 if (SMTP->command_state == smtp_user) {
810 smtp_get_user(cmdbuf);
813 else if (SMTP->command_state == smtp_password) {
814 smtp_get_pass(cmdbuf);
817 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
818 smtp_auth(&cmdbuf[5]);
821 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
825 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
826 smtp_expn(&cmdbuf[5]);
829 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
830 smtp_hello(&cmdbuf[5], 0);
833 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
834 smtp_hello(&cmdbuf[5], 1);
837 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
838 smtp_hello(&cmdbuf[5], 2);
841 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
845 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
846 smtp_mail(&cmdbuf[5]);
849 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
850 cprintf("250 NOOP\r\n");
853 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
854 cprintf("221 Goodbye...\r\n");
859 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
860 smtp_rcpt(&cmdbuf[5]);
863 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
867 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
871 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
872 smtp_vrfy(&cmdbuf[5]);
876 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
885 /*****************************************************************************/
886 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
887 /*****************************************************************************/
894 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
897 void smtp_try(const char *key, const char *addr, int *status,
898 char *dsn, size_t n, long msgnum)
905 char user[SIZ], node[SIZ], name[SIZ];
914 /* Parse out the host portion of the recipient address */
915 process_rfc822_addr(addr, user, node, name);
917 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
920 /* Load the message out of the database */
921 CC->redirect_buffer = malloc(SIZ);
922 CC->redirect_len = 0;
923 CC->redirect_alloc = SIZ;
924 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
925 msgtext = CC->redirect_buffer;
926 msg_size = CC->redirect_len;
927 CC->redirect_buffer = NULL;
928 CC->redirect_len = 0;
929 CC->redirect_alloc = 0;
931 /* Extract something to send later in the 'MAIL From:' command */
932 strcpy(mailfrom, "");
936 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
939 if (!strncasecmp(buf, "From:", 5)) {
940 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
942 for (i=0; i<strlen(mailfrom); ++i) {
943 if (!isprint(mailfrom[i])) {
944 strcpy(&mailfrom[i], &mailfrom[i+1]);
949 /* Strip out parenthesized names */
952 for (i=0; i<strlen(mailfrom); ++i) {
953 if (mailfrom[i] == '(') lp = i;
954 if (mailfrom[i] == ')') rp = i;
956 if ((lp>0)&&(rp>lp)) {
957 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
960 /* Prefer brokketized names */
963 for (i=0; i<strlen(mailfrom); ++i) {
964 if (mailfrom[i] == '<') lp = i;
965 if (mailfrom[i] == '>') rp = i;
967 if ( (lp>=0) && (rp>lp) ) {
969 strcpy(mailfrom, &mailfrom[lp]);
974 } while (scan_done == 0);
975 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
977 /* Figure out what mail exchanger host we have to connect to */
978 num_mxhosts = getmx(mxhosts, node);
979 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
980 if (num_mxhosts < 1) {
982 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
987 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
988 extract_token(buf, mxhosts, mx, '|', sizeof buf);
989 lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
990 sock = sock_connect(buf, "25", "tcp");
991 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
992 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
993 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
997 *status = 4; /* dsn is already filled in */
1001 /* Process the SMTP greeting from the server */
1002 if (ml_sock_gets(sock, buf) < 0) {
1004 strcpy(dsn, "Connection broken during SMTP conversation");
1007 lprintf(CTDL_DEBUG, "<%s\n", buf);
1008 if (buf[0] != '2') {
1009 if (buf[0] == '4') {
1011 safestrncpy(dsn, &buf[4], 1023);
1016 safestrncpy(dsn, &buf[4], 1023);
1021 /* At this point we know we are talking to a real SMTP server */
1023 /* Do a HELO command */
1024 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1025 lprintf(CTDL_DEBUG, ">%s", buf);
1026 sock_write(sock, buf, strlen(buf));
1027 if (ml_sock_gets(sock, buf) < 0) {
1029 strcpy(dsn, "Connection broken during SMTP HELO");
1032 lprintf(CTDL_DEBUG, "<%s\n", buf);
1033 if (buf[0] != '2') {
1034 if (buf[0] == '4') {
1036 safestrncpy(dsn, &buf[4], 1023);
1041 safestrncpy(dsn, &buf[4], 1023);
1046 /* HELO succeeded, now try the MAIL From: command */
1047 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1048 lprintf(CTDL_DEBUG, ">%s", buf);
1049 sock_write(sock, buf, strlen(buf));
1050 if (ml_sock_gets(sock, buf) < 0) {
1052 strcpy(dsn, "Connection broken during SMTP MAIL");
1055 lprintf(CTDL_DEBUG, "<%s\n", buf);
1056 if (buf[0] != '2') {
1057 if (buf[0] == '4') {
1059 safestrncpy(dsn, &buf[4], 1023);
1064 safestrncpy(dsn, &buf[4], 1023);
1069 /* MAIL succeeded, now try the RCPT To: command */
1070 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
1071 lprintf(CTDL_DEBUG, ">%s", buf);
1072 sock_write(sock, buf, strlen(buf));
1073 if (ml_sock_gets(sock, buf) < 0) {
1075 strcpy(dsn, "Connection broken during SMTP RCPT");
1078 lprintf(CTDL_DEBUG, "<%s\n", buf);
1079 if (buf[0] != '2') {
1080 if (buf[0] == '4') {
1082 safestrncpy(dsn, &buf[4], 1023);
1087 safestrncpy(dsn, &buf[4], 1023);
1092 /* RCPT succeeded, now try the DATA command */
1093 lprintf(CTDL_DEBUG, ">DATA\n");
1094 sock_write(sock, "DATA\r\n", 6);
1095 if (ml_sock_gets(sock, buf) < 0) {
1097 strcpy(dsn, "Connection broken during SMTP DATA");
1100 lprintf(CTDL_DEBUG, "<%s\n", buf);
1101 if (buf[0] != '3') {
1102 if (buf[0] == '4') {
1104 safestrncpy(dsn, &buf[4], 1023);
1109 safestrncpy(dsn, &buf[4], 1023);
1114 /* If we reach this point, the server is expecting data */
1115 sock_write(sock, msgtext, msg_size);
1116 if (msgtext[msg_size-1] != 10) {
1117 lprintf(CTDL_WARNING, "Possible problem: message did not "
1118 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1122 sock_write(sock, ".\r\n", 3);
1123 if (ml_sock_gets(sock, buf) < 0) {
1125 strcpy(dsn, "Connection broken during SMTP message transmit");
1128 lprintf(CTDL_DEBUG, "%s\n", buf);
1129 if (buf[0] != '2') {
1130 if (buf[0] == '4') {
1132 safestrncpy(dsn, &buf[4], 1023);
1137 safestrncpy(dsn, &buf[4], 1023);
1143 safestrncpy(dsn, &buf[4], 1023);
1146 lprintf(CTDL_DEBUG, ">QUIT\n");
1147 sock_write(sock, "QUIT\r\n", 6);
1148 ml_sock_gets(sock, buf);
1149 lprintf(CTDL_DEBUG, "<%s\n", buf);
1150 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1153 bail: free(msgtext);
1161 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1162 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1163 * a "bounce" message (delivery status notification).
1165 void smtp_do_bounce(char *instr) {
1173 char bounceto[1024];
1174 int num_bounces = 0;
1175 int bounce_this = 0;
1176 long bounce_msgid = (-1);
1177 time_t submitted = 0L;
1178 struct CtdlMessage *bmsg = NULL;
1180 struct recptypes *valid;
1181 int successful_bounce = 0;
1183 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1184 strcpy(bounceto, "");
1186 lines = num_tokens(instr, '\n');
1189 /* See if it's time to give up on delivery of this message */
1190 for (i=0; i<lines; ++i) {
1191 extract_token(buf, instr, i, '\n', sizeof buf);
1192 extract_token(key, buf, 0, '|', sizeof key);
1193 extract_token(addr, buf, 1, '|', sizeof addr);
1194 if (!strcasecmp(key, "submitted")) {
1195 submitted = atol(addr);
1199 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1205 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1206 if (bmsg == NULL) return;
1207 memset(bmsg, 0, sizeof(struct CtdlMessage));
1209 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1210 bmsg->cm_anon_type = MES_NORMAL;
1211 bmsg->cm_format_type = 1;
1212 bmsg->cm_fields['A'] = strdup("Citadel");
1213 bmsg->cm_fields['O'] = strdup(MAILROOM);
1214 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1216 if (give_up) bmsg->cm_fields['M'] = strdup(
1217 "A message you sent could not be delivered to some or all of its recipients\n"
1218 "due to prolonged unavailability of its destination(s).\n"
1219 "Giving up on the following addresses:\n\n"
1222 else bmsg->cm_fields['M'] = strdup(
1223 "A message you sent could not be delivered to some or all of its recipients.\n"
1224 "The following addresses were undeliverable:\n\n"
1228 * Now go through the instructions checking for stuff.
1230 for (i=0; i<lines; ++i) {
1231 extract_token(buf, instr, i, '\n', sizeof buf);
1232 extract_token(key, buf, 0, '|', sizeof key);
1233 extract_token(addr, buf, 1, '|', sizeof addr);
1234 status = extract_int(buf, 2);
1235 extract_token(dsn, buf, 3, '|', sizeof dsn);
1238 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1239 key, addr, status, dsn);
1241 if (!strcasecmp(key, "bounceto")) {
1242 strcpy(bounceto, addr);
1246 (!strcasecmp(key, "local"))
1247 || (!strcasecmp(key, "remote"))
1248 || (!strcasecmp(key, "ignet"))
1249 || (!strcasecmp(key, "room"))
1251 if (status == 5) bounce_this = 1;
1252 if (give_up) bounce_this = 1;
1258 if (bmsg->cm_fields['M'] == NULL) {
1259 lprintf(CTDL_ERR, "ERROR ... M field is null "
1260 "(%s:%d)\n", __FILE__, __LINE__);
1263 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1264 strlen(bmsg->cm_fields['M']) + 1024 );
1265 strcat(bmsg->cm_fields['M'], addr);
1266 strcat(bmsg->cm_fields['M'], ": ");
1267 strcat(bmsg->cm_fields['M'], dsn);
1268 strcat(bmsg->cm_fields['M'], "\n");
1270 remove_token(instr, i, '\n');
1276 /* Deliver the bounce if there's anything worth mentioning */
1277 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1278 if (num_bounces > 0) {
1280 /* First try the user who sent the message */
1281 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1282 if (strlen(bounceto) == 0) {
1283 lprintf(CTDL_ERR, "No bounce address specified\n");
1284 bounce_msgid = (-1L);
1287 /* Can we deliver the bounce to the original sender? */
1288 valid = validate_recipients(bounceto);
1289 if (valid != NULL) {
1290 if (valid->num_error == 0) {
1291 CtdlSubmitMsg(bmsg, valid, "");
1292 successful_bounce = 1;
1296 /* If not, post it in the Aide> room */
1297 if (successful_bounce == 0) {
1298 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1301 /* Free up the memory we used */
1302 if (valid != NULL) {
1307 CtdlFreeMessage(bmsg);
1308 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1313 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1314 * set of delivery instructions for completed deliveries and remove them.
1316 * It returns the number of incomplete deliveries remaining.
1318 int smtp_purge_completed_deliveries(char *instr) {
1329 lines = num_tokens(instr, '\n');
1330 for (i=0; i<lines; ++i) {
1331 extract_token(buf, instr, i, '\n', sizeof buf);
1332 extract_token(key, buf, 0, '|', sizeof key);
1333 extract_token(addr, buf, 1, '|', sizeof addr);
1334 status = extract_int(buf, 2);
1335 extract_token(dsn, buf, 3, '|', sizeof dsn);
1340 (!strcasecmp(key, "local"))
1341 || (!strcasecmp(key, "remote"))
1342 || (!strcasecmp(key, "ignet"))
1343 || (!strcasecmp(key, "room"))
1345 if (status == 2) completed = 1;
1350 remove_token(instr, i, '\n');
1363 * Called by smtp_do_queue() to handle an individual message.
1365 void smtp_do_procmsg(long msgnum, void *userdata) {
1366 struct CtdlMessage *msg;
1368 char *results = NULL;
1376 long text_msgid = (-1);
1377 int incomplete_deliveries_remaining;
1378 time_t attempted = 0L;
1379 time_t last_attempted = 0L;
1380 time_t retry = SMTP_RETRY_INTERVAL;
1382 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1384 msg = CtdlFetchMessage(msgnum, 1);
1386 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1390 instr = strdup(msg->cm_fields['M']);
1391 CtdlFreeMessage(msg);
1393 /* Strip out the headers amd any other non-instruction line */
1394 lines = num_tokens(instr, '\n');
1395 for (i=0; i<lines; ++i) {
1396 extract_token(buf, instr, i, '\n', sizeof buf);
1397 if (num_tokens(buf, '|') < 2) {
1398 remove_token(instr, i, '\n');
1404 /* Learn the message ID and find out about recent delivery attempts */
1405 lines = num_tokens(instr, '\n');
1406 for (i=0; i<lines; ++i) {
1407 extract_token(buf, instr, i, '\n', sizeof buf);
1408 extract_token(key, buf, 0, '|', sizeof key);
1409 if (!strcasecmp(key, "msgid")) {
1410 text_msgid = extract_long(buf, 1);
1412 if (!strcasecmp(key, "retry")) {
1413 /* double the retry interval after each attempt */
1414 retry = extract_long(buf, 1) * 2L;
1415 if (retry > SMTP_RETRY_MAX) {
1416 retry = SMTP_RETRY_MAX;
1418 remove_token(instr, i, '\n');
1420 if (!strcasecmp(key, "attempted")) {
1421 attempted = extract_long(buf, 1);
1422 if (attempted > last_attempted)
1423 last_attempted = attempted;
1428 * Postpone delivery if we've already tried recently.
1430 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1431 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1438 * Bail out if there's no actual message associated with this
1440 if (text_msgid < 0L) {
1441 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1446 /* Plow through the instructions looking for 'remote' directives and
1447 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1448 * were experienced and it's time to try again)
1450 lines = num_tokens(instr, '\n');
1451 for (i=0; i<lines; ++i) {
1452 extract_token(buf, instr, i, '\n', sizeof buf);
1453 extract_token(key, buf, 0, '|', sizeof key);
1454 extract_token(addr, buf, 1, '|', sizeof addr);
1455 status = extract_int(buf, 2);
1456 extract_token(dsn, buf, 3, '|', sizeof dsn);
1457 if ( (!strcasecmp(key, "remote"))
1458 && ((status==0)||(status==3)||(status==4)) ) {
1460 /* Remove this "remote" instruction from the set,
1461 * but replace the set's final newline if
1462 * remove_token() stripped it. It has to be there.
1464 remove_token(instr, i, '\n');
1465 if (instr[strlen(instr)-1] != '\n') {
1466 strcat(instr, "\n");
1471 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1472 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1474 if (results == NULL) {
1475 results = malloc(1024);
1476 memset(results, 0, 1024);
1479 results = realloc(results,
1480 strlen(results) + 1024);
1482 snprintf(&results[strlen(results)], 1024,
1484 key, addr, status, dsn);
1489 if (results != NULL) {
1490 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1491 strcat(instr, results);
1496 /* Generate 'bounce' messages */
1497 smtp_do_bounce(instr);
1499 /* Go through the delivery list, deleting completed deliveries */
1500 incomplete_deliveries_remaining =
1501 smtp_purge_completed_deliveries(instr);
1505 * No delivery instructions remain, so delete both the instructions
1506 * message and the message message.
1508 if (incomplete_deliveries_remaining <= 0) {
1509 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1510 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1515 * Uncompleted delivery instructions remain, so delete the old
1516 * instructions and replace with the updated ones.
1518 if (incomplete_deliveries_remaining > 0) {
1519 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1520 msg = malloc(sizeof(struct CtdlMessage));
1521 memset(msg, 0, sizeof(struct CtdlMessage));
1522 msg->cm_magic = CTDLMESSAGE_MAGIC;
1523 msg->cm_anon_type = MES_NORMAL;
1524 msg->cm_format_type = FMT_RFC822;
1525 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1526 snprintf(msg->cm_fields['M'],
1528 "Content-type: %s\n\n%s\n"
1531 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1532 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1533 CtdlFreeMessage(msg);
1544 * Run through the queue sending out messages.
1546 void smtp_do_queue(void) {
1547 static int doing_queue = 0;
1550 * This is a simple concurrency check to make sure only one queue run
1551 * is done at a time. We could do this with a mutex, but since we
1552 * don't really require extremely fine granularity here, we'll do it
1553 * with a static variable instead.
1555 if (doing_queue) return;
1559 * Go ahead and run the queue
1561 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1563 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1564 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1567 CtdlForEachMessage(MSGS_ALL, 0L,
1568 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1570 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1577 /*****************************************************************************/
1578 /* SMTP UTILITY COMMANDS */
1579 /*****************************************************************************/
1581 void cmd_smtp(char *argbuf) {
1588 if (CtdlAccessCheck(ac_aide)) return;
1590 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1592 if (!strcasecmp(cmd, "mx")) {
1593 extract_token(node, argbuf, 1, '|', sizeof node);
1594 num_mxhosts = getmx(buf, node);
1595 cprintf("%d %d MX hosts listed for %s\n",
1596 LISTING_FOLLOWS, num_mxhosts, node);
1597 for (i=0; i<num_mxhosts; ++i) {
1598 extract_token(node, buf, i, '|', sizeof node);
1599 cprintf("%s\n", node);
1605 else if (!strcasecmp(cmd, "runqueue")) {
1607 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1612 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1619 * Initialize the SMTP outbound queue
1621 void smtp_init_spoolout(void) {
1622 struct ctdlroom qrbuf;
1625 * Create the room. This will silently fail if the room already
1626 * exists, and that's perfectly ok, because we want it to exist.
1628 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1631 * Make sure it's set to be a "system room" so it doesn't show up
1632 * in the <K>nown rooms list for Aides.
1634 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1635 qrbuf.QRflags2 |= QR2_SYSTEM;
1643 /*****************************************************************************/
1644 /* MODULE INITIALIZATION STUFF */
1645 /*****************************************************************************/
1647 * This cleanup function blows away the temporary memory used by
1650 void smtp_cleanup_function(void) {
1652 /* Don't do this stuff if this is not an SMTP session! */
1653 if (CC->h_command_function != smtp_command_loop) return;
1655 lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1665 char *serv_smtp_init(void)
1667 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1674 CtdlRegisterServiceHook(config.c_smtps_port,
1681 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1687 CtdlRegisterServiceHook(0, /* local LMTP */
1693 smtp_init_spoolout();
1694 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1695 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1696 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");