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) {
215 * Generic SMTP MTA greeting
217 void smtp_mta_greeting(void) {
223 * We also have an unfiltered LMTP socket that bypasses spam filters.
225 void lmtp_unfiltered_greeting(void) {
231 sSMTP->is_unfiltered = 1;
236 * Login greeting common to all auth methods
238 void smtp_auth_greeting(void) {
239 cprintf("235 Hello, %s\r\n", CC->user.fullname);
240 CtdlLogPrintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
241 CC->internal_pgm = 0;
242 CC->cs_flags &= ~CS_STEALTH;
247 * Implement HELO and EHLO commands.
249 * which_command: 0=HELO, 1=EHLO, 2=LHLO
251 void smtp_hello(char *argbuf, int which_command) {
252 citsmtp *sSMTP = SMTP;
254 safestrncpy(sSMTP->helo_node, argbuf, sizeof sSMTP->helo_node);
256 if ( (which_command != 2) && (sSMTP->is_lmtp) ) {
257 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
261 if ( (which_command == 2) && (sSMTP->is_lmtp == 0) ) {
262 cprintf("500 LHLO is only allowed when running LMTP\r\n");
266 if (which_command == 0) {
267 cprintf("250 Hello %s (%s [%s])\r\n",
274 if (which_command == 1) {
275 cprintf("250-Hello %s (%s [%s])\r\n",
282 cprintf("250-Greetings and joyous salutations.\r\n");
284 cprintf("250-HELP\r\n");
285 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
289 * Offer TLS, but only if TLS is not already active.
290 * Furthermore, only offer TLS when running on
291 * the SMTP-MSA port, not on the SMTP-MTA port, due to
292 * questionable reliability of TLS in certain sending MTA's.
294 if ( (!CC->redirect_ssl) && (sSMTP->is_msa) ) {
295 cprintf("250-STARTTLS\r\n");
297 #endif /* HAVE_OPENSSL */
299 cprintf("250-AUTH LOGIN PLAIN\r\n"
300 "250-AUTH=LOGIN PLAIN\r\n"
309 * Implement HELP command.
311 void smtp_help(void) {
312 cprintf("214 RTFM http://www.ietf.org/rfc/rfc2821.txt\r\n");
319 void smtp_get_user(char *argbuf) {
322 citsmtp *sSMTP = SMTP;
324 CtdlDecodeBase64(username, argbuf, SIZ);
325 /* CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", username); */
326 if (CtdlLoginExistingUser(NULL, username) == login_ok) {
327 CtdlEncodeBase64(buf, "Password:", 9, 0);
328 cprintf("334 %s\r\n", buf);
329 sSMTP->command_state = smtp_password;
332 cprintf("500 No such user.\r\n");
333 sSMTP->command_state = smtp_command;
341 void smtp_get_pass(char *argbuf) {
344 memset(password, 0, sizeof(password));
345 CtdlDecodeBase64(password, argbuf, SIZ);
346 /* CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", password); */
347 if (CtdlTryPassword(password) == pass_ok) {
348 smtp_auth_greeting();
351 cprintf("535 Authentication failed.\r\n");
353 SMTP->command_state = smtp_command;
358 * Back end for PLAIN auth method (either inline or multistate)
360 void smtp_try_plain(char *encoded_authstring) {
361 char decoded_authstring[1024];
367 CtdlDecodeBase64(decoded_authstring, encoded_authstring, strlen(encoded_authstring) );
368 safestrncpy(ident, decoded_authstring, sizeof ident);
369 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
370 safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
372 SMTP->command_state = smtp_command;
374 if (!IsEmptyStr(ident)) {
375 result = CtdlLoginExistingUser(user, ident);
378 result = CtdlLoginExistingUser(NULL, user);
381 if (result == login_ok) {
382 if (CtdlTryPassword(pass) == pass_ok) {
383 smtp_auth_greeting();
387 cprintf("504 Authentication failed.\r\n");
392 * Attempt to perform authenticated SMTP
394 void smtp_auth(char *argbuf) {
395 char username_prompt[64];
397 char encoded_authstring[1024];
400 cprintf("504 Already logged in.\r\n");
404 extract_token(method, argbuf, 0, ' ', sizeof method);
406 if (!strncasecmp(method, "login", 5) ) {
407 if (strlen(argbuf) >= 7) {
408 smtp_get_user(&argbuf[6]);
411 CtdlEncodeBase64(username_prompt, "Username:", 9, 0);
412 cprintf("334 %s\r\n", username_prompt);
413 SMTP->command_state = smtp_user;
418 if (!strncasecmp(method, "plain", 5) ) {
419 if (num_tokens(argbuf, ' ') < 2) {
421 SMTP->command_state = smtp_plain;
425 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
427 smtp_try_plain(encoded_authstring);
431 if (strncasecmp(method, "login", 5) ) {
432 cprintf("504 Unknown authentication method.\r\n");
440 * Implements the RSET (reset state) command.
441 * Currently this just zeroes out the state buffer. If pointers to data
442 * allocated with malloc() are ever placed in the state buffer, we have to
443 * be sure to free() them first!
445 * Set do_response to nonzero to output the SMTP RSET response code.
447 void smtp_rset(int do_response) {
450 citsmtp *sSMTP = SMTP;
453 * Our entire SMTP state is discarded when a RSET command is issued,
454 * but we need to preserve this one little piece of information, so
455 * we save it for later.
457 is_lmtp = sSMTP->is_lmtp;
458 is_unfiltered = sSMTP->is_unfiltered;
460 memset(sSMTP, 0, sizeof(citsmtp));
463 * It is somewhat ambiguous whether we want to log out when a RSET
464 * command is issued. Here's the code to do it. It is commented out
465 * because some clients (such as Pine) issue RSET commands before
466 * each message, but still expect to be logged in.
468 * if (CC->logged_in) {
474 * Reinstate this little piece of information we saved (see above).
476 sSMTP->is_lmtp = is_lmtp;
477 sSMTP->is_unfiltered = is_unfiltered;
480 cprintf("250 Zap!\r\n");
485 * Clear out the portions of the state buffer that need to be cleared out
486 * after the DATA command finishes.
488 void smtp_data_clear(void) {
489 citsmtp *sSMTP = SMTP;
491 strcpy(sSMTP->from, "");
492 strcpy(sSMTP->recipients, "");
493 sSMTP->number_of_recipients = 0;
494 sSMTP->delivery_mode = 0;
495 sSMTP->message_originated_locally = 0;
498 const char *smtp_get_Recipients(void)
500 citsmtp *sSMTP = SMTP;
504 else return sSMTP->from;
508 * Implements the "MAIL FROM:" command
510 void smtp_mail(char *argbuf) {
514 citsmtp *sSMTP = SMTP;
516 if (!IsEmptyStr(sSMTP->from)) {
517 cprintf("503 Only one sender permitted\r\n");
521 if (strncasecmp(argbuf, "From:", 5)) {
522 cprintf("501 Syntax error\r\n");
526 strcpy(sSMTP->from, &argbuf[5]);
527 striplt(sSMTP->from);
528 if (haschar(sSMTP->from, '<') > 0) {
529 stripallbut(sSMTP->from, '<', '>');
532 /* We used to reject empty sender names, until it was brought to our
533 * attention that RFC1123 5.2.9 requires that this be allowed. So now
534 * we allow it, but replace the empty string with a fake
535 * address so we don't have to contend with the empty string causing
536 * other code to fail when it's expecting something there.
538 if (IsEmptyStr(sSMTP->from)) {
539 strcpy(sSMTP->from, "someone@example.com");
542 /* If this SMTP connection is from a logged-in user, force the 'from'
543 * to be the user's Internet e-mail address as Citadel knows it.
546 safestrncpy(sSMTP->from, CC->cs_inet_email, sizeof sSMTP->from);
547 cprintf("250 Sender ok <%s>\r\n", sSMTP->from);
548 sSMTP->message_originated_locally = 1;
552 else if (sSMTP->is_lmtp) {
553 /* Bypass forgery checking for LMTP */
556 /* Otherwise, make sure outsiders aren't trying to forge mail from
557 * this system (unless, of course, c_allow_spoofing is enabled)
559 else if (config.c_allow_spoofing == 0) {
560 process_rfc822_addr(sSMTP->from, user, node, name);
561 if (CtdlHostAlias(node) != hostalias_nomatch) {
562 cprintf("550 You must log in to send mail from %s\r\n", node);
563 strcpy(sSMTP->from, "");
568 cprintf("250 Sender ok\r\n");
574 * Implements the "RCPT To:" command
576 void smtp_rcpt(char *argbuf) {
578 char message_to_spammer[SIZ];
579 struct recptypes *valid = NULL;
580 citsmtp *sSMTP = SMTP;
582 if (IsEmptyStr(sSMTP->from)) {
583 cprintf("503 Need MAIL before RCPT\r\n");
587 if (strncasecmp(argbuf, "To:", 3)) {
588 cprintf("501 Syntax error\r\n");
592 if ( (sSMTP->is_msa) && (!CC->logged_in) ) {
593 cprintf("550 You must log in to send mail on this port.\r\n");
594 strcpy(sSMTP->from, "");
598 safestrncpy(recp, &argbuf[3], sizeof recp);
600 stripallbut(recp, '<', '>');
602 if ( (strlen(recp) + strlen(sSMTP->recipients) + 1 ) >= SIZ) {
603 cprintf("452 Too many recipients\r\n");
608 if ( (!CC->logged_in) /* Don't RBL authenticated users */
609 && (!sSMTP->is_lmtp) ) { /* Don't RBL LMTP clients */
610 if (config.c_rbl_at_greeting == 0) { /* Don't RBL again if we already did it */
611 if (rbl_check(message_to_spammer)) {
612 if (CtdlThreadCheckStop())
613 cprintf("421 %s\r\n", message_to_spammer);
615 cprintf("550 %s\r\n", message_to_spammer);
616 /* no need to free_recipients(valid), it's not allocated yet */
622 valid = validate_recipients(recp,
623 smtp_get_Recipients (),
624 (sSMTP->is_lmtp)? POST_LMTP:
625 (CC->logged_in)? POST_LOGGED_IN:
627 if (valid->num_error != 0) {
628 cprintf("550 %s\r\n", valid->errormsg);
629 free_recipients(valid);
633 if (valid->num_internet > 0) {
635 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
636 cprintf("551 <%s> - you do not have permission to send Internet mail\r\n", recp);
637 free_recipients(valid);
643 if (valid->num_internet > 0) {
644 if ( (sSMTP->message_originated_locally == 0)
645 && (sSMTP->is_lmtp == 0) ) {
646 cprintf("551 <%s> - relaying denied\r\n", recp);
647 free_recipients(valid);
652 cprintf("250 RCPT ok <%s>\r\n", recp);
653 if (!IsEmptyStr(sSMTP->recipients)) {
654 strcat(sSMTP->recipients, ",");
656 strcat(sSMTP->recipients, recp);
657 sSMTP->number_of_recipients += 1;
659 free_recipients(valid);
667 * Implements the DATA command
669 void smtp_data(void) {
671 char *defbody; //TODO: remove me
672 struct CtdlMessage *msg = NULL;
675 struct recptypes *valid;
679 citsmtp *sSMTP = SMTP;
681 if (IsEmptyStr(sSMTP->from)) {
682 cprintf("503 Need MAIL command first.\r\n");
686 if (sSMTP->number_of_recipients < 1) {
687 cprintf("503 Need RCPT command first.\r\n");
691 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
693 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
694 defbody = malloc(4096);
696 if (defbody != NULL) {
697 if (sSMTP->is_lmtp && (CC->cs_UDSclientUID != -1)) {
698 snprintf(defbody, 4096,
699 "Received: from %s (Citadel from userid %ld)\n"
702 (long int) CC->cs_UDSclientUID,
707 snprintf(defbody, 4096,
708 "Received: from %s (%s [%s])\n"
717 body = CtdlReadMessageBodyBuf(HKEY("."), config.c_maxmsglen, defbody, 1, NULL);
719 cprintf("550 Unable to save message: internal error.\r\n");
723 CtdlLogPrintf(CTDL_DEBUG, "Converting message...\n");
724 msg = convert_internet_message_buf(&body);
726 /* If the user is locally authenticated, FORCE the From: header to
727 * show up as the real sender. Yes, this violates the RFC standard,
728 * but IT MAKES SENSE. If you prefer strict RFC adherence over
729 * common sense, you can disable this in the configuration.
731 * We also set the "message room name" ('O' field) to MAILROOM
732 * (which is Mail> on most systems) to prevent it from getting set
733 * to something ugly like "0000058008.Sent Items>" when the message
734 * is read with a Citadel client.
736 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
737 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
738 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
739 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
740 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
741 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
742 msg->cm_fields['A'] = strdup(CC->user.fullname);
743 msg->cm_fields['N'] = strdup(config.c_nodename);
744 msg->cm_fields['H'] = strdup(config.c_humannode);
745 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
746 msg->cm_fields['O'] = strdup(MAILROOM);
749 /* Set the "envelope from" address */
750 if (msg->cm_fields['P'] != NULL) {
751 free(msg->cm_fields['P']);
753 msg->cm_fields['P'] = strdup(sSMTP->from);
755 /* Set the "envelope to" address */
756 if (msg->cm_fields['V'] != NULL) {
757 free(msg->cm_fields['V']);
759 msg->cm_fields['V'] = strdup(sSMTP->recipients);
761 /* Submit the message into the Citadel system. */
762 valid = validate_recipients(sSMTP->recipients,
763 smtp_get_Recipients (),
764 (sSMTP->is_lmtp)? POST_LMTP:
765 (CC->logged_in)? POST_LOGGED_IN:
768 /* If there are modules that want to scan this message before final
769 * submission (such as virus checkers or spam filters), call them now
770 * and give them an opportunity to reject the message.
772 if (sSMTP->is_unfiltered) {
776 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
779 if (scan_errors > 0) { /* We don't want this message! */
781 if (msg->cm_fields['0'] == NULL) {
782 msg->cm_fields['0'] = strdup("Message rejected by filter");
785 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
788 else { /* Ok, we'll accept this message. */
789 msgnum = CtdlSubmitMsg(msg, valid, "", 0);
791 sprintf(result, "250 Message accepted.\r\n");
794 sprintf(result, "550 Internal delivery error\r\n");
798 /* For SMTP and ESTMP, just print the result message. For LMTP, we
799 * have to print one result message for each recipient. Since there
800 * is nothing in Citadel which would cause different recipients to
801 * have different results, we can get away with just spitting out the
802 * same message once for each recipient.
804 if (sSMTP->is_lmtp) {
805 for (i=0; i<sSMTP->number_of_recipients; ++i) {
806 cprintf("%s", result);
810 cprintf("%s", result);
813 /* Write something to the syslog (which may or may not be where the
814 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
817 syslog((LOG_MAIL | LOG_INFO),
818 "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
821 sSMTP->number_of_recipients,
829 CtdlFreeMessage(msg);
830 free_recipients(valid);
831 smtp_data_clear(); /* clear out the buffers now */
836 * implements the STARTTLS command (Citadel API version)
838 void smtp_starttls(void)
840 char ok_response[SIZ];
841 char nosup_response[SIZ];
842 char error_response[SIZ];
845 "220 Begin TLS negotiation now\r\n");
846 sprintf(nosup_response,
847 "554 TLS not supported here\r\n");
848 sprintf(error_response,
849 "554 Internal error\r\n");
850 CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
857 * Main command loop for SMTP sessions.
859 void smtp_command_loop(void) {
861 citsmtp *sSMTP = SMTP;
864 CtdlLogPrintf(CTDL_EMERG, "Session SMTP data is null. WTF? We will crash now.\n");
868 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
869 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
870 CtdlLogPrintf(CTDL_CRIT, "Client disconnected: ending session.\n");
874 CtdlLogPrintf(CTDL_INFO, "SMTP server: %s\n", cmdbuf);
875 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
877 if (sSMTP->command_state == smtp_user) {
878 smtp_get_user(cmdbuf);
881 else if (sSMTP->command_state == smtp_password) {
882 smtp_get_pass(cmdbuf);
885 else if (sSMTP->command_state == smtp_plain) {
886 smtp_try_plain(cmdbuf);
889 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
890 smtp_auth(&cmdbuf[5]);
893 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
897 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
898 smtp_hello(&cmdbuf[5], 0);
901 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
902 smtp_hello(&cmdbuf[5], 1);
905 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
906 smtp_hello(&cmdbuf[5], 2);
909 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
913 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
914 smtp_mail(&cmdbuf[5]);
917 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
918 cprintf("250 NOOP\r\n");
921 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
922 cprintf("221 Goodbye...\r\n");
927 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
928 smtp_rcpt(&cmdbuf[5]);
931 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
935 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
940 cprintf("502 I'm afraid I can't do that.\r\n");
949 /*****************************************************************************/
950 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
951 /*****************************************************************************/
958 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
961 void smtp_try(const char *key, const char *addr, int *status,
962 char *dsn, size_t n, long msgnum, char *envelope_from)
969 char user[1024], node[1024], name[1024];
984 /* Parse out the host portion of the recipient address */
985 process_rfc822_addr(addr, user, node, name);
987 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Attempting delivery to <%s> @ <%s> (%s)\n",
990 /* Load the message out of the database */
991 CCC->redirect_buffer = malloc(SIZ);
992 CCC->redirect_len = 0;
993 CCC->redirect_alloc = SIZ;
994 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, ESC_DOT);
995 msgtext = CC->redirect_buffer;
996 msg_size = CC->redirect_len;
997 CCC->redirect_buffer = NULL;
998 CCC->redirect_len = 0;
999 CCC->redirect_alloc = 0;
1001 /* If no envelope_from is supplied, extract one from the message */
1002 if ( (envelope_from == NULL) || (IsEmptyStr(envelope_from)) ) {
1003 strcpy(mailfrom, "");
1007 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
1010 if (!strncasecmp(buf, "From:", 5)) {
1011 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
1013 for (i=0; mailfrom[i]; ++i) {
1014 if (!isprint(mailfrom[i])) {
1015 strcpy(&mailfrom[i], &mailfrom[i+1]);
1020 /* Strip out parenthesized 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)) {
1028 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
1031 /* Prefer brokketized names */
1034 for (i=0; mailfrom[i]; ++i) {
1035 if (mailfrom[i] == '<') lp = i;
1036 if (mailfrom[i] == '>') rp = i;
1038 if ( (lp>=0) && (rp>lp) ) {
1040 strcpy(mailfrom, &mailfrom[lp]);
1045 } while (scan_done == 0);
1046 if (IsEmptyStr(mailfrom)) strcpy(mailfrom, "someone@somewhere.org");
1047 stripallbut(mailfrom, '<', '>');
1048 envelope_from = mailfrom;
1051 /* Figure out what mail exchanger host we have to connect to */
1052 num_mxhosts = getmx(mxhosts, node);
1053 CtdlLogPrintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d [%s]\n", node, num_mxhosts, mxhosts);
1054 if (num_mxhosts < 1) {
1056 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1061 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1063 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1064 strcpy(mx_user, "");
1065 strcpy(mx_pass, "");
1066 if (num_tokens(buf, '@') > 1) {
1067 strcpy (mx_user, buf);
1068 endpart = strrchr(mx_user, '@');
1070 strcpy (mx_host, endpart + 1);
1071 endpart = strrchr(mx_user, ':');
1072 if (endpart != NULL) {
1073 strcpy(mx_pass, endpart+1);
1078 strcpy (mx_host, buf);
1079 endpart = strrchr(mx_host, ':');
1082 strcpy(mx_port, endpart + 1);
1085 strcpy(mx_port, "25");
1087 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connecting to %s : %s ...\n", mx_host, mx_port);
1088 sock = sock_connect(mx_host, mx_port, "tcp");
1089 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1090 if (sock >= 0) CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connected!\n");
1093 snprintf(dsn, SIZ, "%s", strerror(errno));
1096 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
1102 *status = 4; /* dsn is already filled in */
1106 CCC->sReadBuf = NewStrBuf();
1107 CCC->sMigrateBuf = NewStrBuf();
1110 /* Process the SMTP greeting from the server */
1111 if (ml_sock_gets(&sock, buf) < 0) {
1113 strcpy(dsn, "Connection broken during SMTP conversation");
1116 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1117 if (buf[0] != '2') {
1118 if (buf[0] == '4') {
1120 safestrncpy(dsn, &buf[4], 1023);
1125 safestrncpy(dsn, &buf[4], 1023);
1130 /* At this point we know we are talking to a real SMTP server */
1132 /* Do a EHLO command. If it fails, try the HELO command. */
1133 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1134 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1135 sock_write(&sock, buf, strlen(buf));
1136 if (ml_sock_gets(&sock, buf) < 0) {
1138 strcpy(dsn, "Connection broken during SMTP HELO");
1141 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1142 if (buf[0] != '2') {
1143 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1144 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1145 sock_write(&sock, buf, strlen(buf));
1146 if (ml_sock_gets(&sock, buf) < 0) {
1148 strcpy(dsn, "Connection broken during SMTP HELO");
1152 if (buf[0] != '2') {
1153 if (buf[0] == '4') {
1155 safestrncpy(dsn, &buf[4], 1023);
1160 safestrncpy(dsn, &buf[4], 1023);
1165 /* Do an AUTH command if necessary */
1166 if (!IsEmptyStr(mx_user)) {
1168 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
1169 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2, 0);
1170 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
1171 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1172 sock_write(&sock, buf, strlen(buf));
1173 if (ml_sock_gets(&sock, buf) < 0) {
1175 strcpy(dsn, "Connection broken during SMTP AUTH");
1178 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1179 if (buf[0] != '2') {
1180 if (buf[0] == '4') {
1182 safestrncpy(dsn, &buf[4], 1023);
1187 safestrncpy(dsn, &buf[4], 1023);
1193 /* previous command succeeded, now try the MAIL FROM: command */
1194 snprintf(buf, sizeof buf, "MAIL FROM:<%s>\r\n", envelope_from);
1195 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1196 sock_write(&sock, buf, strlen(buf));
1197 if (ml_sock_gets(&sock, buf) < 0) {
1199 strcpy(dsn, "Connection broken during SMTP MAIL");
1202 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1203 if (buf[0] != '2') {
1204 if (buf[0] == '4') {
1206 safestrncpy(dsn, &buf[4], 1023);
1211 safestrncpy(dsn, &buf[4], 1023);
1216 /* MAIL succeeded, now try the RCPT To: command */
1217 snprintf(buf, sizeof buf, "RCPT TO:<%s@%s>\r\n", user, node);
1218 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1219 sock_write(&sock, buf, strlen(buf));
1220 if (ml_sock_gets(&sock, buf) < 0) {
1222 strcpy(dsn, "Connection broken during SMTP RCPT");
1225 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1226 if (buf[0] != '2') {
1227 if (buf[0] == '4') {
1229 safestrncpy(dsn, &buf[4], 1023);
1234 safestrncpy(dsn, &buf[4], 1023);
1239 /* RCPT succeeded, now try the DATA command */
1240 CtdlLogPrintf(CTDL_DEBUG, ">DATA\n");
1241 sock_write(&sock, "DATA\r\n", 6);
1242 if (ml_sock_gets(&sock, buf) < 0) {
1244 strcpy(dsn, "Connection broken during SMTP DATA");
1247 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1248 if (buf[0] != '3') {
1249 if (buf[0] == '4') {
1251 safestrncpy(dsn, &buf[4], 1023);
1256 safestrncpy(dsn, &buf[4], 1023);
1261 /* If we reach this point, the server is expecting data.*/
1262 sock_write(&sock, msgtext, msg_size);
1263 if (msgtext[msg_size-1] != 10) {
1264 CtdlLogPrintf(CTDL_WARNING, "Possible problem: message did not "
1265 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1267 sock_write(&sock, "\r\n", 2);
1270 sock_write(&sock, ".\r\n", 3);
1271 if (ml_sock_gets(&sock, buf) < 0) {
1273 strcpy(dsn, "Connection broken during SMTP message transmit");
1276 CtdlLogPrintf(CTDL_DEBUG, "%s\n", buf);
1277 if (buf[0] != '2') {
1278 if (buf[0] == '4') {
1280 safestrncpy(dsn, &buf[4], 1023);
1285 safestrncpy(dsn, &buf[4], 1023);
1291 safestrncpy(dsn, &buf[4], 1023);
1294 CtdlLogPrintf(CTDL_DEBUG, ">QUIT\n");
1295 sock_write(&sock, "QUIT\r\n", 6);
1296 ml_sock_gets(&sock, buf);
1297 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1298 CtdlLogPrintf(CTDL_INFO, "SMTP client: delivery to <%s> @ <%s> (%s) succeeded\n",
1301 bail: free(msgtext);
1302 FreeStrBuf(&CCC->sReadBuf);
1303 FreeStrBuf(&CCC->sMigrateBuf);
1307 /* Write something to the syslog (which may or may not be where the
1308 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1310 if (enable_syslog) {
1311 syslog((LOG_MAIL | LOG_INFO),
1312 "%ld: to=<%s>, relay=%s, stat=%s",
1326 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1327 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1328 * a "bounce" message (delivery status notification).
1330 void smtp_do_bounce(char *instr) {
1338 char bounceto[1024];
1340 int num_bounces = 0;
1341 int bounce_this = 0;
1342 long bounce_msgid = (-1);
1343 time_t submitted = 0L;
1344 struct CtdlMessage *bmsg = NULL;
1346 struct recptypes *valid;
1347 int successful_bounce = 0;
1353 CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1354 strcpy(bounceto, "");
1355 sprintf(boundary, "=_Citadel_Multipart_%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
1356 lines = num_tokens(instr, '\n');
1358 /* See if it's time to give up on delivery of this message */
1359 for (i=0; i<lines; ++i) {
1360 extract_token(buf, instr, i, '\n', sizeof buf);
1361 extract_token(key, buf, 0, '|', sizeof key);
1362 extract_token(addr, buf, 1, '|', sizeof addr);
1363 if (!strcasecmp(key, "submitted")) {
1364 submitted = atol(addr);
1368 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1372 /* Start building our bounce message */
1374 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1375 if (bmsg == NULL) return;
1376 memset(bmsg, 0, sizeof(struct CtdlMessage));
1378 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1379 bmsg->cm_anon_type = MES_NORMAL;
1380 bmsg->cm_format_type = FMT_RFC822;
1381 bmsg->cm_fields['A'] = strdup("Citadel");
1382 bmsg->cm_fields['O'] = strdup(MAILROOM);
1383 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1384 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1385 bmsg->cm_fields['M'] = malloc(1024);
1387 strcpy(bmsg->cm_fields['M'], "Content-type: multipart/mixed; boundary=\"");
1388 strcat(bmsg->cm_fields['M'], boundary);
1389 strcat(bmsg->cm_fields['M'], "\"\r\n");
1390 strcat(bmsg->cm_fields['M'], "MIME-Version: 1.0\r\n");
1391 strcat(bmsg->cm_fields['M'], "X-Mailer: " CITADEL "\r\n");
1392 strcat(bmsg->cm_fields['M'], "\r\nThis is a multipart message in MIME format.\r\n\r\n");
1393 strcat(bmsg->cm_fields['M'], "--");
1394 strcat(bmsg->cm_fields['M'], boundary);
1395 strcat(bmsg->cm_fields['M'], "\r\n");
1396 strcat(bmsg->cm_fields['M'], "Content-type: text/plain\r\n\r\n");
1398 if (give_up) strcat(bmsg->cm_fields['M'],
1399 "A message you sent could not be delivered to some or all of its recipients\n"
1400 "due to prolonged unavailability of its destination(s).\n"
1401 "Giving up on the following addresses:\n\n"
1404 else strcat(bmsg->cm_fields['M'],
1405 "A message you sent could not be delivered to some or all of its recipients.\n"
1406 "The following addresses were undeliverable:\n\n"
1410 * Now go through the instructions checking for stuff.
1412 for (i=0; i<lines; ++i) {
1413 extract_token(buf, instr, i, '\n', sizeof buf);
1414 extract_token(key, buf, 0, '|', sizeof key);
1415 extract_token(addr, buf, 1, '|', sizeof addr);
1416 status = extract_int(buf, 2);
1417 extract_token(dsn, buf, 3, '|', sizeof dsn);
1420 CtdlLogPrintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1421 key, addr, status, dsn);
1423 if (!strcasecmp(key, "bounceto")) {
1424 strcpy(bounceto, addr);
1427 if (!strcasecmp(key, "msgid")) {
1428 omsgid = atol(addr);
1431 if (!strcasecmp(key, "remote")) {
1432 if (status == 5) bounce_this = 1;
1433 if (give_up) bounce_this = 1;
1439 if (bmsg->cm_fields['M'] == NULL) {
1440 CtdlLogPrintf(CTDL_ERR, "ERROR ... M field is null "
1441 "(%s:%d)\n", __FILE__, __LINE__);
1444 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1445 strlen(bmsg->cm_fields['M']) + 1024 );
1446 strcat(bmsg->cm_fields['M'], addr);
1447 strcat(bmsg->cm_fields['M'], ": ");
1448 strcat(bmsg->cm_fields['M'], dsn);
1449 strcat(bmsg->cm_fields['M'], "\r\n");
1451 remove_token(instr, i, '\n');
1457 /* Attach the original message */
1459 strcat(bmsg->cm_fields['M'], "--");
1460 strcat(bmsg->cm_fields['M'], boundary);
1461 strcat(bmsg->cm_fields['M'], "\r\n");
1462 strcat(bmsg->cm_fields['M'], "Content-type: message/rfc822\r\n");
1463 strcat(bmsg->cm_fields['M'], "Content-Transfer-Encoding: 7bit\r\n");
1464 strcat(bmsg->cm_fields['M'], "Content-Disposition: inline\r\n");
1465 strcat(bmsg->cm_fields['M'], "\r\n");
1467 CC->redirect_buffer = malloc(SIZ);
1468 CC->redirect_len = 0;
1469 CC->redirect_alloc = SIZ;
1470 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0);
1471 omsgtext = CC->redirect_buffer;
1472 omsgsize = CC->redirect_len;
1473 CC->redirect_buffer = NULL;
1474 CC->redirect_len = 0;
1475 CC->redirect_alloc = 0;
1476 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1477 (strlen(bmsg->cm_fields['M']) + omsgsize + 1024) );
1478 strcat(bmsg->cm_fields['M'], omsgtext);
1482 /* Close the multipart MIME scope */
1483 strcat(bmsg->cm_fields['M'], "--");
1484 strcat(bmsg->cm_fields['M'], boundary);
1485 strcat(bmsg->cm_fields['M'], "--\r\n");
1487 /* Deliver the bounce if there's anything worth mentioning */
1488 CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1489 if (num_bounces > 0) {
1491 /* First try the user who sent the message */
1492 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1493 if (IsEmptyStr(bounceto)) {
1494 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
1495 bounce_msgid = (-1L);
1498 /* Can we deliver the bounce to the original sender? */
1499 valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
1500 if (valid != NULL) {
1501 if (valid->num_error == 0) {
1502 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
1503 successful_bounce = 1;
1507 /* If not, post it in the Aide> room */
1508 if (successful_bounce == 0) {
1509 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
1512 /* Free up the memory we used */
1513 if (valid != NULL) {
1514 free_recipients(valid);
1518 CtdlFreeMessage(bmsg);
1519 CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
1524 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1525 * set of delivery instructions for completed deliveries and remove them.
1527 * It returns the number of incomplete deliveries remaining.
1529 int smtp_purge_completed_deliveries(char *instr) {
1540 lines = num_tokens(instr, '\n');
1541 for (i=0; i<lines; ++i) {
1542 extract_token(buf, instr, i, '\n', sizeof buf);
1543 extract_token(key, buf, 0, '|', sizeof key);
1544 extract_token(addr, buf, 1, '|', sizeof addr);
1545 status = extract_int(buf, 2);
1546 extract_token(dsn, buf, 3, '|', sizeof dsn);
1550 if (!strcasecmp(key, "remote")) {
1551 if (status == 2) completed = 1;
1556 remove_token(instr, i, '\n');
1569 * Called by smtp_do_queue() to handle an individual message.
1571 void smtp_do_procmsg(long msgnum, void *userdata) {
1572 struct CtdlMessage *msg = NULL;
1574 char *results = NULL;
1582 char envelope_from[1024];
1583 long text_msgid = (-1);
1584 int incomplete_deliveries_remaining;
1585 time_t attempted = 0L;
1586 time_t last_attempted = 0L;
1587 time_t retry = SMTP_RETRY_INTERVAL;
1589 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: smtp_do_procmsg(%ld)\n", msgnum);
1590 strcpy(envelope_from, "");
1592 msg = CtdlFetchMessage(msgnum, 1);
1594 CtdlLogPrintf(CTDL_ERR, "SMTP client: tried %ld but no such message!\n", msgnum);
1598 instr = strdup(msg->cm_fields['M']);
1599 CtdlFreeMessage(msg);
1601 /* Strip out the headers amd any other non-instruction line */
1602 lines = num_tokens(instr, '\n');
1603 for (i=0; i<lines; ++i) {
1604 extract_token(buf, instr, i, '\n', sizeof buf);
1605 if (num_tokens(buf, '|') < 2) {
1606 remove_token(instr, i, '\n');
1612 /* Learn the message ID and find out about recent delivery attempts */
1613 lines = num_tokens(instr, '\n');
1614 for (i=0; i<lines; ++i) {
1615 extract_token(buf, instr, i, '\n', sizeof buf);
1616 extract_token(key, buf, 0, '|', sizeof key);
1617 if (!strcasecmp(key, "msgid")) {
1618 text_msgid = extract_long(buf, 1);
1620 if (!strcasecmp(key, "envelope_from")) {
1621 extract_token(envelope_from, buf, 1, '|', sizeof envelope_from);
1623 if (!strcasecmp(key, "retry")) {
1624 /* double the retry interval after each attempt */
1625 retry = extract_long(buf, 1) * 2L;
1626 if (retry > SMTP_RETRY_MAX) {
1627 retry = SMTP_RETRY_MAX;
1629 remove_token(instr, i, '\n');
1631 if (!strcasecmp(key, "attempted")) {
1632 attempted = extract_long(buf, 1);
1633 if (attempted > last_attempted)
1634 last_attempted = attempted;
1639 * Postpone delivery if we've already tried recently.
1641 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1642 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1649 * Bail out if there's no actual message associated with this
1651 if (text_msgid < 0L) {
1652 CtdlLogPrintf(CTDL_ERR, "SMTP client: no 'msgid' directive found!\n");
1657 /* Plow through the instructions looking for 'remote' directives and
1658 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1659 * were experienced and it's time to try again)
1661 lines = num_tokens(instr, '\n');
1662 for (i=0; i<lines; ++i) {
1663 extract_token(buf, instr, i, '\n', sizeof buf);
1664 extract_token(key, buf, 0, '|', sizeof key);
1665 extract_token(addr, buf, 1, '|', sizeof addr);
1666 status = extract_int(buf, 2);
1667 extract_token(dsn, buf, 3, '|', sizeof dsn);
1668 if ( (!strcasecmp(key, "remote"))
1669 && ((status==0)||(status==3)||(status==4)) ) {
1671 /* Remove this "remote" instruction from the set,
1672 * but replace the set's final newline if
1673 * remove_token() stripped it. It has to be there.
1675 remove_token(instr, i, '\n');
1676 if (instr[strlen(instr)-1] != '\n') {
1677 strcat(instr, "\n");
1682 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Trying <%s>\n", addr);
1683 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid, envelope_from);
1685 if (results == NULL) {
1686 results = malloc(1024);
1687 memset(results, 0, 1024);
1690 results = realloc(results, strlen(results) + 1024);
1692 snprintf(&results[strlen(results)], 1024,
1694 key, addr, status, dsn);
1699 if (results != NULL) {
1700 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1701 strcat(instr, results);
1706 /* Generate 'bounce' messages */
1707 smtp_do_bounce(instr);
1709 /* Go through the delivery list, deleting completed deliveries */
1710 incomplete_deliveries_remaining =
1711 smtp_purge_completed_deliveries(instr);
1715 * No delivery instructions remain, so delete both the instructions
1716 * message and the message message.
1718 if (incomplete_deliveries_remaining <= 0) {
1720 delmsgs[0] = msgnum;
1721 delmsgs[1] = text_msgid;
1722 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
1726 * Uncompleted delivery instructions remain, so delete the old
1727 * instructions and replace with the updated ones.
1729 if (incomplete_deliveries_remaining > 0) {
1730 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
1731 msg = malloc(sizeof(struct CtdlMessage));
1732 memset(msg, 0, sizeof(struct CtdlMessage));
1733 msg->cm_magic = CTDLMESSAGE_MAGIC;
1734 msg->cm_anon_type = MES_NORMAL;
1735 msg->cm_format_type = FMT_RFC822;
1736 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1737 snprintf(msg->cm_fields['M'],
1739 "Content-type: %s\n\n%s\n"
1742 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1743 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
1744 CtdlFreeMessage(msg);
1756 * Run through the queue sending out messages.
1758 void *smtp_do_queue(void *arg) {
1759 int num_processed = 0;
1760 struct CitContext smtp_queue_CC;
1762 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1764 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1765 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC );
1767 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1768 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1771 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1774 citthread_mutex_unlock (&smtp_send_lock);
1775 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1784 * Create a thread to run the SMTP queue
1786 * This was created as a response to a situation seen on Uncensored where a bad remote was holding
1787 * up SMTP sending for long times.
1788 * Converting to a thread does not fix the problem caused by the bad remote but it does prevent
1789 * the SMTP sending from stopping housekeeping and the EVT_TIMER event system which in turn prevented
1790 * other things from happening.
1792 void smtp_queue_thread (void)
1794 if (citthread_mutex_trylock (&smtp_send_lock)) {
1795 CtdlLogPrintf(CTDL_DEBUG, "SMTP queue run already in progress\n");
1798 CtdlThreadCreate("SMTP Send", CTDLTHREAD_BIGSTACK, smtp_do_queue, NULL);
1804 void smtp_server_going_down (void)
1806 CtdlLogPrintf(CTDL_DEBUG, "SMTP module clean up for shutdown.\n");
1808 citthread_mutex_destroy (&smtp_send_lock);
1813 /*****************************************************************************/
1814 /* SMTP UTILITY COMMANDS */
1815 /*****************************************************************************/
1817 void cmd_smtp(char *argbuf) {
1824 if (CtdlAccessCheck(ac_aide)) return;
1826 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1828 if (!strcasecmp(cmd, "mx")) {
1829 extract_token(node, argbuf, 1, '|', sizeof node);
1830 num_mxhosts = getmx(buf, node);
1831 cprintf("%d %d MX hosts listed for %s\n",
1832 LISTING_FOLLOWS, num_mxhosts, node);
1833 for (i=0; i<num_mxhosts; ++i) {
1834 extract_token(node, buf, i, '|', sizeof node);
1835 cprintf("%s\n", node);
1841 else if (!strcasecmp(cmd, "runqueue")) {
1843 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1848 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1855 * Initialize the SMTP outbound queue
1857 void smtp_init_spoolout(void) {
1858 struct ctdlroom qrbuf;
1861 * Create the room. This will silently fail if the room already
1862 * exists, and that's perfectly ok, because we want it to exist.
1864 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1867 * Make sure it's set to be a "system room" so it doesn't show up
1868 * in the <K>nown rooms list for Aides.
1870 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1871 qrbuf.QRflags2 |= QR2_SYSTEM;
1872 CtdlPutRoomLock(&qrbuf);
1879 /*****************************************************************************/
1880 /* MODULE INITIALIZATION STUFF */
1881 /*****************************************************************************/
1883 * This cleanup function blows away the temporary memory used by
1886 void smtp_cleanup_function(void) {
1888 /* Don't do this stuff if this is not an SMTP session! */
1889 if (CC->h_command_function != smtp_command_loop) return;
1891 CtdlLogPrintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1897 const char *CitadelServiceSMTP_MTA="SMTP-MTA";
1898 const char *CitadelServiceSMTPS_MTA="SMTPs-MTA";
1899 const char *CitadelServiceSMTP_MSA="SMTP-MSA";
1900 const char *CitadelServiceSMTP_LMTP="LMTP";
1901 const char *CitadelServiceSMTP_LMTP_UNF="LMTP-UnF";
1903 CTDL_MODULE_INIT(smtp)
1907 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1912 CitadelServiceSMTP_MTA);
1915 CtdlRegisterServiceHook(config.c_smtps_port,
1920 CitadelServiceSMTPS_MTA);
1923 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1928 CitadelServiceSMTP_MSA);
1930 CtdlRegisterServiceHook(0, /* local LMTP */
1935 CitadelServiceSMTP_LMTP);
1937 CtdlRegisterServiceHook(0, /* local LMTP */
1938 file_lmtp_unfiltered_socket,
1939 lmtp_unfiltered_greeting,
1942 CitadelServiceSMTP_LMTP_UNF);
1944 smtp_init_spoolout();
1945 CtdlRegisterSessionHook(smtp_queue_thread, EVT_TIMER);
1946 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1947 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1948 CtdlRegisterCleanupHook (smtp_server_going_down);
1949 citthread_mutex_init (&smtp_send_lock, NULL);
1952 /* return our Subversion id for the Log */