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 1652 - 8 bit MIME
11 * RFC 1869 - Extended Simple Mail Transfer Protocol
12 * RFC 1870 - SMTP Service Extension for Message Size Declaration
13 * RFC 2033 - Local Mail Transfer Protocol
14 * RFC 2197 - SMTP Service Extension for Command Pipelining
15 * RFC 2476 - Message Submission
16 * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
17 * RFC 2554 - SMTP Service Extension for Authentication
18 * RFC 2821 - Simple Mail Transfer Protocol
19 * RFC 2822 - Internet Message Format
20 * RFC 2920 - SMTP Service Extension for Command Pipelining
22 * The VRFY and EXPN commands have been removed from this implementation
23 * because nobody uses these commands anymore, except for spammers.
25 * Copyright (c) 1998-2009 by the citadel.org team
27 * This program is free software; you can redistribute it and/or modify
28 * it under the terms of the GNU General Public License as published by
29 * the Free Software Foundation; either version 3 of the License, or
30 * (at your option) any later version.
32 * This program is distributed in the hope that it will be useful,
33 * but WITHOUT ANY WARRANTY; without even the implied warranty of
34 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35 * GNU General Public License for more details.
37 * You should have received a copy of the GNU General Public License
38 * along with this program; if not, write to the Free Software
39 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
50 #include <sys/types.h>
53 #if TIME_WITH_SYS_TIME
54 # include <sys/time.h>
58 # include <sys/time.h>
68 #include <sys/socket.h>
69 #include <netinet/in.h>
70 #include <arpa/inet.h>
71 #include <libcitadel.h>
74 #include "citserver.h"
82 #include "internet_addressing.h"
85 #include "clientsocket.h"
86 #include "locate_host.h"
87 #include "citadel_dirs.h"
96 #include "ctdl_module.h"
100 typedef struct _citsmtp { /* Information about the current session */
104 char recipients[SIZ];
105 int number_of_recipients;
107 int message_originated_locally;
113 enum { /* Command states for login authentication */
120 #define SMTP ((citsmtp *)CC->session_specific_data)
123 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
125 citthread_mutex_t smtp_send_lock;
128 /*****************************************************************************/
129 /* SMTP SERVER (INBOUND) STUFF */
130 /*****************************************************************************/
134 * Here's where our SMTP session begins its happy day.
136 void smtp_greeting(int is_msa)
139 char message_to_spammer[1024];
141 strcpy(CC->cs_clientname, "SMTP session");
142 CC->internal_pgm = 1;
143 CC->cs_flags |= CS_STEALTH;
144 CC->session_specific_data = malloc(sizeof(citsmtp));
145 memset(SMTP, 0, sizeof(citsmtp));
147 sSMTP->is_msa = is_msa;
149 /* If this config option is set, reject connections from problem
150 * addresses immediately instead of after they execute a RCPT
152 if ( (config.c_rbl_at_greeting) && (sSMTP->is_msa == 0) ) {
153 if (rbl_check(message_to_spammer)) {
154 if (CtdlThreadCheckStop())
155 cprintf("421 %s\r\n", message_to_spammer);
157 cprintf("550 %s\r\n", message_to_spammer);
159 /* no need to free_recipients(valid), it's not allocated yet */
164 /* Otherwise we're either clean or we check later. */
166 if (CC->nologin==1) {
167 cprintf("500 Too many users are already online (maximum is %d)\r\n",
171 /* no need to free_recipients(valid), it's not allocated yet */
175 /* Note: the FQDN *must* appear as the first thing after the 220 code.
176 * Some clients (including citmail.c) depend on it being there.
178 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
183 * SMTPS is just like SMTP, except it goes crypto right away.
185 void smtps_greeting(void) {
186 CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
188 if (!CC->redirect_ssl) CC->kill_me = 1; /* kill session if no crypto */
195 * SMTP MSA port requires authentication.
197 void smtp_msa_greeting(void) {
203 * LMTP is like SMTP but with some extra bonus footage added.
205 void lmtp_greeting(void) {
212 * Generic SMTP MTA greeting
214 void smtp_mta_greeting(void) {
220 * We also have an unfiltered LMTP socket that bypasses spam filters.
222 void lmtp_unfiltered_greeting(void) {
223 citsmtp *sSMTP = SMTP;
226 sSMTP->is_unfiltered = 1;
231 * Login greeting common to all auth methods
233 void smtp_auth_greeting(void) {
234 cprintf("235 Hello, %s\r\n", CC->user.fullname);
235 CtdlLogPrintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
236 CC->internal_pgm = 0;
237 CC->cs_flags &= ~CS_STEALTH;
242 * Implement HELO and EHLO commands.
244 * which_command: 0=HELO, 1=EHLO, 2=LHLO
246 void smtp_hello(char *argbuf, int which_command) {
247 citsmtp *sSMTP = SMTP;
249 safestrncpy(sSMTP->helo_node, argbuf, sizeof sSMTP->helo_node);
251 if ( (which_command != 2) && (sSMTP->is_lmtp) ) {
252 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
256 if ( (which_command == 2) && (sSMTP->is_lmtp == 0) ) {
257 cprintf("500 LHLO is only allowed when running LMTP\r\n");
261 if (which_command == 0) {
262 cprintf("250 Hello %s (%s [%s])\r\n",
269 if (which_command == 1) {
270 cprintf("250-Hello %s (%s [%s])\r\n",
277 cprintf("250-Greetings and joyous salutations.\r\n");
279 cprintf("250-HELP\r\n");
280 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
284 * Offer TLS, but only if TLS is not already active.
285 * Furthermore, only offer TLS when running on
286 * the SMTP-MSA port, not on the SMTP-MTA port, due to
287 * questionable reliability of TLS in certain sending MTA's.
289 if ( (!CC->redirect_ssl) && (sSMTP->is_msa) ) {
290 cprintf("250-STARTTLS\r\n");
292 #endif /* HAVE_OPENSSL */
294 cprintf("250-AUTH LOGIN PLAIN\r\n"
295 "250-AUTH=LOGIN PLAIN\r\n"
304 * Implement HELP command.
306 void smtp_help(void) {
307 cprintf("214 RTFM http://www.ietf.org/rfc/rfc2821.txt\r\n");
314 void smtp_get_user(char *argbuf) {
317 citsmtp *sSMTP = SMTP;
319 CtdlDecodeBase64(username, argbuf, SIZ);
320 /* CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", username); */
321 if (CtdlLoginExistingUser(NULL, username) == login_ok) {
322 CtdlEncodeBase64(buf, "Password:", 9, 0);
323 cprintf("334 %s\r\n", buf);
324 sSMTP->command_state = smtp_password;
327 cprintf("500 No such user.\r\n");
328 sSMTP->command_state = smtp_command;
336 void smtp_get_pass(char *argbuf) {
339 memset(password, 0, sizeof(password));
340 CtdlDecodeBase64(password, argbuf, SIZ);
341 /* CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", password); */
342 if (CtdlTryPassword(password) == pass_ok) {
343 smtp_auth_greeting();
346 cprintf("535 Authentication failed.\r\n");
348 SMTP->command_state = smtp_command;
353 * Back end for PLAIN auth method (either inline or multistate)
355 void smtp_try_plain(char *encoded_authstring) {
356 char decoded_authstring[1024];
362 CtdlDecodeBase64(decoded_authstring, encoded_authstring, strlen(encoded_authstring) );
363 safestrncpy(ident, decoded_authstring, sizeof ident);
364 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
365 safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
367 SMTP->command_state = smtp_command;
369 if (!IsEmptyStr(ident)) {
370 result = CtdlLoginExistingUser(user, ident);
373 result = CtdlLoginExistingUser(NULL, user);
376 if (result == login_ok) {
377 if (CtdlTryPassword(pass) == pass_ok) {
378 smtp_auth_greeting();
382 cprintf("504 Authentication failed.\r\n");
387 * Attempt to perform authenticated SMTP
389 void smtp_auth(char *argbuf) {
390 char username_prompt[64];
392 char encoded_authstring[1024];
395 cprintf("504 Already logged in.\r\n");
399 extract_token(method, argbuf, 0, ' ', sizeof method);
401 if (!strncasecmp(method, "login", 5) ) {
402 if (strlen(argbuf) >= 7) {
403 smtp_get_user(&argbuf[6]);
406 CtdlEncodeBase64(username_prompt, "Username:", 9, 0);
407 cprintf("334 %s\r\n", username_prompt);
408 SMTP->command_state = smtp_user;
413 if (!strncasecmp(method, "plain", 5) ) {
414 if (num_tokens(argbuf, ' ') < 2) {
416 SMTP->command_state = smtp_plain;
420 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
422 smtp_try_plain(encoded_authstring);
426 if (strncasecmp(method, "login", 5) ) {
427 cprintf("504 Unknown authentication method.\r\n");
435 * Implements the RSET (reset state) command.
436 * Currently this just zeroes out the state buffer. If pointers to data
437 * allocated with malloc() are ever placed in the state buffer, we have to
438 * be sure to free() them first!
440 * Set do_response to nonzero to output the SMTP RSET response code.
442 void smtp_rset(int do_response) {
445 citsmtp *sSMTP = SMTP;
448 * Our entire SMTP state is discarded when a RSET command is issued,
449 * but we need to preserve this one little piece of information, so
450 * we save it for later.
452 is_lmtp = sSMTP->is_lmtp;
453 is_unfiltered = sSMTP->is_unfiltered;
455 memset(sSMTP, 0, sizeof(citsmtp));
458 * It is somewhat ambiguous whether we want to log out when a RSET
459 * command is issued. Here's the code to do it. It is commented out
460 * because some clients (such as Pine) issue RSET commands before
461 * each message, but still expect to be logged in.
463 * if (CC->logged_in) {
469 * Reinstate this little piece of information we saved (see above).
471 sSMTP->is_lmtp = is_lmtp;
472 sSMTP->is_unfiltered = is_unfiltered;
475 cprintf("250 Zap!\r\n");
480 * Clear out the portions of the state buffer that need to be cleared out
481 * after the DATA command finishes.
483 void smtp_data_clear(void) {
484 citsmtp *sSMTP = SMTP;
486 strcpy(sSMTP->from, "");
487 strcpy(sSMTP->recipients, "");
488 sSMTP->number_of_recipients = 0;
489 sSMTP->delivery_mode = 0;
490 sSMTP->message_originated_locally = 0;
493 const char *smtp_get_Recipients(void)
495 citsmtp *sSMTP = SMTP;
499 else return sSMTP->from;
503 * Implements the "MAIL FROM:" command
505 void smtp_mail(char *argbuf) {
509 citsmtp *sSMTP = SMTP;
511 if (!IsEmptyStr(sSMTP->from)) {
512 cprintf("503 Only one sender permitted\r\n");
516 if (strncasecmp(argbuf, "From:", 5)) {
517 cprintf("501 Syntax error\r\n");
521 strcpy(sSMTP->from, &argbuf[5]);
522 striplt(sSMTP->from);
523 if (haschar(sSMTP->from, '<') > 0) {
524 stripallbut(sSMTP->from, '<', '>');
527 /* We used to reject empty sender names, until it was brought to our
528 * attention that RFC1123 5.2.9 requires that this be allowed. So now
529 * we allow it, but replace the empty string with a fake
530 * address so we don't have to contend with the empty string causing
531 * other code to fail when it's expecting something there.
533 if (IsEmptyStr(sSMTP->from)) {
534 strcpy(sSMTP->from, "someone@example.com");
537 /* If this SMTP connection is from a logged-in user, force the 'from'
538 * to be the user's Internet e-mail address as Citadel knows it.
541 safestrncpy(sSMTP->from, CC->cs_inet_email, sizeof sSMTP->from);
542 cprintf("250 Sender ok <%s>\r\n", sSMTP->from);
543 sSMTP->message_originated_locally = 1;
547 else if (sSMTP->is_lmtp) {
548 /* Bypass forgery checking for LMTP */
551 /* Otherwise, make sure outsiders aren't trying to forge mail from
552 * this system (unless, of course, c_allow_spoofing is enabled)
554 else if (config.c_allow_spoofing == 0) {
555 process_rfc822_addr(sSMTP->from, user, node, name);
556 if (CtdlHostAlias(node) != hostalias_nomatch) {
557 cprintf("550 You must log in to send mail from %s\r\n", node);
558 strcpy(sSMTP->from, "");
563 cprintf("250 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;
575 citsmtp *sSMTP = SMTP;
577 if (IsEmptyStr(sSMTP->from)) {
578 cprintf("503 Need MAIL before RCPT\r\n");
582 if (strncasecmp(argbuf, "To:", 3)) {
583 cprintf("501 Syntax error\r\n");
587 if ( (sSMTP->is_msa) && (!CC->logged_in) ) {
588 cprintf("550 You must log in to send mail on this port.\r\n");
589 strcpy(sSMTP->from, "");
593 safestrncpy(recp, &argbuf[3], sizeof recp);
595 stripallbut(recp, '<', '>');
597 if ( (strlen(recp) + strlen(sSMTP->recipients) + 1 ) >= SIZ) {
598 cprintf("452 Too many recipients\r\n");
603 if ( (!CC->logged_in) /* Don't RBL authenticated users */
604 && (!sSMTP->is_lmtp) ) { /* Don't RBL LMTP clients */
605 if (config.c_rbl_at_greeting == 0) { /* Don't RBL again if we already did it */
606 if (rbl_check(message_to_spammer)) {
607 if (CtdlThreadCheckStop())
608 cprintf("421 %s\r\n", message_to_spammer);
610 cprintf("550 %s\r\n", message_to_spammer);
611 /* no need to free_recipients(valid), it's not allocated yet */
617 valid = validate_recipients(recp,
618 smtp_get_Recipients (),
619 (sSMTP->is_lmtp)? POST_LMTP:
620 (CC->logged_in)? POST_LOGGED_IN:
622 if (valid->num_error != 0) {
623 cprintf("550 %s\r\n", valid->errormsg);
624 free_recipients(valid);
628 if (valid->num_internet > 0) {
630 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
631 cprintf("551 <%s> - you do not have permission to send Internet mail\r\n", recp);
632 free_recipients(valid);
638 if (valid->num_internet > 0) {
639 if ( (sSMTP->message_originated_locally == 0)
640 && (sSMTP->is_lmtp == 0) ) {
641 cprintf("551 <%s> - relaying denied\r\n", recp);
642 free_recipients(valid);
647 cprintf("250 RCPT ok <%s>\r\n", recp);
648 if (!IsEmptyStr(sSMTP->recipients)) {
649 strcat(sSMTP->recipients, ",");
651 strcat(sSMTP->recipients, recp);
652 sSMTP->number_of_recipients += 1;
654 free_recipients(valid);
662 * Implements the DATA command
664 void smtp_data(void) {
666 struct CtdlMessage *msg = NULL;
669 struct recptypes *valid;
673 citsmtp *sSMTP = SMTP;
675 if (IsEmptyStr(sSMTP->from)) {
676 cprintf("503 Need MAIL command first.\r\n");
680 if (sSMTP->number_of_recipients < 1) {
681 cprintf("503 Need RCPT command first.\r\n");
685 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
687 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
691 if (sSMTP->is_lmtp && (CC->cs_UDSclientUID != -1)) {
693 "Received: from %s (Citadel from userid %ld)\n"
696 (long int) CC->cs_UDSclientUID,
702 "Received: from %s (%s [%s])\n"
711 body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1, 0);
713 cprintf("550 Unable to save message: internal error.\r\n");
717 CtdlLogPrintf(CTDL_DEBUG, "Converting message...\n");
718 msg = convert_internet_message(body);
720 /* If the user is locally authenticated, FORCE the From: header to
721 * show up as the real sender. Yes, this violates the RFC standard,
722 * but IT MAKES SENSE. If you prefer strict RFC adherence over
723 * common sense, you can disable this in the configuration.
725 * We also set the "message room name" ('O' field) to MAILROOM
726 * (which is Mail> on most systems) to prevent it from getting set
727 * to something ugly like "0000058008.Sent Items>" when the message
728 * is read with a Citadel client.
730 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
731 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
732 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
733 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
734 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
735 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
736 msg->cm_fields['A'] = strdup(CC->user.fullname);
737 msg->cm_fields['N'] = strdup(config.c_nodename);
738 msg->cm_fields['H'] = strdup(config.c_humannode);
739 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
740 msg->cm_fields['O'] = strdup(MAILROOM);
743 /* Set the "envelope from" address */
744 if (msg->cm_fields['P'] != NULL) {
745 free(msg->cm_fields['P']);
747 msg->cm_fields['P'] = strdup(sSMTP->from);
749 /* Set the "envelope to" address */
750 if (msg->cm_fields['V'] != NULL) {
751 free(msg->cm_fields['V']);
753 msg->cm_fields['V'] = strdup(sSMTP->recipients);
755 /* Submit the message into the Citadel system. */
756 valid = validate_recipients(sSMTP->recipients,
757 smtp_get_Recipients (),
758 (sSMTP->is_lmtp)? POST_LMTP:
759 (CC->logged_in)? POST_LOGGED_IN:
762 /* If there are modules that want to scan this message before final
763 * submission (such as virus checkers or spam filters), call them now
764 * and give them an opportunity to reject the message.
766 if (sSMTP->is_unfiltered) {
770 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
773 if (scan_errors > 0) { /* We don't want this message! */
775 if (msg->cm_fields['0'] == NULL) {
776 msg->cm_fields['0'] = strdup("Message rejected by filter");
779 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
782 else { /* Ok, we'll accept this message. */
783 msgnum = CtdlSubmitMsg(msg, valid, "", 0);
785 sprintf(result, "250 Message accepted.\r\n");
788 sprintf(result, "550 Internal delivery error\r\n");
792 /* For SMTP and ESTMP, just print the result message. For LMTP, we
793 * have to print one result message for each recipient. Since there
794 * is nothing in Citadel which would cause different recipients to
795 * have different results, we can get away with just spitting out the
796 * same message once for each recipient.
798 if (sSMTP->is_lmtp) {
799 for (i=0; i<sSMTP->number_of_recipients; ++i) {
800 cprintf("%s", result);
804 cprintf("%s", result);
807 /* Write something to the syslog (which may or may not be where the
808 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
811 syslog((LOG_MAIL | LOG_INFO),
812 "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
815 sSMTP->number_of_recipients,
823 CtdlFreeMessage(msg);
824 free_recipients(valid);
825 smtp_data_clear(); /* clear out the buffers now */
830 * implements the STARTTLS command (Citadel API version)
832 void smtp_starttls(void)
834 char ok_response[SIZ];
835 char nosup_response[SIZ];
836 char error_response[SIZ];
839 "220 Begin TLS negotiation now\r\n");
840 sprintf(nosup_response,
841 "554 TLS not supported here\r\n");
842 sprintf(error_response,
843 "554 Internal error\r\n");
844 CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
851 * Main command loop for SMTP sessions.
853 void smtp_command_loop(void) {
855 citsmtp *sSMTP = SMTP;
858 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
859 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
860 CtdlLogPrintf(CTDL_CRIT, "Client disconnected: ending session.\n");
864 CtdlLogPrintf(CTDL_INFO, "SMTP server: %s\n", cmdbuf);
865 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
867 if (sSMTP->command_state == smtp_user) {
868 smtp_get_user(cmdbuf);
871 else if (sSMTP->command_state == smtp_password) {
872 smtp_get_pass(cmdbuf);
875 else if (sSMTP->command_state == smtp_plain) {
876 smtp_try_plain(cmdbuf);
879 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
880 smtp_auth(&cmdbuf[5]);
883 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
887 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
888 smtp_hello(&cmdbuf[5], 0);
891 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
892 smtp_hello(&cmdbuf[5], 1);
895 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
896 smtp_hello(&cmdbuf[5], 2);
899 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
903 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
904 smtp_mail(&cmdbuf[5]);
907 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
908 cprintf("250 NOOP\r\n");
911 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
912 cprintf("221 Goodbye...\r\n");
917 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
918 smtp_rcpt(&cmdbuf[5]);
921 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
925 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
930 cprintf("502 I'm afraid I can't do that.\r\n");
939 /*****************************************************************************/
940 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
941 /*****************************************************************************/
948 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
951 void smtp_try(const char *key, const char *addr, int *status,
952 char *dsn, size_t n, long msgnum, char *envelope_from)
959 char user[1024], node[1024], name[1024];
973 /* Parse out the host portion of the recipient address */
974 process_rfc822_addr(addr, user, node, name);
976 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Attempting delivery to <%s> @ <%s> (%s)\n",
979 /* Load the message out of the database */
980 CC->redirect_buffer = malloc(SIZ);
981 CC->redirect_len = 0;
982 CC->redirect_alloc = SIZ;
983 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, ESC_DOT);
984 msgtext = CC->redirect_buffer;
985 msg_size = CC->redirect_len;
986 CC->redirect_buffer = NULL;
987 CC->redirect_len = 0;
988 CC->redirect_alloc = 0;
990 /* If no envelope_from is supplied, extract one from the message */
991 if ( (envelope_from == NULL) || (IsEmptyStr(envelope_from)) ) {
992 strcpy(mailfrom, "");
996 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
999 if (!strncasecmp(buf, "From:", 5)) {
1000 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
1002 for (i=0; mailfrom[i]; ++i) {
1003 if (!isprint(mailfrom[i])) {
1004 strcpy(&mailfrom[i], &mailfrom[i+1]);
1009 /* Strip out parenthesized names */
1012 for (i=0; mailfrom[i]; ++i) {
1013 if (mailfrom[i] == '(') lp = i;
1014 if (mailfrom[i] == ')') rp = i;
1016 if ((lp>0)&&(rp>lp)) {
1017 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
1020 /* Prefer brokketized names */
1023 for (i=0; mailfrom[i]; ++i) {
1024 if (mailfrom[i] == '<') lp = i;
1025 if (mailfrom[i] == '>') rp = i;
1027 if ( (lp>=0) && (rp>lp) ) {
1029 strcpy(mailfrom, &mailfrom[lp]);
1034 } while (scan_done == 0);
1035 if (IsEmptyStr(mailfrom)) strcpy(mailfrom, "someone@somewhere.org");
1036 stripallbut(mailfrom, '<', '>');
1037 envelope_from = mailfrom;
1040 /* Figure out what mail exchanger host we have to connect to */
1041 num_mxhosts = getmx(mxhosts, node);
1042 CtdlLogPrintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d [%s]\n", node, num_mxhosts, mxhosts);
1043 if (num_mxhosts < 1) {
1045 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1050 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1052 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1053 strcpy(mx_user, "");
1054 strcpy(mx_pass, "");
1055 if (num_tokens(buf, '@') > 1) {
1056 strcpy (mx_user, buf);
1057 endpart = strrchr(mx_user, '@');
1059 strcpy (mx_host, endpart + 1);
1060 endpart = strrchr(mx_user, ':');
1061 if (endpart != NULL) {
1062 strcpy(mx_pass, endpart+1);
1067 strcpy (mx_host, buf);
1068 endpart = strrchr(mx_host, ':');
1071 strcpy(mx_port, endpart + 1);
1074 strcpy(mx_port, "25");
1076 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connecting to %s : %s ...\n", mx_host, mx_port);
1077 sock = sock_connect(mx_host, mx_port, "tcp");
1078 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1079 if (sock >= 0) CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connected!\n");
1082 snprintf(dsn, SIZ, "%s", strerror(errno));
1085 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
1091 *status = 4; /* dsn is already filled in */
1095 /* Process the SMTP greeting from the server */
1096 if (ml_sock_gets(sock, buf) < 0) {
1098 strcpy(dsn, "Connection broken during SMTP conversation");
1101 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1102 if (buf[0] != '2') {
1103 if (buf[0] == '4') {
1105 safestrncpy(dsn, &buf[4], 1023);
1110 safestrncpy(dsn, &buf[4], 1023);
1115 /* At this point we know we are talking to a real SMTP server */
1117 /* Do a EHLO command. If it fails, try the HELO command. */
1118 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1119 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1120 sock_write(sock, buf, strlen(buf));
1121 if (ml_sock_gets(sock, buf) < 0) {
1123 strcpy(dsn, "Connection broken during SMTP HELO");
1126 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1127 if (buf[0] != '2') {
1128 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1129 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1130 sock_write(sock, buf, strlen(buf));
1131 if (ml_sock_gets(sock, buf) < 0) {
1133 strcpy(dsn, "Connection broken during SMTP HELO");
1137 if (buf[0] != '2') {
1138 if (buf[0] == '4') {
1140 safestrncpy(dsn, &buf[4], 1023);
1145 safestrncpy(dsn, &buf[4], 1023);
1150 /* Do an AUTH command if necessary */
1151 if (!IsEmptyStr(mx_user)) {
1153 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
1154 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2, 0);
1155 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
1156 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1157 sock_write(sock, buf, strlen(buf));
1158 if (ml_sock_gets(sock, buf) < 0) {
1160 strcpy(dsn, "Connection broken during SMTP AUTH");
1163 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1164 if (buf[0] != '2') {
1165 if (buf[0] == '4') {
1167 safestrncpy(dsn, &buf[4], 1023);
1172 safestrncpy(dsn, &buf[4], 1023);
1178 /* previous command succeeded, now try the MAIL FROM: command */
1179 snprintf(buf, sizeof buf, "MAIL FROM:<%s>\r\n", envelope_from);
1180 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1181 sock_write(sock, buf, strlen(buf));
1182 if (ml_sock_gets(sock, buf) < 0) {
1184 strcpy(dsn, "Connection broken during SMTP MAIL");
1187 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1188 if (buf[0] != '2') {
1189 if (buf[0] == '4') {
1191 safestrncpy(dsn, &buf[4], 1023);
1196 safestrncpy(dsn, &buf[4], 1023);
1201 /* MAIL succeeded, now try the RCPT To: command */
1202 snprintf(buf, sizeof buf, "RCPT TO:<%s@%s>\r\n", user, node);
1203 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1204 sock_write(sock, buf, strlen(buf));
1205 if (ml_sock_gets(sock, buf) < 0) {
1207 strcpy(dsn, "Connection broken during SMTP RCPT");
1210 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1211 if (buf[0] != '2') {
1212 if (buf[0] == '4') {
1214 safestrncpy(dsn, &buf[4], 1023);
1219 safestrncpy(dsn, &buf[4], 1023);
1224 /* RCPT succeeded, now try the DATA command */
1225 CtdlLogPrintf(CTDL_DEBUG, ">DATA\n");
1226 sock_write(sock, "DATA\r\n", 6);
1227 if (ml_sock_gets(sock, buf) < 0) {
1229 strcpy(dsn, "Connection broken during SMTP DATA");
1232 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1233 if (buf[0] != '3') {
1234 if (buf[0] == '4') {
1236 safestrncpy(dsn, &buf[4], 1023);
1241 safestrncpy(dsn, &buf[4], 1023);
1246 /* If we reach this point, the server is expecting data.*/
1247 sock_write(sock, msgtext, msg_size);
1248 if (msgtext[msg_size-1] != 10) {
1249 CtdlLogPrintf(CTDL_WARNING, "Possible problem: message did not "
1250 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1254 sock_write(sock, ".\r\n", 3);
1255 if (ml_sock_gets(sock, buf) < 0) {
1257 strcpy(dsn, "Connection broken during SMTP message transmit");
1260 CtdlLogPrintf(CTDL_DEBUG, "%s\n", buf);
1261 if (buf[0] != '2') {
1262 if (buf[0] == '4') {
1264 safestrncpy(dsn, &buf[4], 1023);
1269 safestrncpy(dsn, &buf[4], 1023);
1275 safestrncpy(dsn, &buf[4], 1023);
1278 CtdlLogPrintf(CTDL_DEBUG, ">QUIT\n");
1279 sock_write(sock, "QUIT\r\n", 6);
1280 ml_sock_gets(sock, buf);
1281 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1282 CtdlLogPrintf(CTDL_INFO, "SMTP client: delivery to <%s> @ <%s> (%s) succeeded\n",
1285 bail: free(msgtext);
1288 /* Write something to the syslog (which may or may not be where the
1289 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1291 if (enable_syslog) {
1292 syslog((LOG_MAIL | LOG_INFO),
1293 "%ld: to=<%s>, relay=%s, stat=%s",
1307 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1308 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1309 * a "bounce" message (delivery status notification).
1311 void smtp_do_bounce(char *instr) {
1319 char bounceto[1024];
1321 int num_bounces = 0;
1322 int bounce_this = 0;
1323 long bounce_msgid = (-1);
1324 time_t submitted = 0L;
1325 struct CtdlMessage *bmsg = NULL;
1327 struct recptypes *valid;
1328 int successful_bounce = 0;
1334 CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1335 strcpy(bounceto, "");
1336 sprintf(boundary, "=_Citadel_Multipart_%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
1337 lines = num_tokens(instr, '\n');
1339 /* See if it's time to give up on delivery of this message */
1340 for (i=0; i<lines; ++i) {
1341 extract_token(buf, instr, i, '\n', sizeof buf);
1342 extract_token(key, buf, 0, '|', sizeof key);
1343 extract_token(addr, buf, 1, '|', sizeof addr);
1344 if (!strcasecmp(key, "submitted")) {
1345 submitted = atol(addr);
1349 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1353 /* Start building our bounce message */
1355 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1356 if (bmsg == NULL) return;
1357 memset(bmsg, 0, sizeof(struct CtdlMessage));
1359 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1360 bmsg->cm_anon_type = MES_NORMAL;
1361 bmsg->cm_format_type = FMT_RFC822;
1362 bmsg->cm_fields['A'] = strdup("Citadel");
1363 bmsg->cm_fields['O'] = strdup(MAILROOM);
1364 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1365 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1366 bmsg->cm_fields['M'] = malloc(1024);
1368 strcpy(bmsg->cm_fields['M'], "Content-type: multipart/mixed; boundary=\"");
1369 strcat(bmsg->cm_fields['M'], boundary);
1370 strcat(bmsg->cm_fields['M'], "\"\r\n");
1371 strcat(bmsg->cm_fields['M'], "MIME-Version: 1.0\r\n");
1372 strcat(bmsg->cm_fields['M'], "X-Mailer: " CITADEL "\r\n");
1373 strcat(bmsg->cm_fields['M'], "\r\nThis is a multipart message in MIME format.\r\n\r\n");
1374 strcat(bmsg->cm_fields['M'], "--");
1375 strcat(bmsg->cm_fields['M'], boundary);
1376 strcat(bmsg->cm_fields['M'], "\r\n");
1377 strcat(bmsg->cm_fields['M'], "Content-type: text/plain\r\n\r\n");
1379 if (give_up) strcat(bmsg->cm_fields['M'],
1380 "A message you sent could not be delivered to some or all of its recipients\n"
1381 "due to prolonged unavailability of its destination(s).\n"
1382 "Giving up on the following addresses:\n\n"
1385 else strcat(bmsg->cm_fields['M'],
1386 "A message you sent could not be delivered to some or all of its recipients.\n"
1387 "The following addresses were undeliverable:\n\n"
1391 * Now go through the instructions checking for stuff.
1393 for (i=0; i<lines; ++i) {
1394 extract_token(buf, instr, i, '\n', sizeof buf);
1395 extract_token(key, buf, 0, '|', sizeof key);
1396 extract_token(addr, buf, 1, '|', sizeof addr);
1397 status = extract_int(buf, 2);
1398 extract_token(dsn, buf, 3, '|', sizeof dsn);
1401 CtdlLogPrintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1402 key, addr, status, dsn);
1404 if (!strcasecmp(key, "bounceto")) {
1405 strcpy(bounceto, addr);
1408 if (!strcasecmp(key, "msgid")) {
1409 omsgid = atol(addr);
1412 if (!strcasecmp(key, "remote")) {
1413 if (status == 5) bounce_this = 1;
1414 if (give_up) bounce_this = 1;
1420 if (bmsg->cm_fields['M'] == NULL) {
1421 CtdlLogPrintf(CTDL_ERR, "ERROR ... M field is null "
1422 "(%s:%d)\n", __FILE__, __LINE__);
1425 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1426 strlen(bmsg->cm_fields['M']) + 1024 );
1427 strcat(bmsg->cm_fields['M'], addr);
1428 strcat(bmsg->cm_fields['M'], ": ");
1429 strcat(bmsg->cm_fields['M'], dsn);
1430 strcat(bmsg->cm_fields['M'], "\r\n");
1432 remove_token(instr, i, '\n');
1438 /* Attach the original message */
1440 strcat(bmsg->cm_fields['M'], "--");
1441 strcat(bmsg->cm_fields['M'], boundary);
1442 strcat(bmsg->cm_fields['M'], "\r\n");
1443 strcat(bmsg->cm_fields['M'], "Content-type: message/rfc822\r\n");
1444 strcat(bmsg->cm_fields['M'], "Content-Transfer-Encoding: 7bit\r\n");
1445 strcat(bmsg->cm_fields['M'], "Content-Disposition: inline\r\n");
1446 strcat(bmsg->cm_fields['M'], "\r\n");
1448 CC->redirect_buffer = malloc(SIZ);
1449 CC->redirect_len = 0;
1450 CC->redirect_alloc = SIZ;
1451 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0);
1452 omsgtext = CC->redirect_buffer;
1453 omsgsize = CC->redirect_len;
1454 CC->redirect_buffer = NULL;
1455 CC->redirect_len = 0;
1456 CC->redirect_alloc = 0;
1457 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1458 (strlen(bmsg->cm_fields['M']) + omsgsize + 1024) );
1459 strcat(bmsg->cm_fields['M'], omsgtext);
1463 /* Close the multipart MIME scope */
1464 strcat(bmsg->cm_fields['M'], "--");
1465 strcat(bmsg->cm_fields['M'], boundary);
1466 strcat(bmsg->cm_fields['M'], "--\r\n");
1468 /* Deliver the bounce if there's anything worth mentioning */
1469 CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1470 if (num_bounces > 0) {
1472 /* First try the user who sent the message */
1473 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1474 if (IsEmptyStr(bounceto)) {
1475 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
1476 bounce_msgid = (-1L);
1479 /* Can we deliver the bounce to the original sender? */
1480 valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
1481 if (valid != NULL) {
1482 if (valid->num_error == 0) {
1483 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
1484 successful_bounce = 1;
1488 /* If not, post it in the Aide> room */
1489 if (successful_bounce == 0) {
1490 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
1493 /* Free up the memory we used */
1494 if (valid != NULL) {
1495 free_recipients(valid);
1499 CtdlFreeMessage(bmsg);
1500 CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
1505 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1506 * set of delivery instructions for completed deliveries and remove them.
1508 * It returns the number of incomplete deliveries remaining.
1510 int smtp_purge_completed_deliveries(char *instr) {
1521 lines = num_tokens(instr, '\n');
1522 for (i=0; i<lines; ++i) {
1523 extract_token(buf, instr, i, '\n', sizeof buf);
1524 extract_token(key, buf, 0, '|', sizeof key);
1525 extract_token(addr, buf, 1, '|', sizeof addr);
1526 status = extract_int(buf, 2);
1527 extract_token(dsn, buf, 3, '|', sizeof dsn);
1531 if (!strcasecmp(key, "remote")) {
1532 if (status == 2) completed = 1;
1537 remove_token(instr, i, '\n');
1550 * Called by smtp_do_queue() to handle an individual message.
1552 void smtp_do_procmsg(long msgnum, void *userdata) {
1553 struct CtdlMessage *msg = NULL;
1555 char *results = NULL;
1563 char envelope_from[1024];
1564 long text_msgid = (-1);
1565 int incomplete_deliveries_remaining;
1566 time_t attempted = 0L;
1567 time_t last_attempted = 0L;
1568 time_t retry = SMTP_RETRY_INTERVAL;
1570 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: smtp_do_procmsg(%ld)\n", msgnum);
1571 strcpy(envelope_from, "");
1573 msg = CtdlFetchMessage(msgnum, 1);
1575 CtdlLogPrintf(CTDL_ERR, "SMTP client: tried %ld but no such message!\n", msgnum);
1579 instr = strdup(msg->cm_fields['M']);
1580 CtdlFreeMessage(msg);
1582 /* Strip out the headers amd any other non-instruction line */
1583 lines = num_tokens(instr, '\n');
1584 for (i=0; i<lines; ++i) {
1585 extract_token(buf, instr, i, '\n', sizeof buf);
1586 if (num_tokens(buf, '|') < 2) {
1587 remove_token(instr, i, '\n');
1593 /* Learn the message ID and find out about recent delivery attempts */
1594 lines = num_tokens(instr, '\n');
1595 for (i=0; i<lines; ++i) {
1596 extract_token(buf, instr, i, '\n', sizeof buf);
1597 extract_token(key, buf, 0, '|', sizeof key);
1598 if (!strcasecmp(key, "msgid")) {
1599 text_msgid = extract_long(buf, 1);
1601 if (!strcasecmp(key, "envelope_from")) {
1602 extract_token(envelope_from, buf, 1, '|', sizeof envelope_from);
1604 if (!strcasecmp(key, "retry")) {
1605 /* double the retry interval after each attempt */
1606 retry = extract_long(buf, 1) * 2L;
1607 if (retry > SMTP_RETRY_MAX) {
1608 retry = SMTP_RETRY_MAX;
1610 remove_token(instr, i, '\n');
1612 if (!strcasecmp(key, "attempted")) {
1613 attempted = extract_long(buf, 1);
1614 if (attempted > last_attempted)
1615 last_attempted = attempted;
1620 * Postpone delivery if we've already tried recently.
1622 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1623 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1630 * Bail out if there's no actual message associated with this
1632 if (text_msgid < 0L) {
1633 CtdlLogPrintf(CTDL_ERR, "SMTP client: no 'msgid' directive found!\n");
1638 /* Plow through the instructions looking for 'remote' directives and
1639 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1640 * were experienced and it's time to try again)
1642 lines = num_tokens(instr, '\n');
1643 for (i=0; i<lines; ++i) {
1644 extract_token(buf, instr, i, '\n', sizeof buf);
1645 extract_token(key, buf, 0, '|', sizeof key);
1646 extract_token(addr, buf, 1, '|', sizeof addr);
1647 status = extract_int(buf, 2);
1648 extract_token(dsn, buf, 3, '|', sizeof dsn);
1649 if ( (!strcasecmp(key, "remote"))
1650 && ((status==0)||(status==3)||(status==4)) ) {
1652 /* Remove this "remote" instruction from the set,
1653 * but replace the set's final newline if
1654 * remove_token() stripped it. It has to be there.
1656 remove_token(instr, i, '\n');
1657 if (instr[strlen(instr)-1] != '\n') {
1658 strcat(instr, "\n");
1663 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Trying <%s>\n", addr);
1664 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid, envelope_from);
1666 if (results == NULL) {
1667 results = malloc(1024);
1668 memset(results, 0, 1024);
1671 results = realloc(results, strlen(results) + 1024);
1673 snprintf(&results[strlen(results)], 1024,
1675 key, addr, status, dsn);
1680 if (results != NULL) {
1681 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1682 strcat(instr, results);
1687 /* Generate 'bounce' messages */
1688 smtp_do_bounce(instr);
1690 /* Go through the delivery list, deleting completed deliveries */
1691 incomplete_deliveries_remaining =
1692 smtp_purge_completed_deliveries(instr);
1696 * No delivery instructions remain, so delete both the instructions
1697 * message and the message message.
1699 if (incomplete_deliveries_remaining <= 0) {
1701 delmsgs[0] = msgnum;
1702 delmsgs[1] = text_msgid;
1703 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
1707 * Uncompleted delivery instructions remain, so delete the old
1708 * instructions and replace with the updated ones.
1710 if (incomplete_deliveries_remaining > 0) {
1711 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
1712 msg = malloc(sizeof(struct CtdlMessage));
1713 memset(msg, 0, sizeof(struct CtdlMessage));
1714 msg->cm_magic = CTDLMESSAGE_MAGIC;
1715 msg->cm_anon_type = MES_NORMAL;
1716 msg->cm_format_type = FMT_RFC822;
1717 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1718 snprintf(msg->cm_fields['M'],
1720 "Content-type: %s\n\n%s\n"
1723 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1724 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
1725 CtdlFreeMessage(msg);
1737 * Run through the queue sending out messages.
1739 void *smtp_do_queue(void *arg) {
1740 int num_processed = 0;
1741 struct CitContext smtp_queue_CC;
1743 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1745 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1746 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC );
1748 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1749 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1752 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1755 citthread_mutex_unlock (&smtp_send_lock);
1756 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1765 * Create a thread to run the SMTP queue
1767 * This was created as a response to a situation seen on Uncensored where a bad remote was holding
1768 * up SMTP sending for long times.
1769 * Converting to a thread does not fix the problem caused by the bad remote but it does prevent
1770 * the SMTP sending from stopping housekeeping and the EVT_TIMER event system which in turn prevented
1771 * other things from happening.
1773 void smtp_queue_thread (void)
1775 if (citthread_mutex_trylock (&smtp_send_lock)) {
1776 CtdlLogPrintf(CTDL_DEBUG, "SMTP queue run already in progress\n");
1779 CtdlThreadCreate("SMTP Send", CTDLTHREAD_BIGSTACK, smtp_do_queue, NULL);
1785 void smtp_server_going_down (void)
1787 CtdlLogPrintf(CTDL_DEBUG, "SMTP module clean up for shutdown.\n");
1789 citthread_mutex_destroy (&smtp_send_lock);
1794 /*****************************************************************************/
1795 /* SMTP UTILITY COMMANDS */
1796 /*****************************************************************************/
1798 void cmd_smtp(char *argbuf) {
1805 if (CtdlAccessCheck(ac_aide)) return;
1807 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1809 if (!strcasecmp(cmd, "mx")) {
1810 extract_token(node, argbuf, 1, '|', sizeof node);
1811 num_mxhosts = getmx(buf, node);
1812 cprintf("%d %d MX hosts listed for %s\n",
1813 LISTING_FOLLOWS, num_mxhosts, node);
1814 for (i=0; i<num_mxhosts; ++i) {
1815 extract_token(node, buf, i, '|', sizeof node);
1816 cprintf("%s\n", node);
1822 else if (!strcasecmp(cmd, "runqueue")) {
1824 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1829 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1836 * Initialize the SMTP outbound queue
1838 void smtp_init_spoolout(void) {
1839 struct ctdlroom qrbuf;
1842 * Create the room. This will silently fail if the room already
1843 * exists, and that's perfectly ok, because we want it to exist.
1845 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1848 * Make sure it's set to be a "system room" so it doesn't show up
1849 * in the <K>nown rooms list for Aides.
1851 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1852 qrbuf.QRflags2 |= QR2_SYSTEM;
1853 CtdlPutRoomLock(&qrbuf);
1860 /*****************************************************************************/
1861 /* MODULE INITIALIZATION STUFF */
1862 /*****************************************************************************/
1864 * This cleanup function blows away the temporary memory used by
1867 void smtp_cleanup_function(void) {
1869 /* Don't do this stuff if this is not an SMTP session! */
1870 if (CC->h_command_function != smtp_command_loop) return;
1872 CtdlLogPrintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1878 const char *CitadelServiceSMTP_MTA="SMTP-MTA";
1879 const char *CitadelServiceSMTPS_MTA="SMTPs-MTA";
1880 const char *CitadelServiceSMTP_MSA="SMTP-MSA";
1881 const char *CitadelServiceSMTP_LMTP="LMTP";
1882 const char *CitadelServiceSMTP_LMTP_UNF="LMTP-UnF";
1884 CTDL_MODULE_INIT(smtp)
1888 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1893 CitadelServiceSMTP_MTA);
1896 CtdlRegisterServiceHook(config.c_smtps_port,
1901 CitadelServiceSMTPS_MTA);
1904 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1909 CitadelServiceSMTP_MSA);
1911 CtdlRegisterServiceHook(0, /* local LMTP */
1916 CitadelServiceSMTP_LMTP);
1918 CtdlRegisterServiceHook(0, /* local LMTP */
1919 file_lmtp_unfiltered_socket,
1920 lmtp_unfiltered_greeting,
1923 CitadelServiceSMTP_LMTP_UNF);
1925 smtp_init_spoolout();
1926 CtdlRegisterSessionHook(smtp_queue_thread, EVT_TIMER);
1927 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1928 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1929 CtdlRegisterCleanupHook (smtp_server_going_down);
1930 citthread_mutex_init (&smtp_send_lock, NULL);
1933 /* return our Subversion id for the Log */