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 safestrncpy(SMTP->from, CC->cs_inet_email, sizeof SMTP->from);
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");
976 stripallbut(mailfrom, '<', '>');
978 /* Figure out what mail exchanger host we have to connect to */
979 num_mxhosts = getmx(mxhosts, node);
980 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
981 if (num_mxhosts < 1) {
983 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
988 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
989 extract_token(buf, mxhosts, mx, '|', sizeof buf);
990 lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
991 sock = sock_connect(buf, "25", "tcp");
992 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
993 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
994 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
998 *status = 4; /* dsn is already filled in */
1002 /* Process the SMTP greeting from the server */
1003 if (ml_sock_gets(sock, buf) < 0) {
1005 strcpy(dsn, "Connection broken during SMTP conversation");
1008 lprintf(CTDL_DEBUG, "<%s\n", buf);
1009 if (buf[0] != '2') {
1010 if (buf[0] == '4') {
1012 safestrncpy(dsn, &buf[4], 1023);
1017 safestrncpy(dsn, &buf[4], 1023);
1022 /* At this point we know we are talking to a real SMTP server */
1024 /* Do a HELO command */
1025 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1026 lprintf(CTDL_DEBUG, ">%s", buf);
1027 sock_write(sock, buf, strlen(buf));
1028 if (ml_sock_gets(sock, buf) < 0) {
1030 strcpy(dsn, "Connection broken during SMTP HELO");
1033 lprintf(CTDL_DEBUG, "<%s\n", buf);
1034 if (buf[0] != '2') {
1035 if (buf[0] == '4') {
1037 safestrncpy(dsn, &buf[4], 1023);
1042 safestrncpy(dsn, &buf[4], 1023);
1047 /* HELO succeeded, now try the MAIL From: command */
1048 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1049 lprintf(CTDL_DEBUG, ">%s", buf);
1050 sock_write(sock, buf, strlen(buf));
1051 if (ml_sock_gets(sock, buf) < 0) {
1053 strcpy(dsn, "Connection broken during SMTP MAIL");
1056 lprintf(CTDL_DEBUG, "<%s\n", buf);
1057 if (buf[0] != '2') {
1058 if (buf[0] == '4') {
1060 safestrncpy(dsn, &buf[4], 1023);
1065 safestrncpy(dsn, &buf[4], 1023);
1070 /* MAIL succeeded, now try the RCPT To: command */
1071 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
1072 lprintf(CTDL_DEBUG, ">%s", buf);
1073 sock_write(sock, buf, strlen(buf));
1074 if (ml_sock_gets(sock, buf) < 0) {
1076 strcpy(dsn, "Connection broken during SMTP RCPT");
1079 lprintf(CTDL_DEBUG, "<%s\n", buf);
1080 if (buf[0] != '2') {
1081 if (buf[0] == '4') {
1083 safestrncpy(dsn, &buf[4], 1023);
1088 safestrncpy(dsn, &buf[4], 1023);
1093 /* RCPT succeeded, now try the DATA command */
1094 lprintf(CTDL_DEBUG, ">DATA\n");
1095 sock_write(sock, "DATA\r\n", 6);
1096 if (ml_sock_gets(sock, buf) < 0) {
1098 strcpy(dsn, "Connection broken during SMTP DATA");
1101 lprintf(CTDL_DEBUG, "<%s\n", buf);
1102 if (buf[0] != '3') {
1103 if (buf[0] == '4') {
1105 safestrncpy(dsn, &buf[4], 1023);
1110 safestrncpy(dsn, &buf[4], 1023);
1115 /* If we reach this point, the server is expecting data */
1116 sock_write(sock, msgtext, msg_size);
1117 if (msgtext[msg_size-1] != 10) {
1118 lprintf(CTDL_WARNING, "Possible problem: message did not "
1119 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1123 sock_write(sock, ".\r\n", 3);
1124 if (ml_sock_gets(sock, buf) < 0) {
1126 strcpy(dsn, "Connection broken during SMTP message transmit");
1129 lprintf(CTDL_DEBUG, "%s\n", buf);
1130 if (buf[0] != '2') {
1131 if (buf[0] == '4') {
1133 safestrncpy(dsn, &buf[4], 1023);
1138 safestrncpy(dsn, &buf[4], 1023);
1144 safestrncpy(dsn, &buf[4], 1023);
1147 lprintf(CTDL_DEBUG, ">QUIT\n");
1148 sock_write(sock, "QUIT\r\n", 6);
1149 ml_sock_gets(sock, buf);
1150 lprintf(CTDL_DEBUG, "<%s\n", buf);
1151 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1154 bail: free(msgtext);
1162 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1163 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1164 * a "bounce" message (delivery status notification).
1166 void smtp_do_bounce(char *instr) {
1174 char bounceto[1024];
1175 int num_bounces = 0;
1176 int bounce_this = 0;
1177 long bounce_msgid = (-1);
1178 time_t submitted = 0L;
1179 struct CtdlMessage *bmsg = NULL;
1181 struct recptypes *valid;
1182 int successful_bounce = 0;
1184 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1185 strcpy(bounceto, "");
1187 lines = num_tokens(instr, '\n');
1190 /* See if it's time to give up on delivery of this message */
1191 for (i=0; i<lines; ++i) {
1192 extract_token(buf, instr, i, '\n', sizeof buf);
1193 extract_token(key, buf, 0, '|', sizeof key);
1194 extract_token(addr, buf, 1, '|', sizeof addr);
1195 if (!strcasecmp(key, "submitted")) {
1196 submitted = atol(addr);
1200 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1206 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1207 if (bmsg == NULL) return;
1208 memset(bmsg, 0, sizeof(struct CtdlMessage));
1210 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1211 bmsg->cm_anon_type = MES_NORMAL;
1212 bmsg->cm_format_type = 1;
1213 bmsg->cm_fields['A'] = strdup("Citadel");
1214 bmsg->cm_fields['O'] = strdup(MAILROOM);
1215 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1217 if (give_up) bmsg->cm_fields['M'] = strdup(
1218 "A message you sent could not be delivered to some or all of its recipients\n"
1219 "due to prolonged unavailability of its destination(s).\n"
1220 "Giving up on the following addresses:\n\n"
1223 else bmsg->cm_fields['M'] = strdup(
1224 "A message you sent could not be delivered to some or all of its recipients.\n"
1225 "The following addresses were undeliverable:\n\n"
1229 * Now go through the instructions checking for stuff.
1231 for (i=0; i<lines; ++i) {
1232 extract_token(buf, instr, i, '\n', sizeof buf);
1233 extract_token(key, buf, 0, '|', sizeof key);
1234 extract_token(addr, buf, 1, '|', sizeof addr);
1235 status = extract_int(buf, 2);
1236 extract_token(dsn, buf, 3, '|', sizeof dsn);
1239 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1240 key, addr, status, dsn);
1242 if (!strcasecmp(key, "bounceto")) {
1243 strcpy(bounceto, addr);
1247 (!strcasecmp(key, "local"))
1248 || (!strcasecmp(key, "remote"))
1249 || (!strcasecmp(key, "ignet"))
1250 || (!strcasecmp(key, "room"))
1252 if (status == 5) bounce_this = 1;
1253 if (give_up) bounce_this = 1;
1259 if (bmsg->cm_fields['M'] == NULL) {
1260 lprintf(CTDL_ERR, "ERROR ... M field is null "
1261 "(%s:%d)\n", __FILE__, __LINE__);
1264 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1265 strlen(bmsg->cm_fields['M']) + 1024 );
1266 strcat(bmsg->cm_fields['M'], addr);
1267 strcat(bmsg->cm_fields['M'], ": ");
1268 strcat(bmsg->cm_fields['M'], dsn);
1269 strcat(bmsg->cm_fields['M'], "\n");
1271 remove_token(instr, i, '\n');
1277 /* Deliver the bounce if there's anything worth mentioning */
1278 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1279 if (num_bounces > 0) {
1281 /* First try the user who sent the message */
1282 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1283 if (strlen(bounceto) == 0) {
1284 lprintf(CTDL_ERR, "No bounce address specified\n");
1285 bounce_msgid = (-1L);
1288 /* Can we deliver the bounce to the original sender? */
1289 valid = validate_recipients(bounceto);
1290 if (valid != NULL) {
1291 if (valid->num_error == 0) {
1292 CtdlSubmitMsg(bmsg, valid, "");
1293 successful_bounce = 1;
1297 /* If not, post it in the Aide> room */
1298 if (successful_bounce == 0) {
1299 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1302 /* Free up the memory we used */
1303 if (valid != NULL) {
1308 CtdlFreeMessage(bmsg);
1309 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1314 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1315 * set of delivery instructions for completed deliveries and remove them.
1317 * It returns the number of incomplete deliveries remaining.
1319 int smtp_purge_completed_deliveries(char *instr) {
1330 lines = num_tokens(instr, '\n');
1331 for (i=0; i<lines; ++i) {
1332 extract_token(buf, instr, i, '\n', sizeof buf);
1333 extract_token(key, buf, 0, '|', sizeof key);
1334 extract_token(addr, buf, 1, '|', sizeof addr);
1335 status = extract_int(buf, 2);
1336 extract_token(dsn, buf, 3, '|', sizeof dsn);
1341 (!strcasecmp(key, "local"))
1342 || (!strcasecmp(key, "remote"))
1343 || (!strcasecmp(key, "ignet"))
1344 || (!strcasecmp(key, "room"))
1346 if (status == 2) completed = 1;
1351 remove_token(instr, i, '\n');
1364 * Called by smtp_do_queue() to handle an individual message.
1366 void smtp_do_procmsg(long msgnum, void *userdata) {
1367 struct CtdlMessage *msg;
1369 char *results = NULL;
1377 long text_msgid = (-1);
1378 int incomplete_deliveries_remaining;
1379 time_t attempted = 0L;
1380 time_t last_attempted = 0L;
1381 time_t retry = SMTP_RETRY_INTERVAL;
1383 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1385 msg = CtdlFetchMessage(msgnum, 1);
1387 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1391 instr = strdup(msg->cm_fields['M']);
1392 CtdlFreeMessage(msg);
1394 /* Strip out the headers amd any other non-instruction line */
1395 lines = num_tokens(instr, '\n');
1396 for (i=0; i<lines; ++i) {
1397 extract_token(buf, instr, i, '\n', sizeof buf);
1398 if (num_tokens(buf, '|') < 2) {
1399 remove_token(instr, i, '\n');
1405 /* Learn the message ID and find out about recent delivery attempts */
1406 lines = num_tokens(instr, '\n');
1407 for (i=0; i<lines; ++i) {
1408 extract_token(buf, instr, i, '\n', sizeof buf);
1409 extract_token(key, buf, 0, '|', sizeof key);
1410 if (!strcasecmp(key, "msgid")) {
1411 text_msgid = extract_long(buf, 1);
1413 if (!strcasecmp(key, "retry")) {
1414 /* double the retry interval after each attempt */
1415 retry = extract_long(buf, 1) * 2L;
1416 if (retry > SMTP_RETRY_MAX) {
1417 retry = SMTP_RETRY_MAX;
1419 remove_token(instr, i, '\n');
1421 if (!strcasecmp(key, "attempted")) {
1422 attempted = extract_long(buf, 1);
1423 if (attempted > last_attempted)
1424 last_attempted = attempted;
1429 * Postpone delivery if we've already tried recently.
1431 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1432 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1439 * Bail out if there's no actual message associated with this
1441 if (text_msgid < 0L) {
1442 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1447 /* Plow through the instructions looking for 'remote' directives and
1448 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1449 * were experienced and it's time to try again)
1451 lines = num_tokens(instr, '\n');
1452 for (i=0; i<lines; ++i) {
1453 extract_token(buf, instr, i, '\n', sizeof buf);
1454 extract_token(key, buf, 0, '|', sizeof key);
1455 extract_token(addr, buf, 1, '|', sizeof addr);
1456 status = extract_int(buf, 2);
1457 extract_token(dsn, buf, 3, '|', sizeof dsn);
1458 if ( (!strcasecmp(key, "remote"))
1459 && ((status==0)||(status==3)||(status==4)) ) {
1461 /* Remove this "remote" instruction from the set,
1462 * but replace the set's final newline if
1463 * remove_token() stripped it. It has to be there.
1465 remove_token(instr, i, '\n');
1466 if (instr[strlen(instr)-1] != '\n') {
1467 strcat(instr, "\n");
1472 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1473 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1475 if (results == NULL) {
1476 results = malloc(1024);
1477 memset(results, 0, 1024);
1480 results = realloc(results,
1481 strlen(results) + 1024);
1483 snprintf(&results[strlen(results)], 1024,
1485 key, addr, status, dsn);
1490 if (results != NULL) {
1491 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1492 strcat(instr, results);
1497 /* Generate 'bounce' messages */
1498 smtp_do_bounce(instr);
1500 /* Go through the delivery list, deleting completed deliveries */
1501 incomplete_deliveries_remaining =
1502 smtp_purge_completed_deliveries(instr);
1506 * No delivery instructions remain, so delete both the instructions
1507 * message and the message message.
1509 if (incomplete_deliveries_remaining <= 0) {
1510 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1511 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1516 * Uncompleted delivery instructions remain, so delete the old
1517 * instructions and replace with the updated ones.
1519 if (incomplete_deliveries_remaining > 0) {
1520 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1521 msg = malloc(sizeof(struct CtdlMessage));
1522 memset(msg, 0, sizeof(struct CtdlMessage));
1523 msg->cm_magic = CTDLMESSAGE_MAGIC;
1524 msg->cm_anon_type = MES_NORMAL;
1525 msg->cm_format_type = FMT_RFC822;
1526 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1527 snprintf(msg->cm_fields['M'],
1529 "Content-type: %s\n\n%s\n"
1532 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1533 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1534 CtdlFreeMessage(msg);
1545 * Run through the queue sending out messages.
1547 void smtp_do_queue(void) {
1548 static int doing_queue = 0;
1551 * This is a simple concurrency check to make sure only one queue run
1552 * is done at a time. We could do this with a mutex, but since we
1553 * don't really require extremely fine granularity here, we'll do it
1554 * with a static variable instead.
1556 if (doing_queue) return;
1560 * Go ahead and run the queue
1562 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1564 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1565 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1568 CtdlForEachMessage(MSGS_ALL, 0L,
1569 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1571 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1578 /*****************************************************************************/
1579 /* SMTP UTILITY COMMANDS */
1580 /*****************************************************************************/
1582 void cmd_smtp(char *argbuf) {
1589 if (CtdlAccessCheck(ac_aide)) return;
1591 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1593 if (!strcasecmp(cmd, "mx")) {
1594 extract_token(node, argbuf, 1, '|', sizeof node);
1595 num_mxhosts = getmx(buf, node);
1596 cprintf("%d %d MX hosts listed for %s\n",
1597 LISTING_FOLLOWS, num_mxhosts, node);
1598 for (i=0; i<num_mxhosts; ++i) {
1599 extract_token(node, buf, i, '|', sizeof node);
1600 cprintf("%s\n", node);
1606 else if (!strcasecmp(cmd, "runqueue")) {
1608 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1613 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1620 * Initialize the SMTP outbound queue
1622 void smtp_init_spoolout(void) {
1623 struct ctdlroom qrbuf;
1626 * Create the room. This will silently fail if the room already
1627 * exists, and that's perfectly ok, because we want it to exist.
1629 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1632 * Make sure it's set to be a "system room" so it doesn't show up
1633 * in the <K>nown rooms list for Aides.
1635 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1636 qrbuf.QRflags2 |= QR2_SYSTEM;
1644 /*****************************************************************************/
1645 /* MODULE INITIALIZATION STUFF */
1646 /*****************************************************************************/
1648 * This cleanup function blows away the temporary memory used by
1651 void smtp_cleanup_function(void) {
1653 /* Don't do this stuff if this is not an SMTP session! */
1654 if (CC->h_command_function != smtp_command_loop) return;
1656 lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1666 char *serv_smtp_init(void)
1668 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1675 CtdlRegisterServiceHook(config.c_smtps_port,
1682 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1688 CtdlRegisterServiceHook(0, /* local LMTP */
1694 smtp_init_spoolout();
1695 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1696 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1697 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");