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 ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
110 #define SMTP_RECPS ((char *)CtdlGetUserData(SYM_SMTP_RECPS))
111 #define SMTP_ROOMS ((char *)CtdlGetUserData(SYM_SMTP_ROOMS))
114 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
118 /*****************************************************************************/
119 /* SMTP SERVER (INBOUND) STUFF */
120 /*****************************************************************************/
126 * Here's where our SMTP session begins its happy day.
128 void smtp_greeting(void) {
130 strcpy(CC->cs_clientname, "SMTP session");
131 CC->internal_pgm = 1;
132 CC->cs_flags |= CS_STEALTH;
133 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
134 CtdlAllocUserData(SYM_SMTP_RECPS, SIZ);
135 CtdlAllocUserData(SYM_SMTP_ROOMS, SIZ);
136 snprintf(SMTP_RECPS, SIZ, "%s", "");
137 snprintf(SMTP_ROOMS, SIZ, "%s", "");
139 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
144 * SMTPS is just like SMTP, except it goes crypto right away.
147 void smtps_greeting(void) {
148 CtdlStartTLS(NULL, NULL, NULL);
155 * SMTP MSA port requires authentication.
157 void smtp_msa_greeting(void) {
164 * LMTP is like SMTP but with some extra bonus footage added.
166 void lmtp_greeting(void) {
173 * Login greeting common to all auth methods
175 void smtp_auth_greeting(void) {
176 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
177 lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
178 CC->internal_pgm = 0;
179 CC->cs_flags &= ~CS_STEALTH;
184 * Implement HELO and EHLO commands.
186 * which_command: 0=HELO, 1=EHLO, 2=LHLO
188 void smtp_hello(char *argbuf, int which_command) {
190 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
192 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
193 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
197 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
198 cprintf("500 LHLO is only allowed when running LMTP\r\n");
202 if (which_command == 0) {
203 cprintf("250 Hello %s (%s [%s])\r\n",
210 if (which_command == 1) {
211 cprintf("250-Hello %s (%s [%s])\r\n",
218 cprintf("250-Greetings and joyous salutations.\r\n");
220 cprintf("250-HELP\r\n");
221 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
225 /* Only offer the PIPELINING command if TLS is inactive,
226 * because of flow control issues. Also, avoid offering TLS
227 * if TLS is already active. Finally, we only offer TLS on
228 * the SMTP-MSA port, not on the SMTP-MTA port, due to
229 * questionable reliability of TLS in certain sending MTA's.
231 if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
232 cprintf("250-PIPELINING\r\n");
233 cprintf("250-STARTTLS\r\n");
236 #else /* HAVE_OPENSSL */
238 /* Non SSL enabled server, so always offer PIPELINING. */
239 cprintf("250-PIPELINING\r\n");
241 #endif /* HAVE_OPENSSL */
243 cprintf("250-AUTH LOGIN PLAIN\r\n");
244 cprintf("250-AUTH=LOGIN PLAIN\r\n");
246 cprintf("250 ENHANCEDSTATUSCODES\r\n");
253 * Implement HELP command.
255 void smtp_help(void) {
256 cprintf("214-Commands accepted:\r\n");
257 cprintf("214- DATA\r\n");
258 cprintf("214- EHLO\r\n");
259 cprintf("214- EXPN\r\n");
260 cprintf("214- HELO\r\n");
261 cprintf("214- HELP\r\n");
262 cprintf("214- MAIL\r\n");
263 cprintf("214- NOOP\r\n");
264 cprintf("214- QUIT\r\n");
265 cprintf("214- RCPT\r\n");
266 cprintf("214- RSET\r\n");
267 cprintf("214- VRFY\r\n");
275 void smtp_get_user(char *argbuf) {
279 CtdlDecodeBase64(username, argbuf, SIZ);
280 lprintf(CTDL_DEBUG, "Trying <%s>\n", username);
281 if (CtdlLoginExistingUser(username) == login_ok) {
282 CtdlEncodeBase64(buf, "Password:", 9);
283 cprintf("334 %s\r\n", buf);
284 SMTP->command_state = smtp_password;
287 cprintf("500 5.7.0 No such user.\r\n");
288 SMTP->command_state = smtp_command;
296 void smtp_get_pass(char *argbuf) {
299 CtdlDecodeBase64(password, argbuf, SIZ);
300 lprintf(CTDL_DEBUG, "Trying <%s>\n", password);
301 if (CtdlTryPassword(password) == pass_ok) {
302 smtp_auth_greeting();
305 cprintf("535 5.7.0 Authentication failed.\r\n");
307 SMTP->command_state = smtp_command;
314 void smtp_auth(char *argbuf) {
317 char encoded_authstring[SIZ];
318 char decoded_authstring[SIZ];
324 cprintf("504 5.7.4 Already logged in.\r\n");
328 extract_token(method, argbuf, 0, ' ');
330 if (!strncasecmp(method, "login", 5) ) {
331 if (strlen(argbuf) >= 7) {
332 smtp_get_user(&argbuf[6]);
335 CtdlEncodeBase64(buf, "Username:", 9);
336 cprintf("334 %s\r\n", buf);
337 SMTP->command_state = smtp_user;
342 if (!strncasecmp(method, "plain", 5) ) {
343 extract_token(encoded_authstring, argbuf, 1, ' ');
344 CtdlDecodeBase64(decoded_authstring,
346 strlen(encoded_authstring) );
347 strcpy(ident, decoded_authstring);
348 strcpy(user, &decoded_authstring[strlen(ident) + 1] );
349 strcpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2] );
351 if (CtdlLoginExistingUser(user) == login_ok) {
352 if (CtdlTryPassword(pass) == pass_ok) {
353 smtp_auth_greeting();
357 cprintf("504 5.7.4 Authentication failed.\r\n");
360 if (strncasecmp(method, "login", 5) ) {
361 cprintf("504 5.7.4 Unknown authentication method.\r\n");
369 * Back end for smtp_vrfy() command
371 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
373 if (!fuzzy_match(us, SMTP->vrfy_match)) {
375 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
381 * Implements the VRFY (verify user name) command.
382 * Performs fuzzy match on full user names.
384 void smtp_vrfy(char *argbuf) {
385 SMTP->vrfy_count = 0;
386 strcpy(SMTP->vrfy_match, argbuf);
387 ForEachUser(smtp_vrfy_backend, NULL);
389 if (SMTP->vrfy_count < 1) {
390 cprintf("550 5.1.1 String does not match anything.\r\n");
392 else if (SMTP->vrfy_count == 1) {
393 cprintf("250 %s <cit%ld@%s>\r\n",
394 SMTP->vrfy_buffer.fullname,
395 SMTP->vrfy_buffer.usernum,
398 else if (SMTP->vrfy_count > 1) {
399 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
408 * Back end for smtp_expn() command
410 void smtp_expn_backend(struct ctdluser *us, void *data) {
412 if (!fuzzy_match(us, SMTP->vrfy_match)) {
414 if (SMTP->vrfy_count >= 1) {
415 cprintf("250-%s <cit%ld@%s>\r\n",
416 SMTP->vrfy_buffer.fullname,
417 SMTP->vrfy_buffer.usernum,
422 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
428 * Implements the EXPN (expand user name) command.
429 * Performs fuzzy match on full user names.
431 void smtp_expn(char *argbuf) {
432 SMTP->vrfy_count = 0;
433 strcpy(SMTP->vrfy_match, argbuf);
434 ForEachUser(smtp_expn_backend, NULL);
436 if (SMTP->vrfy_count < 1) {
437 cprintf("550 5.1.1 String does not match anything.\r\n");
439 else if (SMTP->vrfy_count >= 1) {
440 cprintf("250 %s <cit%ld@%s>\r\n",
441 SMTP->vrfy_buffer.fullname,
442 SMTP->vrfy_buffer.usernum,
449 * Implements the RSET (reset state) command.
450 * Currently this just zeroes out the state buffer. If pointers to data
451 * allocated with malloc() are ever placed in the state buffer, we have to
452 * be sure to free() them first!
454 * Set do_response to nonzero to output the SMTP RSET response code.
456 void smtp_rset(int do_response) {
460 * Our entire SMTP state is discarded when a RSET command is issued,
461 * but we need to preserve this one little piece of information, so
462 * we save it for later.
464 is_lmtp = SMTP->is_lmtp;
466 memset(SMTP, 0, sizeof(struct citsmtp));
469 * It is somewhat ambiguous whether we want to log out when a RSET
470 * command is issued. Here's the code to do it. It is commented out
471 * because some clients (such as Pine) issue RSET commands before
472 * each message, but still expect to be logged in.
474 * if (CC->logged_in) {
480 * Reinstate this little piece of information we saved (see above).
482 SMTP->is_lmtp = is_lmtp;
485 cprintf("250 2.0.0 Zap!\r\n");
490 * Clear out the portions of the state buffer that need to be cleared out
491 * after the DATA command finishes.
493 void smtp_data_clear(void) {
494 strcpy(SMTP->from, "");
495 strcpy(SMTP->recipients, "");
496 SMTP->number_of_recipients = 0;
497 SMTP->delivery_mode = 0;
498 SMTP->message_originated_locally = 0;
504 * Implements the "MAIL From:" command
506 void smtp_mail(char *argbuf) {
511 if (strlen(SMTP->from) != 0) {
512 cprintf("503 5.1.0 Only one sender permitted\r\n");
516 if (strncasecmp(argbuf, "From:", 5)) {
517 cprintf("501 5.1.7 Syntax error\r\n");
521 strcpy(SMTP->from, &argbuf[5]);
523 stripallbut(SMTP->from, '<', '>');
525 /* We used to reject empty sender names, until it was brought to our
526 * attention that RFC1123 5.2.9 requires that this be allowed. So now
527 * we allow it, but replace the empty string with a fake
528 * address so we don't have to contend with the empty string causing
529 * other code to fail when it's expecting something there.
531 if (strlen(SMTP->from) == 0) {
532 strcpy(SMTP->from, "someone@somewhere.org");
535 /* If this SMTP connection is from a logged-in user, force the 'from'
536 * to be the user's Internet e-mail address as Citadel knows it.
539 strcpy(SMTP->from, CC->cs_inet_email);
540 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
541 SMTP->message_originated_locally = 1;
545 else if (SMTP->is_lmtp) {
546 /* Bypass forgery checking for LMTP */
549 /* Otherwise, make sure outsiders aren't trying to forge mail from
553 process_rfc822_addr(SMTP->from, user, node, name);
554 if (CtdlHostAlias(node) != hostalias_nomatch) {
556 "You must log in to send mail from %s\r\n",
558 strcpy(SMTP->from, "");
563 cprintf("250 2.0.0 Sender ok\r\n");
569 * Implements the "RCPT To:" command
571 void smtp_rcpt(char *argbuf) {
573 char message_to_spammer[SIZ];
574 struct recptypes *valid = NULL;
576 if (strlen(SMTP->from) == 0) {
577 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
581 if (strncasecmp(argbuf, "To:", 3)) {
582 cprintf("501 5.1.7 Syntax error\r\n");
586 if ( (SMTP->is_msa) && (!CC->logged_in) ) {
588 "You must log in to send mail on this port.\r\n");
589 strcpy(SMTP->from, "");
593 strcpy(recp, &argbuf[3]);
595 stripallbut(recp, '<', '>');
597 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
598 cprintf("452 4.5.3 Too many recipients\r\n");
603 if ( (!CC->logged_in)
604 && (!SMTP->is_lmtp) ) {
605 if (rbl_check(message_to_spammer)) {
606 cprintf("550 %s\r\n", message_to_spammer);
607 /* no need to free(valid), it's not allocated yet */
612 valid = validate_recipients(recp);
613 if (valid->num_error > 0) {
614 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
619 if (valid->num_internet > 0) {
621 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
622 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
629 if (valid->num_internet > 0) {
630 if ( (SMTP->message_originated_locally == 0)
631 && (SMTP->is_lmtp == 0) ) {
632 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
638 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
639 if (strlen(SMTP->recipients) > 0) {
640 strcat(SMTP->recipients, ",");
642 strcat(SMTP->recipients, recp);
643 SMTP->number_of_recipients += 1;
650 * Implements the DATA command
652 void smtp_data(void) {
654 struct CtdlMessage *msg;
657 struct recptypes *valid;
662 if (strlen(SMTP->from) == 0) {
663 cprintf("503 5.5.1 Need MAIL command first.\r\n");
667 if (SMTP->number_of_recipients < 1) {
668 cprintf("503 5.5.1 Need RCPT command first.\r\n");
672 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
674 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
677 if (body != NULL) snprintf(body, 4096,
678 "Received: from %s (%s [%s])\n"
686 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
689 "Unable to save message: internal error.\r\n");
693 lprintf(CTDL_DEBUG, "Converting message...\n");
694 msg = convert_internet_message(body);
696 /* If the user is locally authenticated, FORCE the From: header to
697 * show up as the real sender. Yes, this violates the RFC standard,
698 * but IT MAKES SENSE. If you prefer strict RFC adherence over
699 * common sense, you can disable this in the configuration.
701 * We also set the "message room name" ('O' field) to MAILROOM
702 * (which is Mail> on most systems) to prevent it from getting set
703 * to something ugly like "0000058008.Sent Items>" when the message
704 * is read with a Citadel client.
706 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
707 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
708 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
709 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
710 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
711 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
712 msg->cm_fields['A'] = strdup(CC->user.fullname);
713 msg->cm_fields['N'] = strdup(config.c_nodename);
714 msg->cm_fields['H'] = strdup(config.c_humannode);
715 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
716 msg->cm_fields['O'] = strdup(MAILROOM);
719 /* Submit the message into the Citadel system. */
720 valid = validate_recipients(SMTP->recipients);
722 /* If there are modules that want to scan this message before final
723 * submission (such as virus checkers or spam filters), call them now
724 * and give them an opportunity to reject the message.
726 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
728 if (scan_errors > 0) { /* We don't want this message! */
730 if (msg->cm_fields['0'] == NULL) {
731 msg->cm_fields['0'] = strdup(
732 "5.7.1 Message rejected by filter");
735 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
738 else { /* Ok, we'll accept this message. */
739 msgnum = CtdlSubmitMsg(msg, valid, "");
741 sprintf(result, "250 2.0.0 Message accepted.\r\n");
744 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
748 /* For SMTP and ESTMP, just print the result message. For LMTP, we
749 * have to print one result message for each recipient. Since there
750 * is nothing in Citadel which would cause different recipients to
751 * have different results, we can get away with just spitting out the
752 * same message once for each recipient.
755 for (i=0; i<SMTP->number_of_recipients; ++i) {
756 cprintf("%s", result);
760 cprintf("%s", result);
763 CtdlFreeMessage(msg);
765 smtp_data_clear(); /* clear out the buffers now */
770 * implements the STARTTLS command (Citadel API version)
773 void smtp_starttls(void)
775 char ok_response[SIZ];
776 char nosup_response[SIZ];
777 char error_response[SIZ];
780 "200 2.0.0 Begin TLS negotiation now\r\n");
781 sprintf(nosup_response,
782 "554 5.7.3 TLS not supported here\r\n");
783 sprintf(error_response,
784 "554 5.7.3 Internal error\r\n");
785 CtdlStartTLS(ok_response, nosup_response, error_response);
793 * Main command loop for SMTP sessions.
795 void smtp_command_loop(void) {
799 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
800 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
801 lprintf(CTDL_CRIT, "SMTP socket is broken. Ending session.\n");
805 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
806 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
808 if (SMTP->command_state == smtp_user) {
809 smtp_get_user(cmdbuf);
812 else if (SMTP->command_state == smtp_password) {
813 smtp_get_pass(cmdbuf);
816 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
817 smtp_auth(&cmdbuf[5]);
820 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
824 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
825 smtp_expn(&cmdbuf[5]);
828 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
829 smtp_hello(&cmdbuf[5], 0);
832 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
833 smtp_hello(&cmdbuf[5], 1);
836 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
837 smtp_hello(&cmdbuf[5], 2);
840 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
844 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
845 smtp_mail(&cmdbuf[5]);
848 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
849 cprintf("250 NOOP\r\n");
852 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
853 cprintf("221 Goodbye...\r\n");
858 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
859 smtp_rcpt(&cmdbuf[5]);
862 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
866 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
870 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
871 smtp_vrfy(&cmdbuf[5]);
875 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
884 /*****************************************************************************/
885 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
886 /*****************************************************************************/
893 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
896 void smtp_try(const char *key, const char *addr, int *status,
897 char *dsn, size_t n, long msgnum)
904 char user[SIZ], node[SIZ], name[SIZ];
913 /* Parse out the host portion of the recipient address */
914 process_rfc822_addr(addr, user, node, name);
916 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
919 /* Load the message out of the database */
920 CC->redirect_buffer = malloc(SIZ);
921 CC->redirect_len = 0;
922 CC->redirect_alloc = SIZ;
923 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
924 msgtext = CC->redirect_buffer;
925 msg_size = CC->redirect_len;
926 CC->redirect_buffer = NULL;
927 CC->redirect_len = 0;
928 CC->redirect_alloc = 0;
930 /* Extract something to send later in the 'MAIL From:' command */
931 strcpy(mailfrom, "");
935 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
938 if (!strncasecmp(buf, "From:", 5)) {
939 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
941 for (i=0; i<strlen(mailfrom); ++i) {
942 if (!isprint(mailfrom[i])) {
943 strcpy(&mailfrom[i], &mailfrom[i+1]);
948 /* Strip out parenthesized names */
951 for (i=0; i<strlen(mailfrom); ++i) {
952 if (mailfrom[i] == '(') lp = i;
953 if (mailfrom[i] == ')') rp = i;
955 if ((lp>0)&&(rp>lp)) {
956 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
959 /* Prefer brokketized names */
962 for (i=0; i<strlen(mailfrom); ++i) {
963 if (mailfrom[i] == '<') lp = i;
964 if (mailfrom[i] == '>') rp = i;
966 if ( (lp>=0) && (rp>lp) ) {
968 strcpy(mailfrom, &mailfrom[lp]);
973 } while (scan_done == 0);
974 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
976 /* Figure out what mail exchanger host we have to connect to */
977 num_mxhosts = getmx(mxhosts, node);
978 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
979 if (num_mxhosts < 1) {
981 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
986 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
987 extract(buf, mxhosts, mx);
988 lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
989 sock = sock_connect(buf, "25", "tcp");
990 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
991 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
992 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
996 *status = 4; /* dsn is already filled in */
1000 /* Process the SMTP greeting from the server */
1001 if (ml_sock_gets(sock, buf) < 0) {
1003 strcpy(dsn, "Connection broken during SMTP conversation");
1006 lprintf(CTDL_DEBUG, "<%s\n", buf);
1007 if (buf[0] != '2') {
1008 if (buf[0] == '4') {
1010 safestrncpy(dsn, &buf[4], 1023);
1015 safestrncpy(dsn, &buf[4], 1023);
1020 /* At this point we know we are talking to a real SMTP server */
1022 /* Do a HELO command */
1023 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1024 lprintf(CTDL_DEBUG, ">%s", buf);
1025 sock_write(sock, buf, strlen(buf));
1026 if (ml_sock_gets(sock, buf) < 0) {
1028 strcpy(dsn, "Connection broken during SMTP HELO");
1031 lprintf(CTDL_DEBUG, "<%s\n", buf);
1032 if (buf[0] != '2') {
1033 if (buf[0] == '4') {
1035 safestrncpy(dsn, &buf[4], 1023);
1040 safestrncpy(dsn, &buf[4], 1023);
1045 /* HELO succeeded, now try the MAIL From: command */
1046 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1047 lprintf(CTDL_DEBUG, ">%s", buf);
1048 sock_write(sock, buf, strlen(buf));
1049 if (ml_sock_gets(sock, buf) < 0) {
1051 strcpy(dsn, "Connection broken during SMTP MAIL");
1054 lprintf(CTDL_DEBUG, "<%s\n", buf);
1055 if (buf[0] != '2') {
1056 if (buf[0] == '4') {
1058 safestrncpy(dsn, &buf[4], 1023);
1063 safestrncpy(dsn, &buf[4], 1023);
1068 /* MAIL succeeded, now try the RCPT To: command */
1069 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
1070 lprintf(CTDL_DEBUG, ">%s", buf);
1071 sock_write(sock, buf, strlen(buf));
1072 if (ml_sock_gets(sock, buf) < 0) {
1074 strcpy(dsn, "Connection broken during SMTP RCPT");
1077 lprintf(CTDL_DEBUG, "<%s\n", buf);
1078 if (buf[0] != '2') {
1079 if (buf[0] == '4') {
1081 safestrncpy(dsn, &buf[4], 1023);
1086 safestrncpy(dsn, &buf[4], 1023);
1091 /* RCPT succeeded, now try the DATA command */
1092 lprintf(CTDL_DEBUG, ">DATA\n");
1093 sock_write(sock, "DATA\r\n", 6);
1094 if (ml_sock_gets(sock, buf) < 0) {
1096 strcpy(dsn, "Connection broken during SMTP DATA");
1099 lprintf(CTDL_DEBUG, "<%s\n", buf);
1100 if (buf[0] != '3') {
1101 if (buf[0] == '4') {
1103 safestrncpy(dsn, &buf[4], 1023);
1108 safestrncpy(dsn, &buf[4], 1023);
1113 /* If we reach this point, the server is expecting data */
1114 sock_write(sock, msgtext, msg_size);
1115 if (msgtext[msg_size-1] != 10) {
1116 lprintf(CTDL_WARNING, "Possible problem: message did not "
1117 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1121 sock_write(sock, ".\r\n", 3);
1122 if (ml_sock_gets(sock, buf) < 0) {
1124 strcpy(dsn, "Connection broken during SMTP message transmit");
1127 lprintf(CTDL_DEBUG, "%s\n", buf);
1128 if (buf[0] != '2') {
1129 if (buf[0] == '4') {
1131 safestrncpy(dsn, &buf[4], 1023);
1136 safestrncpy(dsn, &buf[4], 1023);
1142 safestrncpy(dsn, &buf[4], 1023);
1145 lprintf(CTDL_DEBUG, ">QUIT\n");
1146 sock_write(sock, "QUIT\r\n", 6);
1147 ml_sock_gets(sock, buf);
1148 lprintf(CTDL_DEBUG, "<%s\n", buf);
1149 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1152 bail: free(msgtext);
1160 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1161 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1162 * a "bounce" message (delivery status notification).
1164 void smtp_do_bounce(char *instr) {
1172 char bounceto[1024];
1173 int num_bounces = 0;
1174 int bounce_this = 0;
1175 long bounce_msgid = (-1);
1176 time_t submitted = 0L;
1177 struct CtdlMessage *bmsg = NULL;
1179 struct recptypes *valid;
1180 int successful_bounce = 0;
1182 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1183 strcpy(bounceto, "");
1185 lines = num_tokens(instr, '\n');
1188 /* See if it's time to give up on delivery of this message */
1189 for (i=0; i<lines; ++i) {
1190 extract_token(buf, instr, i, '\n');
1191 extract(key, buf, 0);
1192 extract(addr, buf, 1);
1193 if (!strcasecmp(key, "submitted")) {
1194 submitted = atol(addr);
1198 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1204 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1205 if (bmsg == NULL) return;
1206 memset(bmsg, 0, sizeof(struct CtdlMessage));
1208 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1209 bmsg->cm_anon_type = MES_NORMAL;
1210 bmsg->cm_format_type = 1;
1211 bmsg->cm_fields['A'] = strdup("Citadel");
1212 bmsg->cm_fields['O'] = strdup(MAILROOM);
1213 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1215 if (give_up) bmsg->cm_fields['M'] = strdup(
1216 "A message you sent could not be delivered to some or all of its recipients\n"
1217 "due to prolonged unavailability of its destination(s).\n"
1218 "Giving up on the following addresses:\n\n"
1221 else bmsg->cm_fields['M'] = strdup(
1222 "A message you sent could not be delivered to some or all of its recipients.\n"
1223 "The following addresses were undeliverable:\n\n"
1227 * Now go through the instructions checking for stuff.
1229 for (i=0; i<lines; ++i) {
1230 extract_token(buf, instr, i, '\n');
1231 extract(key, buf, 0);
1232 extract(addr, buf, 1);
1233 status = extract_int(buf, 2);
1234 extract(dsn, buf, 3);
1237 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1238 key, addr, status, dsn);
1240 if (!strcasecmp(key, "bounceto")) {
1241 strcpy(bounceto, addr);
1245 (!strcasecmp(key, "local"))
1246 || (!strcasecmp(key, "remote"))
1247 || (!strcasecmp(key, "ignet"))
1248 || (!strcasecmp(key, "room"))
1250 if (status == 5) bounce_this = 1;
1251 if (give_up) bounce_this = 1;
1257 if (bmsg->cm_fields['M'] == NULL) {
1258 lprintf(CTDL_ERR, "ERROR ... M field is null "
1259 "(%s:%d)\n", __FILE__, __LINE__);
1262 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1263 strlen(bmsg->cm_fields['M']) + 1024 );
1264 strcat(bmsg->cm_fields['M'], addr);
1265 strcat(bmsg->cm_fields['M'], ": ");
1266 strcat(bmsg->cm_fields['M'], dsn);
1267 strcat(bmsg->cm_fields['M'], "\n");
1269 remove_token(instr, i, '\n');
1275 /* Deliver the bounce if there's anything worth mentioning */
1276 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1277 if (num_bounces > 0) {
1279 /* First try the user who sent the message */
1280 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1281 if (strlen(bounceto) == 0) {
1282 lprintf(CTDL_ERR, "No bounce address specified\n");
1283 bounce_msgid = (-1L);
1286 /* Can we deliver the bounce to the original sender? */
1287 valid = validate_recipients(bounceto);
1288 if (valid != NULL) {
1289 if (valid->num_error == 0) {
1290 CtdlSubmitMsg(bmsg, valid, "");
1291 successful_bounce = 1;
1295 /* If not, post it in the Aide> room */
1296 if (successful_bounce == 0) {
1297 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1300 /* Free up the memory we used */
1301 if (valid != NULL) {
1306 CtdlFreeMessage(bmsg);
1307 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1312 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1313 * set of delivery instructions for completed deliveries and remove them.
1315 * It returns the number of incomplete deliveries remaining.
1317 int smtp_purge_completed_deliveries(char *instr) {
1328 lines = num_tokens(instr, '\n');
1329 for (i=0; i<lines; ++i) {
1330 extract_token(buf, instr, i, '\n');
1331 extract(key, buf, 0);
1332 extract(addr, buf, 1);
1333 status = extract_int(buf, 2);
1334 extract(dsn, buf, 3);
1339 (!strcasecmp(key, "local"))
1340 || (!strcasecmp(key, "remote"))
1341 || (!strcasecmp(key, "ignet"))
1342 || (!strcasecmp(key, "room"))
1344 if (status == 2) completed = 1;
1349 remove_token(instr, i, '\n');
1362 * Called by smtp_do_queue() to handle an individual message.
1364 void smtp_do_procmsg(long msgnum, void *userdata) {
1365 struct CtdlMessage *msg;
1367 char *results = NULL;
1375 long text_msgid = (-1);
1376 int incomplete_deliveries_remaining;
1377 time_t attempted = 0L;
1378 time_t last_attempted = 0L;
1379 time_t retry = SMTP_RETRY_INTERVAL;
1381 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1383 msg = CtdlFetchMessage(msgnum, 1);
1385 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1389 instr = strdup(msg->cm_fields['M']);
1390 CtdlFreeMessage(msg);
1392 /* Strip out the headers amd any other non-instruction line */
1393 lines = num_tokens(instr, '\n');
1394 for (i=0; i<lines; ++i) {
1395 extract_token(buf, instr, i, '\n');
1396 if (num_tokens(buf, '|') < 2) {
1397 remove_token(instr, i, '\n');
1403 /* Learn the message ID and find out about recent delivery attempts */
1404 lines = num_tokens(instr, '\n');
1405 for (i=0; i<lines; ++i) {
1406 extract_token(buf, instr, i, '\n');
1407 extract(key, buf, 0);
1408 if (!strcasecmp(key, "msgid")) {
1409 text_msgid = extract_long(buf, 1);
1411 if (!strcasecmp(key, "retry")) {
1412 /* double the retry interval after each attempt */
1413 retry = extract_long(buf, 1) * 2L;
1414 if (retry > SMTP_RETRY_MAX) {
1415 retry = SMTP_RETRY_MAX;
1417 remove_token(instr, i, '\n');
1419 if (!strcasecmp(key, "attempted")) {
1420 attempted = extract_long(buf, 1);
1421 if (attempted > last_attempted)
1422 last_attempted = attempted;
1427 * Postpone delivery if we've already tried recently.
1429 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1430 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1437 * Bail out if there's no actual message associated with this
1439 if (text_msgid < 0L) {
1440 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1445 /* Plow through the instructions looking for 'remote' directives and
1446 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1447 * were experienced and it's time to try again)
1449 lines = num_tokens(instr, '\n');
1450 for (i=0; i<lines; ++i) {
1451 extract_token(buf, instr, i, '\n');
1452 extract(key, buf, 0);
1453 extract(addr, buf, 1);
1454 status = extract_int(buf, 2);
1455 extract(dsn, buf, 3);
1456 if ( (!strcasecmp(key, "remote"))
1457 && ((status==0)||(status==3)||(status==4)) ) {
1459 /* Remove this "remote" instruction from the set,
1460 * but replace the set's final newline if
1461 * remove_token() stripped it. It has to be there.
1463 remove_token(instr, i, '\n');
1464 if (instr[strlen(instr)-1] != '\n') {
1465 strcat(instr, "\n");
1470 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1471 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1473 if (results == NULL) {
1474 results = malloc(1024);
1475 memset(results, 0, 1024);
1478 results = realloc(results,
1479 strlen(results) + 1024);
1481 snprintf(&results[strlen(results)], 1024,
1483 key, addr, status, dsn);
1488 if (results != NULL) {
1489 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1490 strcat(instr, results);
1495 /* Generate 'bounce' messages */
1496 smtp_do_bounce(instr);
1498 /* Go through the delivery list, deleting completed deliveries */
1499 incomplete_deliveries_remaining =
1500 smtp_purge_completed_deliveries(instr);
1504 * No delivery instructions remain, so delete both the instructions
1505 * message and the message message.
1507 if (incomplete_deliveries_remaining <= 0) {
1508 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1509 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1514 * Uncompleted delivery instructions remain, so delete the old
1515 * instructions and replace with the updated ones.
1517 if (incomplete_deliveries_remaining > 0) {
1518 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1519 msg = malloc(sizeof(struct CtdlMessage));
1520 memset(msg, 0, sizeof(struct CtdlMessage));
1521 msg->cm_magic = CTDLMESSAGE_MAGIC;
1522 msg->cm_anon_type = MES_NORMAL;
1523 msg->cm_format_type = FMT_RFC822;
1524 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1525 snprintf(msg->cm_fields['M'],
1527 "Content-type: %s\n\n%s\n"
1530 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1531 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1532 CtdlFreeMessage(msg);
1543 * Run through the queue sending out messages.
1545 void smtp_do_queue(void) {
1546 static int doing_queue = 0;
1549 * This is a simple concurrency check to make sure only one queue run
1550 * is done at a time. We could do this with a mutex, but since we
1551 * don't really require extremely fine granularity here, we'll do it
1552 * with a static variable instead.
1554 if (doing_queue) return;
1558 * Go ahead and run the queue
1560 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1562 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1563 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1566 CtdlForEachMessage(MSGS_ALL, 0L,
1567 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1569 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1576 /*****************************************************************************/
1577 /* SMTP UTILITY COMMANDS */
1578 /*****************************************************************************/
1580 void cmd_smtp(char *argbuf) {
1587 if (CtdlAccessCheck(ac_aide)) return;
1589 extract(cmd, argbuf, 0);
1591 if (!strcasecmp(cmd, "mx")) {
1592 extract(node, argbuf, 1);
1593 num_mxhosts = getmx(buf, node);
1594 cprintf("%d %d MX hosts listed for %s\n",
1595 LISTING_FOLLOWS, num_mxhosts, node);
1596 for (i=0; i<num_mxhosts; ++i) {
1597 extract(node, buf, i);
1598 cprintf("%s\n", node);
1604 else if (!strcasecmp(cmd, "runqueue")) {
1606 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1611 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1618 * Initialize the SMTP outbound queue
1620 void smtp_init_spoolout(void) {
1621 struct ctdlroom qrbuf;
1624 * Create the room. This will silently fail if the room already
1625 * exists, and that's perfectly ok, because we want it to exist.
1627 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1630 * Make sure it's set to be a "system room" so it doesn't show up
1631 * in the <K>nown rooms list for Aides.
1633 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1634 qrbuf.QRflags2 |= QR2_SYSTEM;
1642 /*****************************************************************************/
1643 /* MODULE INITIALIZATION STUFF */
1644 /*****************************************************************************/
1647 char *serv_smtp_init(void)
1649 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1656 CtdlRegisterServiceHook(config.c_smtps_port,
1663 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1669 CtdlRegisterServiceHook(0, /* local LMTP */
1675 smtp_init_spoolout();
1676 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1677 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");