4 * This module is an SMTP and ESMTP implementation for the Citadel system.
5 * It is compliant with all of the following:
7 * RFC 821 - Simple Mail Transfer Protocol
8 * RFC 876 - Survey of SMTP Implementations
9 * RFC 1047 - Duplicate messages and SMTP
10 * RFC 1652 - 8 bit MIME
11 * RFC 1869 - Extended Simple Mail Transfer Protocol
12 * RFC 1870 - SMTP Service Extension for Message Size Declaration
13 * RFC 2033 - Local Mail Transfer Protocol
14 * RFC 2197 - SMTP Service Extension for Command Pipelining
15 * RFC 2476 - Message Submission
16 * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
17 * RFC 2554 - SMTP Service Extension for Authentication
18 * RFC 2821 - Simple Mail Transfer Protocol
19 * RFC 2822 - Internet Message Format
20 * RFC 2920 - SMTP Service Extension for Command Pipelining
22 * The VRFY and EXPN commands have been removed from this implementation
23 * because nobody uses these commands anymore, except for spammers.
25 * Copyright (c) 1998-2009 by the citadel.org team
27 * This program is free software; you can redistribute it and/or modify
28 * it under the terms of the GNU General Public License as published by
29 * the Free Software Foundation; either version 3 of the License, or
30 * (at your option) any later version.
32 * This program is distributed in the hope that it will be useful,
33 * but WITHOUT ANY WARRANTY; without even the implied warranty of
34 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35 * GNU General Public License for more details.
37 * You should have received a copy of the GNU General Public License
38 * along with this program; if not, write to the Free Software
39 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
50 #include <sys/types.h>
53 #if TIME_WITH_SYS_TIME
54 # include <sys/time.h>
58 # include <sys/time.h>
68 #include <sys/socket.h>
69 #include <netinet/in.h>
70 #include <arpa/inet.h>
71 #include <libcitadel.h>
74 #include "citserver.h"
81 #include "internet_addressing.h"
84 #include "clientsocket.h"
85 #include "locate_host.h"
86 #include "citadel_dirs.h"
95 #include "ctdl_module.h"
99 typedef struct _citsmtp { /* Information about the current session */
103 char recipients[SIZ];
104 int number_of_recipients;
106 int message_originated_locally;
112 enum { /* Command states for login authentication */
119 #define SMTP ((citsmtp *)CC->session_specific_data)
122 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
124 citthread_mutex_t smtp_send_lock;
127 /*****************************************************************************/
128 /* SMTP SERVER (INBOUND) STUFF */
129 /*****************************************************************************/
133 * Here's where our SMTP session begins its happy day.
135 void smtp_greeting(int is_msa)
138 char message_to_spammer[1024];
140 strcpy(CC->cs_clientname, "SMTP session");
141 CC->internal_pgm = 1;
142 CC->cs_flags |= CS_STEALTH;
143 CC->session_specific_data = malloc(sizeof(citsmtp));
144 memset(SMTP, 0, sizeof(citsmtp));
146 sSMTP->is_msa = is_msa;
148 /* If this config option is set, reject connections from problem
149 * addresses immediately instead of after they execute a RCPT
151 if ( (config.c_rbl_at_greeting) && (sSMTP->is_msa == 0) ) {
152 if (rbl_check(message_to_spammer)) {
153 if (CtdlThreadCheckStop())
154 cprintf("421 %s\r\n", message_to_spammer);
156 cprintf("550 %s\r\n", message_to_spammer);
158 /* no need to free_recipients(valid), it's not allocated yet */
163 /* Otherwise we're either clean or we check later. */
165 if (CC->nologin==1) {
166 cprintf("500 Too many users are already online (maximum is %d)\r\n",
170 /* no need to free_recipients(valid), it's not allocated yet */
174 /* Note: the FQDN *must* appear as the first thing after the 220 code.
175 * Some clients (including citmail.c) depend on it being there.
177 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
182 * SMTPS is just like SMTP, except it goes crypto right away.
184 void smtps_greeting(void) {
185 CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
187 if (!CC->redirect_ssl) CC->kill_me = 1; /* kill session if no crypto */
194 * SMTP MSA port requires authentication.
196 void smtp_msa_greeting(void) {
202 * LMTP is like SMTP but with some extra bonus footage added.
204 void lmtp_greeting(void) {
214 * Generic SMTP MTA greeting
216 void smtp_mta_greeting(void) {
222 * We also have an unfiltered LMTP socket that bypasses spam filters.
224 void lmtp_unfiltered_greeting(void) {
230 sSMTP->is_unfiltered = 1;
235 * Login greeting common to all auth methods
237 void smtp_auth_greeting(void) {
238 cprintf("235 Hello, %s\r\n", CC->user.fullname);
239 CtdlLogPrintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
240 CC->internal_pgm = 0;
241 CC->cs_flags &= ~CS_STEALTH;
246 * Implement HELO and EHLO commands.
248 * which_command: 0=HELO, 1=EHLO, 2=LHLO
250 void smtp_hello(char *argbuf, int which_command) {
251 citsmtp *sSMTP = SMTP;
253 safestrncpy(sSMTP->helo_node, argbuf, sizeof sSMTP->helo_node);
255 if ( (which_command != 2) && (sSMTP->is_lmtp) ) {
256 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
260 if ( (which_command == 2) && (sSMTP->is_lmtp == 0) ) {
261 cprintf("500 LHLO is only allowed when running LMTP\r\n");
265 if (which_command == 0) {
266 cprintf("250 Hello %s (%s [%s])\r\n",
273 if (which_command == 1) {
274 cprintf("250-Hello %s (%s [%s])\r\n",
281 cprintf("250-Greetings and joyous salutations.\r\n");
283 cprintf("250-HELP\r\n");
284 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
288 * Offer TLS, but only if TLS is not already active.
289 * Furthermore, only offer TLS when running on
290 * the SMTP-MSA port, not on the SMTP-MTA port, due to
291 * questionable reliability of TLS in certain sending MTA's.
293 if ( (!CC->redirect_ssl) && (sSMTP->is_msa) ) {
294 cprintf("250-STARTTLS\r\n");
296 #endif /* HAVE_OPENSSL */
298 cprintf("250-AUTH LOGIN PLAIN\r\n"
299 "250-AUTH=LOGIN PLAIN\r\n"
308 * Implement HELP command.
310 void smtp_help(void) {
311 cprintf("214 RTFM http://www.ietf.org/rfc/rfc2821.txt\r\n");
318 void smtp_get_user(char *argbuf) {
321 citsmtp *sSMTP = SMTP;
323 CtdlDecodeBase64(username, argbuf, SIZ);
324 /* CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", username); */
325 if (CtdlLoginExistingUser(NULL, username) == login_ok) {
326 CtdlEncodeBase64(buf, "Password:", 9, 0);
327 cprintf("334 %s\r\n", buf);
328 sSMTP->command_state = smtp_password;
331 cprintf("500 No such user.\r\n");
332 sSMTP->command_state = smtp_command;
340 void smtp_get_pass(char *argbuf) {
344 memset(password, 0, sizeof(password));
345 len = CtdlDecodeBase64(password, argbuf, SIZ);
346 /* CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", password); */
347 if (CtdlTryPassword(password, len) == 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];
368 CtdlDecodeBase64(decoded_authstring, encoded_authstring, strlen(encoded_authstring) );
369 safestrncpy(ident, decoded_authstring, sizeof ident);
370 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
371 len = safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
373 len = sizeof(pass) - 1;
375 SMTP->command_state = smtp_command;
377 if (!IsEmptyStr(ident)) {
378 result = CtdlLoginExistingUser(user, ident);
381 result = CtdlLoginExistingUser(NULL, user);
384 if (result == login_ok) {
385 if (CtdlTryPassword(pass, len) == pass_ok) {
386 smtp_auth_greeting();
390 cprintf("504 Authentication failed.\r\n");
395 * Attempt to perform authenticated SMTP
397 void smtp_auth(char *argbuf) {
398 char username_prompt[64];
400 char encoded_authstring[1024];
403 cprintf("504 Already logged in.\r\n");
407 extract_token(method, argbuf, 0, ' ', sizeof method);
409 if (!strncasecmp(method, "login", 5) ) {
410 if (strlen(argbuf) >= 7) {
411 smtp_get_user(&argbuf[6]);
414 CtdlEncodeBase64(username_prompt, "Username:", 9, 0);
415 cprintf("334 %s\r\n", username_prompt);
416 SMTP->command_state = smtp_user;
421 if (!strncasecmp(method, "plain", 5) ) {
422 if (num_tokens(argbuf, ' ') < 2) {
424 SMTP->command_state = smtp_plain;
428 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
430 smtp_try_plain(encoded_authstring);
434 if (strncasecmp(method, "login", 5) ) {
435 cprintf("504 Unknown authentication method.\r\n");
443 * Implements the RSET (reset state) command.
444 * Currently this just zeroes out the state buffer. If pointers to data
445 * allocated with malloc() are ever placed in the state buffer, we have to
446 * be sure to free() them first!
448 * Set do_response to nonzero to output the SMTP RSET response code.
450 void smtp_rset(int do_response) {
453 citsmtp *sSMTP = SMTP;
456 * Our entire SMTP state is discarded when a RSET command is issued,
457 * but we need to preserve this one little piece of information, so
458 * we save it for later.
460 is_lmtp = sSMTP->is_lmtp;
461 is_unfiltered = sSMTP->is_unfiltered;
463 memset(sSMTP, 0, sizeof(citsmtp));
466 * It is somewhat ambiguous whether we want to log out when a RSET
467 * command is issued. Here's the code to do it. It is commented out
468 * because some clients (such as Pine) issue RSET commands before
469 * each message, but still expect to be logged in.
471 * if (CC->logged_in) {
477 * Reinstate this little piece of information we saved (see above).
479 sSMTP->is_lmtp = is_lmtp;
480 sSMTP->is_unfiltered = is_unfiltered;
483 cprintf("250 Zap!\r\n");
488 * Clear out the portions of the state buffer that need to be cleared out
489 * after the DATA command finishes.
491 void smtp_data_clear(void) {
492 citsmtp *sSMTP = SMTP;
494 strcpy(sSMTP->from, "");
495 strcpy(sSMTP->recipients, "");
496 sSMTP->number_of_recipients = 0;
497 sSMTP->delivery_mode = 0;
498 sSMTP->message_originated_locally = 0;
501 const char *smtp_get_Recipients(void)
503 citsmtp *sSMTP = SMTP;
507 else return sSMTP->from;
511 * Implements the "MAIL FROM:" command
513 void smtp_mail(char *argbuf) {
517 citsmtp *sSMTP = SMTP;
519 if (!IsEmptyStr(sSMTP->from)) {
520 cprintf("503 Only one sender permitted\r\n");
524 if (strncasecmp(argbuf, "From:", 5)) {
525 cprintf("501 Syntax error\r\n");
529 strcpy(sSMTP->from, &argbuf[5]);
530 striplt(sSMTP->from);
531 if (haschar(sSMTP->from, '<') > 0) {
532 stripallbut(sSMTP->from, '<', '>');
535 /* We used to reject empty sender names, until it was brought to our
536 * attention that RFC1123 5.2.9 requires that this be allowed. So now
537 * we allow it, but replace the empty string with a fake
538 * address so we don't have to contend with the empty string causing
539 * other code to fail when it's expecting something there.
541 if (IsEmptyStr(sSMTP->from)) {
542 strcpy(sSMTP->from, "someone@example.com");
545 /* If this SMTP connection is from a logged-in user, force the 'from'
546 * to be the user's Internet e-mail address as Citadel knows it.
549 safestrncpy(sSMTP->from, CC->cs_inet_email, sizeof sSMTP->from);
550 cprintf("250 Sender ok <%s>\r\n", sSMTP->from);
551 sSMTP->message_originated_locally = 1;
555 else if (sSMTP->is_lmtp) {
556 /* Bypass forgery checking for LMTP */
559 /* Otherwise, make sure outsiders aren't trying to forge mail from
560 * this system (unless, of course, c_allow_spoofing is enabled)
562 else if (config.c_allow_spoofing == 0) {
563 process_rfc822_addr(sSMTP->from, user, node, name);
564 if (CtdlHostAlias(node) != hostalias_nomatch) {
565 cprintf("550 You must log in to send mail from %s\r\n", node);
566 strcpy(sSMTP->from, "");
571 cprintf("250 Sender ok\r\n");
577 * Implements the "RCPT To:" command
579 void smtp_rcpt(char *argbuf) {
581 char message_to_spammer[SIZ];
582 struct recptypes *valid = NULL;
583 citsmtp *sSMTP = SMTP;
585 if (IsEmptyStr(sSMTP->from)) {
586 cprintf("503 Need MAIL before RCPT\r\n");
590 if (strncasecmp(argbuf, "To:", 3)) {
591 cprintf("501 Syntax error\r\n");
595 if ( (sSMTP->is_msa) && (!CC->logged_in) ) {
596 cprintf("550 You must log in to send mail on this port.\r\n");
597 strcpy(sSMTP->from, "");
601 safestrncpy(recp, &argbuf[3], sizeof recp);
603 stripallbut(recp, '<', '>');
605 if ( (strlen(recp) + strlen(sSMTP->recipients) + 1 ) >= SIZ) {
606 cprintf("452 Too many recipients\r\n");
611 if ( (!CC->logged_in) /* Don't RBL authenticated users */
612 && (!sSMTP->is_lmtp) ) { /* Don't RBL LMTP clients */
613 if (config.c_rbl_at_greeting == 0) { /* Don't RBL again if we already did it */
614 if (rbl_check(message_to_spammer)) {
615 if (CtdlThreadCheckStop())
616 cprintf("421 %s\r\n", message_to_spammer);
618 cprintf("550 %s\r\n", message_to_spammer);
619 /* no need to free_recipients(valid), it's not allocated yet */
625 valid = validate_recipients(recp,
626 smtp_get_Recipients (),
627 (sSMTP->is_lmtp)? POST_LMTP:
628 (CC->logged_in)? POST_LOGGED_IN:
630 if (valid->num_error != 0) {
631 cprintf("550 %s\r\n", valid->errormsg);
632 free_recipients(valid);
636 if (valid->num_internet > 0) {
638 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
639 cprintf("551 <%s> - you do not have permission to send Internet mail\r\n", recp);
640 free_recipients(valid);
646 if (valid->num_internet > 0) {
647 if ( (sSMTP->message_originated_locally == 0)
648 && (sSMTP->is_lmtp == 0) ) {
649 cprintf("551 <%s> - relaying denied\r\n", recp);
650 free_recipients(valid);
655 cprintf("250 RCPT ok <%s>\r\n", recp);
656 if (!IsEmptyStr(sSMTP->recipients)) {
657 strcat(sSMTP->recipients, ",");
659 strcat(sSMTP->recipients, recp);
660 sSMTP->number_of_recipients += 1;
662 free_recipients(valid);
670 * Implements the DATA command
672 void smtp_data(void) {
674 char *defbody; //TODO: remove me
675 struct CtdlMessage *msg = NULL;
678 struct recptypes *valid;
682 citsmtp *sSMTP = SMTP;
684 if (IsEmptyStr(sSMTP->from)) {
685 cprintf("503 Need MAIL command first.\r\n");
689 if (sSMTP->number_of_recipients < 1) {
690 cprintf("503 Need RCPT command first.\r\n");
694 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
696 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
697 defbody = malloc(4096);
699 if (defbody != NULL) {
700 if (sSMTP->is_lmtp && (CC->cs_UDSclientUID != -1)) {
701 snprintf(defbody, 4096,
702 "Received: from %s (Citadel from userid %ld)\n"
705 (long int) CC->cs_UDSclientUID,
710 snprintf(defbody, 4096,
711 "Received: from %s (%s [%s])\n"
720 body = CtdlReadMessageBodyBuf(HKEY("."), config.c_maxmsglen, defbody, 1, NULL);
722 cprintf("550 Unable to save message: internal error.\r\n");
726 CtdlLogPrintf(CTDL_DEBUG, "Converting message...\n");
727 msg = convert_internet_message_buf(&body);
729 /* If the user is locally authenticated, FORCE the From: header to
730 * show up as the real sender. Yes, this violates the RFC standard,
731 * but IT MAKES SENSE. If you prefer strict RFC adherence over
732 * common sense, you can disable this in the configuration.
734 * We also set the "message room name" ('O' field) to MAILROOM
735 * (which is Mail> on most systems) to prevent it from getting set
736 * to something ugly like "0000058008.Sent Items>" when the message
737 * is read with a Citadel client.
739 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
740 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
741 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
742 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
743 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
744 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
745 msg->cm_fields['A'] = strdup(CC->user.fullname);
746 msg->cm_fields['N'] = strdup(config.c_nodename);
747 msg->cm_fields['H'] = strdup(config.c_humannode);
748 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
749 msg->cm_fields['O'] = strdup(MAILROOM);
752 /* Set the "envelope from" address */
753 if (msg->cm_fields['P'] != NULL) {
754 free(msg->cm_fields['P']);
756 msg->cm_fields['P'] = strdup(sSMTP->from);
758 /* Set the "envelope to" address */
759 if (msg->cm_fields['V'] != NULL) {
760 free(msg->cm_fields['V']);
762 msg->cm_fields['V'] = strdup(sSMTP->recipients);
764 /* Submit the message into the Citadel system. */
765 valid = validate_recipients(sSMTP->recipients,
766 smtp_get_Recipients (),
767 (sSMTP->is_lmtp)? POST_LMTP:
768 (CC->logged_in)? POST_LOGGED_IN:
771 /* If there are modules that want to scan this message before final
772 * submission (such as virus checkers or spam filters), call them now
773 * and give them an opportunity to reject the message.
775 if (sSMTP->is_unfiltered) {
779 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
782 if (scan_errors > 0) { /* We don't want this message! */
784 if (msg->cm_fields['0'] == NULL) {
785 msg->cm_fields['0'] = strdup("Message rejected by filter");
788 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
791 else { /* Ok, we'll accept this message. */
792 msgnum = CtdlSubmitMsg(msg, valid, "", 0);
794 sprintf(result, "250 Message accepted.\r\n");
797 sprintf(result, "550 Internal delivery error\r\n");
801 /* For SMTP and ESTMP, just print the result message. For LMTP, we
802 * have to print one result message for each recipient. Since there
803 * is nothing in Citadel which would cause different recipients to
804 * have different results, we can get away with just spitting out the
805 * same message once for each recipient.
807 if (sSMTP->is_lmtp) {
808 for (i=0; i<sSMTP->number_of_recipients; ++i) {
809 cprintf("%s", result);
813 cprintf("%s", result);
816 /* Write something to the syslog (which may or may not be where the
817 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
820 syslog((LOG_MAIL | LOG_INFO),
821 "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
824 sSMTP->number_of_recipients,
832 CtdlFreeMessage(msg);
833 free_recipients(valid);
834 smtp_data_clear(); /* clear out the buffers now */
839 * implements the STARTTLS command (Citadel API version)
841 void smtp_starttls(void)
843 char ok_response[SIZ];
844 char nosup_response[SIZ];
845 char error_response[SIZ];
848 "220 Begin TLS negotiation now\r\n");
849 sprintf(nosup_response,
850 "554 TLS not supported here\r\n");
851 sprintf(error_response,
852 "554 Internal error\r\n");
853 CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
860 * Main command loop for SMTP sessions.
862 void smtp_command_loop(void) {
864 citsmtp *sSMTP = SMTP;
867 CtdlLogPrintf(CTDL_EMERG, "Session SMTP data is null. WTF? We will crash now.\n");
871 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
872 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
873 CtdlLogPrintf(CTDL_CRIT, "Client disconnected: ending session.\n");
877 CtdlLogPrintf(CTDL_INFO, "SMTP server: %s\n", cmdbuf);
878 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
880 if (sSMTP->command_state == smtp_user) {
881 smtp_get_user(cmdbuf);
884 else if (sSMTP->command_state == smtp_password) {
885 smtp_get_pass(cmdbuf);
888 else if (sSMTP->command_state == smtp_plain) {
889 smtp_try_plain(cmdbuf);
892 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
893 smtp_auth(&cmdbuf[5]);
896 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
900 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
901 smtp_hello(&cmdbuf[5], 0);
904 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
905 smtp_hello(&cmdbuf[5], 1);
908 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
909 smtp_hello(&cmdbuf[5], 2);
912 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
916 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
917 smtp_mail(&cmdbuf[5]);
920 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
921 cprintf("250 NOOP\r\n");
924 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
925 cprintf("221 Goodbye...\r\n");
930 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
931 smtp_rcpt(&cmdbuf[5]);
934 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
938 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
943 cprintf("502 I'm afraid I can't do that.\r\n");
952 /*****************************************************************************/
953 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
954 /*****************************************************************************/
961 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
964 void smtp_try(const char *key, const char *addr, int *status,
965 char *dsn, size_t n, long msgnum, char *envelope_from)
972 char user[1024], node[1024], name[1024];
987 /* Parse out the host portion of the recipient address */
988 process_rfc822_addr(addr, user, node, name);
990 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Attempting delivery to <%s> @ <%s> (%s)\n",
993 /* Load the message out of the database */
994 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
995 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, ESC_DOT);
996 msg_size = StrLength(CC->redirect_buffer);
997 msgtext = SmashStrBuf(&CC->redirect_buffer);
999 /* If no envelope_from is supplied, extract one from the message */
1000 if ( (envelope_from == NULL) || (IsEmptyStr(envelope_from)) ) {
1001 strcpy(mailfrom, "");
1005 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
1008 if (!strncasecmp(buf, "From:", 5)) {
1009 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
1011 for (i=0; mailfrom[i]; ++i) {
1012 if (!isprint(mailfrom[i])) {
1013 strcpy(&mailfrom[i], &mailfrom[i+1]);
1018 /* Strip out parenthesized names */
1021 for (i=0; mailfrom[i]; ++i) {
1022 if (mailfrom[i] == '(') lp = i;
1023 if (mailfrom[i] == ')') rp = i;
1025 if ((lp>0)&&(rp>lp)) {
1026 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
1029 /* Prefer brokketized names */
1032 for (i=0; mailfrom[i]; ++i) {
1033 if (mailfrom[i] == '<') lp = i;
1034 if (mailfrom[i] == '>') rp = i;
1036 if ( (lp>=0) && (rp>lp) ) {
1038 strcpy(mailfrom, &mailfrom[lp]);
1043 } while (scan_done == 0);
1044 if (IsEmptyStr(mailfrom)) strcpy(mailfrom, "someone@somewhere.org");
1045 stripallbut(mailfrom, '<', '>');
1046 envelope_from = mailfrom;
1049 /* Figure out what mail exchanger host we have to connect to */
1050 num_mxhosts = getmx(mxhosts, node);
1051 CtdlLogPrintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d [%s]\n", node, num_mxhosts, mxhosts);
1052 if (num_mxhosts < 1) {
1054 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1059 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1061 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1062 strcpy(mx_user, "");
1063 strcpy(mx_pass, "");
1064 if (num_tokens(buf, '@') > 1) {
1065 strcpy (mx_user, buf);
1066 endpart = strrchr(mx_user, '@');
1068 strcpy (mx_host, endpart + 1);
1069 endpart = strrchr(mx_user, ':');
1070 if (endpart != NULL) {
1071 strcpy(mx_pass, endpart+1);
1076 strcpy (mx_host, buf);
1077 endpart = strrchr(mx_host, ':');
1080 strcpy(mx_port, endpart + 1);
1083 strcpy(mx_port, "25");
1085 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connecting to %s : %s ...\n", mx_host, mx_port);
1086 sock = sock_connect(mx_host, mx_port, "tcp");
1087 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1090 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connected!\n");
1092 fdflags = fcntl(sock, F_GETFL);
1094 CtdlLogPrintf(CTDL_DEBUG,
1095 "unable to get SMTP-Client socket flags! %s \n",
1097 fdflags = fdflags | O_NONBLOCK;
1098 if (fcntl(sock, F_SETFL, fdflags) < 0)
1099 CtdlLogPrintf(CTDL_DEBUG,
1100 "unable to set SMTP-Client socket nonblocking flags! %s \n",
1105 snprintf(dsn, SIZ, "%s", strerror(errno));
1108 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
1114 *status = 4; /* dsn is already filled in */
1118 CCC->sReadBuf = NewStrBuf();
1119 CCC->sMigrateBuf = NewStrBuf();
1122 /* Process the SMTP greeting from the server */
1123 if (ml_sock_gets(&sock, buf) < 0) {
1125 strcpy(dsn, "Connection broken during SMTP conversation");
1128 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1129 if (buf[0] != '2') {
1130 if (buf[0] == '4') {
1132 safestrncpy(dsn, &buf[4], 1023);
1137 safestrncpy(dsn, &buf[4], 1023);
1142 /* At this point we know we are talking to a real SMTP server */
1144 /* Do a EHLO command. If it fails, try the HELO command. */
1145 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1146 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1147 sock_write(&sock, buf, strlen(buf));
1148 if (ml_sock_gets(&sock, buf) < 0) {
1150 strcpy(dsn, "Connection broken during SMTP HELO");
1153 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1154 if (buf[0] != '2') {
1155 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1156 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1157 sock_write(&sock, buf, strlen(buf));
1158 if (ml_sock_gets(&sock, buf) < 0) {
1160 strcpy(dsn, "Connection broken during SMTP HELO");
1164 if (buf[0] != '2') {
1165 if (buf[0] == '4') {
1167 safestrncpy(dsn, &buf[4], 1023);
1172 safestrncpy(dsn, &buf[4], 1023);
1177 /* Do an AUTH command if necessary */
1178 if (!IsEmptyStr(mx_user)) {
1180 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
1181 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2, 0);
1182 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
1183 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1184 sock_write(&sock, buf, strlen(buf));
1185 if (ml_sock_gets(&sock, buf) < 0) {
1187 strcpy(dsn, "Connection broken during SMTP AUTH");
1190 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1191 if (buf[0] != '2') {
1192 if (buf[0] == '4') {
1194 safestrncpy(dsn, &buf[4], 1023);
1199 safestrncpy(dsn, &buf[4], 1023);
1205 /* previous command succeeded, now try the MAIL FROM: command */
1206 snprintf(buf, sizeof buf, "MAIL FROM:<%s>\r\n", envelope_from);
1207 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1208 sock_write(&sock, buf, strlen(buf));
1209 if (ml_sock_gets(&sock, buf) < 0) {
1211 strcpy(dsn, "Connection broken during SMTP MAIL");
1214 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1215 if (buf[0] != '2') {
1216 if (buf[0] == '4') {
1218 safestrncpy(dsn, &buf[4], 1023);
1223 safestrncpy(dsn, &buf[4], 1023);
1228 /* MAIL succeeded, now try the RCPT To: command */
1229 snprintf(buf, sizeof buf, "RCPT TO:<%s@%s>\r\n", user, node);
1230 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1231 sock_write(&sock, buf, strlen(buf));
1232 if (ml_sock_gets(&sock, buf) < 0) {
1234 strcpy(dsn, "Connection broken during SMTP RCPT");
1237 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1238 if (buf[0] != '2') {
1239 if (buf[0] == '4') {
1241 safestrncpy(dsn, &buf[4], 1023);
1246 safestrncpy(dsn, &buf[4], 1023);
1251 /* RCPT succeeded, now try the DATA command */
1252 CtdlLogPrintf(CTDL_DEBUG, ">DATA\n");
1253 sock_write(&sock, "DATA\r\n", 6);
1254 if (ml_sock_gets(&sock, buf) < 0) {
1256 strcpy(dsn, "Connection broken during SMTP DATA");
1259 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1260 if (buf[0] != '3') {
1261 if (buf[0] == '4') {
1263 safestrncpy(dsn, &buf[4], 1023);
1268 safestrncpy(dsn, &buf[4], 1023);
1273 /* If we reach this point, the server is expecting data.*/
1274 sock_write(&sock, msgtext, msg_size);
1275 if (msgtext[msg_size-1] != 10) {
1276 CtdlLogPrintf(CTDL_WARNING, "Possible problem: message did not "
1277 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1279 sock_write(&sock, "\r\n", 2);
1282 sock_write(&sock, ".\r\n", 3);
1283 if (ml_sock_gets(&sock, buf) < 0) {
1285 strcpy(dsn, "Connection broken during SMTP message transmit");
1288 CtdlLogPrintf(CTDL_DEBUG, "%s\n", buf);
1289 if (buf[0] != '2') {
1290 if (buf[0] == '4') {
1292 safestrncpy(dsn, &buf[4], 1023);
1297 safestrncpy(dsn, &buf[4], 1023);
1303 safestrncpy(dsn, &buf[4], 1023);
1306 CtdlLogPrintf(CTDL_DEBUG, ">QUIT\n");
1307 sock_write(&sock, "QUIT\r\n", 6);
1308 ml_sock_gets(&sock, buf);
1309 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1310 CtdlLogPrintf(CTDL_INFO, "SMTP client: delivery to <%s> @ <%s> (%s) succeeded\n",
1313 bail: free(msgtext);
1314 FreeStrBuf(&CCC->sReadBuf);
1315 FreeStrBuf(&CCC->sMigrateBuf);
1319 /* Write something to the syslog (which may or may not be where the
1320 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1322 if (enable_syslog) {
1323 syslog((LOG_MAIL | LOG_INFO),
1324 "%ld: to=<%s>, relay=%s, stat=%s",
1338 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1339 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1340 * a "bounce" message (delivery status notification).
1342 void smtp_do_bounce(char *instr) {
1350 char bounceto[1024];
1352 int num_bounces = 0;
1353 int bounce_this = 0;
1354 long bounce_msgid = (-1);
1355 time_t submitted = 0L;
1356 struct CtdlMessage *bmsg = NULL;
1358 struct recptypes *valid;
1359 int successful_bounce = 0;
1364 CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1365 strcpy(bounceto, "");
1366 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
1367 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
1368 lines = num_tokens(instr, '\n');
1370 /* See if it's time to give up on delivery of this message */
1371 for (i=0; i<lines; ++i) {
1372 extract_token(buf, instr, i, '\n', sizeof buf);
1373 extract_token(key, buf, 0, '|', sizeof key);
1374 extract_token(addr, buf, 1, '|', sizeof addr);
1375 if (!strcasecmp(key, "submitted")) {
1376 submitted = atol(addr);
1380 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1384 /* Start building our bounce message */
1386 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1387 if (bmsg == NULL) return;
1388 memset(bmsg, 0, sizeof(struct CtdlMessage));
1389 BounceMB = NewStrBufPlain(NULL, 1024);
1391 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1392 bmsg->cm_anon_type = MES_NORMAL;
1393 bmsg->cm_format_type = FMT_RFC822;
1394 bmsg->cm_fields['A'] = strdup("Citadel");
1395 bmsg->cm_fields['O'] = strdup(MAILROOM);
1396 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1397 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1398 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
1399 StrBufAppendBuf(BounceMB, boundary, 0);
1400 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
1401 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
1402 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
1403 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
1404 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1405 StrBufAppendBuf(BounceMB, boundary, 0);
1406 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1407 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
1409 if (give_up) StrBufAppendBufPlain(BounceMB, HKEY(
1410 "A message you sent could not be delivered to some or all of its recipients\n"
1411 "due to prolonged unavailability of its destination(s).\n"
1412 "Giving up on the following addresses:\n\n"
1415 else StrBufAppendBufPlain(BounceMB, HKEY(
1416 "A message you sent could not be delivered to some or all of its recipients.\n"
1417 "The following addresses were undeliverable:\n\n"
1421 * Now go through the instructions checking for stuff.
1423 for (i=0; i<lines; ++i) {
1426 extract_token(buf, instr, i, '\n', sizeof buf);
1427 extract_token(key, buf, 0, '|', sizeof key);
1428 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
1429 status = extract_int(buf, 2);
1430 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
1433 CtdlLogPrintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1434 key, addr, status, dsn);
1436 if (!strcasecmp(key, "bounceto")) {
1437 strcpy(bounceto, addr);
1440 if (!strcasecmp(key, "msgid")) {
1441 omsgid = atol(addr);
1444 if (!strcasecmp(key, "remote")) {
1445 if (status == 5) bounce_this = 1;
1446 if (give_up) bounce_this = 1;
1452 StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
1453 StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
1454 StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
1455 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1457 remove_token(instr, i, '\n');
1463 /* Attach the original message */
1465 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1466 StrBufAppendBuf(BounceMB, boundary, 0);
1467 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1468 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
1469 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
1470 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
1471 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1473 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
1474 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0);
1475 StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
1476 FreeStrBuf(&CC->redirect_buffer);
1479 /* Close the multipart MIME scope */
1480 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1481 StrBufAppendBuf(BounceMB, boundary, 0);
1482 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
1483 if (bmsg->cm_fields['A'] != NULL)
1484 free(bmsg->cm_fields['A']);
1485 bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB);
1486 /* Deliver the bounce if there's anything worth mentioning */
1487 CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1488 if (num_bounces > 0) {
1490 /* First try the user who sent the message */
1491 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1492 if (IsEmptyStr(bounceto)) {
1493 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
1494 bounce_msgid = (-1L);
1497 /* Can we deliver the bounce to the original sender? */
1498 valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
1499 if (valid != NULL) {
1500 if (valid->num_error == 0) {
1501 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
1502 successful_bounce = 1;
1506 /* If not, post it in the Aide> room */
1507 if (successful_bounce == 0) {
1508 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
1511 /* Free up the memory we used */
1512 if (valid != NULL) {
1513 free_recipients(valid);
1516 FreeStrBuf(&boundary);
1517 CtdlFreeMessage(bmsg);
1518 CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
1523 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1524 * set of delivery instructions for completed deliveries and remove them.
1526 * It returns the number of incomplete deliveries remaining.
1528 int smtp_purge_completed_deliveries(char *instr) {
1539 lines = num_tokens(instr, '\n');
1540 for (i=0; i<lines; ++i) {
1541 extract_token(buf, instr, i, '\n', sizeof buf);
1542 extract_token(key, buf, 0, '|', sizeof key);
1543 extract_token(addr, buf, 1, '|', sizeof addr);
1544 status = extract_int(buf, 2);
1545 extract_token(dsn, buf, 3, '|', sizeof dsn);
1549 if (!strcasecmp(key, "remote")) {
1550 if (status == 2) completed = 1;
1555 remove_token(instr, i, '\n');
1568 * Called by smtp_do_queue() to handle an individual message.
1570 void smtp_do_procmsg(long msgnum, void *userdata) {
1571 struct CtdlMessage *msg = NULL;
1573 char *results = NULL;
1581 char envelope_from[1024];
1582 long text_msgid = (-1);
1583 int incomplete_deliveries_remaining;
1584 time_t attempted = 0L;
1585 time_t last_attempted = 0L;
1586 time_t retry = SMTP_RETRY_INTERVAL;
1588 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: smtp_do_procmsg(%ld)\n", msgnum);
1589 strcpy(envelope_from, "");
1591 msg = CtdlFetchMessage(msgnum, 1);
1593 CtdlLogPrintf(CTDL_ERR, "SMTP client: tried %ld but no such message!\n", msgnum);
1597 instr = strdup(msg->cm_fields['M']);
1598 CtdlFreeMessage(msg);
1600 /* Strip out the headers amd any other non-instruction line */
1601 lines = num_tokens(instr, '\n');
1602 for (i=0; i<lines; ++i) {
1603 extract_token(buf, instr, i, '\n', sizeof buf);
1604 if (num_tokens(buf, '|') < 2) {
1605 remove_token(instr, i, '\n');
1611 /* Learn the message ID and find out about recent delivery attempts */
1612 lines = num_tokens(instr, '\n');
1613 for (i=0; i<lines; ++i) {
1614 extract_token(buf, instr, i, '\n', sizeof buf);
1615 extract_token(key, buf, 0, '|', sizeof key);
1616 if (!strcasecmp(key, "msgid")) {
1617 text_msgid = extract_long(buf, 1);
1619 if (!strcasecmp(key, "envelope_from")) {
1620 extract_token(envelope_from, buf, 1, '|', sizeof envelope_from);
1622 if (!strcasecmp(key, "retry")) {
1623 /* double the retry interval after each attempt */
1624 retry = extract_long(buf, 1) * 2L;
1625 if (retry > SMTP_RETRY_MAX) {
1626 retry = SMTP_RETRY_MAX;
1628 remove_token(instr, i, '\n');
1630 if (!strcasecmp(key, "attempted")) {
1631 attempted = extract_long(buf, 1);
1632 if (attempted > last_attempted)
1633 last_attempted = attempted;
1638 * Postpone delivery if we've already tried recently.
1640 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1641 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1648 * Bail out if there's no actual message associated with this
1650 if (text_msgid < 0L) {
1651 CtdlLogPrintf(CTDL_ERR, "SMTP client: no 'msgid' directive found!\n");
1656 /* Plow through the instructions looking for 'remote' directives and
1657 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1658 * were experienced and it's time to try again)
1660 lines = num_tokens(instr, '\n');
1661 for (i=0; i<lines; ++i) {
1662 extract_token(buf, instr, i, '\n', sizeof buf);
1663 extract_token(key, buf, 0, '|', sizeof key);
1664 extract_token(addr, buf, 1, '|', sizeof addr);
1665 status = extract_int(buf, 2);
1666 extract_token(dsn, buf, 3, '|', sizeof dsn);
1667 if ( (!strcasecmp(key, "remote"))
1668 && ((status==0)||(status==3)||(status==4)) ) {
1670 /* Remove this "remote" instruction from the set,
1671 * but replace the set's final newline if
1672 * remove_token() stripped it. It has to be there.
1674 remove_token(instr, i, '\n');
1675 if (instr[strlen(instr)-1] != '\n') {
1676 strcat(instr, "\n");
1681 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Trying <%s>\n", addr);
1682 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid, envelope_from);
1684 if (results == NULL) {
1685 results = malloc(1024);
1686 memset(results, 0, 1024);
1689 results = realloc(results, strlen(results) + 1024);
1691 snprintf(&results[strlen(results)], 1024,
1693 key, addr, status, dsn);
1698 if (results != NULL) {
1699 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1700 strcat(instr, results);
1705 /* Generate 'bounce' messages */
1706 smtp_do_bounce(instr);
1708 /* Go through the delivery list, deleting completed deliveries */
1709 incomplete_deliveries_remaining =
1710 smtp_purge_completed_deliveries(instr);
1714 * No delivery instructions remain, so delete both the instructions
1715 * message and the message message.
1717 if (incomplete_deliveries_remaining <= 0) {
1719 delmsgs[0] = msgnum;
1720 delmsgs[1] = text_msgid;
1721 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
1725 * Uncompleted delivery instructions remain, so delete the old
1726 * instructions and replace with the updated ones.
1728 if (incomplete_deliveries_remaining > 0) {
1729 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
1730 msg = malloc(sizeof(struct CtdlMessage));
1731 memset(msg, 0, sizeof(struct CtdlMessage));
1732 msg->cm_magic = CTDLMESSAGE_MAGIC;
1733 msg->cm_anon_type = MES_NORMAL;
1734 msg->cm_format_type = FMT_RFC822;
1735 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1736 snprintf(msg->cm_fields['M'],
1738 "Content-type: %s\n\n%s\n"
1741 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1742 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
1743 CtdlFreeMessage(msg);
1755 * Run through the queue sending out messages.
1757 void *smtp_do_queue(void *arg) {
1758 int num_processed = 0;
1759 struct CitContext smtp_queue_CC;
1761 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1762 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC );
1763 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1765 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1766 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1769 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1772 citthread_mutex_unlock (&smtp_send_lock);
1773 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1775 CtdlClearSystemContext();
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 */