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 stripallbut(SMTP->from, '<', '>');
524 /* We used to reject empty sender names, until it was brought to our
525 * attention that RFC1123 5.2.9 requires that this be allowed. So now
526 * we allow it, but replace the empty string with a fake
527 * address so we don't have to contend with the empty string causing
528 * other code to fail when it's expecting something there.
530 if (strlen(SMTP->from) == 0) {
531 strcpy(SMTP->from, "someone@somewhere.org");
534 /* If this SMTP connection is from a logged-in user, force the 'from'
535 * to be the user's Internet e-mail address as Citadel knows it.
538 strcpy(SMTP->from, CC->cs_inet_email);
539 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
540 SMTP->message_originated_locally = 1;
544 else if (SMTP->is_lmtp) {
545 /* Bypass forgery checking for LMTP */
548 /* Otherwise, make sure outsiders aren't trying to forge mail from
552 process_rfc822_addr(SMTP->from, user, node, name);
553 if (CtdlHostAlias(node) != hostalias_nomatch) {
555 "You must log in to send mail from %s\r\n",
557 strcpy(SMTP->from, "");
562 cprintf("250 2.0.0 Sender ok\r\n");
568 * Implements the "RCPT To:" command
570 void smtp_rcpt(char *argbuf) {
572 char message_to_spammer[SIZ];
573 struct recptypes *valid = NULL;
575 if (strlen(SMTP->from) == 0) {
576 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
580 if (strncasecmp(argbuf, "To:", 3)) {
581 cprintf("501 5.1.7 Syntax error\r\n");
585 if ( (SMTP->is_msa) && (!CC->logged_in) ) {
587 "You must log in to send mail on this port.\r\n");
588 strcpy(SMTP->from, "");
592 strcpy(recp, &argbuf[3]);
594 stripallbut(recp, '<', '>');
596 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
597 cprintf("452 4.5.3 Too many recipients\r\n");
602 if ( (!CC->logged_in)
603 && (!SMTP->is_lmtp) ) {
604 if (rbl_check(message_to_spammer)) {
605 cprintf("550 %s\r\n", message_to_spammer);
606 /* no need to free(valid), it's not allocated yet */
611 valid = validate_recipients(recp);
612 if (valid->num_error > 0) {
613 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
618 if (valid->num_internet > 0) {
620 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
621 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
628 if (valid->num_internet > 0) {
629 if ( (SMTP->message_originated_locally == 0)
630 && (SMTP->is_lmtp == 0) ) {
631 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
637 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
638 if (strlen(SMTP->recipients) > 0) {
639 strcat(SMTP->recipients, ",");
641 strcat(SMTP->recipients, recp);
642 SMTP->number_of_recipients += 1;
649 * Implements the DATA command
651 void smtp_data(void) {
653 struct CtdlMessage *msg;
656 struct recptypes *valid;
661 if (strlen(SMTP->from) == 0) {
662 cprintf("503 5.5.1 Need MAIL command first.\r\n");
666 if (SMTP->number_of_recipients < 1) {
667 cprintf("503 5.5.1 Need RCPT command first.\r\n");
671 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
673 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
676 if (body != NULL) snprintf(body, 4096,
677 "Received: from %s (%s [%s])\n"
685 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
688 "Unable to save message: internal error.\r\n");
692 lprintf(CTDL_DEBUG, "Converting message...\n");
693 msg = convert_internet_message(body);
695 /* If the user is locally authenticated, FORCE the From: header to
696 * show up as the real sender. Yes, this violates the RFC standard,
697 * but IT MAKES SENSE. If you prefer strict RFC adherence over
698 * common sense, you can disable this in the configuration.
700 * We also set the "message room name" ('O' field) to MAILROOM
701 * (which is Mail> on most systems) to prevent it from getting set
702 * to something ugly like "0000058008.Sent Items>" when the message
703 * is read with a Citadel client.
705 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
706 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
707 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
708 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
709 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
710 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
711 msg->cm_fields['A'] = strdup(CC->user.fullname);
712 msg->cm_fields['N'] = strdup(config.c_nodename);
713 msg->cm_fields['H'] = strdup(config.c_humannode);
714 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
715 msg->cm_fields['O'] = strdup(MAILROOM);
718 /* Submit the message into the Citadel system. */
719 valid = validate_recipients(SMTP->recipients);
721 /* If there are modules that want to scan this message before final
722 * submission (such as virus checkers or spam filters), call them now
723 * and give them an opportunity to reject the message.
725 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
727 if (scan_errors > 0) { /* We don't want this message! */
729 if (msg->cm_fields['0'] == NULL) {
730 msg->cm_fields['0'] = strdup(
731 "5.7.1 Message rejected by filter");
734 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
737 else { /* Ok, we'll accept this message. */
738 msgnum = CtdlSubmitMsg(msg, valid, "");
740 sprintf(result, "250 2.0.0 Message accepted.\r\n");
743 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
747 /* For SMTP and ESTMP, just print the result message. For LMTP, we
748 * have to print one result message for each recipient. Since there
749 * is nothing in Citadel which would cause different recipients to
750 * have different results, we can get away with just spitting out the
751 * same message once for each recipient.
754 for (i=0; i<SMTP->number_of_recipients; ++i) {
755 cprintf("%s", result);
759 cprintf("%s", result);
762 CtdlFreeMessage(msg);
764 smtp_data_clear(); /* clear out the buffers now */
769 * implements the STARTTLS command (Citadel API version)
772 void smtp_starttls(void)
774 char ok_response[SIZ];
775 char nosup_response[SIZ];
776 char error_response[SIZ];
779 "200 2.0.0 Begin TLS negotiation now\r\n");
780 sprintf(nosup_response,
781 "554 5.7.3 TLS not supported here\r\n");
782 sprintf(error_response,
783 "554 5.7.3 Internal error\r\n");
784 CtdlStartTLS(ok_response, nosup_response, error_response);
792 * Main command loop for SMTP sessions.
794 void smtp_command_loop(void) {
798 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
799 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
800 lprintf(CTDL_CRIT, "SMTP socket is broken. Ending session.\n");
804 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
805 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
807 if (SMTP->command_state == smtp_user) {
808 smtp_get_user(cmdbuf);
811 else if (SMTP->command_state == smtp_password) {
812 smtp_get_pass(cmdbuf);
815 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
816 smtp_auth(&cmdbuf[5]);
819 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
823 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
824 smtp_expn(&cmdbuf[5]);
827 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
828 smtp_hello(&cmdbuf[5], 0);
831 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
832 smtp_hello(&cmdbuf[5], 1);
835 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
836 smtp_hello(&cmdbuf[5], 2);
839 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
843 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
844 smtp_mail(&cmdbuf[5]);
847 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
848 cprintf("250 NOOP\r\n");
851 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
852 cprintf("221 Goodbye...\r\n");
857 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
858 smtp_rcpt(&cmdbuf[5]);
861 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
865 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
869 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
870 smtp_vrfy(&cmdbuf[5]);
874 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
883 /*****************************************************************************/
884 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
885 /*****************************************************************************/
892 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
895 void smtp_try(const char *key, const char *addr, int *status,
896 char *dsn, size_t n, long msgnum)
903 char user[SIZ], node[SIZ], name[SIZ];
912 /* Parse out the host portion of the recipient address */
913 process_rfc822_addr(addr, user, node, name);
915 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
918 /* Load the message out of the database */
919 CC->redirect_buffer = malloc(SIZ);
920 CC->redirect_len = 0;
921 CC->redirect_alloc = SIZ;
922 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
923 msgtext = CC->redirect_buffer;
924 msg_size = CC->redirect_len;
925 CC->redirect_buffer = NULL;
926 CC->redirect_len = 0;
927 CC->redirect_alloc = 0;
929 /* Extract something to send later in the 'MAIL From:' command */
930 strcpy(mailfrom, "");
934 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
937 if (!strncasecmp(buf, "From:", 5)) {
938 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
940 for (i=0; i<strlen(mailfrom); ++i) {
941 if (!isprint(mailfrom[i])) {
942 strcpy(&mailfrom[i], &mailfrom[i+1]);
947 /* Strip out parenthesized names */
950 for (i=0; i<strlen(mailfrom); ++i) {
951 if (mailfrom[i] == '(') lp = i;
952 if (mailfrom[i] == ')') rp = i;
954 if ((lp>0)&&(rp>lp)) {
955 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
958 /* Prefer brokketized names */
961 for (i=0; i<strlen(mailfrom); ++i) {
962 if (mailfrom[i] == '<') lp = i;
963 if (mailfrom[i] == '>') rp = i;
965 if ( (lp>=0) && (rp>lp) ) {
967 strcpy(mailfrom, &mailfrom[lp]);
972 } while (scan_done == 0);
973 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
975 /* Figure out what mail exchanger host we have to connect to */
976 num_mxhosts = getmx(mxhosts, node);
977 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
978 if (num_mxhosts < 1) {
980 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
985 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
986 extract_token(buf, mxhosts, mx, '|', sizeof buf);
987 lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
988 sock = sock_connect(buf, "25", "tcp");
989 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
990 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
991 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
995 *status = 4; /* dsn is already filled in */
999 /* Process the SMTP greeting from the server */
1000 if (ml_sock_gets(sock, buf) < 0) {
1002 strcpy(dsn, "Connection broken during SMTP conversation");
1005 lprintf(CTDL_DEBUG, "<%s\n", buf);
1006 if (buf[0] != '2') {
1007 if (buf[0] == '4') {
1009 safestrncpy(dsn, &buf[4], 1023);
1014 safestrncpy(dsn, &buf[4], 1023);
1019 /* At this point we know we are talking to a real SMTP server */
1021 /* Do a HELO command */
1022 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1023 lprintf(CTDL_DEBUG, ">%s", buf);
1024 sock_write(sock, buf, strlen(buf));
1025 if (ml_sock_gets(sock, buf) < 0) {
1027 strcpy(dsn, "Connection broken during SMTP HELO");
1030 lprintf(CTDL_DEBUG, "<%s\n", buf);
1031 if (buf[0] != '2') {
1032 if (buf[0] == '4') {
1034 safestrncpy(dsn, &buf[4], 1023);
1039 safestrncpy(dsn, &buf[4], 1023);
1044 /* HELO succeeded, now try the MAIL From: command */
1045 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1046 lprintf(CTDL_DEBUG, ">%s", buf);
1047 sock_write(sock, buf, strlen(buf));
1048 if (ml_sock_gets(sock, buf) < 0) {
1050 strcpy(dsn, "Connection broken during SMTP MAIL");
1053 lprintf(CTDL_DEBUG, "<%s\n", buf);
1054 if (buf[0] != '2') {
1055 if (buf[0] == '4') {
1057 safestrncpy(dsn, &buf[4], 1023);
1062 safestrncpy(dsn, &buf[4], 1023);
1067 /* MAIL succeeded, now try the RCPT To: command */
1068 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
1069 lprintf(CTDL_DEBUG, ">%s", buf);
1070 sock_write(sock, buf, strlen(buf));
1071 if (ml_sock_gets(sock, buf) < 0) {
1073 strcpy(dsn, "Connection broken during SMTP RCPT");
1076 lprintf(CTDL_DEBUG, "<%s\n", buf);
1077 if (buf[0] != '2') {
1078 if (buf[0] == '4') {
1080 safestrncpy(dsn, &buf[4], 1023);
1085 safestrncpy(dsn, &buf[4], 1023);
1090 /* RCPT succeeded, now try the DATA command */
1091 lprintf(CTDL_DEBUG, ">DATA\n");
1092 sock_write(sock, "DATA\r\n", 6);
1093 if (ml_sock_gets(sock, buf) < 0) {
1095 strcpy(dsn, "Connection broken during SMTP DATA");
1098 lprintf(CTDL_DEBUG, "<%s\n", buf);
1099 if (buf[0] != '3') {
1100 if (buf[0] == '4') {
1102 safestrncpy(dsn, &buf[4], 1023);
1107 safestrncpy(dsn, &buf[4], 1023);
1112 /* If we reach this point, the server is expecting data */
1113 sock_write(sock, msgtext, msg_size);
1114 if (msgtext[msg_size-1] != 10) {
1115 lprintf(CTDL_WARNING, "Possible problem: message did not "
1116 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1120 sock_write(sock, ".\r\n", 3);
1121 if (ml_sock_gets(sock, buf) < 0) {
1123 strcpy(dsn, "Connection broken during SMTP message transmit");
1126 lprintf(CTDL_DEBUG, "%s\n", buf);
1127 if (buf[0] != '2') {
1128 if (buf[0] == '4') {
1130 safestrncpy(dsn, &buf[4], 1023);
1135 safestrncpy(dsn, &buf[4], 1023);
1141 safestrncpy(dsn, &buf[4], 1023);
1144 lprintf(CTDL_DEBUG, ">QUIT\n");
1145 sock_write(sock, "QUIT\r\n", 6);
1146 ml_sock_gets(sock, buf);
1147 lprintf(CTDL_DEBUG, "<%s\n", buf);
1148 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1151 bail: free(msgtext);
1159 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1160 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1161 * a "bounce" message (delivery status notification).
1163 void smtp_do_bounce(char *instr) {
1171 char bounceto[1024];
1172 int num_bounces = 0;
1173 int bounce_this = 0;
1174 long bounce_msgid = (-1);
1175 time_t submitted = 0L;
1176 struct CtdlMessage *bmsg = NULL;
1178 struct recptypes *valid;
1179 int successful_bounce = 0;
1181 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1182 strcpy(bounceto, "");
1184 lines = num_tokens(instr, '\n');
1187 /* See if it's time to give up on delivery of this message */
1188 for (i=0; i<lines; ++i) {
1189 extract_token(buf, instr, i, '\n', sizeof buf);
1190 extract_token(key, buf, 0, '|', sizeof key);
1191 extract_token(addr, buf, 1, '|', sizeof addr);
1192 if (!strcasecmp(key, "submitted")) {
1193 submitted = atol(addr);
1197 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1203 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1204 if (bmsg == NULL) return;
1205 memset(bmsg, 0, sizeof(struct CtdlMessage));
1207 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1208 bmsg->cm_anon_type = MES_NORMAL;
1209 bmsg->cm_format_type = 1;
1210 bmsg->cm_fields['A'] = strdup("Citadel");
1211 bmsg->cm_fields['O'] = strdup(MAILROOM);
1212 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1214 if (give_up) bmsg->cm_fields['M'] = strdup(
1215 "A message you sent could not be delivered to some or all of its recipients\n"
1216 "due to prolonged unavailability of its destination(s).\n"
1217 "Giving up on the following addresses:\n\n"
1220 else bmsg->cm_fields['M'] = strdup(
1221 "A message you sent could not be delivered to some or all of its recipients.\n"
1222 "The following addresses were undeliverable:\n\n"
1226 * Now go through the instructions checking for stuff.
1228 for (i=0; i<lines; ++i) {
1229 extract_token(buf, instr, i, '\n', sizeof buf);
1230 extract_token(key, buf, 0, '|', sizeof key);
1231 extract_token(addr, buf, 1, '|', sizeof addr);
1232 status = extract_int(buf, 2);
1233 extract_token(dsn, buf, 3, '|', sizeof dsn);
1236 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1237 key, addr, status, dsn);
1239 if (!strcasecmp(key, "bounceto")) {
1240 strcpy(bounceto, addr);
1244 (!strcasecmp(key, "local"))
1245 || (!strcasecmp(key, "remote"))
1246 || (!strcasecmp(key, "ignet"))
1247 || (!strcasecmp(key, "room"))
1249 if (status == 5) bounce_this = 1;
1250 if (give_up) bounce_this = 1;
1256 if (bmsg->cm_fields['M'] == NULL) {
1257 lprintf(CTDL_ERR, "ERROR ... M field is null "
1258 "(%s:%d)\n", __FILE__, __LINE__);
1261 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1262 strlen(bmsg->cm_fields['M']) + 1024 );
1263 strcat(bmsg->cm_fields['M'], addr);
1264 strcat(bmsg->cm_fields['M'], ": ");
1265 strcat(bmsg->cm_fields['M'], dsn);
1266 strcat(bmsg->cm_fields['M'], "\n");
1268 remove_token(instr, i, '\n');
1274 /* Deliver the bounce if there's anything worth mentioning */
1275 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1276 if (num_bounces > 0) {
1278 /* First try the user who sent the message */
1279 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1280 if (strlen(bounceto) == 0) {
1281 lprintf(CTDL_ERR, "No bounce address specified\n");
1282 bounce_msgid = (-1L);
1285 /* Can we deliver the bounce to the original sender? */
1286 valid = validate_recipients(bounceto);
1287 if (valid != NULL) {
1288 if (valid->num_error == 0) {
1289 CtdlSubmitMsg(bmsg, valid, "");
1290 successful_bounce = 1;
1294 /* If not, post it in the Aide> room */
1295 if (successful_bounce == 0) {
1296 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1299 /* Free up the memory we used */
1300 if (valid != NULL) {
1305 CtdlFreeMessage(bmsg);
1306 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1311 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1312 * set of delivery instructions for completed deliveries and remove them.
1314 * It returns the number of incomplete deliveries remaining.
1316 int smtp_purge_completed_deliveries(char *instr) {
1327 lines = num_tokens(instr, '\n');
1328 for (i=0; i<lines; ++i) {
1329 extract_token(buf, instr, i, '\n', sizeof buf);
1330 extract_token(key, buf, 0, '|', sizeof key);
1331 extract_token(addr, buf, 1, '|', sizeof addr);
1332 status = extract_int(buf, 2);
1333 extract_token(dsn, buf, 3, '|', sizeof dsn);
1338 (!strcasecmp(key, "local"))
1339 || (!strcasecmp(key, "remote"))
1340 || (!strcasecmp(key, "ignet"))
1341 || (!strcasecmp(key, "room"))
1343 if (status == 2) completed = 1;
1348 remove_token(instr, i, '\n');
1361 * Called by smtp_do_queue() to handle an individual message.
1363 void smtp_do_procmsg(long msgnum, void *userdata) {
1364 struct CtdlMessage *msg;
1366 char *results = NULL;
1374 long text_msgid = (-1);
1375 int incomplete_deliveries_remaining;
1376 time_t attempted = 0L;
1377 time_t last_attempted = 0L;
1378 time_t retry = SMTP_RETRY_INTERVAL;
1380 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1382 msg = CtdlFetchMessage(msgnum, 1);
1384 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1388 instr = strdup(msg->cm_fields['M']);
1389 CtdlFreeMessage(msg);
1391 /* Strip out the headers amd any other non-instruction line */
1392 lines = num_tokens(instr, '\n');
1393 for (i=0; i<lines; ++i) {
1394 extract_token(buf, instr, i, '\n', sizeof buf);
1395 if (num_tokens(buf, '|') < 2) {
1396 remove_token(instr, i, '\n');
1402 /* Learn the message ID and find out about recent delivery attempts */
1403 lines = num_tokens(instr, '\n');
1404 for (i=0; i<lines; ++i) {
1405 extract_token(buf, instr, i, '\n', sizeof buf);
1406 extract_token(key, buf, 0, '|', sizeof key);
1407 if (!strcasecmp(key, "msgid")) {
1408 text_msgid = extract_long(buf, 1);
1410 if (!strcasecmp(key, "retry")) {
1411 /* double the retry interval after each attempt */
1412 retry = extract_long(buf, 1) * 2L;
1413 if (retry > SMTP_RETRY_MAX) {
1414 retry = SMTP_RETRY_MAX;
1416 remove_token(instr, i, '\n');
1418 if (!strcasecmp(key, "attempted")) {
1419 attempted = extract_long(buf, 1);
1420 if (attempted > last_attempted)
1421 last_attempted = attempted;
1426 * Postpone delivery if we've already tried recently.
1428 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1429 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1436 * Bail out if there's no actual message associated with this
1438 if (text_msgid < 0L) {
1439 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1444 /* Plow through the instructions looking for 'remote' directives and
1445 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1446 * were experienced and it's time to try again)
1448 lines = num_tokens(instr, '\n');
1449 for (i=0; i<lines; ++i) {
1450 extract_token(buf, instr, i, '\n', sizeof buf);
1451 extract_token(key, buf, 0, '|', sizeof key);
1452 extract_token(addr, buf, 1, '|', sizeof addr);
1453 status = extract_int(buf, 2);
1454 extract_token(dsn, buf, 3, '|', sizeof dsn);
1455 if ( (!strcasecmp(key, "remote"))
1456 && ((status==0)||(status==3)||(status==4)) ) {
1458 /* Remove this "remote" instruction from the set,
1459 * but replace the set's final newline if
1460 * remove_token() stripped it. It has to be there.
1462 remove_token(instr, i, '\n');
1463 if (instr[strlen(instr)-1] != '\n') {
1464 strcat(instr, "\n");
1469 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1470 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1472 if (results == NULL) {
1473 results = malloc(1024);
1474 memset(results, 0, 1024);
1477 results = realloc(results,
1478 strlen(results) + 1024);
1480 snprintf(&results[strlen(results)], 1024,
1482 key, addr, status, dsn);
1487 if (results != NULL) {
1488 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1489 strcat(instr, results);
1494 /* Generate 'bounce' messages */
1495 smtp_do_bounce(instr);
1497 /* Go through the delivery list, deleting completed deliveries */
1498 incomplete_deliveries_remaining =
1499 smtp_purge_completed_deliveries(instr);
1503 * No delivery instructions remain, so delete both the instructions
1504 * message and the message message.
1506 if (incomplete_deliveries_remaining <= 0) {
1507 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1508 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1513 * Uncompleted delivery instructions remain, so delete the old
1514 * instructions and replace with the updated ones.
1516 if (incomplete_deliveries_remaining > 0) {
1517 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1518 msg = malloc(sizeof(struct CtdlMessage));
1519 memset(msg, 0, sizeof(struct CtdlMessage));
1520 msg->cm_magic = CTDLMESSAGE_MAGIC;
1521 msg->cm_anon_type = MES_NORMAL;
1522 msg->cm_format_type = FMT_RFC822;
1523 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1524 snprintf(msg->cm_fields['M'],
1526 "Content-type: %s\n\n%s\n"
1529 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1530 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1531 CtdlFreeMessage(msg);
1542 * Run through the queue sending out messages.
1544 void smtp_do_queue(void) {
1545 static int doing_queue = 0;
1548 * This is a simple concurrency check to make sure only one queue run
1549 * is done at a time. We could do this with a mutex, but since we
1550 * don't really require extremely fine granularity here, we'll do it
1551 * with a static variable instead.
1553 if (doing_queue) return;
1557 * Go ahead and run the queue
1559 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1561 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1562 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1565 CtdlForEachMessage(MSGS_ALL, 0L,
1566 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1568 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1575 /*****************************************************************************/
1576 /* SMTP UTILITY COMMANDS */
1577 /*****************************************************************************/
1579 void cmd_smtp(char *argbuf) {
1586 if (CtdlAccessCheck(ac_aide)) return;
1588 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1590 if (!strcasecmp(cmd, "mx")) {
1591 extract_token(node, argbuf, 1, '|', sizeof node);
1592 num_mxhosts = getmx(buf, node);
1593 cprintf("%d %d MX hosts listed for %s\n",
1594 LISTING_FOLLOWS, num_mxhosts, node);
1595 for (i=0; i<num_mxhosts; ++i) {
1596 extract_token(node, buf, i, '|', sizeof node);
1597 cprintf("%s\n", node);
1603 else if (!strcasecmp(cmd, "runqueue")) {
1605 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1610 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1617 * Initialize the SMTP outbound queue
1619 void smtp_init_spoolout(void) {
1620 struct ctdlroom qrbuf;
1623 * Create the room. This will silently fail if the room already
1624 * exists, and that's perfectly ok, because we want it to exist.
1626 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1629 * Make sure it's set to be a "system room" so it doesn't show up
1630 * in the <K>nown rooms list for Aides.
1632 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1633 qrbuf.QRflags2 |= QR2_SYSTEM;
1641 /*****************************************************************************/
1642 /* MODULE INITIALIZATION STUFF */
1643 /*****************************************************************************/
1645 * This cleanup function blows away the temporary memory used by
1648 void smtp_cleanup_function(void) {
1650 /* Don't do this stuff if this is not an SMTP session! */
1651 if (CC->h_command_function != smtp_command_loop) return;
1653 lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1663 char *serv_smtp_init(void)
1665 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1672 CtdlRegisterServiceHook(config.c_smtps_port,
1679 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1685 CtdlRegisterServiceHook(0, /* local LMTP */
1691 smtp_init_spoolout();
1692 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1693 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1694 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");