2 * This module is an SMTP and ESMTP implementation for the Citadel system.
3 * It is compliant with all of the following:
5 * RFC 821 - Simple Mail Transfer Protocol
6 * RFC 876 - Survey of SMTP Implementations
7 * RFC 1047 - Duplicate messages and SMTP
8 * RFC 1652 - 8 bit MIME
9 * RFC 1869 - Extended Simple Mail Transfer Protocol
10 * RFC 1870 - SMTP Service Extension for Message Size Declaration
11 * RFC 2033 - Local Mail Transfer Protocol
12 * RFC 2197 - SMTP Service Extension for Command Pipelining
13 * RFC 2476 - Message Submission
14 * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
15 * RFC 2554 - SMTP Service Extension for Authentication
16 * RFC 2821 - Simple Mail Transfer Protocol
17 * RFC 2822 - Internet Message Format
18 * RFC 2920 - SMTP Service Extension for Command Pipelining
20 * The VRFY and EXPN commands have been removed from this implementation
21 * because nobody uses these commands anymore, except for spammers.
23 * Copyright (c) 1998-2009 by the citadel.org team
25 * This program is free software; you can redistribute it and/or modify
26 * it under the terms of the GNU General Public License as published by
27 * the Free Software Foundation; either version 3 of the License, or
28 * (at your option) any later version.
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 * GNU General Public License for more details.
35 * You should have received a copy of the GNU General Public License
36 * along with this program; if not, write to the Free Software
37 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
48 #include <sys/types.h>
51 #if TIME_WITH_SYS_TIME
52 # include <sys/time.h>
56 # include <sys/time.h>
66 #include <sys/socket.h>
67 #include <netinet/in.h>
68 #include <arpa/inet.h>
69 #include <libcitadel.h>
72 #include "citserver.h"
79 #include "internet_addressing.h"
82 #include "clientsocket.h"
83 #include "locate_host.h"
84 #include "citadel_dirs.h"
93 #include "ctdl_module.h"
97 typedef struct _citsmtp { /* Information about the current session */
101 char recipients[SIZ];
102 int number_of_recipients;
104 int message_originated_locally;
110 enum { /* Command states for login authentication */
117 #define SMTP ((citsmtp *)CC->session_specific_data)
120 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
122 citthread_mutex_t smtp_send_lock;
125 /*****************************************************************************/
126 /* SMTP SERVER (INBOUND) STUFF */
127 /*****************************************************************************/
131 * Here's where our SMTP session begins its happy day.
133 void smtp_greeting(int is_msa)
136 char message_to_spammer[1024];
138 strcpy(CC->cs_clientname, "SMTP session");
139 CC->internal_pgm = 1;
140 CC->cs_flags |= CS_STEALTH;
141 CC->session_specific_data = malloc(sizeof(citsmtp));
142 memset(SMTP, 0, sizeof(citsmtp));
144 sSMTP->is_msa = is_msa;
146 /* If this config option is set, reject connections from problem
147 * addresses immediately instead of after they execute a RCPT
149 if ( (config.c_rbl_at_greeting) && (sSMTP->is_msa == 0) ) {
150 if (rbl_check(message_to_spammer)) {
151 if (CtdlThreadCheckStop())
152 cprintf("421 %s\r\n", message_to_spammer);
154 cprintf("550 %s\r\n", message_to_spammer);
156 /* no need to free_recipients(valid), it's not allocated yet */
161 /* Otherwise we're either clean or we check later. */
163 if (CC->nologin==1) {
164 cprintf("500 Too many users are already online (maximum is %d)\r\n",
168 /* no need to free_recipients(valid), it's not allocated yet */
172 /* Note: the FQDN *must* appear as the first thing after the 220 code.
173 * Some clients (including citmail.c) depend on it being there.
175 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
180 * SMTPS is just like SMTP, except it goes crypto right away.
182 void smtps_greeting(void) {
183 CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
185 if (!CC->redirect_ssl) CC->kill_me = 1; /* kill session if no crypto */
192 * SMTP MSA port requires authentication.
194 void smtp_msa_greeting(void) {
200 * LMTP is like SMTP but with some extra bonus footage added.
202 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) {
228 sSMTP->is_unfiltered = 1;
233 * Login greeting common to all auth methods
235 void smtp_auth_greeting(void) {
236 cprintf("235 Hello, %s\r\n", CC->user.fullname);
237 CtdlLogPrintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
238 CC->internal_pgm = 0;
239 CC->cs_flags &= ~CS_STEALTH;
244 * Implement HELO and EHLO commands.
246 * which_command: 0=HELO, 1=EHLO, 2=LHLO
248 void smtp_hello(char *argbuf, int which_command) {
249 citsmtp *sSMTP = SMTP;
251 safestrncpy(sSMTP->helo_node, argbuf, sizeof sSMTP->helo_node);
253 if ( (which_command != 2) && (sSMTP->is_lmtp) ) {
254 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
258 if ( (which_command == 2) && (sSMTP->is_lmtp == 0) ) {
259 cprintf("500 LHLO is only allowed when running LMTP\r\n");
263 if (which_command == 0) {
264 cprintf("250 Hello %s (%s [%s])\r\n",
271 if (which_command == 1) {
272 cprintf("250-Hello %s (%s [%s])\r\n",
279 cprintf("250-Greetings and joyous salutations.\r\n");
281 cprintf("250-HELP\r\n");
282 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
286 * Offer TLS, but only if TLS is not already active.
287 * Furthermore, only offer TLS when running on
288 * the SMTP-MSA port, not on the SMTP-MTA port, due to
289 * questionable reliability of TLS in certain sending MTA's.
291 if ( (!CC->redirect_ssl) && (sSMTP->is_msa) ) {
292 cprintf("250-STARTTLS\r\n");
294 #endif /* HAVE_OPENSSL */
296 cprintf("250-AUTH LOGIN PLAIN\r\n"
297 "250-AUTH=LOGIN PLAIN\r\n"
306 * Implement HELP command.
308 void smtp_help(void) {
309 cprintf("214 RTFM http://www.ietf.org/rfc/rfc2821.txt\r\n");
316 void smtp_get_user(char *argbuf) {
319 citsmtp *sSMTP = SMTP;
321 CtdlDecodeBase64(username, argbuf, SIZ);
322 /* CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", username); */
323 if (CtdlLoginExistingUser(NULL, username) == login_ok) {
324 CtdlEncodeBase64(buf, "Password:", 9, 0);
325 cprintf("334 %s\r\n", buf);
326 sSMTP->command_state = smtp_password;
329 cprintf("500 No such user.\r\n");
330 sSMTP->command_state = smtp_command;
338 void smtp_get_pass(char *argbuf) {
342 memset(password, 0, sizeof(password));
343 len = CtdlDecodeBase64(password, argbuf, SIZ);
344 /* CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", password); */
345 if (CtdlTryPassword(password, len) == pass_ok) {
346 smtp_auth_greeting();
349 cprintf("535 Authentication failed.\r\n");
351 SMTP->command_state = smtp_command;
356 * Back end for PLAIN auth method (either inline or multistate)
358 void smtp_try_plain(char *encoded_authstring) {
359 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 len = safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
371 len = sizeof(pass) - 1;
373 SMTP->command_state = smtp_command;
375 if (!IsEmptyStr(ident)) {
376 result = CtdlLoginExistingUser(user, ident);
379 result = CtdlLoginExistingUser(NULL, user);
382 if (result == login_ok) {
383 if (CtdlTryPassword(pass, len) == pass_ok) {
384 smtp_auth_greeting();
388 cprintf("504 Authentication failed.\r\n");
393 * Attempt to perform authenticated SMTP
395 void smtp_auth(char *argbuf) {
396 char username_prompt[64];
398 char encoded_authstring[1024];
401 cprintf("504 Already logged in.\r\n");
405 extract_token(method, argbuf, 0, ' ', sizeof method);
407 if (!strncasecmp(method, "login", 5) ) {
408 if (strlen(argbuf) >= 7) {
409 smtp_get_user(&argbuf[6]);
412 CtdlEncodeBase64(username_prompt, "Username:", 9, 0);
413 cprintf("334 %s\r\n", username_prompt);
414 SMTP->command_state = smtp_user;
419 if (!strncasecmp(method, "plain", 5) ) {
420 if (num_tokens(argbuf, ' ') < 2) {
422 SMTP->command_state = smtp_plain;
426 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
428 smtp_try_plain(encoded_authstring);
432 if (strncasecmp(method, "login", 5) ) {
433 cprintf("504 Unknown authentication method.\r\n");
441 * Implements the RSET (reset state) command.
442 * Currently this just zeroes out the state buffer. If pointers to data
443 * allocated with malloc() are ever placed in the state buffer, we have to
444 * be sure to free() them first!
446 * Set do_response to nonzero to output the SMTP RSET response code.
448 void smtp_rset(int do_response) {
451 citsmtp *sSMTP = SMTP;
454 * Our entire SMTP state is discarded when a RSET command is issued,
455 * but we need to preserve this one little piece of information, so
456 * we save it for later.
458 is_lmtp = sSMTP->is_lmtp;
459 is_unfiltered = sSMTP->is_unfiltered;
461 memset(sSMTP, 0, sizeof(citsmtp));
464 * It is somewhat ambiguous whether we want to log out when a RSET
465 * command is issued. Here's the code to do it. It is commented out
466 * because some clients (such as Pine) issue RSET commands before
467 * each message, but still expect to be logged in.
469 * if (CC->logged_in) {
475 * Reinstate this little piece of information we saved (see above).
477 sSMTP->is_lmtp = is_lmtp;
478 sSMTP->is_unfiltered = is_unfiltered;
481 cprintf("250 Zap!\r\n");
486 * Clear out the portions of the state buffer that need to be cleared out
487 * after the DATA command finishes.
489 void smtp_data_clear(void) {
490 citsmtp *sSMTP = SMTP;
492 strcpy(sSMTP->from, "");
493 strcpy(sSMTP->recipients, "");
494 sSMTP->number_of_recipients = 0;
495 sSMTP->delivery_mode = 0;
496 sSMTP->message_originated_locally = 0;
499 const char *smtp_get_Recipients(void)
501 citsmtp *sSMTP = SMTP;
505 else return sSMTP->from;
509 * Implements the "MAIL FROM:" command
511 void smtp_mail(char *argbuf) {
515 citsmtp *sSMTP = SMTP;
517 if (!IsEmptyStr(sSMTP->from)) {
518 cprintf("503 Only one sender permitted\r\n");
522 if (strncasecmp(argbuf, "From:", 5)) {
523 cprintf("501 Syntax error\r\n");
527 strcpy(sSMTP->from, &argbuf[5]);
528 striplt(sSMTP->from);
529 if (haschar(sSMTP->from, '<') > 0) {
530 stripallbut(sSMTP->from, '<', '>');
533 /* We used to reject empty sender names, until it was brought to our
534 * attention that RFC1123 5.2.9 requires that this be allowed. So now
535 * we allow it, but replace the empty string with a fake
536 * address so we don't have to contend with the empty string causing
537 * other code to fail when it's expecting something there.
539 if (IsEmptyStr(sSMTP->from)) {
540 strcpy(sSMTP->from, "someone@example.com");
543 /* If this SMTP connection is from a logged-in user, force the 'from'
544 * to be the user's Internet e-mail address as Citadel knows it.
547 safestrncpy(sSMTP->from, CC->cs_inet_email, sizeof sSMTP->from);
548 cprintf("250 Sender ok <%s>\r\n", sSMTP->from);
549 sSMTP->message_originated_locally = 1;
553 else if (sSMTP->is_lmtp) {
554 /* Bypass forgery checking for LMTP */
557 /* Otherwise, make sure outsiders aren't trying to forge mail from
558 * this system (unless, of course, c_allow_spoofing is enabled)
560 else if (config.c_allow_spoofing == 0) {
561 process_rfc822_addr(sSMTP->from, user, node, name);
562 if (CtdlHostAlias(node) != hostalias_nomatch) {
563 cprintf("550 You must log in to send mail from %s\r\n", node);
564 strcpy(sSMTP->from, "");
569 cprintf("250 Sender ok\r\n");
575 * Implements the "RCPT To:" command
577 void smtp_rcpt(char *argbuf) {
579 char message_to_spammer[SIZ];
580 struct recptypes *valid = NULL;
581 citsmtp *sSMTP = SMTP;
583 if (IsEmptyStr(sSMTP->from)) {
584 cprintf("503 Need MAIL before RCPT\r\n");
588 if (strncasecmp(argbuf, "To:", 3)) {
589 cprintf("501 Syntax error\r\n");
593 if ( (sSMTP->is_msa) && (!CC->logged_in) ) {
594 cprintf("550 You must log in to send mail on this port.\r\n");
595 strcpy(sSMTP->from, "");
599 safestrncpy(recp, &argbuf[3], sizeof recp);
601 stripallbut(recp, '<', '>');
603 if ( (strlen(recp) + strlen(sSMTP->recipients) + 1 ) >= SIZ) {
604 cprintf("452 Too many recipients\r\n");
609 if ( (!CC->logged_in) /* Don't RBL authenticated users */
610 && (!sSMTP->is_lmtp) ) { /* Don't RBL LMTP clients */
611 if (config.c_rbl_at_greeting == 0) { /* Don't RBL again if we already did it */
612 if (rbl_check(message_to_spammer)) {
613 if (CtdlThreadCheckStop())
614 cprintf("421 %s\r\n", message_to_spammer);
616 cprintf("550 %s\r\n", message_to_spammer);
617 /* no need to free_recipients(valid), it's not allocated yet */
623 valid = validate_recipients(recp,
624 smtp_get_Recipients (),
625 (sSMTP->is_lmtp)? POST_LMTP:
626 (CC->logged_in)? POST_LOGGED_IN:
628 if (valid->num_error != 0) {
629 cprintf("550 %s\r\n", valid->errormsg);
630 free_recipients(valid);
634 if (valid->num_internet > 0) {
636 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
637 cprintf("551 <%s> - you do not have permission to send Internet mail\r\n", recp);
638 free_recipients(valid);
644 if (valid->num_internet > 0) {
645 if ( (sSMTP->message_originated_locally == 0)
646 && (sSMTP->is_lmtp == 0) ) {
647 cprintf("551 <%s> - relaying denied\r\n", recp);
648 free_recipients(valid);
653 cprintf("250 RCPT ok <%s>\r\n", recp);
654 if (!IsEmptyStr(sSMTP->recipients)) {
655 strcat(sSMTP->recipients, ",");
657 strcat(sSMTP->recipients, recp);
658 sSMTP->number_of_recipients += 1;
660 free_recipients(valid);
668 * Implements the DATA command
670 void smtp_data(void) {
672 char *defbody; //TODO: remove me
673 struct CtdlMessage *msg = NULL;
676 struct recptypes *valid;
680 citsmtp *sSMTP = SMTP;
682 if (IsEmptyStr(sSMTP->from)) {
683 cprintf("503 Need MAIL command first.\r\n");
687 if (sSMTP->number_of_recipients < 1) {
688 cprintf("503 Need RCPT command first.\r\n");
692 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
694 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
695 defbody = malloc(4096);
697 if (defbody != NULL) {
698 if (sSMTP->is_lmtp && (CC->cs_UDSclientUID != -1)) {
699 snprintf(defbody, 4096,
700 "Received: from %s (Citadel from userid %ld)\n"
703 (long int) CC->cs_UDSclientUID,
708 snprintf(defbody, 4096,
709 "Received: from %s (%s [%s])\n"
718 body = CtdlReadMessageBodyBuf(HKEY("."), config.c_maxmsglen, defbody, 1, NULL);
720 cprintf("550 Unable to save message: internal error.\r\n");
724 CtdlLogPrintf(CTDL_DEBUG, "Converting message...\n");
725 msg = convert_internet_message_buf(&body);
727 /* If the user is locally authenticated, FORCE the From: header to
728 * show up as the real sender. Yes, this violates the RFC standard,
729 * but IT MAKES SENSE. If you prefer strict RFC adherence over
730 * common sense, you can disable this in the configuration.
732 * We also set the "message room name" ('O' field) to MAILROOM
733 * (which is Mail> on most systems) to prevent it from getting set
734 * to something ugly like "0000058008.Sent Items>" when the message
735 * is read with a Citadel client.
737 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
738 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
739 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
740 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
741 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
742 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
743 msg->cm_fields['A'] = strdup(CC->user.fullname);
744 msg->cm_fields['N'] = strdup(config.c_nodename);
745 msg->cm_fields['H'] = strdup(config.c_humannode);
746 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
747 msg->cm_fields['O'] = strdup(MAILROOM);
750 /* Set the "envelope from" address */
751 if (msg->cm_fields['P'] != NULL) {
752 free(msg->cm_fields['P']);
754 msg->cm_fields['P'] = strdup(sSMTP->from);
756 /* Set the "envelope to" address */
757 if (msg->cm_fields['V'] != NULL) {
758 free(msg->cm_fields['V']);
760 msg->cm_fields['V'] = strdup(sSMTP->recipients);
762 /* Submit the message into the Citadel system. */
763 valid = validate_recipients(sSMTP->recipients,
764 smtp_get_Recipients (),
765 (sSMTP->is_lmtp)? POST_LMTP:
766 (CC->logged_in)? POST_LOGGED_IN:
769 /* If there are modules that want to scan this message before final
770 * submission (such as virus checkers or spam filters), call them now
771 * and give them an opportunity to reject the message.
773 if (sSMTP->is_unfiltered) {
777 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
780 if (scan_errors > 0) { /* We don't want this message! */
782 if (msg->cm_fields['0'] == NULL) {
783 msg->cm_fields['0'] = strdup("Message rejected by filter");
786 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
789 else { /* Ok, we'll accept this message. */
790 msgnum = CtdlSubmitMsg(msg, valid, "", 0);
792 sprintf(result, "250 Message accepted.\r\n");
795 sprintf(result, "550 Internal delivery error\r\n");
799 /* For SMTP and ESTMP, just print the result message. For LMTP, we
800 * have to print one result message for each recipient. Since there
801 * is nothing in Citadel which would cause different recipients to
802 * have different results, we can get away with just spitting out the
803 * same message once for each recipient.
805 if (sSMTP->is_lmtp) {
806 for (i=0; i<sSMTP->number_of_recipients; ++i) {
807 cprintf("%s", result);
811 cprintf("%s", result);
814 /* Write something to the syslog (which may or may not be where the
815 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
818 syslog((LOG_MAIL | LOG_INFO),
819 "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
822 sSMTP->number_of_recipients,
830 CtdlFreeMessage(msg);
831 free_recipients(valid);
832 smtp_data_clear(); /* clear out the buffers now */
837 * implements the STARTTLS command (Citadel API version)
839 void smtp_starttls(void)
841 char ok_response[SIZ];
842 char nosup_response[SIZ];
843 char error_response[SIZ];
846 "220 Begin TLS negotiation now\r\n");
847 sprintf(nosup_response,
848 "554 TLS not supported here\r\n");
849 sprintf(error_response,
850 "554 Internal error\r\n");
851 CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
858 * Main command loop for SMTP sessions.
860 void smtp_command_loop(void) {
862 citsmtp *sSMTP = SMTP;
865 CtdlLogPrintf(CTDL_EMERG, "Session SMTP data is null. WTF? We will crash now.\n");
869 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
870 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
871 CtdlLogPrintf(CTDL_CRIT, "Client disconnected: ending session.\n");
875 CtdlLogPrintf(CTDL_INFO, "SMTP server: %s\n", cmdbuf);
876 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
878 if (sSMTP->command_state == smtp_user) {
879 smtp_get_user(cmdbuf);
882 else if (sSMTP->command_state == smtp_password) {
883 smtp_get_pass(cmdbuf);
886 else if (sSMTP->command_state == smtp_plain) {
887 smtp_try_plain(cmdbuf);
890 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
891 smtp_auth(&cmdbuf[5]);
894 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
898 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
899 smtp_hello(&cmdbuf[5], 0);
902 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
903 smtp_hello(&cmdbuf[5], 1);
906 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
907 smtp_hello(&cmdbuf[5], 2);
910 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
914 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
915 smtp_mail(&cmdbuf[5]);
918 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
919 cprintf("250 NOOP\r\n");
922 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
923 cprintf("221 Goodbye...\r\n");
928 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
929 smtp_rcpt(&cmdbuf[5]);
932 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
936 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
941 cprintf("502 I'm afraid I can't do that.\r\n");
950 /*****************************************************************************/
951 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
952 /*****************************************************************************/
959 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
962 void smtp_try(const char *key, const char *addr, int *status,
963 char *dsn, size_t n, long msgnum, char *envelope_from)
970 char user[1024], node[1024], name[1024];
985 /* Parse out the host portion of the recipient address */
986 process_rfc822_addr(addr, user, node, name);
988 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Attempting delivery to <%s> @ <%s> (%s)\n",
991 /* Load the message out of the database */
992 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
993 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
994 msg_size = StrLength(CC->redirect_buffer);
995 msgtext = SmashStrBuf(&CC->redirect_buffer);
997 /* If no envelope_from is supplied, extract one from the message */
998 if ( (envelope_from == NULL) || (IsEmptyStr(envelope_from)) ) {
999 strcpy(mailfrom, "");
1003 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
1006 if (!strncasecmp(buf, "From:", 5)) {
1007 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
1009 for (i=0; mailfrom[i]; ++i) {
1010 if (!isprint(mailfrom[i])) {
1011 strcpy(&mailfrom[i], &mailfrom[i+1]);
1016 /* Strip out parenthesized names */
1019 for (i=0; mailfrom[i]; ++i) {
1020 if (mailfrom[i] == '(') lp = i;
1021 if (mailfrom[i] == ')') rp = i;
1023 if ((lp>0)&&(rp>lp)) {
1024 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
1027 /* Prefer brokketized names */
1030 for (i=0; mailfrom[i]; ++i) {
1031 if (mailfrom[i] == '<') lp = i;
1032 if (mailfrom[i] == '>') rp = i;
1034 if ( (lp>=0) && (rp>lp) ) {
1036 strcpy(mailfrom, &mailfrom[lp]);
1041 } while (scan_done == 0);
1042 if (IsEmptyStr(mailfrom)) strcpy(mailfrom, "someone@somewhere.org");
1043 stripallbut(mailfrom, '<', '>');
1044 envelope_from = mailfrom;
1047 /* Figure out what mail exchanger host we have to connect to */
1048 num_mxhosts = getmx(mxhosts, node);
1049 CtdlLogPrintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d [%s]\n", node, num_mxhosts, mxhosts);
1050 if (num_mxhosts < 1) {
1052 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1057 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1059 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1060 strcpy(mx_user, "");
1061 strcpy(mx_pass, "");
1062 if (num_tokens(buf, '@') > 1) {
1063 strcpy (mx_user, buf);
1064 endpart = strrchr(mx_user, '@');
1066 strcpy (mx_host, endpart + 1);
1067 endpart = strrchr(mx_user, ':');
1068 if (endpart != NULL) {
1069 strcpy(mx_pass, endpart+1);
1074 strcpy (mx_host, buf);
1075 endpart = strrchr(mx_host, ':');
1078 strcpy(mx_port, endpart + 1);
1081 strcpy(mx_port, "25");
1083 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connecting to %s : %s ...\n", mx_host, mx_port);
1084 sock = sock_connect(mx_host, mx_port);
1085 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1086 if (sock >= 0) CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connected!\n");
1089 snprintf(dsn, SIZ, "%s", strerror(errno));
1092 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
1098 *status = 4; /* dsn is already filled in */
1102 CCC->sReadBuf = NewStrBuf();
1103 CCC->sMigrateBuf = NewStrBuf();
1106 /* Process the SMTP greeting from the server */
1107 if (ml_sock_gets(&sock, buf) < 0) {
1109 strcpy(dsn, "Connection broken during SMTP conversation");
1112 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1113 if (buf[0] != '2') {
1114 if (buf[0] == '4') {
1116 safestrncpy(dsn, &buf[4], 1023);
1121 safestrncpy(dsn, &buf[4], 1023);
1126 /* At this point we know we are talking to a real SMTP server */
1128 /* Do a EHLO command. If it fails, try the HELO command. */
1129 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1130 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1131 sock_write(&sock, buf, strlen(buf));
1132 if (ml_sock_gets(&sock, buf) < 0) {
1134 strcpy(dsn, "Connection broken during SMTP HELO");
1137 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1138 if (buf[0] != '2') {
1139 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1140 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1141 sock_write(&sock, buf, strlen(buf));
1142 if (ml_sock_gets(&sock, buf) < 0) {
1144 strcpy(dsn, "Connection broken during SMTP HELO");
1148 if (buf[0] != '2') {
1149 if (buf[0] == '4') {
1151 safestrncpy(dsn, &buf[4], 1023);
1156 safestrncpy(dsn, &buf[4], 1023);
1161 /* Do an AUTH command if necessary */
1162 if (!IsEmptyStr(mx_user)) {
1164 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
1165 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2, 0);
1166 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
1167 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1168 sock_write(&sock, buf, strlen(buf));
1169 if (ml_sock_gets(&sock, buf) < 0) {
1171 strcpy(dsn, "Connection broken during SMTP AUTH");
1174 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1175 if (buf[0] != '2') {
1176 if (buf[0] == '4') {
1178 safestrncpy(dsn, &buf[4], 1023);
1183 safestrncpy(dsn, &buf[4], 1023);
1189 /* previous command succeeded, now try the MAIL FROM: command */
1190 snprintf(buf, sizeof buf, "MAIL FROM:<%s>\r\n", envelope_from);
1191 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1192 sock_write(&sock, buf, strlen(buf));
1193 if (ml_sock_gets(&sock, buf) < 0) {
1195 strcpy(dsn, "Connection broken during SMTP MAIL");
1198 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1199 if (buf[0] != '2') {
1200 if (buf[0] == '4') {
1202 safestrncpy(dsn, &buf[4], 1023);
1207 safestrncpy(dsn, &buf[4], 1023);
1212 /* MAIL succeeded, now try the RCPT To: command */
1213 snprintf(buf, sizeof buf, "RCPT TO:<%s@%s>\r\n", user, node);
1214 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1215 sock_write(&sock, buf, strlen(buf));
1216 if (ml_sock_gets(&sock, buf) < 0) {
1218 strcpy(dsn, "Connection broken during SMTP RCPT");
1221 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1222 if (buf[0] != '2') {
1223 if (buf[0] == '4') {
1225 safestrncpy(dsn, &buf[4], 1023);
1230 safestrncpy(dsn, &buf[4], 1023);
1235 /* RCPT succeeded, now try the DATA command */
1236 CtdlLogPrintf(CTDL_DEBUG, ">DATA\n");
1237 sock_write(&sock, "DATA\r\n", 6);
1238 if (ml_sock_gets(&sock, buf) < 0) {
1240 strcpy(dsn, "Connection broken during SMTP DATA");
1243 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1244 if (buf[0] != '3') {
1245 if (buf[0] == '4') {
1247 safestrncpy(dsn, &buf[4], 1023);
1252 safestrncpy(dsn, &buf[4], 1023);
1257 /* If we reach this point, the server is expecting data.*/
1258 sock_write(&sock, msgtext, msg_size);
1259 if (msgtext[msg_size-1] != 10) {
1260 CtdlLogPrintf(CTDL_WARNING, "Possible problem: message did not "
1261 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1263 sock_write(&sock, "\r\n", 2);
1266 sock_write(&sock, ".\r\n", 3);
1267 if (ml_sock_gets(&sock, buf) < 0) {
1269 strcpy(dsn, "Connection broken during SMTP message transmit");
1272 CtdlLogPrintf(CTDL_DEBUG, "%s\n", buf);
1273 if (buf[0] != '2') {
1274 if (buf[0] == '4') {
1276 safestrncpy(dsn, &buf[4], 1023);
1281 safestrncpy(dsn, &buf[4], 1023);
1287 safestrncpy(dsn, &buf[4], 1023);
1290 CtdlLogPrintf(CTDL_DEBUG, ">QUIT\n");
1291 sock_write(&sock, "QUIT\r\n", 6);
1292 ml_sock_gets(&sock, buf);
1293 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1294 CtdlLogPrintf(CTDL_INFO, "SMTP client: delivery to <%s> @ <%s> (%s) succeeded\n",
1297 bail: free(msgtext);
1298 FreeStrBuf(&CCC->sReadBuf);
1299 FreeStrBuf(&CCC->sMigrateBuf);
1303 /* Write something to the syslog (which may or may not be where the
1304 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1306 if (enable_syslog) {
1307 syslog((LOG_MAIL | LOG_INFO),
1308 "%ld: to=<%s>, relay=%s, stat=%s",
1322 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1323 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1324 * a "bounce" message (delivery status notification).
1326 void smtp_do_bounce(char *instr) {
1334 char bounceto[1024];
1336 int num_bounces = 0;
1337 int bounce_this = 0;
1338 long bounce_msgid = (-1);
1339 time_t submitted = 0L;
1340 struct CtdlMessage *bmsg = NULL;
1342 struct recptypes *valid;
1343 int successful_bounce = 0;
1348 CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1349 strcpy(bounceto, "");
1350 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
1351 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
1352 lines = num_tokens(instr, '\n');
1354 /* See if it's time to give up on delivery of this message */
1355 for (i=0; i<lines; ++i) {
1356 extract_token(buf, instr, i, '\n', sizeof buf);
1357 extract_token(key, buf, 0, '|', sizeof key);
1358 extract_token(addr, buf, 1, '|', sizeof addr);
1359 if (!strcasecmp(key, "submitted")) {
1360 submitted = atol(addr);
1364 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1368 /* Start building our bounce message */
1370 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1371 if (bmsg == NULL) return;
1372 memset(bmsg, 0, sizeof(struct CtdlMessage));
1373 BounceMB = NewStrBufPlain(NULL, 1024);
1375 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1376 bmsg->cm_anon_type = MES_NORMAL;
1377 bmsg->cm_format_type = FMT_RFC822;
1378 bmsg->cm_fields['A'] = strdup("Citadel");
1379 bmsg->cm_fields['O'] = strdup(MAILROOM);
1380 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1381 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1382 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
1383 StrBufAppendBuf(BounceMB, boundary, 0);
1384 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
1385 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
1386 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
1387 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
1388 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1389 StrBufAppendBuf(BounceMB, boundary, 0);
1390 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1391 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
1393 if (give_up) StrBufAppendBufPlain(BounceMB, HKEY(
1394 "A message you sent could not be delivered to some or all of its recipients\n"
1395 "due to prolonged unavailability of its destination(s).\n"
1396 "Giving up on the following addresses:\n\n"
1399 else StrBufAppendBufPlain(BounceMB, HKEY(
1400 "A message you sent could not be delivered to some or all of its recipients.\n"
1401 "The following addresses were undeliverable:\n\n"
1405 * Now go through the instructions checking for stuff.
1407 for (i=0; i<lines; ++i) {
1410 extract_token(buf, instr, i, '\n', sizeof buf);
1411 extract_token(key, buf, 0, '|', sizeof key);
1412 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
1413 status = extract_int(buf, 2);
1414 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
1417 CtdlLogPrintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1418 key, addr, status, dsn);
1420 if (!strcasecmp(key, "bounceto")) {
1421 strcpy(bounceto, addr);
1424 if (!strcasecmp(key, "msgid")) {
1425 omsgid = atol(addr);
1428 if (!strcasecmp(key, "remote")) {
1429 if (status == 5) bounce_this = 1;
1430 if (give_up) bounce_this = 1;
1436 StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
1437 StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
1438 StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
1439 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1441 remove_token(instr, i, '\n');
1447 /* Attach the original message */
1449 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1450 StrBufAppendBuf(BounceMB, boundary, 0);
1451 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1452 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
1453 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
1454 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
1455 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1457 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
1458 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0);
1459 StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
1460 FreeStrBuf(&CC->redirect_buffer);
1463 /* Close the multipart MIME scope */
1464 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1465 StrBufAppendBuf(BounceMB, boundary, 0);
1466 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
1467 bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB);
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);
1498 FreeStrBuf(&boundary);
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 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1744 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC );
1745 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1747 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1748 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1751 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1754 citthread_mutex_unlock (&smtp_send_lock);
1755 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1757 CtdlClearSystemContext();
1766 * Create a thread to run the SMTP queue
1768 * This was created as a response to a situation seen on Uncensored where a bad remote was holding
1769 * up SMTP sending for long times.
1770 * Converting to a thread does not fix the problem caused by the bad remote but it does prevent
1771 * the SMTP sending from stopping housekeeping and the EVT_TIMER event system which in turn prevented
1772 * other things from happening.
1774 void smtp_queue_thread (void)
1776 if (citthread_mutex_trylock (&smtp_send_lock)) {
1777 CtdlLogPrintf(CTDL_DEBUG, "SMTP queue run already in progress\n");
1780 CtdlThreadCreate("SMTP Send", CTDLTHREAD_BIGSTACK, smtp_do_queue, NULL);
1786 void smtp_server_going_down (void)
1788 CtdlLogPrintf(CTDL_DEBUG, "SMTP module clean up for shutdown.\n");
1790 citthread_mutex_destroy (&smtp_send_lock);
1795 /*****************************************************************************/
1796 /* SMTP UTILITY COMMANDS */
1797 /*****************************************************************************/
1799 void cmd_smtp(char *argbuf) {
1806 if (CtdlAccessCheck(ac_aide)) return;
1808 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1810 if (!strcasecmp(cmd, "mx")) {
1811 extract_token(node, argbuf, 1, '|', sizeof node);
1812 num_mxhosts = getmx(buf, node);
1813 cprintf("%d %d MX hosts listed for %s\n",
1814 LISTING_FOLLOWS, num_mxhosts, node);
1815 for (i=0; i<num_mxhosts; ++i) {
1816 extract_token(node, buf, i, '|', sizeof node);
1817 cprintf("%s\n", node);
1823 else if (!strcasecmp(cmd, "runqueue")) {
1825 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1830 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1837 * Initialize the SMTP outbound queue
1839 void smtp_init_spoolout(void) {
1840 struct ctdlroom qrbuf;
1843 * Create the room. This will silently fail if the room already
1844 * exists, and that's perfectly ok, because we want it to exist.
1846 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1849 * Make sure it's set to be a "system room" so it doesn't show up
1850 * in the <K>nown rooms list for Aides.
1852 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1853 qrbuf.QRflags2 |= QR2_SYSTEM;
1854 CtdlPutRoomLock(&qrbuf);
1861 /*****************************************************************************/
1862 /* MODULE INITIALIZATION STUFF */
1863 /*****************************************************************************/
1865 * This cleanup function blows away the temporary memory used by
1868 void smtp_cleanup_function(void) {
1870 /* Don't do this stuff if this is not an SMTP session! */
1871 if (CC->h_command_function != smtp_command_loop) return;
1873 CtdlLogPrintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1879 const char *CitadelServiceSMTP_MTA="SMTP-MTA";
1880 const char *CitadelServiceSMTPS_MTA="SMTPs-MTA";
1881 const char *CitadelServiceSMTP_MSA="SMTP-MSA";
1882 const char *CitadelServiceSMTP_LMTP="LMTP";
1883 const char *CitadelServiceSMTP_LMTP_UNF="LMTP-UnF";
1885 CTDL_MODULE_INIT(smtp)
1889 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1894 CitadelServiceSMTP_MTA);
1897 CtdlRegisterServiceHook(config.c_smtps_port,
1902 CitadelServiceSMTPS_MTA);
1905 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1910 CitadelServiceSMTP_MSA);
1912 CtdlRegisterServiceHook(0, /* local LMTP */
1917 CitadelServiceSMTP_LMTP);
1919 CtdlRegisterServiceHook(0, /* local LMTP */
1920 file_lmtp_unfiltered_socket,
1921 lmtp_unfiltered_greeting,
1924 CitadelServiceSMTP_LMTP_UNF);
1926 smtp_init_spoolout();
1927 CtdlRegisterSessionHook(smtp_queue_thread, EVT_TIMER);
1928 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1929 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1930 CtdlRegisterCleanupHook (smtp_server_going_down);
1931 citthread_mutex_init (&smtp_send_lock, NULL);
1934 /* return our Subversion id for the Log */