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>
36 #if TIME_WITH_SYS_TIME
37 # include <sys/time.h>
41 # include <sys/time.h>
51 #include <sys/socket.h>
52 #include <netinet/in.h>
53 #include <arpa/inet.h>
56 #include "sysdep_decls.h"
57 #include "citserver.h"
61 #include "serv_extensions.h"
68 #include "internet_addressing.h"
71 #include "clientsocket.h"
72 #include "locate_host.h"
75 #include "serv_crypto.h"
84 struct citsmtp { /* Information about the current session */
87 struct ctdluser vrfy_buffer;
92 int number_of_recipients;
94 int message_originated_locally;
100 enum { /* Command states for login authentication */
106 enum { /* Delivery modes */
111 #define SMTP CC->SMTP
112 #define SMTP_RECPS CC->SMTP_RECPS
113 #define SMTP_ROOMS CC->SMTP_ROOMS
116 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
120 /*****************************************************************************/
121 /* SMTP SERVER (INBOUND) STUFF */
122 /*****************************************************************************/
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 SMTP = malloc(sizeof(struct citsmtp));
134 SMTP_RECPS = malloc(SIZ);
135 SMTP_ROOMS = malloc(SIZ);
136 memset(SMTP, 0, sizeof(struct citsmtp));
137 memset(SMTP_RECPS, 0, SIZ);
138 memset(SMTP_ROOMS, 0, SIZ);
140 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
145 * SMTPS is just like SMTP, except it goes crypto right away.
148 void smtps_greeting(void) {
149 CtdlStartTLS(NULL, NULL, NULL);
156 * SMTP MSA port requires authentication.
158 void smtp_msa_greeting(void) {
165 * LMTP is like SMTP but with some extra bonus footage added.
167 void lmtp_greeting(void) {
174 * We also have an unfiltered LMTP socket that bypasses spam filters.
176 void lmtp_unfiltered_greeting(void) {
179 SMTP->is_unfiltered = 1;
184 * Login greeting common to all auth methods
186 void smtp_auth_greeting(void) {
187 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
188 lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
189 CC->internal_pgm = 0;
190 CC->cs_flags &= ~CS_STEALTH;
195 * Implement HELO and EHLO commands.
197 * which_command: 0=HELO, 1=EHLO, 2=LHLO
199 void smtp_hello(char *argbuf, int which_command) {
201 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
203 if ( (which_command != 2) && (SMTP->is_lmtp) ) {
204 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
208 if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
209 cprintf("500 LHLO is only allowed when running LMTP\r\n");
213 if (which_command == 0) {
214 cprintf("250 Hello %s (%s [%s])\r\n",
221 if (which_command == 1) {
222 cprintf("250-Hello %s (%s [%s])\r\n",
229 cprintf("250-Greetings and joyous salutations.\r\n");
231 cprintf("250-HELP\r\n");
232 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
236 /* Only offer the PIPELINING command if TLS is inactive,
237 * because of flow control issues. Also, avoid offering TLS
238 * if TLS is already active. Finally, we only offer TLS on
239 * the SMTP-MSA port, not on the SMTP-MTA port, due to
240 * questionable reliability of TLS in certain sending MTA's.
242 if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
243 cprintf("250-PIPELINING\r\n");
244 cprintf("250-STARTTLS\r\n");
247 #else /* HAVE_OPENSSL */
249 /* Non SSL enabled server, so always offer PIPELINING. */
250 cprintf("250-PIPELINING\r\n");
252 #endif /* HAVE_OPENSSL */
254 cprintf("250-AUTH LOGIN PLAIN\r\n");
255 cprintf("250-AUTH=LOGIN PLAIN\r\n");
257 cprintf("250 ENHANCEDSTATUSCODES\r\n");
264 * Implement HELP command.
266 void smtp_help(void) {
267 cprintf("214-Commands accepted:\r\n");
268 cprintf("214- DATA\r\n");
269 cprintf("214- EHLO\r\n");
270 cprintf("214- EXPN\r\n");
271 cprintf("214- HELO\r\n");
272 cprintf("214- HELP\r\n");
273 cprintf("214- MAIL\r\n");
274 cprintf("214- NOOP\r\n");
275 cprintf("214- QUIT\r\n");
276 cprintf("214- RCPT\r\n");
277 cprintf("214- RSET\r\n");
278 cprintf("214- VRFY\r\n");
286 void smtp_get_user(char *argbuf) {
290 CtdlDecodeBase64(username, argbuf, SIZ);
291 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", username); */
292 if (CtdlLoginExistingUser(username) == login_ok) {
293 CtdlEncodeBase64(buf, "Password:", 9);
294 cprintf("334 %s\r\n", buf);
295 SMTP->command_state = smtp_password;
298 cprintf("500 5.7.0 No such user.\r\n");
299 SMTP->command_state = smtp_command;
307 void smtp_get_pass(char *argbuf) {
310 CtdlDecodeBase64(password, argbuf, SIZ);
311 /* lprintf(CTDL_DEBUG, "Trying <%s>\n", password); */
312 if (CtdlTryPassword(password) == pass_ok) {
313 smtp_auth_greeting();
316 cprintf("535 5.7.0 Authentication failed.\r\n");
318 SMTP->command_state = smtp_command;
325 void smtp_auth(char *argbuf) {
326 char username_prompt[64];
328 char encoded_authstring[1024];
329 char decoded_authstring[1024];
335 cprintf("504 5.7.4 Already logged in.\r\n");
339 extract_token(method, argbuf, 0, ' ', sizeof method);
341 if (!strncasecmp(method, "login", 5) ) {
342 if (strlen(argbuf) >= 7) {
343 smtp_get_user(&argbuf[6]);
346 CtdlEncodeBase64(username_prompt, "Username:", 9);
347 cprintf("334 %s\r\n", username_prompt);
348 SMTP->command_state = smtp_user;
353 if (!strncasecmp(method, "plain", 5) ) {
354 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
355 CtdlDecodeBase64(decoded_authstring,
357 strlen(encoded_authstring) );
358 safestrncpy(ident, decoded_authstring, sizeof ident);
359 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
360 safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
362 if (CtdlLoginExistingUser(user) == login_ok) {
363 if (CtdlTryPassword(pass) == pass_ok) {
364 smtp_auth_greeting();
368 cprintf("504 5.7.4 Authentication failed.\r\n");
371 if (strncasecmp(method, "login", 5) ) {
372 cprintf("504 5.7.4 Unknown authentication method.\r\n");
380 * Back end for smtp_vrfy() command
382 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
384 if (!fuzzy_match(us, SMTP->vrfy_match)) {
386 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
392 * Implements the VRFY (verify user name) command.
393 * Performs fuzzy match on full user names.
395 void smtp_vrfy(char *argbuf) {
396 SMTP->vrfy_count = 0;
397 strcpy(SMTP->vrfy_match, argbuf);
398 ForEachUser(smtp_vrfy_backend, NULL);
400 if (SMTP->vrfy_count < 1) {
401 cprintf("550 5.1.1 String does not match anything.\r\n");
403 else if (SMTP->vrfy_count == 1) {
404 cprintf("250 %s <cit%ld@%s>\r\n",
405 SMTP->vrfy_buffer.fullname,
406 SMTP->vrfy_buffer.usernum,
409 else if (SMTP->vrfy_count > 1) {
410 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
419 * Back end for smtp_expn() command
421 void smtp_expn_backend(struct ctdluser *us, void *data) {
423 if (!fuzzy_match(us, SMTP->vrfy_match)) {
425 if (SMTP->vrfy_count >= 1) {
426 cprintf("250-%s <cit%ld@%s>\r\n",
427 SMTP->vrfy_buffer.fullname,
428 SMTP->vrfy_buffer.usernum,
433 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
439 * Implements the EXPN (expand user name) command.
440 * Performs fuzzy match on full user names.
442 void smtp_expn(char *argbuf) {
443 SMTP->vrfy_count = 0;
444 strcpy(SMTP->vrfy_match, argbuf);
445 ForEachUser(smtp_expn_backend, NULL);
447 if (SMTP->vrfy_count < 1) {
448 cprintf("550 5.1.1 String does not match anything.\r\n");
450 else if (SMTP->vrfy_count >= 1) {
451 cprintf("250 %s <cit%ld@%s>\r\n",
452 SMTP->vrfy_buffer.fullname,
453 SMTP->vrfy_buffer.usernum,
460 * Implements the RSET (reset state) command.
461 * Currently this just zeroes out the state buffer. If pointers to data
462 * allocated with malloc() are ever placed in the state buffer, we have to
463 * be sure to free() them first!
465 * Set do_response to nonzero to output the SMTP RSET response code.
467 void smtp_rset(int do_response) {
472 * Our entire SMTP state is discarded when a RSET command is issued,
473 * but we need to preserve this one little piece of information, so
474 * we save it for later.
476 is_lmtp = SMTP->is_lmtp;
477 is_unfiltered = SMTP->is_unfiltered;
479 memset(SMTP, 0, sizeof(struct citsmtp));
482 * It is somewhat ambiguous whether we want to log out when a RSET
483 * command is issued. Here's the code to do it. It is commented out
484 * because some clients (such as Pine) issue RSET commands before
485 * each message, but still expect to be logged in.
487 * if (CC->logged_in) {
493 * Reinstate this little piece of information we saved (see above).
495 SMTP->is_lmtp = is_lmtp;
496 SMTP->is_unfiltered = is_unfiltered;
499 cprintf("250 2.0.0 Zap!\r\n");
504 * Clear out the portions of the state buffer that need to be cleared out
505 * after the DATA command finishes.
507 void smtp_data_clear(void) {
508 strcpy(SMTP->from, "");
509 strcpy(SMTP->recipients, "");
510 SMTP->number_of_recipients = 0;
511 SMTP->delivery_mode = 0;
512 SMTP->message_originated_locally = 0;
518 * Implements the "MAIL From:" command
520 void smtp_mail(char *argbuf) {
525 if (strlen(SMTP->from) != 0) {
526 cprintf("503 5.1.0 Only one sender permitted\r\n");
530 if (strncasecmp(argbuf, "From:", 5)) {
531 cprintf("501 5.1.7 Syntax error\r\n");
535 strcpy(SMTP->from, &argbuf[5]);
537 if (haschar(SMTP->from, '<') > 0) {
538 stripallbut(SMTP->from, '<', '>');
541 /* We used to reject empty sender names, until it was brought to our
542 * attention that RFC1123 5.2.9 requires that this be allowed. So now
543 * we allow it, but replace the empty string with a fake
544 * address so we don't have to contend with the empty string causing
545 * other code to fail when it's expecting something there.
547 if (strlen(SMTP->from) == 0) {
548 strcpy(SMTP->from, "someone@somewhere.org");
551 /* If this SMTP connection is from a logged-in user, force the 'from'
552 * to be the user's Internet e-mail address as Citadel knows it.
555 safestrncpy(SMTP->from, CC->cs_inet_email, sizeof SMTP->from);
556 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
557 SMTP->message_originated_locally = 1;
561 else if (SMTP->is_lmtp) {
562 /* Bypass forgery checking for LMTP */
565 /* Otherwise, make sure outsiders aren't trying to forge mail from
566 * this system (unless, of course, c_allow_spoofing is enabled)
568 else if (config.c_allow_spoofing == 0) {
569 process_rfc822_addr(SMTP->from, user, node, name);
570 if (CtdlHostAlias(node) != hostalias_nomatch) {
572 "You must log in to send mail from %s\r\n",
574 strcpy(SMTP->from, "");
579 cprintf("250 2.0.0 Sender ok\r\n");
585 * Implements the "RCPT To:" command
587 void smtp_rcpt(char *argbuf) {
589 char message_to_spammer[SIZ];
590 struct recptypes *valid = NULL;
592 if (strlen(SMTP->from) == 0) {
593 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
597 if (strncasecmp(argbuf, "To:", 3)) {
598 cprintf("501 5.1.7 Syntax error\r\n");
602 if ( (SMTP->is_msa) && (!CC->logged_in) ) {
604 "You must log in to send mail on this port.\r\n");
605 strcpy(SMTP->from, "");
609 strcpy(recp, &argbuf[3]);
611 stripallbut(recp, '<', '>');
613 if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
614 cprintf("452 4.5.3 Too many recipients\r\n");
619 if ( (!CC->logged_in)
620 && (!SMTP->is_lmtp) ) {
621 if (rbl_check(message_to_spammer)) {
622 cprintf("550 %s\r\n", message_to_spammer);
623 /* no need to free(valid), it's not allocated yet */
628 valid = validate_recipients(recp);
629 if (valid->num_error != 0) {
630 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
635 if (valid->num_internet > 0) {
637 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
638 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
645 if (valid->num_internet > 0) {
646 if ( (SMTP->message_originated_locally == 0)
647 && (SMTP->is_lmtp == 0) ) {
648 cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
654 cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
655 if (strlen(SMTP->recipients) > 0) {
656 strcat(SMTP->recipients, ",");
658 strcat(SMTP->recipients, recp);
659 SMTP->number_of_recipients += 1;
666 * Implements the DATA command
668 void smtp_data(void) {
670 struct CtdlMessage *msg;
673 struct recptypes *valid;
678 if (strlen(SMTP->from) == 0) {
679 cprintf("503 5.5.1 Need MAIL command first.\r\n");
683 if (SMTP->number_of_recipients < 1) {
684 cprintf("503 5.5.1 Need RCPT command first.\r\n");
688 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
690 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
693 if (body != NULL) snprintf(body, 4096,
694 "Received: from %s (%s [%s])\n"
702 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
705 "Unable to save message: internal error.\r\n");
709 lprintf(CTDL_DEBUG, "Converting message...\n");
710 msg = convert_internet_message(body);
712 /* If the user is locally authenticated, FORCE the From: header to
713 * show up as the real sender. Yes, this violates the RFC standard,
714 * but IT MAKES SENSE. If you prefer strict RFC adherence over
715 * common sense, you can disable this in the configuration.
717 * We also set the "message room name" ('O' field) to MAILROOM
718 * (which is Mail> on most systems) to prevent it from getting set
719 * to something ugly like "0000058008.Sent Items>" when the message
720 * is read with a Citadel client.
722 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
723 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
724 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
725 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
726 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
727 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
728 msg->cm_fields['A'] = strdup(CC->user.fullname);
729 msg->cm_fields['N'] = strdup(config.c_nodename);
730 msg->cm_fields['H'] = strdup(config.c_humannode);
731 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
732 msg->cm_fields['O'] = strdup(MAILROOM);
735 /* Submit the message into the Citadel system. */
736 valid = validate_recipients(SMTP->recipients);
738 /* If there are modules that want to scan this message before final
739 * submission (such as virus checkers or spam filters), call them now
740 * and give them an opportunity to reject the message.
742 if (SMTP->is_unfiltered) {
746 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
749 if (scan_errors > 0) { /* We don't want this message! */
751 if (msg->cm_fields['0'] == NULL) {
752 msg->cm_fields['0'] = strdup(
753 "5.7.1 Message rejected by filter");
756 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
759 else { /* Ok, we'll accept this message. */
760 msgnum = CtdlSubmitMsg(msg, valid, "");
762 sprintf(result, "250 2.0.0 Message accepted.\r\n");
765 sprintf(result, "550 5.5.0 Internal delivery error\r\n");
769 /* For SMTP and ESTMP, just print the result message. For LMTP, we
770 * have to print one result message for each recipient. Since there
771 * is nothing in Citadel which would cause different recipients to
772 * have different results, we can get away with just spitting out the
773 * same message once for each recipient.
776 for (i=0; i<SMTP->number_of_recipients; ++i) {
777 cprintf("%s", result);
781 cprintf("%s", result);
784 /* Write something to the syslog (which may or may not be where the
785 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
788 syslog((LOG_MAIL | LOG_INFO),
789 "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
792 SMTP->number_of_recipients,
800 CtdlFreeMessage(msg);
802 smtp_data_clear(); /* clear out the buffers now */
807 * implements the STARTTLS command (Citadel API version)
810 void smtp_starttls(void)
812 char ok_response[SIZ];
813 char nosup_response[SIZ];
814 char error_response[SIZ];
817 "200 2.0.0 Begin TLS negotiation now\r\n");
818 sprintf(nosup_response,
819 "554 5.7.3 TLS not supported here\r\n");
820 sprintf(error_response,
821 "554 5.7.3 Internal error\r\n");
822 CtdlStartTLS(ok_response, nosup_response, error_response);
830 * Main command loop for SMTP sessions.
832 void smtp_command_loop(void) {
836 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
837 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
838 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
842 lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
843 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
845 if (SMTP->command_state == smtp_user) {
846 smtp_get_user(cmdbuf);
849 else if (SMTP->command_state == smtp_password) {
850 smtp_get_pass(cmdbuf);
853 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
854 smtp_auth(&cmdbuf[5]);
857 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
861 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
862 smtp_expn(&cmdbuf[5]);
865 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
866 smtp_hello(&cmdbuf[5], 0);
869 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
870 smtp_hello(&cmdbuf[5], 1);
873 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
874 smtp_hello(&cmdbuf[5], 2);
877 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
881 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
882 smtp_mail(&cmdbuf[5]);
885 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
886 cprintf("250 NOOP\r\n");
889 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
890 cprintf("221 Goodbye...\r\n");
895 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
896 smtp_rcpt(&cmdbuf[5]);
899 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
903 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
907 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
908 smtp_vrfy(&cmdbuf[5]);
912 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
921 /*****************************************************************************/
922 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
923 /*****************************************************************************/
930 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
933 void smtp_try(const char *key, const char *addr, int *status,
934 char *dsn, size_t n, long msgnum)
941 char user[1024], node[1024], name[1024];
952 /* Parse out the host portion of the recipient address */
953 process_rfc822_addr(addr, user, node, name);
955 lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
958 /* Load the message out of the database */
959 CC->redirect_buffer = malloc(SIZ);
960 CC->redirect_len = 0;
961 CC->redirect_alloc = SIZ;
962 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
963 msgtext = CC->redirect_buffer;
964 msg_size = CC->redirect_len;
965 CC->redirect_buffer = NULL;
966 CC->redirect_len = 0;
967 CC->redirect_alloc = 0;
969 /* Extract something to send later in the 'MAIL From:' command */
970 strcpy(mailfrom, "");
974 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
977 if (!strncasecmp(buf, "From:", 5)) {
978 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
980 for (i=0; i<strlen(mailfrom); ++i) {
981 if (!isprint(mailfrom[i])) {
982 strcpy(&mailfrom[i], &mailfrom[i+1]);
987 /* Strip out parenthesized names */
990 for (i=0; i<strlen(mailfrom); ++i) {
991 if (mailfrom[i] == '(') lp = i;
992 if (mailfrom[i] == ')') rp = i;
994 if ((lp>0)&&(rp>lp)) {
995 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
998 /* Prefer brokketized names */
1001 for (i=0; i<strlen(mailfrom); ++i) {
1002 if (mailfrom[i] == '<') lp = i;
1003 if (mailfrom[i] == '>') rp = i;
1005 if ( (lp>=0) && (rp>lp) ) {
1007 strcpy(mailfrom, &mailfrom[lp]);
1012 } while (scan_done == 0);
1013 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
1014 stripallbut(mailfrom, '<', '>');
1016 /* Figure out what mail exchanger host we have to connect to */
1017 num_mxhosts = getmx(mxhosts, node);
1018 lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
1019 if (num_mxhosts < 1) {
1021 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1026 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1027 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1028 extract_token(mx_host, buf, 0, ':', sizeof mx_host);
1029 extract_token(mx_port, buf, 1, ':', sizeof mx_port);
1031 strcpy(mx_port, "25");
1033 lprintf(CTDL_DEBUG, "Trying %s : %s ...\n", mx_host, mx_port);
1034 sock = sock_connect(mx_host, mx_port, "tcp");
1035 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1036 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
1037 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
1041 *status = 4; /* dsn is already filled in */
1045 /* Process the SMTP greeting from the server */
1046 if (ml_sock_gets(sock, buf) < 0) {
1048 strcpy(dsn, "Connection broken during SMTP conversation");
1051 lprintf(CTDL_DEBUG, "<%s\n", buf);
1052 if (buf[0] != '2') {
1053 if (buf[0] == '4') {
1055 safestrncpy(dsn, &buf[4], 1023);
1060 safestrncpy(dsn, &buf[4], 1023);
1065 /* At this point we know we are talking to a real SMTP server */
1067 /* Do a HELO command */
1068 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
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 HELO");
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 /* HELO succeeded, now try the MAIL From: command */
1091 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1092 lprintf(CTDL_DEBUG, ">%s", buf);
1093 sock_write(sock, buf, strlen(buf));
1094 if (ml_sock_gets(sock, buf) < 0) {
1096 strcpy(dsn, "Connection broken during SMTP MAIL");
1099 lprintf(CTDL_DEBUG, "<%s\n", buf);
1100 if (buf[0] != '2') {
1101 if (buf[0] == '4') {
1103 safestrncpy(dsn, &buf[4], 1023);
1108 safestrncpy(dsn, &buf[4], 1023);
1113 /* MAIL succeeded, now try the RCPT To: command */
1114 snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
1115 lprintf(CTDL_DEBUG, ">%s", buf);
1116 sock_write(sock, buf, strlen(buf));
1117 if (ml_sock_gets(sock, buf) < 0) {
1119 strcpy(dsn, "Connection broken during SMTP RCPT");
1122 lprintf(CTDL_DEBUG, "<%s\n", buf);
1123 if (buf[0] != '2') {
1124 if (buf[0] == '4') {
1126 safestrncpy(dsn, &buf[4], 1023);
1131 safestrncpy(dsn, &buf[4], 1023);
1136 /* RCPT succeeded, now try the DATA command */
1137 lprintf(CTDL_DEBUG, ">DATA\n");
1138 sock_write(sock, "DATA\r\n", 6);
1139 if (ml_sock_gets(sock, buf) < 0) {
1141 strcpy(dsn, "Connection broken during SMTP DATA");
1144 lprintf(CTDL_DEBUG, "<%s\n", buf);
1145 if (buf[0] != '3') {
1146 if (buf[0] == '4') {
1148 safestrncpy(dsn, &buf[4], 1023);
1153 safestrncpy(dsn, &buf[4], 1023);
1158 /* If we reach this point, the server is expecting data */
1159 sock_write(sock, msgtext, msg_size);
1160 if (msgtext[msg_size-1] != 10) {
1161 lprintf(CTDL_WARNING, "Possible problem: message did not "
1162 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1166 sock_write(sock, ".\r\n", 3);
1167 if (ml_sock_gets(sock, buf) < 0) {
1169 strcpy(dsn, "Connection broken during SMTP message transmit");
1172 lprintf(CTDL_DEBUG, "%s\n", buf);
1173 if (buf[0] != '2') {
1174 if (buf[0] == '4') {
1176 safestrncpy(dsn, &buf[4], 1023);
1181 safestrncpy(dsn, &buf[4], 1023);
1187 safestrncpy(dsn, &buf[4], 1023);
1190 lprintf(CTDL_DEBUG, ">QUIT\n");
1191 sock_write(sock, "QUIT\r\n", 6);
1192 ml_sock_gets(sock, buf);
1193 lprintf(CTDL_DEBUG, "<%s\n", buf);
1194 lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1197 bail: free(msgtext);
1200 /* Write something to the syslog (which may or may not be where the
1201 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1203 if (enable_syslog) {
1204 syslog((LOG_MAIL | LOG_INFO),
1205 "%ld: to=<%s>, relay=%s, stat=%s",
1219 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1220 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1221 * a "bounce" message (delivery status notification).
1223 void smtp_do_bounce(char *instr) {
1231 char bounceto[1024];
1232 int num_bounces = 0;
1233 int bounce_this = 0;
1234 long bounce_msgid = (-1);
1235 time_t submitted = 0L;
1236 struct CtdlMessage *bmsg = NULL;
1238 struct recptypes *valid;
1239 int successful_bounce = 0;
1241 lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1242 strcpy(bounceto, "");
1244 lines = num_tokens(instr, '\n');
1247 /* See if it's time to give up on delivery of this message */
1248 for (i=0; i<lines; ++i) {
1249 extract_token(buf, instr, i, '\n', sizeof buf);
1250 extract_token(key, buf, 0, '|', sizeof key);
1251 extract_token(addr, buf, 1, '|', sizeof addr);
1252 if (!strcasecmp(key, "submitted")) {
1253 submitted = atol(addr);
1257 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1263 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1264 if (bmsg == NULL) return;
1265 memset(bmsg, 0, sizeof(struct CtdlMessage));
1267 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1268 bmsg->cm_anon_type = MES_NORMAL;
1269 bmsg->cm_format_type = 1;
1270 bmsg->cm_fields['A'] = strdup("Citadel");
1271 bmsg->cm_fields['O'] = strdup(MAILROOM);
1272 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1273 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1275 if (give_up) bmsg->cm_fields['M'] = strdup(
1276 "A message you sent could not be delivered to some or all of its recipients\n"
1277 "due to prolonged unavailability of its destination(s).\n"
1278 "Giving up on the following addresses:\n\n"
1281 else bmsg->cm_fields['M'] = strdup(
1282 "A message you sent could not be delivered to some or all of its recipients.\n"
1283 "The following addresses were undeliverable:\n\n"
1287 * Now go through the instructions checking for stuff.
1289 for (i=0; i<lines; ++i) {
1290 extract_token(buf, instr, i, '\n', sizeof buf);
1291 extract_token(key, buf, 0, '|', sizeof key);
1292 extract_token(addr, buf, 1, '|', sizeof addr);
1293 status = extract_int(buf, 2);
1294 extract_token(dsn, buf, 3, '|', sizeof dsn);
1297 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1298 key, addr, status, dsn);
1300 if (!strcasecmp(key, "bounceto")) {
1301 strcpy(bounceto, addr);
1305 (!strcasecmp(key, "local"))
1306 || (!strcasecmp(key, "remote"))
1307 || (!strcasecmp(key, "ignet"))
1308 || (!strcasecmp(key, "room"))
1310 if (status == 5) bounce_this = 1;
1311 if (give_up) bounce_this = 1;
1317 if (bmsg->cm_fields['M'] == NULL) {
1318 lprintf(CTDL_ERR, "ERROR ... M field is null "
1319 "(%s:%d)\n", __FILE__, __LINE__);
1322 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1323 strlen(bmsg->cm_fields['M']) + 1024 );
1324 strcat(bmsg->cm_fields['M'], addr);
1325 strcat(bmsg->cm_fields['M'], ": ");
1326 strcat(bmsg->cm_fields['M'], dsn);
1327 strcat(bmsg->cm_fields['M'], "\n");
1329 remove_token(instr, i, '\n');
1335 /* Deliver the bounce if there's anything worth mentioning */
1336 lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1337 if (num_bounces > 0) {
1339 /* First try the user who sent the message */
1340 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1341 if (strlen(bounceto) == 0) {
1342 lprintf(CTDL_ERR, "No bounce address specified\n");
1343 bounce_msgid = (-1L);
1346 /* Can we deliver the bounce to the original sender? */
1347 valid = validate_recipients(bounceto);
1348 if (valid != NULL) {
1349 if (valid->num_error == 0) {
1350 CtdlSubmitMsg(bmsg, valid, "");
1351 successful_bounce = 1;
1355 /* If not, post it in the Aide> room */
1356 if (successful_bounce == 0) {
1357 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1360 /* Free up the memory we used */
1361 if (valid != NULL) {
1366 CtdlFreeMessage(bmsg);
1367 lprintf(CTDL_DEBUG, "Done processing bounces\n");
1372 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1373 * set of delivery instructions for completed deliveries and remove them.
1375 * It returns the number of incomplete deliveries remaining.
1377 int smtp_purge_completed_deliveries(char *instr) {
1388 lines = num_tokens(instr, '\n');
1389 for (i=0; i<lines; ++i) {
1390 extract_token(buf, instr, i, '\n', sizeof buf);
1391 extract_token(key, buf, 0, '|', sizeof key);
1392 extract_token(addr, buf, 1, '|', sizeof addr);
1393 status = extract_int(buf, 2);
1394 extract_token(dsn, buf, 3, '|', sizeof dsn);
1399 (!strcasecmp(key, "local"))
1400 || (!strcasecmp(key, "remote"))
1401 || (!strcasecmp(key, "ignet"))
1402 || (!strcasecmp(key, "room"))
1404 if (status == 2) completed = 1;
1409 remove_token(instr, i, '\n');
1422 * Called by smtp_do_queue() to handle an individual message.
1424 void smtp_do_procmsg(long msgnum, void *userdata) {
1425 struct CtdlMessage *msg;
1427 char *results = NULL;
1435 long text_msgid = (-1);
1436 int incomplete_deliveries_remaining;
1437 time_t attempted = 0L;
1438 time_t last_attempted = 0L;
1439 time_t retry = SMTP_RETRY_INTERVAL;
1441 lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1443 msg = CtdlFetchMessage(msgnum, 1);
1445 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1449 instr = strdup(msg->cm_fields['M']);
1450 CtdlFreeMessage(msg);
1452 /* Strip out the headers amd any other non-instruction line */
1453 lines = num_tokens(instr, '\n');
1454 for (i=0; i<lines; ++i) {
1455 extract_token(buf, instr, i, '\n', sizeof buf);
1456 if (num_tokens(buf, '|') < 2) {
1457 remove_token(instr, i, '\n');
1463 /* Learn the message ID and find out about recent delivery attempts */
1464 lines = num_tokens(instr, '\n');
1465 for (i=0; i<lines; ++i) {
1466 extract_token(buf, instr, i, '\n', sizeof buf);
1467 extract_token(key, buf, 0, '|', sizeof key);
1468 if (!strcasecmp(key, "msgid")) {
1469 text_msgid = extract_long(buf, 1);
1471 if (!strcasecmp(key, "retry")) {
1472 /* double the retry interval after each attempt */
1473 retry = extract_long(buf, 1) * 2L;
1474 if (retry > SMTP_RETRY_MAX) {
1475 retry = SMTP_RETRY_MAX;
1477 remove_token(instr, i, '\n');
1479 if (!strcasecmp(key, "attempted")) {
1480 attempted = extract_long(buf, 1);
1481 if (attempted > last_attempted)
1482 last_attempted = attempted;
1487 * Postpone delivery if we've already tried recently.
1489 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1490 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1497 * Bail out if there's no actual message associated with this
1499 if (text_msgid < 0L) {
1500 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1505 /* Plow through the instructions looking for 'remote' directives and
1506 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1507 * were experienced and it's time to try again)
1509 lines = num_tokens(instr, '\n');
1510 for (i=0; i<lines; ++i) {
1511 extract_token(buf, instr, i, '\n', sizeof buf);
1512 extract_token(key, buf, 0, '|', sizeof key);
1513 extract_token(addr, buf, 1, '|', sizeof addr);
1514 status = extract_int(buf, 2);
1515 extract_token(dsn, buf, 3, '|', sizeof dsn);
1516 if ( (!strcasecmp(key, "remote"))
1517 && ((status==0)||(status==3)||(status==4)) ) {
1519 /* Remove this "remote" instruction from the set,
1520 * but replace the set's final newline if
1521 * remove_token() stripped it. It has to be there.
1523 remove_token(instr, i, '\n');
1524 if (instr[strlen(instr)-1] != '\n') {
1525 strcat(instr, "\n");
1530 lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1531 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1533 if (results == NULL) {
1534 results = malloc(1024);
1535 memset(results, 0, 1024);
1538 results = realloc(results,
1539 strlen(results) + 1024);
1541 snprintf(&results[strlen(results)], 1024,
1543 key, addr, status, dsn);
1548 if (results != NULL) {
1549 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1550 strcat(instr, results);
1555 /* Generate 'bounce' messages */
1556 smtp_do_bounce(instr);
1558 /* Go through the delivery list, deleting completed deliveries */
1559 incomplete_deliveries_remaining =
1560 smtp_purge_completed_deliveries(instr);
1564 * No delivery instructions remain, so delete both the instructions
1565 * message and the message message.
1567 if (incomplete_deliveries_remaining <= 0) {
1568 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "", 0);
1569 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "", 0);
1574 * Uncompleted delivery instructions remain, so delete the old
1575 * instructions and replace with the updated ones.
1577 if (incomplete_deliveries_remaining > 0) {
1578 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "", 0);
1579 msg = malloc(sizeof(struct CtdlMessage));
1580 memset(msg, 0, sizeof(struct CtdlMessage));
1581 msg->cm_magic = CTDLMESSAGE_MAGIC;
1582 msg->cm_anon_type = MES_NORMAL;
1583 msg->cm_format_type = FMT_RFC822;
1584 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1585 snprintf(msg->cm_fields['M'],
1587 "Content-type: %s\n\n%s\n"
1590 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1591 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1592 CtdlFreeMessage(msg);
1603 * Run through the queue sending out messages.
1605 void smtp_do_queue(void) {
1606 static int doing_queue = 0;
1609 * This is a simple concurrency check to make sure only one queue run
1610 * is done at a time. We could do this with a mutex, but since we
1611 * don't really require extremely fine granularity here, we'll do it
1612 * with a static variable instead.
1614 if (doing_queue) return;
1618 * Go ahead and run the queue
1620 lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1622 if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1623 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1626 CtdlForEachMessage(MSGS_ALL, 0L,
1627 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1629 lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1636 /*****************************************************************************/
1637 /* SMTP UTILITY COMMANDS */
1638 /*****************************************************************************/
1640 void cmd_smtp(char *argbuf) {
1647 if (CtdlAccessCheck(ac_aide)) return;
1649 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1651 if (!strcasecmp(cmd, "mx")) {
1652 extract_token(node, argbuf, 1, '|', sizeof node);
1653 num_mxhosts = getmx(buf, node);
1654 cprintf("%d %d MX hosts listed for %s\n",
1655 LISTING_FOLLOWS, num_mxhosts, node);
1656 for (i=0; i<num_mxhosts; ++i) {
1657 extract_token(node, buf, i, '|', sizeof node);
1658 cprintf("%s\n", node);
1664 else if (!strcasecmp(cmd, "runqueue")) {
1666 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1671 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1678 * Initialize the SMTP outbound queue
1680 void smtp_init_spoolout(void) {
1681 struct ctdlroom qrbuf;
1684 * Create the room. This will silently fail if the room already
1685 * exists, and that's perfectly ok, because we want it to exist.
1687 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1690 * Make sure it's set to be a "system room" so it doesn't show up
1691 * in the <K>nown rooms list for Aides.
1693 if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1694 qrbuf.QRflags2 |= QR2_SYSTEM;
1702 /*****************************************************************************/
1703 /* MODULE INITIALIZATION STUFF */
1704 /*****************************************************************************/
1706 * This cleanup function blows away the temporary memory used by
1709 void smtp_cleanup_function(void) {
1711 /* Don't do this stuff if this is not an SMTP session! */
1712 if (CC->h_command_function != smtp_command_loop) return;
1714 lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1724 char *serv_smtp_init(void)
1728 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1735 CtdlRegisterServiceHook(config.c_smtps_port,
1742 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1752 CtdlRegisterServiceHook(0, /* local LMTP */
1760 "%s/lmtp-unfiltered.sock",
1762 CtdlRegisterServiceHook(0, /* local LMTP */
1764 lmtp_unfiltered_greeting,
1768 smtp_init_spoolout();
1769 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1770 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1771 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");