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-2011 by the citadel.org team
25 * This program is open source 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
49 #include <sys/types.h>
52 #if TIME_WITH_SYS_TIME
53 # include <sys/time.h>
57 # include <sys/time.h>
67 #include <sys/socket.h>
68 #include <netinet/in.h>
69 #include <arpa/inet.h>
70 #include <libcitadel.h>
73 #include "citserver.h"
80 #include "internet_addressing.h"
83 #include "clientsocket.h"
84 #include "locate_host.h"
85 #include "citadel_dirs.h"
94 #include "ctdl_module.h"
98 typedef struct _citsmtp { /* Information about the current session */
102 char recipients[SIZ];
103 int number_of_recipients;
105 int message_originated_locally;
111 enum { /* Command states for login authentication */
118 #define SMTP ((citsmtp *)CC->session_specific_data)
121 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
123 citthread_mutex_t smtp_send_lock;
126 /*****************************************************************************/
127 /* SMTP SERVER (INBOUND) STUFF */
128 /*****************************************************************************/
132 * Here's where our SMTP session begins its happy day.
134 void smtp_greeting(int is_msa)
137 char message_to_spammer[1024];
139 strcpy(CC->cs_clientname, "SMTP session");
140 CC->internal_pgm = 1;
141 CC->cs_flags |= CS_STEALTH;
142 CC->session_specific_data = malloc(sizeof(citsmtp));
143 memset(SMTP, 0, sizeof(citsmtp));
145 sSMTP->is_msa = is_msa;
147 /* If this config option is set, reject connections from problem
148 * addresses immediately instead of after they execute a RCPT
150 if ( (config.c_rbl_at_greeting) && (sSMTP->is_msa == 0) ) {
151 if (rbl_check(message_to_spammer)) {
152 if (CtdlThreadCheckStop())
153 cprintf("421 %s\r\n", message_to_spammer);
155 cprintf("550 %s\r\n", message_to_spammer);
157 /* no need to free_recipients(valid), it's not allocated yet */
162 /* Otherwise we're either clean or we check later. */
164 if (CC->nologin==1) {
165 cprintf("500 Too many users are already online (maximum is %d)\r\n",
169 /* no need to free_recipients(valid), it's not allocated yet */
173 /* Note: the FQDN *must* appear as the first thing after the 220 code.
174 * Some clients (including citmail.c) depend on it being there.
176 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
181 * SMTPS is just like SMTP, except it goes crypto right away.
183 void smtps_greeting(void) {
184 CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
186 if (!CC->redirect_ssl) CC->kill_me = 1; /* kill session if no crypto */
193 * SMTP MSA port requires authentication.
195 void smtp_msa_greeting(void) {
201 * LMTP is like SMTP but with some extra bonus footage added.
203 void lmtp_greeting(void) {
213 * Generic SMTP MTA greeting
215 void smtp_mta_greeting(void) {
221 * We also have an unfiltered LMTP socket that bypasses spam filters.
223 void lmtp_unfiltered_greeting(void) {
229 sSMTP->is_unfiltered = 1;
234 * Login greeting common to all auth methods
236 void smtp_auth_greeting(void) {
237 cprintf("235 Hello, %s\r\n", CC->user.fullname);
238 CtdlLogPrintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
239 CC->internal_pgm = 0;
240 CC->cs_flags &= ~CS_STEALTH;
245 * Implement HELO and EHLO commands.
247 * which_command: 0=HELO, 1=EHLO, 2=LHLO
249 void smtp_hello(char *argbuf, int which_command) {
250 citsmtp *sSMTP = SMTP;
252 safestrncpy(sSMTP->helo_node, argbuf, sizeof sSMTP->helo_node);
254 if ( (which_command != 2) && (sSMTP->is_lmtp) ) {
255 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
259 if ( (which_command == 2) && (sSMTP->is_lmtp == 0) ) {
260 cprintf("500 LHLO is only allowed when running LMTP\r\n");
264 if (which_command == 0) {
265 cprintf("250 Hello %s (%s [%s])\r\n",
272 if (which_command == 1) {
273 cprintf("250-Hello %s (%s [%s])\r\n",
280 cprintf("250-Greetings and joyous salutations.\r\n");
282 cprintf("250-HELP\r\n");
283 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
287 * Offer TLS, but only if TLS is not already active.
288 * Furthermore, only offer TLS when running on
289 * the SMTP-MSA port, not on the SMTP-MTA port, due to
290 * questionable reliability of TLS in certain sending MTA's.
292 if ( (!CC->redirect_ssl) && (sSMTP->is_msa) ) {
293 cprintf("250-STARTTLS\r\n");
295 #endif /* HAVE_OPENSSL */
297 cprintf("250-AUTH LOGIN PLAIN\r\n"
298 "250-AUTH=LOGIN PLAIN\r\n"
307 * Implement HELP command.
309 void smtp_help(void) {
310 cprintf("214 RTFM http://www.ietf.org/rfc/rfc2821.txt\r\n");
317 void smtp_get_user(char *argbuf) {
320 citsmtp *sSMTP = SMTP;
322 CtdlDecodeBase64(username, argbuf, SIZ);
323 /* CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", username); */
324 if (CtdlLoginExistingUser(NULL, username) == login_ok) {
325 CtdlEncodeBase64(buf, "Password:", 9, 0);
326 cprintf("334 %s\r\n", buf);
327 sSMTP->command_state = smtp_password;
330 cprintf("500 No such user.\r\n");
331 sSMTP->command_state = smtp_command;
339 void smtp_get_pass(char *argbuf) {
343 memset(password, 0, sizeof(password));
344 len = CtdlDecodeBase64(password, argbuf, SIZ);
345 /* CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", password); */
346 if (CtdlTryPassword(password, len) == 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];
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 len = safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
372 len = sizeof(pass) - 1;
374 SMTP->command_state = smtp_command;
376 if (!IsEmptyStr(ident)) {
377 result = CtdlLoginExistingUser(user, ident);
380 result = CtdlLoginExistingUser(NULL, user);
383 if (result == login_ok) {
384 if (CtdlTryPassword(pass, len) == pass_ok) {
385 smtp_auth_greeting();
389 cprintf("504 Authentication failed.\r\n");
394 * Attempt to perform authenticated SMTP
396 void smtp_auth(char *argbuf) {
397 char username_prompt[64];
399 char encoded_authstring[1024];
402 cprintf("504 Already logged in.\r\n");
406 extract_token(method, argbuf, 0, ' ', sizeof method);
408 if (!strncasecmp(method, "login", 5) ) {
409 if (strlen(argbuf) >= 7) {
410 smtp_get_user(&argbuf[6]);
413 CtdlEncodeBase64(username_prompt, "Username:", 9, 0);
414 cprintf("334 %s\r\n", username_prompt);
415 SMTP->command_state = smtp_user;
420 if (!strncasecmp(method, "plain", 5) ) {
421 if (num_tokens(argbuf, ' ') < 2) {
423 SMTP->command_state = smtp_plain;
427 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
429 smtp_try_plain(encoded_authstring);
433 if (strncasecmp(method, "login", 5) ) {
434 cprintf("504 Unknown authentication method.\r\n");
442 * Implements the RSET (reset state) command.
443 * Currently this just zeroes out the state buffer. If pointers to data
444 * allocated with malloc() are ever placed in the state buffer, we have to
445 * be sure to free() them first!
447 * Set do_response to nonzero to output the SMTP RSET response code.
449 void smtp_rset(int do_response) {
452 citsmtp *sSMTP = SMTP;
455 * Our entire SMTP state is discarded when a RSET command is issued,
456 * but we need to preserve this one little piece of information, so
457 * we save it for later.
459 is_lmtp = sSMTP->is_lmtp;
460 is_unfiltered = sSMTP->is_unfiltered;
462 memset(sSMTP, 0, sizeof(citsmtp));
465 * It is somewhat ambiguous whether we want to log out when a RSET
466 * command is issued. Here's the code to do it. It is commented out
467 * because some clients (such as Pine) issue RSET commands before
468 * each message, but still expect to be logged in.
470 * if (CC->logged_in) {
476 * Reinstate this little piece of information we saved (see above).
478 sSMTP->is_lmtp = is_lmtp;
479 sSMTP->is_unfiltered = is_unfiltered;
482 cprintf("250 Zap!\r\n");
487 * Clear out the portions of the state buffer that need to be cleared out
488 * after the DATA command finishes.
490 void smtp_data_clear(void) {
491 citsmtp *sSMTP = SMTP;
493 strcpy(sSMTP->from, "");
494 strcpy(sSMTP->recipients, "");
495 sSMTP->number_of_recipients = 0;
496 sSMTP->delivery_mode = 0;
497 sSMTP->message_originated_locally = 0;
500 const char *smtp_get_Recipients(void)
502 citsmtp *sSMTP = SMTP;
506 else return sSMTP->from;
510 * Implements the "MAIL FROM:" command
512 void smtp_mail(char *argbuf) {
516 citsmtp *sSMTP = SMTP;
518 if (!IsEmptyStr(sSMTP->from)) {
519 cprintf("503 Only one sender permitted\r\n");
523 if (strncasecmp(argbuf, "From:", 5)) {
524 cprintf("501 Syntax error\r\n");
528 strcpy(sSMTP->from, &argbuf[5]);
529 striplt(sSMTP->from);
530 if (haschar(sSMTP->from, '<') > 0) {
531 stripallbut(sSMTP->from, '<', '>');
534 /* We used to reject empty sender names, until it was brought to our
535 * attention that RFC1123 5.2.9 requires that this be allowed. So now
536 * we allow it, but replace the empty string with a fake
537 * address so we don't have to contend with the empty string causing
538 * other code to fail when it's expecting something there.
540 if (IsEmptyStr(sSMTP->from)) {
541 strcpy(sSMTP->from, "someone@example.com");
544 /* If this SMTP connection is from a logged-in user, force the 'from'
545 * to be the user's Internet e-mail address as Citadel knows it.
548 safestrncpy(sSMTP->from, CC->cs_inet_email, sizeof sSMTP->from);
549 cprintf("250 Sender ok <%s>\r\n", sSMTP->from);
550 sSMTP->message_originated_locally = 1;
554 else if (sSMTP->is_lmtp) {
555 /* Bypass forgery checking for LMTP */
558 /* Otherwise, make sure outsiders aren't trying to forge mail from
559 * this system (unless, of course, c_allow_spoofing is enabled)
561 else if (config.c_allow_spoofing == 0) {
562 process_rfc822_addr(sSMTP->from, user, node, name);
563 if (CtdlHostAlias(node) != hostalias_nomatch) {
564 cprintf("550 You must log in to send mail from %s\r\n", node);
565 strcpy(sSMTP->from, "");
570 cprintf("250 Sender ok\r\n");
576 * Implements the "RCPT To:" command
578 void smtp_rcpt(char *argbuf) {
580 char message_to_spammer[SIZ];
581 struct recptypes *valid = NULL;
582 citsmtp *sSMTP = SMTP;
584 if (IsEmptyStr(sSMTP->from)) {
585 cprintf("503 Need MAIL before RCPT\r\n");
589 if (strncasecmp(argbuf, "To:", 3)) {
590 cprintf("501 Syntax error\r\n");
594 if ( (sSMTP->is_msa) && (!CC->logged_in) ) {
595 cprintf("550 You must log in to send mail on this port.\r\n");
596 strcpy(sSMTP->from, "");
600 safestrncpy(recp, &argbuf[3], sizeof recp);
602 stripallbut(recp, '<', '>');
604 if ( (strlen(recp) + strlen(sSMTP->recipients) + 1 ) >= SIZ) {
605 cprintf("452 Too many recipients\r\n");
610 if ( (!CC->logged_in) /* Don't RBL authenticated users */
611 && (!sSMTP->is_lmtp) ) { /* Don't RBL LMTP clients */
612 if (config.c_rbl_at_greeting == 0) { /* Don't RBL again if we already did it */
613 if (rbl_check(message_to_spammer)) {
614 if (CtdlThreadCheckStop())
615 cprintf("421 %s\r\n", message_to_spammer);
617 cprintf("550 %s\r\n", message_to_spammer);
618 /* no need to free_recipients(valid), it's not allocated yet */
624 valid = validate_recipients(recp,
625 smtp_get_Recipients (),
626 (sSMTP->is_lmtp)? POST_LMTP:
627 (CC->logged_in)? POST_LOGGED_IN:
629 if (valid->num_error != 0) {
630 cprintf("550 %s\r\n", valid->errormsg);
631 free_recipients(valid);
635 if (valid->num_internet > 0) {
637 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
638 cprintf("551 <%s> - you do not have permission to send Internet mail\r\n", recp);
639 free_recipients(valid);
645 if (valid->num_internet > 0) {
646 if ( (sSMTP->message_originated_locally == 0)
647 && (sSMTP->is_lmtp == 0) ) {
648 cprintf("551 <%s> - relaying denied\r\n", recp);
649 free_recipients(valid);
654 cprintf("250 RCPT ok <%s>\r\n", recp);
655 if (!IsEmptyStr(sSMTP->recipients)) {
656 strcat(sSMTP->recipients, ",");
658 strcat(sSMTP->recipients, recp);
659 sSMTP->number_of_recipients += 1;
661 free_recipients(valid);
669 * Implements the DATA command
671 void smtp_data(void) {
673 char *defbody; //TODO: remove me
674 struct CtdlMessage *msg = NULL;
677 struct recptypes *valid;
681 citsmtp *sSMTP = SMTP;
683 if (IsEmptyStr(sSMTP->from)) {
684 cprintf("503 Need MAIL command first.\r\n");
688 if (sSMTP->number_of_recipients < 1) {
689 cprintf("503 Need RCPT command first.\r\n");
693 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
695 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
696 defbody = malloc(4096);
698 if (defbody != NULL) {
699 if (sSMTP->is_lmtp && (CC->cs_UDSclientUID != -1)) {
700 snprintf(defbody, 4096,
701 "Received: from %s (Citadel from userid %ld)\n"
704 (long int) CC->cs_UDSclientUID,
709 snprintf(defbody, 4096,
710 "Received: from %s (%s [%s])\n"
719 body = CtdlReadMessageBodyBuf(HKEY("."), config.c_maxmsglen, defbody, 1, NULL);
721 cprintf("550 Unable to save message: internal error.\r\n");
725 CtdlLogPrintf(CTDL_DEBUG, "Converting message...\n");
726 msg = convert_internet_message_buf(&body);
728 /* If the user is locally authenticated, FORCE the From: header to
729 * show up as the real sender. Yes, this violates the RFC standard,
730 * but IT MAKES SENSE. If you prefer strict RFC adherence over
731 * common sense, you can disable this in the configuration.
733 * We also set the "message room name" ('O' field) to MAILROOM
734 * (which is Mail> on most systems) to prevent it from getting set
735 * to something ugly like "0000058008.Sent Items>" when the message
736 * is read with a Citadel client.
738 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
739 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
740 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
741 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
742 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
743 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
744 msg->cm_fields['A'] = strdup(CC->user.fullname);
745 msg->cm_fields['N'] = strdup(config.c_nodename);
746 msg->cm_fields['H'] = strdup(config.c_humannode);
747 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
748 msg->cm_fields['O'] = strdup(MAILROOM);
751 /* Set the "envelope from" address */
752 if (msg->cm_fields['P'] != NULL) {
753 free(msg->cm_fields['P']);
755 msg->cm_fields['P'] = strdup(sSMTP->from);
757 /* Set the "envelope to" address */
758 if (msg->cm_fields['V'] != NULL) {
759 free(msg->cm_fields['V']);
761 msg->cm_fields['V'] = strdup(sSMTP->recipients);
763 /* Submit the message into the Citadel system. */
764 valid = validate_recipients(sSMTP->recipients,
765 smtp_get_Recipients (),
766 (sSMTP->is_lmtp)? POST_LMTP:
767 (CC->logged_in)? POST_LOGGED_IN:
770 /* If there are modules that want to scan this message before final
771 * submission (such as virus checkers or spam filters), call them now
772 * and give them an opportunity to reject the message.
774 if (sSMTP->is_unfiltered) {
778 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
781 if (scan_errors > 0) { /* We don't want this message! */
783 if (msg->cm_fields['0'] == NULL) {
784 msg->cm_fields['0'] = strdup("Message rejected by filter");
787 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
790 else { /* Ok, we'll accept this message. */
791 msgnum = CtdlSubmitMsg(msg, valid, "", 0);
793 sprintf(result, "250 Message accepted.\r\n");
796 sprintf(result, "550 Internal delivery error\r\n");
800 /* For SMTP and ESTMP, just print the result message. For LMTP, we
801 * have to print one result message for each recipient. Since there
802 * is nothing in Citadel which would cause different recipients to
803 * have different results, we can get away with just spitting out the
804 * same message once for each recipient.
806 if (sSMTP->is_lmtp) {
807 for (i=0; i<sSMTP->number_of_recipients; ++i) {
808 cprintf("%s", result);
812 cprintf("%s", result);
815 /* Write something to the syslog (which may or may not be where the
816 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
819 syslog((LOG_MAIL | LOG_INFO),
820 "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
823 sSMTP->number_of_recipients,
831 CtdlFreeMessage(msg);
832 free_recipients(valid);
833 smtp_data_clear(); /* clear out the buffers now */
838 * implements the STARTTLS command (Citadel API version)
840 void smtp_starttls(void)
842 char ok_response[SIZ];
843 char nosup_response[SIZ];
844 char error_response[SIZ];
847 "220 Begin TLS negotiation now\r\n");
848 sprintf(nosup_response,
849 "554 TLS not supported here\r\n");
850 sprintf(error_response,
851 "554 Internal error\r\n");
852 CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
859 * Main command loop for SMTP sessions.
861 void smtp_command_loop(void) {
863 citsmtp *sSMTP = SMTP;
866 CtdlLogPrintf(CTDL_EMERG, "Session SMTP data is null. WTF? We will crash now.\n");
870 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
871 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
872 CtdlLogPrintf(CTDL_CRIT, "Client disconnected: ending session.\n");
876 CtdlLogPrintf(CTDL_INFO, "SMTP server: %s\n", cmdbuf);
877 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
879 if (sSMTP->command_state == smtp_user) {
880 smtp_get_user(cmdbuf);
883 else if (sSMTP->command_state == smtp_password) {
884 smtp_get_pass(cmdbuf);
887 else if (sSMTP->command_state == smtp_plain) {
888 smtp_try_plain(cmdbuf);
891 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
892 smtp_auth(&cmdbuf[5]);
895 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
899 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
900 smtp_hello(&cmdbuf[5], 0);
903 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
904 smtp_hello(&cmdbuf[5], 1);
907 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
908 smtp_hello(&cmdbuf[5], 2);
911 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
915 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
916 smtp_mail(&cmdbuf[5]);
919 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
920 cprintf("250 NOOP\r\n");
923 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
924 cprintf("221 Goodbye...\r\n");
929 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
930 smtp_rcpt(&cmdbuf[5]);
933 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
937 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
942 cprintf("502 I'm afraid I can't do that.\r\n");
951 /*****************************************************************************/
952 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
953 /*****************************************************************************/
960 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
963 void smtp_try(const char *key, const char *addr, int *status,
964 char *dsn, size_t n, long msgnum, char *envelope_from)
971 char user[1024], node[1024], name[1024];
986 /* Parse out the host portion of the recipient address */
987 process_rfc822_addr(addr, user, node, name);
989 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Attempting delivery to <%s> @ <%s> (%s)\n",
992 /* Load the message out of the database */
993 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
994 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, ESC_DOT);
995 msg_size = StrLength(CC->redirect_buffer);
996 msgtext = SmashStrBuf(&CC->redirect_buffer);
998 /* If no envelope_from is supplied, extract one from the message */
999 if ( (envelope_from == NULL) || (IsEmptyStr(envelope_from)) ) {
1000 strcpy(mailfrom, "");
1004 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
1007 if (!strncasecmp(buf, "From:", 5)) {
1008 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
1010 for (i=0; mailfrom[i]; ++i) {
1011 if (!isprint(mailfrom[i])) {
1012 strcpy(&mailfrom[i], &mailfrom[i+1]);
1017 /* Strip out parenthesized names */
1020 for (i=0; mailfrom[i]; ++i) {
1021 if (mailfrom[i] == '(') lp = i;
1022 if (mailfrom[i] == ')') rp = i;
1024 if ((lp>0)&&(rp>lp)) {
1025 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
1028 /* Prefer brokketized names */
1031 for (i=0; mailfrom[i]; ++i) {
1032 if (mailfrom[i] == '<') lp = i;
1033 if (mailfrom[i] == '>') rp = i;
1035 if ( (lp>=0) && (rp>lp) ) {
1037 strcpy(mailfrom, &mailfrom[lp + 1]);
1042 } while (scan_done == 0);
1043 if (IsEmptyStr(mailfrom)) strcpy(mailfrom, "someone@somewhere.org");
1044 stripallbut(mailfrom, '<', '>');
1045 envelope_from = mailfrom;
1048 /* Figure out what mail exchanger host we have to connect to */
1049 num_mxhosts = getmx(mxhosts, node);
1050 CtdlLogPrintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d [%s]\n", node, num_mxhosts, mxhosts);
1051 if (num_mxhosts < 1) {
1053 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1058 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1060 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1061 strcpy(mx_user, "");
1062 strcpy(mx_pass, "");
1063 if (num_tokens(buf, '@') > 1) {
1064 strcpy (mx_user, buf);
1065 endpart = strrchr(mx_user, '@');
1067 strcpy (mx_host, endpart + 1);
1068 endpart = strrchr(mx_user, ':');
1069 if (endpart != NULL) {
1070 strcpy(mx_pass, endpart+1);
1075 strcpy (mx_host, buf);
1076 endpart = strrchr(mx_host, ':');
1079 strcpy(mx_port, endpart + 1);
1082 strcpy(mx_port, "25");
1084 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connecting to %s : %s ...\n", mx_host, mx_port);
1085 sock = sock_connect(mx_host, mx_port, "tcp");
1086 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1089 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connected!\n");
1091 fdflags = fcntl(sock, F_GETFL);
1093 CtdlLogPrintf(CTDL_DEBUG,
1094 "unable to get SMTP-Client socket flags! %s \n",
1096 fdflags = fdflags | O_NONBLOCK;
1097 if (fcntl(sock, F_SETFL, fdflags) < 0)
1098 CtdlLogPrintf(CTDL_DEBUG,
1099 "unable to set SMTP-Client socket nonblocking flags! %s \n",
1104 snprintf(dsn, SIZ, "%s", strerror(errno));
1107 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
1113 *status = 4; /* dsn is already filled in */
1117 CCC->sReadBuf = NewStrBuf();
1118 CCC->sMigrateBuf = NewStrBuf();
1121 /* Process the SMTP greeting from the server */
1122 if (ml_sock_gets(&sock, buf, 90) < 0) {
1124 strcpy(dsn, "Connection broken during SMTP conversation");
1127 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1128 if (buf[0] != '2') {
1129 if (buf[0] == '4') {
1131 safestrncpy(dsn, &buf[4], 1023);
1136 safestrncpy(dsn, &buf[4], 1023);
1141 /* At this point we know we are talking to a real SMTP server */
1143 /* Do a EHLO command. If it fails, try the HELO command. */
1144 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1145 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1146 sock_write(&sock, buf, strlen(buf));
1147 if (ml_sock_gets(&sock, buf, 30) < 0) {
1149 strcpy(dsn, "Connection broken during SMTP HELO");
1152 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1153 if (buf[0] != '2') {
1154 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1155 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1156 sock_write(&sock, buf, strlen(buf));
1157 if (ml_sock_gets(&sock, buf, 30) < 0) {
1159 strcpy(dsn, "Connection broken during SMTP HELO");
1163 if (buf[0] != '2') {
1164 if (buf[0] == '4') {
1166 safestrncpy(dsn, &buf[4], 1023);
1171 safestrncpy(dsn, &buf[4], 1023);
1176 /* Do an AUTH command if necessary */
1177 if (!IsEmptyStr(mx_user)) {
1179 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
1180 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2, 0);
1181 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
1182 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1183 sock_write(&sock, buf, strlen(buf));
1184 if (ml_sock_gets(&sock, buf, 30) < 0) {
1186 strcpy(dsn, "Connection broken during SMTP AUTH");
1189 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1190 if (buf[0] != '2') {
1191 if (buf[0] == '4') {
1193 safestrncpy(dsn, &buf[4], 1023);
1198 safestrncpy(dsn, &buf[4], 1023);
1204 /* previous command succeeded, now try the MAIL FROM: command */
1205 snprintf(buf, sizeof buf, "MAIL FROM:<%s>\r\n", envelope_from);
1206 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1207 sock_write(&sock, buf, strlen(buf));
1208 if (ml_sock_gets(&sock, buf, 30) < 0) {
1210 strcpy(dsn, "Connection broken during SMTP MAIL");
1213 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1214 if (buf[0] != '2') {
1215 if (buf[0] == '4') {
1217 safestrncpy(dsn, &buf[4], 1023);
1222 safestrncpy(dsn, &buf[4], 1023);
1227 /* MAIL succeeded, now try the RCPT To: command */
1228 snprintf(buf, sizeof buf, "RCPT TO:<%s@%s>\r\n", user, node);
1229 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1230 sock_write(&sock, buf, strlen(buf));
1231 if (ml_sock_gets(&sock, buf, 30) < 0) {
1233 strcpy(dsn, "Connection broken during SMTP RCPT");
1236 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1237 if (buf[0] != '2') {
1238 if (buf[0] == '4') {
1240 safestrncpy(dsn, &buf[4], 1023);
1245 safestrncpy(dsn, &buf[4], 1023);
1250 /* RCPT succeeded, now try the DATA command */
1251 CtdlLogPrintf(CTDL_DEBUG, ">DATA\n");
1252 sock_write(&sock, "DATA\r\n", 6);
1253 if (ml_sock_gets(&sock, buf, 30) < 0) {
1255 strcpy(dsn, "Connection broken during SMTP DATA");
1258 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1259 if (buf[0] != '3') {
1260 if (buf[0] == '4') {
1262 safestrncpy(dsn, &buf[4], 1023);
1267 safestrncpy(dsn, &buf[4], 1023);
1272 /* If we reach this point, the server is expecting data.*/
1273 sock_write_timeout(&sock,
1276 (msg_size / 128) + 50);
1277 if (msgtext[msg_size-1] != 10) {
1278 CtdlLogPrintf(CTDL_WARNING, "Possible problem: message did not "
1279 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1281 sock_write(&sock, "\r\n", 2);
1284 sock_write(&sock, ".\r\n", 3);
1286 if (ml_sock_gets(&sock, buf, 90) < 0) {
1288 strcpy(dsn, "Connection broken during SMTP message transmit");
1291 CtdlLogPrintf(CTDL_DEBUG, "%s\n", buf);
1292 if (buf[0] != '2') {
1293 if (buf[0] == '4') {
1295 safestrncpy(dsn, &buf[4], 1023);
1300 safestrncpy(dsn, &buf[4], 1023);
1306 safestrncpy(dsn, &buf[4], 1023);
1309 CtdlLogPrintf(CTDL_DEBUG, ">QUIT\n");
1310 sock_write(&sock, "QUIT\r\n", 6);
1311 ml_sock_gets(&sock, buf, 30);
1312 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1313 CtdlLogPrintf(CTDL_INFO, "SMTP client: delivery to <%s> @ <%s> (%s) succeeded\n",
1316 bail: free(msgtext);
1317 FreeStrBuf(&CCC->sReadBuf);
1318 FreeStrBuf(&CCC->sMigrateBuf);
1322 /* Write something to the syslog (which may or may not be where the
1323 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1325 if (enable_syslog) {
1326 syslog((LOG_MAIL | LOG_INFO),
1327 "%ld: to=<%s>, relay=%s, stat=%s",
1341 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1342 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1343 * a "bounce" message (delivery status notification).
1345 void smtp_do_bounce(char *instr) {
1353 char bounceto[1024];
1355 int num_bounces = 0;
1356 int bounce_this = 0;
1357 long bounce_msgid = (-1);
1358 time_t submitted = 0L;
1359 struct CtdlMessage *bmsg = NULL;
1361 struct recptypes *valid;
1362 int successful_bounce = 0;
1367 CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1368 strcpy(bounceto, "");
1369 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
1370 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
1371 lines = num_tokens(instr, '\n');
1373 /* See if it's time to give up on delivery of this message */
1374 for (i=0; i<lines; ++i) {
1375 extract_token(buf, instr, i, '\n', sizeof buf);
1376 extract_token(key, buf, 0, '|', sizeof key);
1377 extract_token(addr, buf, 1, '|', sizeof addr);
1378 if (!strcasecmp(key, "submitted")) {
1379 submitted = atol(addr);
1383 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1387 /* Start building our bounce message */
1389 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1390 if (bmsg == NULL) return;
1391 memset(bmsg, 0, sizeof(struct CtdlMessage));
1392 BounceMB = NewStrBufPlain(NULL, 1024);
1394 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1395 bmsg->cm_anon_type = MES_NORMAL;
1396 bmsg->cm_format_type = FMT_RFC822;
1397 bmsg->cm_fields['A'] = strdup("Citadel");
1398 bmsg->cm_fields['O'] = strdup(MAILROOM);
1399 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1400 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1401 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
1402 StrBufAppendBuf(BounceMB, boundary, 0);
1403 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
1404 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
1405 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
1406 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
1407 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1408 StrBufAppendBuf(BounceMB, boundary, 0);
1409 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1410 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
1412 if (give_up) StrBufAppendBufPlain(BounceMB, HKEY(
1413 "A message you sent could not be delivered to some or all of its recipients\n"
1414 "due to prolonged unavailability of its destination(s).\n"
1415 "Giving up on the following addresses:\n\n"
1418 else StrBufAppendBufPlain(BounceMB, HKEY(
1419 "A message you sent could not be delivered to some or all of its recipients.\n"
1420 "The following addresses were undeliverable:\n\n"
1424 * Now go through the instructions checking for stuff.
1426 for (i=0; i<lines; ++i) {
1429 extract_token(buf, instr, i, '\n', sizeof buf);
1430 extract_token(key, buf, 0, '|', sizeof key);
1431 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
1432 status = extract_int(buf, 2);
1433 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
1436 CtdlLogPrintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1437 key, addr, status, dsn);
1439 if (!strcasecmp(key, "bounceto")) {
1440 strcpy(bounceto, addr);
1443 if (!strcasecmp(key, "msgid")) {
1444 omsgid = atol(addr);
1447 if (!strcasecmp(key, "remote")) {
1448 if (status == 5) bounce_this = 1;
1449 if (give_up) bounce_this = 1;
1455 StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
1456 StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
1457 StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
1458 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1460 remove_token(instr, i, '\n');
1466 /* Attach the original message */
1468 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1469 StrBufAppendBuf(BounceMB, boundary, 0);
1470 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1471 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
1472 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
1473 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
1474 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1476 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
1477 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0);
1478 StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
1479 FreeStrBuf(&CC->redirect_buffer);
1482 /* Close the multipart MIME scope */
1483 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1484 StrBufAppendBuf(BounceMB, boundary, 0);
1485 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
1486 bmsg->cm_fields['M'] = SmashStrBuf(&BounceMB);
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);
1517 FreeStrBuf(&boundary);
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 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1763 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC );
1764 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1766 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1767 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1770 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1773 citthread_mutex_unlock (&smtp_send_lock);
1774 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1777 CtdlClearSystemContext();
1786 * Create a thread to run the SMTP queue
1788 * This was created as a response to a situation seen on Uncensored where a bad remote was holding
1789 * up SMTP sending for long times.
1790 * Converting to a thread does not fix the problem caused by the bad remote but it does prevent
1791 * the SMTP sending from stopping housekeeping and the EVT_TIMER event system which in turn prevented
1792 * other things from happening.
1794 void smtp_queue_thread (void)
1796 if (citthread_mutex_trylock (&smtp_send_lock)) {
1797 CtdlLogPrintf(CTDL_DEBUG, "SMTP queue run already in progress\n");
1800 CtdlThreadCreate("SMTP Send", CTDLTHREAD_BIGSTACK, smtp_do_queue, NULL);
1806 void smtp_server_going_down (void)
1808 CtdlLogPrintf(CTDL_DEBUG, "SMTP module clean up for shutdown.\n");
1810 citthread_mutex_destroy (&smtp_send_lock);
1815 /*****************************************************************************/
1816 /* SMTP UTILITY COMMANDS */
1817 /*****************************************************************************/
1819 void cmd_smtp(char *argbuf) {
1826 if (CtdlAccessCheck(ac_aide)) return;
1828 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1830 if (!strcasecmp(cmd, "mx")) {
1831 extract_token(node, argbuf, 1, '|', sizeof node);
1832 num_mxhosts = getmx(buf, node);
1833 cprintf("%d %d MX hosts listed for %s\n",
1834 LISTING_FOLLOWS, num_mxhosts, node);
1835 for (i=0; i<num_mxhosts; ++i) {
1836 extract_token(node, buf, i, '|', sizeof node);
1837 cprintf("%s\n", node);
1843 else if (!strcasecmp(cmd, "runqueue")) {
1845 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1850 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1857 * Initialize the SMTP outbound queue
1859 void smtp_init_spoolout(void) {
1860 struct ctdlroom qrbuf;
1863 * Create the room. This will silently fail if the room already
1864 * exists, and that's perfectly ok, because we want it to exist.
1866 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1869 * Make sure it's set to be a "system room" so it doesn't show up
1870 * in the <K>nown rooms list for Aides.
1872 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1873 qrbuf.QRflags2 |= QR2_SYSTEM;
1874 CtdlPutRoomLock(&qrbuf);
1881 /*****************************************************************************/
1882 /* MODULE INITIALIZATION STUFF */
1883 /*****************************************************************************/
1885 * This cleanup function blows away the temporary memory used by
1888 void smtp_cleanup_function(void) {
1890 /* Don't do this stuff if this is not an SMTP session! */
1891 if (CC->h_command_function != smtp_command_loop) return;
1893 CtdlLogPrintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1899 const char *CitadelServiceSMTP_MTA="SMTP-MTA";
1900 const char *CitadelServiceSMTPS_MTA="SMTPs-MTA";
1901 const char *CitadelServiceSMTP_MSA="SMTP-MSA";
1902 const char *CitadelServiceSMTP_LMTP="LMTP";
1903 const char *CitadelServiceSMTP_LMTP_UNF="LMTP-UnF";
1905 CTDL_MODULE_INIT(smtp)
1909 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1914 CitadelServiceSMTP_MTA);
1917 CtdlRegisterServiceHook(config.c_smtps_port,
1922 CitadelServiceSMTPS_MTA);
1925 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1930 CitadelServiceSMTP_MSA);
1932 CtdlRegisterServiceHook(0, /* local LMTP */
1937 CitadelServiceSMTP_LMTP);
1939 CtdlRegisterServiceHook(0, /* local LMTP */
1940 file_lmtp_unfiltered_socket,
1941 lmtp_unfiltered_greeting,
1944 CitadelServiceSMTP_LMTP_UNF);
1946 smtp_init_spoolout();
1947 CtdlRegisterSessionHook(smtp_queue_thread, EVT_TIMER);
1948 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1949 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1950 CtdlRegisterCleanupHook (smtp_server_going_down);
1951 citthread_mutex_init (&smtp_send_lock, NULL);
1954 /* return our Subversion id for the Log */