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"
81 #include "internet_addressing.h"
84 #include "clientsocket.h"
85 #include "locate_host.h"
86 #include "citadel_dirs.h"
95 #include "ctdl_module.h"
99 typedef struct _citsmtp { /* Information about the current session */
103 char recipients[SIZ];
104 int number_of_recipients;
106 int message_originated_locally;
112 enum { /* Command states for login authentication */
119 #define SMTP ((citsmtp *)CC->session_specific_data)
122 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
124 citthread_mutex_t smtp_send_lock;
127 /*****************************************************************************/
128 /* SMTP SERVER (INBOUND) STUFF */
129 /*****************************************************************************/
133 * Here's where our SMTP session begins its happy day.
135 void smtp_greeting(int is_msa)
138 char message_to_spammer[1024];
140 strcpy(CC->cs_clientname, "SMTP session");
141 CC->internal_pgm = 1;
142 CC->cs_flags |= CS_STEALTH;
143 CC->session_specific_data = malloc(sizeof(citsmtp));
144 memset(SMTP, 0, sizeof(citsmtp));
146 sSMTP->is_msa = is_msa;
148 /* If this config option is set, reject connections from problem
149 * addresses immediately instead of after they execute a RCPT
151 if ( (config.c_rbl_at_greeting) && (sSMTP->is_msa == 0) ) {
152 if (rbl_check(message_to_spammer)) {
153 if (CtdlThreadCheckStop())
154 cprintf("421 %s\r\n", message_to_spammer);
156 cprintf("550 %s\r\n", message_to_spammer);
158 /* no need to free_recipients(valid), it's not allocated yet */
163 /* Otherwise we're either clean or we check later. */
165 if (CC->nologin==1) {
166 cprintf("500 Too many users are already online (maximum is %d)\r\n",
170 /* no need to free_recipients(valid), it's not allocated yet */
174 /* Note: the FQDN *must* appear as the first thing after the 220 code.
175 * Some clients (including citmail.c) depend on it being there.
177 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
182 * SMTPS is just like SMTP, except it goes crypto right away.
184 void smtps_greeting(void) {
185 CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
187 if (!CC->redirect_ssl) CC->kill_me = 1; /* kill session if no crypto */
194 * SMTP MSA port requires authentication.
196 void smtp_msa_greeting(void) {
202 * LMTP is like SMTP but with some extra bonus footage added.
204 void lmtp_greeting(void) {
214 * Generic SMTP MTA greeting
216 void smtp_mta_greeting(void) {
222 * We also have an unfiltered LMTP socket that bypasses spam filters.
224 void lmtp_unfiltered_greeting(void) {
230 sSMTP->is_unfiltered = 1;
235 * Login greeting common to all auth methods
237 void smtp_auth_greeting(void) {
238 cprintf("235 Hello, %s\r\n", CC->user.fullname);
239 CtdlLogPrintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
240 CC->internal_pgm = 0;
241 CC->cs_flags &= ~CS_STEALTH;
246 * Implement HELO and EHLO commands.
248 * which_command: 0=HELO, 1=EHLO, 2=LHLO
250 void smtp_hello(char *argbuf, int which_command) {
251 citsmtp *sSMTP = SMTP;
253 safestrncpy(sSMTP->helo_node, argbuf, sizeof sSMTP->helo_node);
255 if ( (which_command != 2) && (sSMTP->is_lmtp) ) {
256 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
260 if ( (which_command == 2) && (sSMTP->is_lmtp == 0) ) {
261 cprintf("500 LHLO is only allowed when running LMTP\r\n");
265 if (which_command == 0) {
266 cprintf("250 Hello %s (%s [%s])\r\n",
273 if (which_command == 1) {
274 cprintf("250-Hello %s (%s [%s])\r\n",
281 cprintf("250-Greetings and joyous salutations.\r\n");
283 cprintf("250-HELP\r\n");
284 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
288 * Offer TLS, but only if TLS is not already active.
289 * Furthermore, only offer TLS when running on
290 * the SMTP-MSA port, not on the SMTP-MTA port, due to
291 * questionable reliability of TLS in certain sending MTA's.
293 if ( (!CC->redirect_ssl) && (sSMTP->is_msa) ) {
294 cprintf("250-STARTTLS\r\n");
296 #endif /* HAVE_OPENSSL */
298 cprintf("250-AUTH LOGIN PLAIN\r\n"
299 "250-AUTH=LOGIN PLAIN\r\n"
308 * Implement HELP command.
310 void smtp_help(void) {
311 cprintf("214 RTFM http://www.ietf.org/rfc/rfc2821.txt\r\n");
318 void smtp_get_user(char *argbuf) {
321 citsmtp *sSMTP = SMTP;
323 CtdlDecodeBase64(username, argbuf, SIZ);
324 /* CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", username); */
325 if (CtdlLoginExistingUser(NULL, username) == login_ok) {
326 CtdlEncodeBase64(buf, "Password:", 9, 0);
327 cprintf("334 %s\r\n", buf);
328 sSMTP->command_state = smtp_password;
331 cprintf("500 No such user.\r\n");
332 sSMTP->command_state = smtp_command;
340 void smtp_get_pass(char *argbuf) {
343 memset(password, 0, sizeof(password));
344 CtdlDecodeBase64(password, argbuf, SIZ);
345 /* CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", password); */
346 if (CtdlTryPassword(password) == pass_ok) {
347 smtp_auth_greeting();
350 cprintf("535 Authentication failed.\r\n");
352 SMTP->command_state = smtp_command;
357 * Back end for PLAIN auth method (either inline or multistate)
359 void smtp_try_plain(char *encoded_authstring) {
360 char decoded_authstring[1024];
366 CtdlDecodeBase64(decoded_authstring, encoded_authstring, strlen(encoded_authstring) );
367 safestrncpy(ident, decoded_authstring, sizeof ident);
368 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
369 safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
371 SMTP->command_state = smtp_command;
373 if (!IsEmptyStr(ident)) {
374 result = CtdlLoginExistingUser(user, ident);
377 result = CtdlLoginExistingUser(NULL, user);
380 if (result == login_ok) {
381 if (CtdlTryPassword(pass) == pass_ok) {
382 smtp_auth_greeting();
386 cprintf("504 Authentication failed.\r\n");
391 * Attempt to perform authenticated SMTP
393 void smtp_auth(char *argbuf) {
394 char username_prompt[64];
396 char encoded_authstring[1024];
399 cprintf("504 Already logged in.\r\n");
403 extract_token(method, argbuf, 0, ' ', sizeof method);
405 if (!strncasecmp(method, "login", 5) ) {
406 if (strlen(argbuf) >= 7) {
407 smtp_get_user(&argbuf[6]);
410 CtdlEncodeBase64(username_prompt, "Username:", 9, 0);
411 cprintf("334 %s\r\n", username_prompt);
412 SMTP->command_state = smtp_user;
417 if (!strncasecmp(method, "plain", 5) ) {
418 if (num_tokens(argbuf, ' ') < 2) {
420 SMTP->command_state = smtp_plain;
424 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
426 smtp_try_plain(encoded_authstring);
430 if (strncasecmp(method, "login", 5) ) {
431 cprintf("504 Unknown authentication method.\r\n");
439 * Implements the RSET (reset state) command.
440 * Currently this just zeroes out the state buffer. If pointers to data
441 * allocated with malloc() are ever placed in the state buffer, we have to
442 * be sure to free() them first!
444 * Set do_response to nonzero to output the SMTP RSET response code.
446 void smtp_rset(int do_response) {
449 citsmtp *sSMTP = SMTP;
452 * Our entire SMTP state is discarded when a RSET command is issued,
453 * but we need to preserve this one little piece of information, so
454 * we save it for later.
456 is_lmtp = sSMTP->is_lmtp;
457 is_unfiltered = sSMTP->is_unfiltered;
459 memset(sSMTP, 0, sizeof(citsmtp));
462 * It is somewhat ambiguous whether we want to log out when a RSET
463 * command is issued. Here's the code to do it. It is commented out
464 * because some clients (such as Pine) issue RSET commands before
465 * each message, but still expect to be logged in.
467 * if (CC->logged_in) {
473 * Reinstate this little piece of information we saved (see above).
475 sSMTP->is_lmtp = is_lmtp;
476 sSMTP->is_unfiltered = is_unfiltered;
479 cprintf("250 Zap!\r\n");
484 * Clear out the portions of the state buffer that need to be cleared out
485 * after the DATA command finishes.
487 void smtp_data_clear(void) {
488 citsmtp *sSMTP = SMTP;
490 strcpy(sSMTP->from, "");
491 strcpy(sSMTP->recipients, "");
492 sSMTP->number_of_recipients = 0;
493 sSMTP->delivery_mode = 0;
494 sSMTP->message_originated_locally = 0;
497 const char *smtp_get_Recipients(void)
499 citsmtp *sSMTP = SMTP;
503 else return sSMTP->from;
507 * Implements the "MAIL FROM:" command
509 void smtp_mail(char *argbuf) {
513 citsmtp *sSMTP = SMTP;
515 if (!IsEmptyStr(sSMTP->from)) {
516 cprintf("503 Only one sender permitted\r\n");
520 if (strncasecmp(argbuf, "From:", 5)) {
521 cprintf("501 Syntax error\r\n");
525 strcpy(sSMTP->from, &argbuf[5]);
526 striplt(sSMTP->from);
527 if (haschar(sSMTP->from, '<') > 0) {
528 stripallbut(sSMTP->from, '<', '>');
531 /* We used to reject empty sender names, until it was brought to our
532 * attention that RFC1123 5.2.9 requires that this be allowed. So now
533 * we allow it, but replace the empty string with a fake
534 * address so we don't have to contend with the empty string causing
535 * other code to fail when it's expecting something there.
537 if (IsEmptyStr(sSMTP->from)) {
538 strcpy(sSMTP->from, "someone@example.com");
541 /* If this SMTP connection is from a logged-in user, force the 'from'
542 * to be the user's Internet e-mail address as Citadel knows it.
545 safestrncpy(sSMTP->from, CC->cs_inet_email, sizeof sSMTP->from);
546 cprintf("250 Sender ok <%s>\r\n", sSMTP->from);
547 sSMTP->message_originated_locally = 1;
551 else if (sSMTP->is_lmtp) {
552 /* Bypass forgery checking for LMTP */
555 /* Otherwise, make sure outsiders aren't trying to forge mail from
556 * this system (unless, of course, c_allow_spoofing is enabled)
558 else if (config.c_allow_spoofing == 0) {
559 process_rfc822_addr(sSMTP->from, user, node, name);
560 if (CtdlHostAlias(node) != hostalias_nomatch) {
561 cprintf("550 You must log in to send mail from %s\r\n", node);
562 strcpy(sSMTP->from, "");
567 cprintf("250 Sender ok\r\n");
573 * Implements the "RCPT To:" command
575 void smtp_rcpt(char *argbuf) {
577 char message_to_spammer[SIZ];
578 struct recptypes *valid = NULL;
579 citsmtp *sSMTP = SMTP;
581 if (IsEmptyStr(sSMTP->from)) {
582 cprintf("503 Need MAIL before RCPT\r\n");
586 if (strncasecmp(argbuf, "To:", 3)) {
587 cprintf("501 Syntax error\r\n");
591 if ( (sSMTP->is_msa) && (!CC->logged_in) ) {
592 cprintf("550 You must log in to send mail on this port.\r\n");
593 strcpy(sSMTP->from, "");
597 safestrncpy(recp, &argbuf[3], sizeof recp);
599 stripallbut(recp, '<', '>');
601 if ( (strlen(recp) + strlen(sSMTP->recipients) + 1 ) >= SIZ) {
602 cprintf("452 Too many recipients\r\n");
607 if ( (!CC->logged_in) /* Don't RBL authenticated users */
608 && (!sSMTP->is_lmtp) ) { /* Don't RBL LMTP clients */
609 if (config.c_rbl_at_greeting == 0) { /* Don't RBL again if we already did it */
610 if (rbl_check(message_to_spammer)) {
611 if (CtdlThreadCheckStop())
612 cprintf("421 %s\r\n", message_to_spammer);
614 cprintf("550 %s\r\n", message_to_spammer);
615 /* no need to free_recipients(valid), it's not allocated yet */
621 valid = validate_recipients(recp,
622 smtp_get_Recipients (),
623 (sSMTP->is_lmtp)? POST_LMTP:
624 (CC->logged_in)? POST_LOGGED_IN:
626 if (valid->num_error != 0) {
627 cprintf("550 %s\r\n", valid->errormsg);
628 free_recipients(valid);
632 if (valid->num_internet > 0) {
634 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
635 cprintf("551 <%s> - you do not have permission to send Internet mail\r\n", recp);
636 free_recipients(valid);
642 if (valid->num_internet > 0) {
643 if ( (sSMTP->message_originated_locally == 0)
644 && (sSMTP->is_lmtp == 0) ) {
645 cprintf("551 <%s> - relaying denied\r\n", recp);
646 free_recipients(valid);
651 cprintf("250 RCPT ok <%s>\r\n", recp);
652 if (!IsEmptyStr(sSMTP->recipients)) {
653 strcat(sSMTP->recipients, ",");
655 strcat(sSMTP->recipients, recp);
656 sSMTP->number_of_recipients += 1;
658 free_recipients(valid);
666 * Implements the DATA command
668 void smtp_data(void) {
670 char *defbody; //TODO: remove me
671 struct CtdlMessage *msg = NULL;
674 struct recptypes *valid;
678 citsmtp *sSMTP = SMTP;
680 if (IsEmptyStr(sSMTP->from)) {
681 cprintf("503 Need MAIL command first.\r\n");
685 if (sSMTP->number_of_recipients < 1) {
686 cprintf("503 Need RCPT command first.\r\n");
690 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
692 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
693 defbody = malloc(4096);
695 if (defbody != NULL) {
696 if (sSMTP->is_lmtp && (CC->cs_UDSclientUID != -1)) {
697 snprintf(defbody, 4096,
698 "Received: from %s (Citadel from userid %ld)\n"
701 (long int) CC->cs_UDSclientUID,
706 snprintf(defbody, 4096,
707 "Received: from %s (%s [%s])\n"
716 body = CtdlReadMessageBodyBuf(HKEY("."), config.c_maxmsglen, defbody, 1, NULL);
718 cprintf("550 Unable to save message: internal error.\r\n");
722 CtdlLogPrintf(CTDL_DEBUG, "Converting message...\n");
723 msg = convert_internet_message_buf(&body);
725 /* If the user is locally authenticated, FORCE the From: header to
726 * show up as the real sender. Yes, this violates the RFC standard,
727 * but IT MAKES SENSE. If you prefer strict RFC adherence over
728 * common sense, you can disable this in the configuration.
730 * We also set the "message room name" ('O' field) to MAILROOM
731 * (which is Mail> on most systems) to prevent it from getting set
732 * to something ugly like "0000058008.Sent Items>" when the message
733 * is read with a Citadel client.
735 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
736 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
737 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
738 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
739 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
740 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
741 msg->cm_fields['A'] = strdup(CC->user.fullname);
742 msg->cm_fields['N'] = strdup(config.c_nodename);
743 msg->cm_fields['H'] = strdup(config.c_humannode);
744 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
745 msg->cm_fields['O'] = strdup(MAILROOM);
748 /* Set the "envelope from" address */
749 if (msg->cm_fields['P'] != NULL) {
750 free(msg->cm_fields['P']);
752 msg->cm_fields['P'] = strdup(sSMTP->from);
754 /* Set the "envelope to" address */
755 if (msg->cm_fields['V'] != NULL) {
756 free(msg->cm_fields['V']);
758 msg->cm_fields['V'] = strdup(sSMTP->recipients);
760 /* Submit the message into the Citadel system. */
761 valid = validate_recipients(sSMTP->recipients,
762 smtp_get_Recipients (),
763 (sSMTP->is_lmtp)? POST_LMTP:
764 (CC->logged_in)? POST_LOGGED_IN:
767 /* If there are modules that want to scan this message before final
768 * submission (such as virus checkers or spam filters), call them now
769 * and give them an opportunity to reject the message.
771 if (sSMTP->is_unfiltered) {
775 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
778 if (scan_errors > 0) { /* We don't want this message! */
780 if (msg->cm_fields['0'] == NULL) {
781 msg->cm_fields['0'] = strdup("Message rejected by filter");
784 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
787 else { /* Ok, we'll accept this message. */
788 msgnum = CtdlSubmitMsg(msg, valid, "", 0);
790 sprintf(result, "250 Message accepted.\r\n");
793 sprintf(result, "550 Internal delivery error\r\n");
797 /* For SMTP and ESTMP, just print the result message. For LMTP, we
798 * have to print one result message for each recipient. Since there
799 * is nothing in Citadel which would cause different recipients to
800 * have different results, we can get away with just spitting out the
801 * same message once for each recipient.
803 if (sSMTP->is_lmtp) {
804 for (i=0; i<sSMTP->number_of_recipients; ++i) {
805 cprintf("%s", result);
809 cprintf("%s", result);
812 /* Write something to the syslog (which may or may not be where the
813 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
816 syslog((LOG_MAIL | LOG_INFO),
817 "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
820 sSMTP->number_of_recipients,
828 CtdlFreeMessage(msg);
829 free_recipients(valid);
830 smtp_data_clear(); /* clear out the buffers now */
835 * implements the STARTTLS command (Citadel API version)
837 void smtp_starttls(void)
839 char ok_response[SIZ];
840 char nosup_response[SIZ];
841 char error_response[SIZ];
844 "220 Begin TLS negotiation now\r\n");
845 sprintf(nosup_response,
846 "554 TLS not supported here\r\n");
847 sprintf(error_response,
848 "554 Internal error\r\n");
849 CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
856 * Main command loop for SMTP sessions.
858 void smtp_command_loop(void) {
860 citsmtp *sSMTP = SMTP;
863 CtdlLogPrintf(CTDL_EMERG, "Session SMTP data is null. WTF? We will crash now.\n");
867 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
868 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
869 CtdlLogPrintf(CTDL_CRIT, "Client disconnected: ending session.\n");
873 CtdlLogPrintf(CTDL_INFO, "SMTP server: %s\n", cmdbuf);
874 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
876 if (sSMTP->command_state == smtp_user) {
877 smtp_get_user(cmdbuf);
880 else if (sSMTP->command_state == smtp_password) {
881 smtp_get_pass(cmdbuf);
884 else if (sSMTP->command_state == smtp_plain) {
885 smtp_try_plain(cmdbuf);
888 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
889 smtp_auth(&cmdbuf[5]);
892 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
896 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
897 smtp_hello(&cmdbuf[5], 0);
900 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
901 smtp_hello(&cmdbuf[5], 1);
904 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
905 smtp_hello(&cmdbuf[5], 2);
908 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
912 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
913 smtp_mail(&cmdbuf[5]);
916 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
917 cprintf("250 NOOP\r\n");
920 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
921 cprintf("221 Goodbye...\r\n");
926 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
927 smtp_rcpt(&cmdbuf[5]);
930 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
934 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
939 cprintf("502 I'm afraid I can't do that.\r\n");
948 /*****************************************************************************/
949 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
950 /*****************************************************************************/
957 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
960 void smtp_try(const char *key, const char *addr, int *status,
961 char *dsn, size_t n, long msgnum, char *envelope_from)
968 char user[1024], node[1024], name[1024];
983 /* Parse out the host portion of the recipient address */
984 process_rfc822_addr(addr, user, node, name);
986 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Attempting delivery to <%s> @ <%s> (%s)\n",
989 /* Load the message out of the database */
990 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
991 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, ESC_DOT);
992 msg_size = StrLength(CC->redirect_buffer);
993 msgtext = SmashStrBuf(&CC->redirect_buffer);
995 /* If no envelope_from is supplied, extract one from the message */
996 if ( (envelope_from == NULL) || (IsEmptyStr(envelope_from)) ) {
997 strcpy(mailfrom, "");
1001 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
1004 if (!strncasecmp(buf, "From:", 5)) {
1005 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
1007 for (i=0; mailfrom[i]; ++i) {
1008 if (!isprint(mailfrom[i])) {
1009 strcpy(&mailfrom[i], &mailfrom[i+1]);
1014 /* Strip out parenthesized names */
1017 for (i=0; mailfrom[i]; ++i) {
1018 if (mailfrom[i] == '(') lp = i;
1019 if (mailfrom[i] == ')') rp = i;
1021 if ((lp>0)&&(rp>lp)) {
1022 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
1025 /* Prefer brokketized names */
1028 for (i=0; mailfrom[i]; ++i) {
1029 if (mailfrom[i] == '<') lp = i;
1030 if (mailfrom[i] == '>') rp = i;
1032 if ( (lp>=0) && (rp>lp) ) {
1034 strcpy(mailfrom, &mailfrom[lp]);
1039 } while (scan_done == 0);
1040 if (IsEmptyStr(mailfrom)) strcpy(mailfrom, "someone@somewhere.org");
1041 stripallbut(mailfrom, '<', '>');
1042 envelope_from = mailfrom;
1045 /* Figure out what mail exchanger host we have to connect to */
1046 num_mxhosts = getmx(mxhosts, node);
1047 CtdlLogPrintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d [%s]\n", node, num_mxhosts, mxhosts);
1048 if (num_mxhosts < 1) {
1050 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1055 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1057 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1058 strcpy(mx_user, "");
1059 strcpy(mx_pass, "");
1060 if (num_tokens(buf, '@') > 1) {
1061 strcpy (mx_user, buf);
1062 endpart = strrchr(mx_user, '@');
1064 strcpy (mx_host, endpart + 1);
1065 endpart = strrchr(mx_user, ':');
1066 if (endpart != NULL) {
1067 strcpy(mx_pass, endpart+1);
1072 strcpy (mx_host, buf);
1073 endpart = strrchr(mx_host, ':');
1076 strcpy(mx_port, endpart + 1);
1079 strcpy(mx_port, "25");
1081 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connecting to %s : %s ...\n", mx_host, mx_port);
1082 sock = sock_connect(mx_host, mx_port, "tcp");
1083 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1084 if (sock >= 0) CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connected!\n");
1087 snprintf(dsn, SIZ, "%s", strerror(errno));
1090 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
1096 *status = 4; /* dsn is already filled in */
1100 CCC->sReadBuf = NewStrBuf();
1101 CCC->sMigrateBuf = NewStrBuf();
1104 /* Process the SMTP greeting from the server */
1105 if (ml_sock_gets(&sock, buf) < 0) {
1107 strcpy(dsn, "Connection broken during SMTP conversation");
1110 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1111 if (buf[0] != '2') {
1112 if (buf[0] == '4') {
1114 safestrncpy(dsn, &buf[4], 1023);
1119 safestrncpy(dsn, &buf[4], 1023);
1124 /* At this point we know we are talking to a real SMTP server */
1126 /* Do a EHLO command. If it fails, try the HELO command. */
1127 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1128 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1129 sock_write(&sock, buf, strlen(buf));
1130 if (ml_sock_gets(&sock, buf) < 0) {
1132 strcpy(dsn, "Connection broken during SMTP HELO");
1135 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1136 if (buf[0] != '2') {
1137 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1138 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1139 sock_write(&sock, buf, strlen(buf));
1140 if (ml_sock_gets(&sock, buf) < 0) {
1142 strcpy(dsn, "Connection broken during SMTP HELO");
1146 if (buf[0] != '2') {
1147 if (buf[0] == '4') {
1149 safestrncpy(dsn, &buf[4], 1023);
1154 safestrncpy(dsn, &buf[4], 1023);
1159 /* Do an AUTH command if necessary */
1160 if (!IsEmptyStr(mx_user)) {
1162 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
1163 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2, 0);
1164 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
1165 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1166 sock_write(&sock, buf, strlen(buf));
1167 if (ml_sock_gets(&sock, buf) < 0) {
1169 strcpy(dsn, "Connection broken during SMTP AUTH");
1172 CtdlLogPrintf(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 /* previous command succeeded, now try the MAIL FROM: command */
1188 snprintf(buf, sizeof buf, "MAIL FROM:<%s>\r\n", envelope_from);
1189 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1190 sock_write(&sock, buf, strlen(buf));
1191 if (ml_sock_gets(&sock, buf) < 0) {
1193 strcpy(dsn, "Connection broken during SMTP MAIL");
1196 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1197 if (buf[0] != '2') {
1198 if (buf[0] == '4') {
1200 safestrncpy(dsn, &buf[4], 1023);
1205 safestrncpy(dsn, &buf[4], 1023);
1210 /* MAIL succeeded, now try the RCPT To: command */
1211 snprintf(buf, sizeof buf, "RCPT TO:<%s@%s>\r\n", user, node);
1212 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1213 sock_write(&sock, buf, strlen(buf));
1214 if (ml_sock_gets(&sock, buf) < 0) {
1216 strcpy(dsn, "Connection broken during SMTP RCPT");
1219 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1220 if (buf[0] != '2') {
1221 if (buf[0] == '4') {
1223 safestrncpy(dsn, &buf[4], 1023);
1228 safestrncpy(dsn, &buf[4], 1023);
1233 /* RCPT succeeded, now try the DATA command */
1234 CtdlLogPrintf(CTDL_DEBUG, ">DATA\n");
1235 sock_write(&sock, "DATA\r\n", 6);
1236 if (ml_sock_gets(&sock, buf) < 0) {
1238 strcpy(dsn, "Connection broken during SMTP DATA");
1241 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1242 if (buf[0] != '3') {
1243 if (buf[0] == '4') {
1245 safestrncpy(dsn, &buf[4], 1023);
1250 safestrncpy(dsn, &buf[4], 1023);
1255 /* If we reach this point, the server is expecting data.*/
1256 sock_write(&sock, msgtext, msg_size);
1257 if (msgtext[msg_size-1] != 10) {
1258 CtdlLogPrintf(CTDL_WARNING, "Possible problem: message did not "
1259 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1261 sock_write(&sock, "\r\n", 2);
1264 sock_write(&sock, ".\r\n", 3);
1265 if (ml_sock_gets(&sock, buf) < 0) {
1267 strcpy(dsn, "Connection broken during SMTP message transmit");
1270 CtdlLogPrintf(CTDL_DEBUG, "%s\n", buf);
1271 if (buf[0] != '2') {
1272 if (buf[0] == '4') {
1274 safestrncpy(dsn, &buf[4], 1023);
1279 safestrncpy(dsn, &buf[4], 1023);
1285 safestrncpy(dsn, &buf[4], 1023);
1288 CtdlLogPrintf(CTDL_DEBUG, ">QUIT\n");
1289 sock_write(&sock, "QUIT\r\n", 6);
1290 ml_sock_gets(&sock, buf);
1291 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1292 CtdlLogPrintf(CTDL_INFO, "SMTP client: delivery to <%s> @ <%s> (%s) succeeded\n",
1295 bail: free(msgtext);
1296 FreeStrBuf(&CCC->sReadBuf);
1297 FreeStrBuf(&CCC->sMigrateBuf);
1301 /* Write something to the syslog (which may or may not be where the
1302 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1304 if (enable_syslog) {
1305 syslog((LOG_MAIL | LOG_INFO),
1306 "%ld: to=<%s>, relay=%s, stat=%s",
1320 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1321 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1322 * a "bounce" message (delivery status notification).
1324 void smtp_do_bounce(char *instr) {
1332 char bounceto[1024];
1334 int num_bounces = 0;
1335 int bounce_this = 0;
1336 long bounce_msgid = (-1);
1337 time_t submitted = 0L;
1338 struct CtdlMessage *bmsg = NULL;
1340 struct recptypes *valid;
1341 int successful_bounce = 0;
1346 CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1347 strcpy(bounceto, "");
1348 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
1349 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
1350 lines = num_tokens(instr, '\n');
1352 /* See if it's time to give up on delivery of this message */
1353 for (i=0; i<lines; ++i) {
1354 extract_token(buf, instr, i, '\n', sizeof buf);
1355 extract_token(key, buf, 0, '|', sizeof key);
1356 extract_token(addr, buf, 1, '|', sizeof addr);
1357 if (!strcasecmp(key, "submitted")) {
1358 submitted = atol(addr);
1362 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1366 /* Start building our bounce message */
1368 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1369 if (bmsg == NULL) return;
1370 memset(bmsg, 0, sizeof(struct CtdlMessage));
1371 BounceMB = NewStrBufPlain(NULL, 1024);
1373 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1374 bmsg->cm_anon_type = MES_NORMAL;
1375 bmsg->cm_format_type = FMT_RFC822;
1376 bmsg->cm_fields['A'] = strdup("Citadel");
1377 bmsg->cm_fields['O'] = strdup(MAILROOM);
1378 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1379 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1380 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
1381 StrBufAppendBuf(BounceMB, boundary, 0);
1382 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
1383 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
1384 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
1385 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
1386 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1387 StrBufAppendBuf(BounceMB, boundary, 0);
1388 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1389 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
1391 if (give_up) StrBufAppendBufPlain(BounceMB, HKEY(
1392 "A message you sent could not be delivered to some or all of its recipients\n"
1393 "due to prolonged unavailability of its destination(s).\n"
1394 "Giving up on the following addresses:\n\n"
1397 else StrBufAppendBufPlain(BounceMB, HKEY(
1398 "A message you sent could not be delivered to some or all of its recipients.\n"
1399 "The following addresses were undeliverable:\n\n"
1403 * Now go through the instructions checking for stuff.
1405 for (i=0; i<lines; ++i) {
1408 extract_token(buf, instr, i, '\n', sizeof buf);
1409 extract_token(key, buf, 0, '|', sizeof key);
1410 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
1411 status = extract_int(buf, 2);
1412 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
1415 CtdlLogPrintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1416 key, addr, status, dsn);
1418 if (!strcasecmp(key, "bounceto")) {
1419 strcpy(bounceto, addr);
1422 if (!strcasecmp(key, "msgid")) {
1423 omsgid = atol(addr);
1426 if (!strcasecmp(key, "remote")) {
1427 if (status == 5) bounce_this = 1;
1428 if (give_up) bounce_this = 1;
1434 StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
1435 StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
1436 StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
1437 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1439 remove_token(instr, i, '\n');
1445 /* Attach the original message */
1447 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1448 StrBufAppendBuf(BounceMB, boundary, 0);
1449 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1450 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
1451 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
1452 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
1453 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1455 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
1456 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0);
1457 StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
1458 FreeStrBuf(&CC->redirect_buffer);
1461 /* Close the multipart MIME scope */
1462 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1463 StrBufAppendBuf(BounceMB, boundary, 0);
1464 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
1465 bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB);
1466 /* Deliver the bounce if there's anything worth mentioning */
1467 CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1468 if (num_bounces > 0) {
1470 /* First try the user who sent the message */
1471 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1472 if (IsEmptyStr(bounceto)) {
1473 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
1474 bounce_msgid = (-1L);
1477 /* Can we deliver the bounce to the original sender? */
1478 valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
1479 if (valid != NULL) {
1480 if (valid->num_error == 0) {
1481 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
1482 successful_bounce = 1;
1486 /* If not, post it in the Aide> room */
1487 if (successful_bounce == 0) {
1488 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
1491 /* Free up the memory we used */
1492 if (valid != NULL) {
1493 free_recipients(valid);
1496 FreeStrBuf(&boundary);
1497 CtdlFreeMessage(bmsg);
1498 CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
1503 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1504 * set of delivery instructions for completed deliveries and remove them.
1506 * It returns the number of incomplete deliveries remaining.
1508 int smtp_purge_completed_deliveries(char *instr) {
1519 lines = num_tokens(instr, '\n');
1520 for (i=0; i<lines; ++i) {
1521 extract_token(buf, instr, i, '\n', sizeof buf);
1522 extract_token(key, buf, 0, '|', sizeof key);
1523 extract_token(addr, buf, 1, '|', sizeof addr);
1524 status = extract_int(buf, 2);
1525 extract_token(dsn, buf, 3, '|', sizeof dsn);
1529 if (!strcasecmp(key, "remote")) {
1530 if (status == 2) completed = 1;
1535 remove_token(instr, i, '\n');
1548 * Called by smtp_do_queue() to handle an individual message.
1550 void smtp_do_procmsg(long msgnum, void *userdata) {
1551 struct CtdlMessage *msg = NULL;
1553 char *results = NULL;
1561 char envelope_from[1024];
1562 long text_msgid = (-1);
1563 int incomplete_deliveries_remaining;
1564 time_t attempted = 0L;
1565 time_t last_attempted = 0L;
1566 time_t retry = SMTP_RETRY_INTERVAL;
1568 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: smtp_do_procmsg(%ld)\n", msgnum);
1569 strcpy(envelope_from, "");
1571 msg = CtdlFetchMessage(msgnum, 1);
1573 CtdlLogPrintf(CTDL_ERR, "SMTP client: tried %ld but no such message!\n", msgnum);
1577 instr = strdup(msg->cm_fields['M']);
1578 CtdlFreeMessage(msg);
1580 /* Strip out the headers amd any other non-instruction line */
1581 lines = num_tokens(instr, '\n');
1582 for (i=0; i<lines; ++i) {
1583 extract_token(buf, instr, i, '\n', sizeof buf);
1584 if (num_tokens(buf, '|') < 2) {
1585 remove_token(instr, i, '\n');
1591 /* Learn the message ID and find out about recent delivery attempts */
1592 lines = num_tokens(instr, '\n');
1593 for (i=0; i<lines; ++i) {
1594 extract_token(buf, instr, i, '\n', sizeof buf);
1595 extract_token(key, buf, 0, '|', sizeof key);
1596 if (!strcasecmp(key, "msgid")) {
1597 text_msgid = extract_long(buf, 1);
1599 if (!strcasecmp(key, "envelope_from")) {
1600 extract_token(envelope_from, buf, 1, '|', sizeof envelope_from);
1602 if (!strcasecmp(key, "retry")) {
1603 /* double the retry interval after each attempt */
1604 retry = extract_long(buf, 1) * 2L;
1605 if (retry > SMTP_RETRY_MAX) {
1606 retry = SMTP_RETRY_MAX;
1608 remove_token(instr, i, '\n');
1610 if (!strcasecmp(key, "attempted")) {
1611 attempted = extract_long(buf, 1);
1612 if (attempted > last_attempted)
1613 last_attempted = attempted;
1618 * Postpone delivery if we've already tried recently.
1620 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1621 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1628 * Bail out if there's no actual message associated with this
1630 if (text_msgid < 0L) {
1631 CtdlLogPrintf(CTDL_ERR, "SMTP client: no 'msgid' directive found!\n");
1636 /* Plow through the instructions looking for 'remote' directives and
1637 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1638 * were experienced and it's time to try again)
1640 lines = num_tokens(instr, '\n');
1641 for (i=0; i<lines; ++i) {
1642 extract_token(buf, instr, i, '\n', sizeof buf);
1643 extract_token(key, buf, 0, '|', sizeof key);
1644 extract_token(addr, buf, 1, '|', sizeof addr);
1645 status = extract_int(buf, 2);
1646 extract_token(dsn, buf, 3, '|', sizeof dsn);
1647 if ( (!strcasecmp(key, "remote"))
1648 && ((status==0)||(status==3)||(status==4)) ) {
1650 /* Remove this "remote" instruction from the set,
1651 * but replace the set's final newline if
1652 * remove_token() stripped it. It has to be there.
1654 remove_token(instr, i, '\n');
1655 if (instr[strlen(instr)-1] != '\n') {
1656 strcat(instr, "\n");
1661 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Trying <%s>\n", addr);
1662 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid, envelope_from);
1664 if (results == NULL) {
1665 results = malloc(1024);
1666 memset(results, 0, 1024);
1669 results = realloc(results, strlen(results) + 1024);
1671 snprintf(&results[strlen(results)], 1024,
1673 key, addr, status, dsn);
1678 if (results != NULL) {
1679 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1680 strcat(instr, results);
1685 /* Generate 'bounce' messages */
1686 smtp_do_bounce(instr);
1688 /* Go through the delivery list, deleting completed deliveries */
1689 incomplete_deliveries_remaining =
1690 smtp_purge_completed_deliveries(instr);
1694 * No delivery instructions remain, so delete both the instructions
1695 * message and the message message.
1697 if (incomplete_deliveries_remaining <= 0) {
1699 delmsgs[0] = msgnum;
1700 delmsgs[1] = text_msgid;
1701 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
1705 * Uncompleted delivery instructions remain, so delete the old
1706 * instructions and replace with the updated ones.
1708 if (incomplete_deliveries_remaining > 0) {
1709 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
1710 msg = malloc(sizeof(struct CtdlMessage));
1711 memset(msg, 0, sizeof(struct CtdlMessage));
1712 msg->cm_magic = CTDLMESSAGE_MAGIC;
1713 msg->cm_anon_type = MES_NORMAL;
1714 msg->cm_format_type = FMT_RFC822;
1715 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1716 snprintf(msg->cm_fields['M'],
1718 "Content-type: %s\n\n%s\n"
1721 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1722 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
1723 CtdlFreeMessage(msg);
1735 * Run through the queue sending out messages.
1737 void *smtp_do_queue(void *arg) {
1738 int num_processed = 0;
1739 struct CitContext smtp_queue_CC;
1741 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1743 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1744 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC );
1746 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1747 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1750 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1753 citthread_mutex_unlock (&smtp_send_lock);
1754 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1763 * Create a thread to run the SMTP queue
1765 * This was created as a response to a situation seen on Uncensored where a bad remote was holding
1766 * up SMTP sending for long times.
1767 * Converting to a thread does not fix the problem caused by the bad remote but it does prevent
1768 * the SMTP sending from stopping housekeeping and the EVT_TIMER event system which in turn prevented
1769 * other things from happening.
1771 void smtp_queue_thread (void)
1773 if (citthread_mutex_trylock (&smtp_send_lock)) {
1774 CtdlLogPrintf(CTDL_DEBUG, "SMTP queue run already in progress\n");
1777 CtdlThreadCreate("SMTP Send", CTDLTHREAD_BIGSTACK, smtp_do_queue, NULL);
1783 void smtp_server_going_down (void)
1785 CtdlLogPrintf(CTDL_DEBUG, "SMTP module clean up for shutdown.\n");
1787 citthread_mutex_destroy (&smtp_send_lock);
1792 /*****************************************************************************/
1793 /* SMTP UTILITY COMMANDS */
1794 /*****************************************************************************/
1796 void cmd_smtp(char *argbuf) {
1803 if (CtdlAccessCheck(ac_aide)) return;
1805 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1807 if (!strcasecmp(cmd, "mx")) {
1808 extract_token(node, argbuf, 1, '|', sizeof node);
1809 num_mxhosts = getmx(buf, node);
1810 cprintf("%d %d MX hosts listed for %s\n",
1811 LISTING_FOLLOWS, num_mxhosts, node);
1812 for (i=0; i<num_mxhosts; ++i) {
1813 extract_token(node, buf, i, '|', sizeof node);
1814 cprintf("%s\n", node);
1820 else if (!strcasecmp(cmd, "runqueue")) {
1822 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1827 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1834 * Initialize the SMTP outbound queue
1836 void smtp_init_spoolout(void) {
1837 struct ctdlroom qrbuf;
1840 * Create the room. This will silently fail if the room already
1841 * exists, and that's perfectly ok, because we want it to exist.
1843 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1846 * Make sure it's set to be a "system room" so it doesn't show up
1847 * in the <K>nown rooms list for Aides.
1849 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1850 qrbuf.QRflags2 |= QR2_SYSTEM;
1851 CtdlPutRoomLock(&qrbuf);
1858 /*****************************************************************************/
1859 /* MODULE INITIALIZATION STUFF */
1860 /*****************************************************************************/
1862 * This cleanup function blows away the temporary memory used by
1865 void smtp_cleanup_function(void) {
1867 /* Don't do this stuff if this is not an SMTP session! */
1868 if (CC->h_command_function != smtp_command_loop) return;
1870 CtdlLogPrintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1876 const char *CitadelServiceSMTP_MTA="SMTP-MTA";
1877 const char *CitadelServiceSMTPS_MTA="SMTPs-MTA";
1878 const char *CitadelServiceSMTP_MSA="SMTP-MSA";
1879 const char *CitadelServiceSMTP_LMTP="LMTP";
1880 const char *CitadelServiceSMTP_LMTP_UNF="LMTP-UnF";
1882 CTDL_MODULE_INIT(smtp)
1886 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1891 CitadelServiceSMTP_MTA);
1894 CtdlRegisterServiceHook(config.c_smtps_port,
1899 CitadelServiceSMTPS_MTA);
1902 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1907 CitadelServiceSMTP_MSA);
1909 CtdlRegisterServiceHook(0, /* local LMTP */
1914 CitadelServiceSMTP_LMTP);
1916 CtdlRegisterServiceHook(0, /* local LMTP */
1917 file_lmtp_unfiltered_socket,
1918 lmtp_unfiltered_greeting,
1921 CitadelServiceSMTP_LMTP_UNF);
1923 smtp_init_spoolout();
1924 CtdlRegisterSessionHook(smtp_queue_thread, EVT_TIMER);
1925 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1926 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1927 CtdlRegisterCleanupHook (smtp_server_going_down);
1928 citthread_mutex_init (&smtp_send_lock, NULL);
1931 /* return our Subversion id for the Log */