4 * This module is an SMTP and ESMTP implementation for the Citadel system.
5 * It is compliant with all of the following:
7 * RFC 821 - Simple Mail Transfer Protocol
8 * RFC 876 - Survey of SMTP Implementations
9 * RFC 1047 - Duplicate messages and SMTP
10 * RFC 1854 - command pipelining
11 * RFC 1869 - Extended Simple Mail Transfer Protocol
12 * RFC 1870 - SMTP Service Extension for Message Size Declaration
13 * RFC 1893 - Enhanced Mail System Status Codes
14 * RFC 2033 - Local Mail Transfer Protocol
15 * RFC 2034 - SMTP Service Extension for Returning Enhanced Error Codes
16 * RFC 2197 - SMTP Service Extension for Command Pipelining
17 * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
18 * RFC 2554 - SMTP Service Extension for Authentication
19 * RFC 2821 - Simple Mail Transfer Protocol
20 * RFC 2822 - Internet Message Format
21 * RFC 2920 - SMTP Service Extension for Command Pipelining
33 #include <sys/types.h>
35 #if TIME_WITH_SYS_TIME
36 # include <sys/time.h>
40 # include <sys/time.h>
50 #include <sys/socket.h>
51 #include <netinet/in.h>
52 #include <arpa/inet.h>
55 #include "sysdep_decls.h"
56 #include "citserver.h"
60 #include "serv_extensions.h"
67 #include "internet_addressing.h"
70 #include "clientsocket.h"
71 #include "locate_host.h"
74 #include "serv_crypto.h"
83 struct citsmtp { /* Information about the current session */
86 struct ctdluser vrfy_buffer;
91 int number_of_recipients;
93 int message_originated_locally;
99 enum { /* Command states for login authentication */
105 enum { /* Delivery modes */
110 #define SMTP CC->SMTP
111 #define SMTP_RECPS CC->SMTP_RECPS
112 #define SMTP_ROOMS CC->SMTP_ROOMS
115 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
119 /*****************************************************************************/
120 /* SMTP SERVER (INBOUND) STUFF */
121 /*****************************************************************************/
125 * Here's where our SMTP session begins its happy day.
127 void smtp_greeting(void) {
129 strcpy(CC->cs_clientname, "SMTP session");
130 CC->internal_pgm = 1;
131 CC->cs_flags |= CS_STEALTH;
132 SMTP = malloc(sizeof(struct citsmtp));
133 SMTP_RECPS = malloc(SIZ);
134 SMTP_ROOMS = malloc(SIZ);
135 memset(SMTP, 0, sizeof(struct citsmtp));
136 memset(SMTP_RECPS, 0, SIZ);
137 memset(SMTP_ROOMS, 0, SIZ);
139 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
144 * SMTPS is just like SMTP, except it goes crypto right away.
147 void smtps_greeting(void) {
148 CtdlStartTLS(NULL, NULL, NULL);
155 * SMTP MSA port requires authentication.
157 void smtp_msa_greeting(void) {
164 * LMTP is like SMTP but with some extra bonus footage added.
166 void lmtp_greeting(void) {
173 * We also have an unfiltered LMTP socket that bypasses spam filters.
175 void lmtp_unfiltered_greeting(void) {
178 SMTP->is_unfiltered = 1;
183 * Login greeting common to all auth methods
185 void smtp_auth_greeting(void) {
186 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
187 lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
188 CC->internal_pgm = 0;
189 CC->cs_flags &= ~CS_STEALTH;
194 * Implement HELO and EHLO commands.
196 * which_command: 0=HELO, 1=EHLO, 2=LHLO
198 void smtp_hello(char *argbuf, int which_command) {
200 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
202 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
203 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
207 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
208 cprintf("500 LHLO is only allowed when running LMTP\r\n");
212 if (which_command == 0) {
213 cprintf("250 Hello %s (%s [%s])\r\n",
220 if (which_command == 1) {
221 cprintf("250-Hello %s (%s [%s])\r\n",
228 cprintf("250-Greetings and joyous salutations.\r\n");
230 cprintf("250-HELP\r\n");
231 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
235 /* Only offer the PIPELINING command if TLS is inactive,
236 * because of flow control issues. Also, avoid offering TLS
237 * if TLS is already active. Finally, we only offer TLS on
238 * the SMTP-MSA port, not on the SMTP-MTA port, due to
239 * questionable reliability of TLS in certain sending MTA's.
241 if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
242 cprintf("250-PIPELINING\r\n");
243 cprintf("250-STARTTLS\r\n");
246 #else /* HAVE_OPENSSL */
248 /* Non SSL enabled server, so always offer PIPELINING. */
249 cprintf("250-PIPELINING\r\n");
251 #endif /* HAVE_OPENSSL */
253 cprintf("250-AUTH LOGIN PLAIN\r\n");
254 cprintf("250-AUTH=LOGIN PLAIN\r\n");
256 cprintf("250 ENHANCEDSTATUSCODES\r\n");
263 * Implement HELP command.
265 void smtp_help(void) {
266 cprintf("214-Commands accepted:\r\n");
267 cprintf("214- DATA\r\n");
268 cprintf("214- EHLO\r\n");
269 cprintf("214- EXPN\r\n");
270 cprintf("214- HELO\r\n");
271 cprintf("214- HELP\r\n");
272 cprintf("214- MAIL\r\n");
273 cprintf("214- NOOP\r\n");
274 cprintf("214- QUIT\r\n");
275 cprintf("214- RCPT\r\n");
276 cprintf("214- RSET\r\n");
277 cprintf("214- VRFY\r\n");
285 void smtp_get_user(char *argbuf) {
289 CtdlDecodeBase64(username, argbuf, SIZ);
290 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", username); */
291 if (CtdlLoginExistingUser(username) == login_ok) {
292 CtdlEncodeBase64(buf, "Password:", 9);
293 cprintf("334 %s\r\n", buf);
294 SMTP->command_state = smtp_password;
297 cprintf("500 5.7.0 No such user.\r\n");
298 SMTP->command_state = smtp_command;
306 void smtp_get_pass(char *argbuf) {
309 CtdlDecodeBase64(password, argbuf, SIZ);
310 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", password); */
311 if (CtdlTryPassword(password) == pass_ok) {
312 smtp_auth_greeting();
315 cprintf("535 5.7.0 Authentication failed.\r\n");
317 SMTP->command_state = smtp_command;
324 void smtp_auth(char *argbuf) {
325 char username_prompt[64];
327 char encoded_authstring[1024];
328 char decoded_authstring[1024];
334 cprintf("504 5.7.4 Already logged in.\r\n");
338 extract_token(method, argbuf, 0, ' ', sizeof method);
340 if (!strncasecmp(method, "login", 5) ) {
341 if (strlen(argbuf) >= 7) {
342 smtp_get_user(&argbuf[6]);
345 CtdlEncodeBase64(username_prompt, "Username:", 9);
346 cprintf("334 %s\r\n", username_prompt);
347 SMTP->command_state = smtp_user;
352 if (!strncasecmp(method, "plain", 5) ) {
353 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
354 CtdlDecodeBase64(decoded_authstring,
356 strlen(encoded_authstring) );
357 safestrncpy(ident, decoded_authstring, sizeof ident);
358 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
359 safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
361 if (CtdlLoginExistingUser(user) == login_ok) {
362 if (CtdlTryPassword(pass) == pass_ok) {
363 smtp_auth_greeting();
367 cprintf("504 5.7.4 Authentication failed.\r\n");
370 if (strncasecmp(method, "login", 5) ) {
371 cprintf("504 5.7.4 Unknown authentication method.\r\n");
379 * Back end for smtp_vrfy() command
381 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
383 if (!fuzzy_match(us, SMTP->vrfy_match)) {
385 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
391 * Implements the VRFY (verify user name) command.
392 * Performs fuzzy match on full user names.
394 void smtp_vrfy(char *argbuf) {
395 SMTP->vrfy_count = 0;
396 strcpy(SMTP->vrfy_match, argbuf);
397 ForEachUser(smtp_vrfy_backend, NULL);
399 if (SMTP->vrfy_count < 1) {
400 cprintf("550 5.1.1 String does not match anything.\r\n");
402 else if (SMTP->vrfy_count == 1) {
403 cprintf("250 %s <cit%ld@%s>\r\n",
404 SMTP->vrfy_buffer.fullname,
405 SMTP->vrfy_buffer.usernum,
408 else if (SMTP->vrfy_count > 1) {
409 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
418 * Back end for smtp_expn() command
420 void smtp_expn_backend(struct ctdluser *us, void *data) {
422 if (!fuzzy_match(us, SMTP->vrfy_match)) {
424 if (SMTP->vrfy_count >= 1) {
425 cprintf("250-%s <cit%ld@%s>\r\n",
426 SMTP->vrfy_buffer.fullname,
427 SMTP->vrfy_buffer.usernum,
432 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
438 * Implements the EXPN (expand user name) command.
439 * Performs fuzzy match on full user names.
441 void smtp_expn(char *argbuf) {
442 SMTP->vrfy_count = 0;
443 strcpy(SMTP->vrfy_match, argbuf);
444 ForEachUser(smtp_expn_backend, NULL);
446 if (SMTP->vrfy_count < 1) {
447 cprintf("550 5.1.1 String does not match anything.\r\n");
449 else if (SMTP->vrfy_count >= 1) {
450 cprintf("250 %s <cit%ld@%s>\r\n",
451 SMTP->vrfy_buffer.fullname,
452 SMTP->vrfy_buffer.usernum,
459 * Implements the RSET (reset state) command.
460 * Currently this just zeroes out the state buffer. If pointers to data
461 * allocated with malloc() are ever placed in the state buffer, we have to
462 * be sure to free() them first!
464 * Set do_response to nonzero to output the SMTP RSET response code.
466 void smtp_rset(int do_response) {
471 * Our entire SMTP state is discarded when a RSET command is issued,
472 * but we need to preserve this one little piece of information, so
473 * we save it for later.
475 is_lmtp = SMTP->is_lmtp;
476 is_unfiltered = SMTP->is_unfiltered;
478 memset(SMTP, 0, sizeof(struct citsmtp));
481 * It is somewhat ambiguous whether we want to log out when a RSET
482 * command is issued. Here's the code to do it. It is commented out
483 * because some clients (such as Pine) issue RSET commands before
484 * each message, but still expect to be logged in.
486 * if (CC->logged_in) {
492 * Reinstate this little piece of information we saved (see above).
494 SMTP->is_lmtp = is_lmtp;
495 SMTP->is_unfiltered = is_unfiltered;
498 cprintf("250 2.0.0 Zap!\r\n");
503 * Clear out the portions of the state buffer that need to be cleared out
504 * after the DATA command finishes.
506 void smtp_data_clear(void) {
507 strcpy(SMTP->from, "");
508 strcpy(SMTP->recipients, "");
509 SMTP->number_of_recipients = 0;
510 SMTP->delivery_mode = 0;
511 SMTP->message_originated_locally = 0;
517 * Implements the "MAIL From:" command
519 void smtp_mail(char *argbuf) {
524 if (strlen(SMTP->from) != 0) {
525 cprintf("503 5.1.0 Only one sender permitted\r\n");
529 if (strncasecmp(argbuf, "From:", 5)) {
530 cprintf("501 5.1.7 Syntax error\r\n");
534 strcpy(SMTP->from, &argbuf[5]);
536 if (haschar(SMTP->from, '<') > 0) {
537 stripallbut(SMTP->from, '<', '>');
540 /* We used to reject empty sender names, until it was brought to our
541 * attention that RFC1123 5.2.9 requires that this be allowed. So now
542 * we allow it, but replace the empty string with a fake
543 * address so we don't have to contend with the empty string causing
544 * other code to fail when it's expecting something there.
546 if (strlen(SMTP->from) == 0) {
547 strcpy(SMTP->from, "someone@somewhere.org");
550 /* If this SMTP connection is from a logged-in user, force the 'from'
551 * to be the user's Internet e-mail address as Citadel knows it.
554 safestrncpy(SMTP->from, CC->cs_inet_email, sizeof SMTP->from);
555 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
556 SMTP->message_originated_locally = 1;
560 else if (SMTP->is_lmtp) {
561 /* Bypass forgery checking for LMTP */
564 /* Otherwise, make sure outsiders aren't trying to forge mail from
565 * this system (unless, of course, c_allow_spoofing is enabled)
567 else if (config.c_allow_spoofing == 0) {
568 process_rfc822_addr(SMTP->from, user, node, name);
569 if (CtdlHostAlias(node) != hostalias_nomatch) {
571 "You must log in to send mail from %s\r\n",
573 strcpy(SMTP->from, "");
578 cprintf("250 2.0.0 Sender ok\r\n");
584 * Implements the "RCPT To:" command
586 void smtp_rcpt(char *argbuf) {
588 char message_to_spammer[SIZ];
589 struct recptypes *valid = NULL;
591 if (strlen(SMTP->from) == 0) {
592 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
596 if (strncasecmp(argbuf, "To:", 3)) {
597 cprintf("501 5.1.7 Syntax error\r\n");
601 if ( (SMTP->is_msa) && (!CC->logged_in) ) {
603 "You must log in to send mail on this port.\r\n");
604 strcpy(SMTP->from, "");
608 strcpy(recp, &argbuf[3]);
610 stripallbut(recp, '<', '>');
612 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
613 cprintf("452 4.5.3 Too many recipients\r\n");
618 if ( (!CC->logged_in)
619 && (!SMTP->is_lmtp) ) {
620 if (rbl_check(message_to_spammer)) {
621 cprintf("550 %s\r\n", message_to_spammer);
622 /* no need to free(valid), it's not allocated yet */
627 valid = validate_recipients(recp);
628 if (valid->num_error != 0) {
629 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
634 if (valid->num_internet > 0) {
636 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
637 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
644 if (valid->num_internet > 0) {
645 if ( (SMTP->message_originated_locally == 0)
646 && (SMTP->is_lmtp == 0) ) {
647 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
653 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
654 if (strlen(SMTP->recipients) > 0) {
655 strcat(SMTP->recipients, ",");
657 strcat(SMTP->recipients, recp);
658 SMTP->number_of_recipients += 1;
665 * Implements the DATA command
667 void smtp_data(void) {
669 struct CtdlMessage *msg;
672 struct recptypes *valid;
677 if (strlen(SMTP->from) == 0) {
678 cprintf("503 5.5.1 Need MAIL command first.\r\n");
682 if (SMTP->number_of_recipients < 1) {
683 cprintf("503 5.5.1 Need RCPT command first.\r\n");
687 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
689 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
692 if (body != NULL) snprintf(body, 4096,
693 "Received: from %s (%s [%s])\n"
701 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
704 "Unable to save message: internal error.\r\n");
708 lprintf(CTDL_DEBUG, "Converting message...\n");
709 msg = convert_internet_message(body);
711 /* If the user is locally authenticated, FORCE the From: header to
712 * show up as the real sender. Yes, this violates the RFC standard,
713 * but IT MAKES SENSE. If you prefer strict RFC adherence over
714 * common sense, you can disable this in the configuration.
716 * We also set the "message room name" ('O' field) to MAILROOM
717 * (which is Mail> on most systems) to prevent it from getting set
718 * to something ugly like "0000058008.Sent Items>" when the message
719 * is read with a Citadel client.
721 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
722 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
723 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
724 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
725 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
726 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
727 msg->cm_fields['A'] = strdup(CC->user.fullname);
728 msg->cm_fields['N'] = strdup(config.c_nodename);
729 msg->cm_fields['H'] = strdup(config.c_humannode);
730 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
731 msg->cm_fields['O'] = strdup(MAILROOM);
734 /* Submit the message into the Citadel system. */
735 valid = validate_recipients(SMTP->recipients);
737 /* If there are modules that want to scan this message before final
738 * submission (such as virus checkers or spam filters), call them now
739 * and give them an opportunity to reject the message.
741 if (SMTP->is_unfiltered) {
745 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
748 if (scan_errors > 0) { /* We don't want this message! */
750 if (msg->cm_fields['0'] == NULL) {
751 msg->cm_fields['0'] = strdup(
752 "5.7.1 Message rejected by filter");
755 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
758 else { /* Ok, we'll accept this message. */
759 msgnum = CtdlSubmitMsg(msg, valid, "");
761 sprintf(result, "250 2.0.0 Message accepted.\r\n");
764 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
768 /* For SMTP and ESTMP, just print the result message. For LMTP, we
769 * have to print one result message for each recipient. Since there
770 * is nothing in Citadel which would cause different recipients to
771 * have different results, we can get away with just spitting out the
772 * same message once for each recipient.
775 for (i=0; i<SMTP->number_of_recipients; ++i) {
776 cprintf("%s", result);
780 cprintf("%s", result);
783 CtdlFreeMessage(msg);
785 smtp_data_clear(); /* clear out the buffers now */
790 * implements the STARTTLS command (Citadel API version)
793 void smtp_starttls(void)
795 char ok_response[SIZ];
796 char nosup_response[SIZ];
797 char error_response[SIZ];
800 "200 2.0.0 Begin TLS negotiation now\r\n");
801 sprintf(nosup_response,
802 "554 5.7.3 TLS not supported here\r\n");
803 sprintf(error_response,
804 "554 5.7.3 Internal error\r\n");
805 CtdlStartTLS(ok_response, nosup_response, error_response);
813 * Main command loop for SMTP sessions.
815 void smtp_command_loop(void) {
819 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
820 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
821 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
825 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
826 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
828 if (SMTP->command_state == smtp_user) {
829 smtp_get_user(cmdbuf);
832 else if (SMTP->command_state == smtp_password) {
833 smtp_get_pass(cmdbuf);
836 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
837 smtp_auth(&cmdbuf[5]);
840 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
844 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
845 smtp_expn(&cmdbuf[5]);
848 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
849 smtp_hello(&cmdbuf[5], 0);
852 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
853 smtp_hello(&cmdbuf[5], 1);
856 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
857 smtp_hello(&cmdbuf[5], 2);
860 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
864 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
865 smtp_mail(&cmdbuf[5]);
868 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
869 cprintf("250 NOOP\r\n");
872 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
873 cprintf("221 Goodbye...\r\n");
878 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
879 smtp_rcpt(&cmdbuf[5]);
882 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
886 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
890 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
891 smtp_vrfy(&cmdbuf[5]);
895 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
904 /*****************************************************************************/
905 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
906 /*****************************************************************************/
913 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
916 void smtp_try(const char *key, const char *addr, int *status,
917 char *dsn, size_t n, long msgnum)
924 char user[1024], node[1024], name[1024];
935 /* Parse out the host portion of the recipient address */
936 process_rfc822_addr(addr, user, node, name);
938 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
941 /* Load the message out of the database */
942 CC->redirect_buffer = malloc(SIZ);
943 CC->redirect_len = 0;
944 CC->redirect_alloc = SIZ;
945 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
946 msgtext = CC->redirect_buffer;
947 msg_size = CC->redirect_len;
948 CC->redirect_buffer = NULL;
949 CC->redirect_len = 0;
950 CC->redirect_alloc = 0;
952 /* Extract something to send later in the 'MAIL From:' command */
953 strcpy(mailfrom, "");
957 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
960 if (!strncasecmp(buf, "From:", 5)) {
961 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
963 for (i=0; i<strlen(mailfrom); ++i) {
964 if (!isprint(mailfrom[i])) {
965 strcpy(&mailfrom[i], &mailfrom[i+1]);
970 /* Strip out parenthesized names */
973 for (i=0; i<strlen(mailfrom); ++i) {
974 if (mailfrom[i] == '(') lp = i;
975 if (mailfrom[i] == ')') rp = i;
977 if ((lp>0)&&(rp>lp)) {
978 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
981 /* Prefer brokketized names */
984 for (i=0; i<strlen(mailfrom); ++i) {
985 if (mailfrom[i] == '<') lp = i;
986 if (mailfrom[i] == '>') rp = i;
988 if ( (lp>=0) && (rp>lp) ) {
990 strcpy(mailfrom, &mailfrom[lp]);
995 } while (scan_done == 0);
996 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
997 stripallbut(mailfrom, '<', '>');
999 /* Figure out what mail exchanger host we have to connect to */
1000 num_mxhosts = getmx(mxhosts, node);
1001 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
1002 if (num_mxhosts < 1) {
1004 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1009 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1010 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1011 extract_token(mx_host, buf, 0, ':', sizeof mx_host);
1012 extract_token(mx_port, buf, 1, ':', sizeof mx_port);
1014 strcpy(mx_port, "25");
1016 lprintf(CTDL_DEBUG, "Trying %s : %s ...\n", mx_host, mx_port);
1017 sock = sock_connect(mx_host, mx_port, "tcp");
1018 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1019 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
1020 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
1024 *status = 4; /* dsn is already filled in */
1028 /* Process the SMTP greeting from the server */
1029 if (ml_sock_gets(sock, buf) < 0) {
1031 strcpy(dsn, "Connection broken during SMTP conversation");
1034 lprintf(CTDL_DEBUG, "<%s\n", buf);
1035 if (buf[0] != '2') {
1036 if (buf[0] == '4') {
1038 safestrncpy(dsn, &buf[4], 1023);
1043 safestrncpy(dsn, &buf[4], 1023);
1048 /* At this point we know we are talking to a real SMTP server */
1050 /* Do a HELO command */
1051 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1052 lprintf(CTDL_DEBUG, ">%s", buf);
1053 sock_write(sock, buf, strlen(buf));
1054 if (ml_sock_gets(sock, buf) < 0) {
1056 strcpy(dsn, "Connection broken during SMTP HELO");
1059 lprintf(CTDL_DEBUG, "<%s\n", buf);
1060 if (buf[0] != '2') {
1061 if (buf[0] == '4') {
1063 safestrncpy(dsn, &buf[4], 1023);
1068 safestrncpy(dsn, &buf[4], 1023);
1073 /* HELO succeeded, now try the MAIL From: command */
1074 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1075 lprintf(CTDL_DEBUG, ">%s", buf);
1076 sock_write(sock, buf, strlen(buf));
1077 if (ml_sock_gets(sock, buf) < 0) {
1079 strcpy(dsn, "Connection broken during SMTP MAIL");
1082 lprintf(CTDL_DEBUG, "<%s\n", buf);
1083 if (buf[0] != '2') {
1084 if (buf[0] == '4') {
1086 safestrncpy(dsn, &buf[4], 1023);
1091 safestrncpy(dsn, &buf[4], 1023);
1096 /* MAIL succeeded, now try the RCPT To: command */
1097 snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
1098 lprintf(CTDL_DEBUG, ">%s", buf);
1099 sock_write(sock, buf, strlen(buf));
1100 if (ml_sock_gets(sock, buf) < 0) {
1102 strcpy(dsn, "Connection broken during SMTP RCPT");
1105 lprintf(CTDL_DEBUG, "<%s\n", buf);
1106 if (buf[0] != '2') {
1107 if (buf[0] == '4') {
1109 safestrncpy(dsn, &buf[4], 1023);
1114 safestrncpy(dsn, &buf[4], 1023);
1119 /* RCPT succeeded, now try the DATA command */
1120 lprintf(CTDL_DEBUG, ">DATA\n");
1121 sock_write(sock, "DATA\r\n", 6);
1122 if (ml_sock_gets(sock, buf) < 0) {
1124 strcpy(dsn, "Connection broken during SMTP DATA");
1127 lprintf(CTDL_DEBUG, "<%s\n", buf);
1128 if (buf[0] != '3') {
1129 if (buf[0] == '4') {
1131 safestrncpy(dsn, &buf[4], 1023);
1136 safestrncpy(dsn, &buf[4], 1023);
1141 /* If we reach this point, the server is expecting data */
1142 sock_write(sock, msgtext, msg_size);
1143 if (msgtext[msg_size-1] != 10) {
1144 lprintf(CTDL_WARNING, "Possible problem: message did not "
1145 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1149 sock_write(sock, ".\r\n", 3);
1150 if (ml_sock_gets(sock, buf) < 0) {
1152 strcpy(dsn, "Connection broken during SMTP message transmit");
1155 lprintf(CTDL_DEBUG, "%s\n", buf);
1156 if (buf[0] != '2') {
1157 if (buf[0] == '4') {
1159 safestrncpy(dsn, &buf[4], 1023);
1164 safestrncpy(dsn, &buf[4], 1023);
1170 safestrncpy(dsn, &buf[4], 1023);
1173 lprintf(CTDL_DEBUG, ">QUIT\n");
1174 sock_write(sock, "QUIT\r\n", 6);
1175 ml_sock_gets(sock, buf);
1176 lprintf(CTDL_DEBUG, "<%s\n", buf);
1177 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1180 bail: free(msgtext);
1188 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1189 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1190 * a "bounce" message (delivery status notification).
1192 void smtp_do_bounce(char *instr) {
1200 char bounceto[1024];
1201 int num_bounces = 0;
1202 int bounce_this = 0;
1203 long bounce_msgid = (-1);
1204 time_t submitted = 0L;
1205 struct CtdlMessage *bmsg = NULL;
1207 struct recptypes *valid;
1208 int successful_bounce = 0;
1210 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1211 strcpy(bounceto, "");
1213 lines = num_tokens(instr, '\n');
1216 /* See if it's time to give up on delivery of this message */
1217 for (i=0; i<lines; ++i) {
1218 extract_token(buf, instr, i, '\n', sizeof buf);
1219 extract_token(key, buf, 0, '|', sizeof key);
1220 extract_token(addr, buf, 1, '|', sizeof addr);
1221 if (!strcasecmp(key, "submitted")) {
1222 submitted = atol(addr);
1226 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1232 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1233 if (bmsg == NULL) return;
1234 memset(bmsg, 0, sizeof(struct CtdlMessage));
1236 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1237 bmsg->cm_anon_type = MES_NORMAL;
1238 bmsg->cm_format_type = 1;
1239 bmsg->cm_fields['A'] = strdup("Citadel");
1240 bmsg->cm_fields['O'] = strdup(MAILROOM);
1241 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1242 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1244 if (give_up) bmsg->cm_fields['M'] = strdup(
1245 "A message you sent could not be delivered to some or all of its recipients\n"
1246 "due to prolonged unavailability of its destination(s).\n"
1247 "Giving up on the following addresses:\n\n"
1250 else bmsg->cm_fields['M'] = strdup(
1251 "A message you sent could not be delivered to some or all of its recipients.\n"
1252 "The following addresses were undeliverable:\n\n"
1256 * Now go through the instructions checking for stuff.
1258 for (i=0; i<lines; ++i) {
1259 extract_token(buf, instr, i, '\n', sizeof buf);
1260 extract_token(key, buf, 0, '|', sizeof key);
1261 extract_token(addr, buf, 1, '|', sizeof addr);
1262 status = extract_int(buf, 2);
1263 extract_token(dsn, buf, 3, '|', sizeof dsn);
1266 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1267 key, addr, status, dsn);
1269 if (!strcasecmp(key, "bounceto")) {
1270 strcpy(bounceto, addr);
1274 (!strcasecmp(key, "local"))
1275 || (!strcasecmp(key, "remote"))
1276 || (!strcasecmp(key, "ignet"))
1277 || (!strcasecmp(key, "room"))
1279 if (status == 5) bounce_this = 1;
1280 if (give_up) bounce_this = 1;
1286 if (bmsg->cm_fields['M'] == NULL) {
1287 lprintf(CTDL_ERR, "ERROR ... M field is null "
1288 "(%s:%d)\n", __FILE__, __LINE__);
1291 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1292 strlen(bmsg->cm_fields['M']) + 1024 );
1293 strcat(bmsg->cm_fields['M'], addr);
1294 strcat(bmsg->cm_fields['M'], ": ");
1295 strcat(bmsg->cm_fields['M'], dsn);
1296 strcat(bmsg->cm_fields['M'], "\n");
1298 remove_token(instr, i, '\n');
1304 /* Deliver the bounce if there's anything worth mentioning */
1305 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1306 if (num_bounces > 0) {
1308 /* First try the user who sent the message */
1309 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1310 if (strlen(bounceto) == 0) {
1311 lprintf(CTDL_ERR, "No bounce address specified\n");
1312 bounce_msgid = (-1L);
1315 /* Can we deliver the bounce to the original sender? */
1316 valid = validate_recipients(bounceto);
1317 if (valid != NULL) {
1318 if (valid->num_error == 0) {
1319 CtdlSubmitMsg(bmsg, valid, "");
1320 successful_bounce = 1;
1324 /* If not, post it in the Aide> room */
1325 if (successful_bounce == 0) {
1326 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1329 /* Free up the memory we used */
1330 if (valid != NULL) {
1335 CtdlFreeMessage(bmsg);
1336 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1341 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1342 * set of delivery instructions for completed deliveries and remove them.
1344 * It returns the number of incomplete deliveries remaining.
1346 int smtp_purge_completed_deliveries(char *instr) {
1357 lines = num_tokens(instr, '\n');
1358 for (i=0; i<lines; ++i) {
1359 extract_token(buf, instr, i, '\n', sizeof buf);
1360 extract_token(key, buf, 0, '|', sizeof key);
1361 extract_token(addr, buf, 1, '|', sizeof addr);
1362 status = extract_int(buf, 2);
1363 extract_token(dsn, buf, 3, '|', sizeof dsn);
1368 (!strcasecmp(key, "local"))
1369 || (!strcasecmp(key, "remote"))
1370 || (!strcasecmp(key, "ignet"))
1371 || (!strcasecmp(key, "room"))
1373 if (status == 2) completed = 1;
1378 remove_token(instr, i, '\n');
1391 * Called by smtp_do_queue() to handle an individual message.
1393 void smtp_do_procmsg(long msgnum, void *userdata) {
1394 struct CtdlMessage *msg;
1396 char *results = NULL;
1404 long text_msgid = (-1);
1405 int incomplete_deliveries_remaining;
1406 time_t attempted = 0L;
1407 time_t last_attempted = 0L;
1408 time_t retry = SMTP_RETRY_INTERVAL;
1410 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1412 msg = CtdlFetchMessage(msgnum, 1);
1414 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1418 instr = strdup(msg->cm_fields['M']);
1419 CtdlFreeMessage(msg);
1421 /* Strip out the headers amd any other non-instruction line */
1422 lines = num_tokens(instr, '\n');
1423 for (i=0; i<lines; ++i) {
1424 extract_token(buf, instr, i, '\n', sizeof buf);
1425 if (num_tokens(buf, '|') < 2) {
1426 remove_token(instr, i, '\n');
1432 /* Learn the message ID and find out about recent delivery attempts */
1433 lines = num_tokens(instr, '\n');
1434 for (i=0; i<lines; ++i) {
1435 extract_token(buf, instr, i, '\n', sizeof buf);
1436 extract_token(key, buf, 0, '|', sizeof key);
1437 if (!strcasecmp(key, "msgid")) {
1438 text_msgid = extract_long(buf, 1);
1440 if (!strcasecmp(key, "retry")) {
1441 /* double the retry interval after each attempt */
1442 retry = extract_long(buf, 1) * 2L;
1443 if (retry > SMTP_RETRY_MAX) {
1444 retry = SMTP_RETRY_MAX;
1446 remove_token(instr, i, '\n');
1448 if (!strcasecmp(key, "attempted")) {
1449 attempted = extract_long(buf, 1);
1450 if (attempted > last_attempted)
1451 last_attempted = attempted;
1456 * Postpone delivery if we've already tried recently.
1458 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1459 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1466 * Bail out if there's no actual message associated with this
1468 if (text_msgid < 0L) {
1469 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1474 /* Plow through the instructions looking for 'remote' directives and
1475 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1476 * were experienced and it's time to try again)
1478 lines = num_tokens(instr, '\n');
1479 for (i=0; i<lines; ++i) {
1480 extract_token(buf, instr, i, '\n', sizeof buf);
1481 extract_token(key, buf, 0, '|', sizeof key);
1482 extract_token(addr, buf, 1, '|', sizeof addr);
1483 status = extract_int(buf, 2);
1484 extract_token(dsn, buf, 3, '|', sizeof dsn);
1485 if ( (!strcasecmp(key, "remote"))
1486 && ((status==0)||(status==3)||(status==4)) ) {
1488 /* Remove this "remote" instruction from the set,
1489 * but replace the set's final newline if
1490 * remove_token() stripped it. It has to be there.
1492 remove_token(instr, i, '\n');
1493 if (instr[strlen(instr)-1] != '\n') {
1494 strcat(instr, "\n");
1499 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1500 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1502 if (results == NULL) {
1503 results = malloc(1024);
1504 memset(results, 0, 1024);
1507 results = realloc(results,
1508 strlen(results) + 1024);
1510 snprintf(&results[strlen(results)], 1024,
1512 key, addr, status, dsn);
1517 if (results != NULL) {
1518 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1519 strcat(instr, results);
1524 /* Generate 'bounce' messages */
1525 smtp_do_bounce(instr);
1527 /* Go through the delivery list, deleting completed deliveries */
1528 incomplete_deliveries_remaining =
1529 smtp_purge_completed_deliveries(instr);
1533 * No delivery instructions remain, so delete both the instructions
1534 * message and the message message.
1536 if (incomplete_deliveries_remaining <= 0) {
1537 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "", 0);
1538 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "", 0);
1543 * Uncompleted delivery instructions remain, so delete the old
1544 * instructions and replace with the updated ones.
1546 if (incomplete_deliveries_remaining > 0) {
1547 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "", 0);
1548 msg = malloc(sizeof(struct CtdlMessage));
1549 memset(msg, 0, sizeof(struct CtdlMessage));
1550 msg->cm_magic = CTDLMESSAGE_MAGIC;
1551 msg->cm_anon_type = MES_NORMAL;
1552 msg->cm_format_type = FMT_RFC822;
1553 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1554 snprintf(msg->cm_fields['M'],
1556 "Content-type: %s\n\n%s\n"
1559 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1560 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1561 CtdlFreeMessage(msg);
1572 * Run through the queue sending out messages.
1574 void smtp_do_queue(void) {
1575 static int doing_queue = 0;
1578 * This is a simple concurrency check to make sure only one queue run
1579 * is done at a time. We could do this with a mutex, but since we
1580 * don't really require extremely fine granularity here, we'll do it
1581 * with a static variable instead.
1583 if (doing_queue) return;
1587 * Go ahead and run the queue
1589 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1591 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1592 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1595 CtdlForEachMessage(MSGS_ALL, 0L,
1596 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1598 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1605 /*****************************************************************************/
1606 /* SMTP UTILITY COMMANDS */
1607 /*****************************************************************************/
1609 void cmd_smtp(char *argbuf) {
1616 if (CtdlAccessCheck(ac_aide)) return;
1618 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1620 if (!strcasecmp(cmd, "mx")) {
1621 extract_token(node, argbuf, 1, '|', sizeof node);
1622 num_mxhosts = getmx(buf, node);
1623 cprintf("%d %d MX hosts listed for %s\n",
1624 LISTING_FOLLOWS, num_mxhosts, node);
1625 for (i=0; i<num_mxhosts; ++i) {
1626 extract_token(node, buf, i, '|', sizeof node);
1627 cprintf("%s\n", node);
1633 else if (!strcasecmp(cmd, "runqueue")) {
1635 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1640 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1647 * Initialize the SMTP outbound queue
1649 void smtp_init_spoolout(void) {
1650 struct ctdlroom qrbuf;
1653 * Create the room. This will silently fail if the room already
1654 * exists, and that's perfectly ok, because we want it to exist.
1656 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1659 * Make sure it's set to be a "system room" so it doesn't show up
1660 * in the <K>nown rooms list for Aides.
1662 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1663 qrbuf.QRflags2 |= QR2_SYSTEM;
1671 /*****************************************************************************/
1672 /* MODULE INITIALIZATION STUFF */
1673 /*****************************************************************************/
1675 * This cleanup function blows away the temporary memory used by
1678 void smtp_cleanup_function(void) {
1680 /* Don't do this stuff if this is not an SMTP session! */
1681 if (CC->h_command_function != smtp_command_loop) return;
1683 lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1693 char *serv_smtp_init(void)
1695 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1702 CtdlRegisterServiceHook(config.c_smtps_port,
1709 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1715 CtdlRegisterServiceHook(0, /* local LMTP */
1716 #ifndef HAVE_RUN_DIR
1726 CtdlRegisterServiceHook(0, /* local LMTP */
1727 #ifndef HAVE_RUN_DIR
1732 "/lmtp-unfiltered.socket",
1733 lmtp_unfiltered_greeting,
1737 smtp_init_spoolout();
1738 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1739 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1740 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");