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
51 #include <sys/types.h>
54 #if TIME_WITH_SYS_TIME
55 # include <sys/time.h>
59 # include <sys/time.h>
69 #include <sys/socket.h>
70 #include <netinet/in.h>
71 #include <arpa/inet.h>
72 #include <libcitadel.h>
75 #include "citserver.h"
82 #include "internet_addressing.h"
85 #include "clientsocket.h"
86 #include "locate_host.h"
87 #include "citadel_dirs.h"
96 #include "ctdl_module.h"
100 typedef struct _citsmtp { /* Information about the current session */
104 char recipients[SIZ];
105 int number_of_recipients;
107 int message_originated_locally;
113 enum { /* Command states for login authentication */
120 #define SMTP ((citsmtp *)CC->session_specific_data)
123 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
125 citthread_mutex_t smtp_send_lock;
128 /*****************************************************************************/
129 /* SMTP SERVER (INBOUND) STUFF */
130 /*****************************************************************************/
134 * Here's where our SMTP session begins its happy day.
136 void smtp_greeting(int is_msa)
139 char message_to_spammer[1024];
141 strcpy(CC->cs_clientname, "SMTP session");
142 CC->internal_pgm = 1;
143 CC->cs_flags |= CS_STEALTH;
144 CC->session_specific_data = malloc(sizeof(citsmtp));
145 memset(SMTP, 0, sizeof(citsmtp));
147 sSMTP->is_msa = is_msa;
149 /* If this config option is set, reject connections from problem
150 * addresses immediately instead of after they execute a RCPT
152 if ( (config.c_rbl_at_greeting) && (sSMTP->is_msa == 0) ) {
153 if (rbl_check(message_to_spammer)) {
154 if (CtdlThreadCheckStop())
155 cprintf("421 %s\r\n", message_to_spammer);
157 cprintf("550 %s\r\n", message_to_spammer);
159 /* no need to free_recipients(valid), it's not allocated yet */
164 /* Otherwise we're either clean or we check later. */
166 if (CC->nologin==1) {
167 cprintf("500 Too many users are already online (maximum is %d)\r\n",
171 /* no need to free_recipients(valid), it's not allocated yet */
175 /* Note: the FQDN *must* appear as the first thing after the 220 code.
176 * Some clients (including citmail.c) depend on it being there.
178 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
183 * SMTPS is just like SMTP, except it goes crypto right away.
185 void smtps_greeting(void) {
186 CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
188 if (!CC->redirect_ssl) CC->kill_me = 1; /* kill session if no crypto */
195 * SMTP MSA port requires authentication.
197 void smtp_msa_greeting(void) {
203 * LMTP is like SMTP but with some extra bonus footage added.
205 void lmtp_greeting(void) {
215 * Generic SMTP MTA greeting
217 void smtp_mta_greeting(void) {
223 * We also have an unfiltered LMTP socket that bypasses spam filters.
225 void lmtp_unfiltered_greeting(void) {
231 sSMTP->is_unfiltered = 1;
236 * Login greeting common to all auth methods
238 void smtp_auth_greeting(void) {
239 cprintf("235 Hello, %s\r\n", CC->user.fullname);
240 CtdlLogPrintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
241 CC->internal_pgm = 0;
242 CC->cs_flags &= ~CS_STEALTH;
247 * Implement HELO and EHLO commands.
249 * which_command: 0=HELO, 1=EHLO, 2=LHLO
251 void smtp_hello(char *argbuf, int which_command) {
252 citsmtp *sSMTP = SMTP;
254 safestrncpy(sSMTP->helo_node, argbuf, sizeof sSMTP->helo_node);
256 if ( (which_command != 2) && (sSMTP->is_lmtp) ) {
257 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
261 if ( (which_command == 2) && (sSMTP->is_lmtp == 0) ) {
262 cprintf("500 LHLO is only allowed when running LMTP\r\n");
266 if (which_command == 0) {
267 cprintf("250 Hello %s (%s [%s])\r\n",
274 if (which_command == 1) {
275 cprintf("250-Hello %s (%s [%s])\r\n",
282 cprintf("250-Greetings and joyous salutations.\r\n");
284 cprintf("250-HELP\r\n");
285 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
289 * Offer TLS, but only if TLS is not already active.
290 * Furthermore, only offer TLS when running on
291 * the SMTP-MSA port, not on the SMTP-MTA port, due to
292 * questionable reliability of TLS in certain sending MTA's.
294 if ( (!CC->redirect_ssl) && (sSMTP->is_msa) ) {
295 cprintf("250-STARTTLS\r\n");
297 #endif /* HAVE_OPENSSL */
299 cprintf("250-AUTH LOGIN PLAIN\r\n"
300 "250-AUTH=LOGIN PLAIN\r\n"
309 * Implement HELP command.
311 void smtp_help(void) {
312 cprintf("214 RTFM http://www.ietf.org/rfc/rfc2821.txt\r\n");
319 void smtp_get_user(char *argbuf) {
322 citsmtp *sSMTP = SMTP;
324 CtdlDecodeBase64(username, argbuf, SIZ);
325 /* CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", username); */
326 if (CtdlLoginExistingUser(NULL, username) == login_ok) {
327 CtdlEncodeBase64(buf, "Password:", 9, 0);
328 cprintf("334 %s\r\n", buf);
329 sSMTP->command_state = smtp_password;
332 cprintf("500 No such user.\r\n");
333 sSMTP->command_state = smtp_command;
341 void smtp_get_pass(char *argbuf) {
345 memset(password, 0, sizeof(password));
346 len = CtdlDecodeBase64(password, argbuf, SIZ);
347 /* CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", password); */
348 if (CtdlTryPassword(password, len) == pass_ok) {
349 smtp_auth_greeting();
352 cprintf("535 Authentication failed.\r\n");
354 SMTP->command_state = smtp_command;
359 * Back end for PLAIN auth method (either inline or multistate)
361 void smtp_try_plain(char *encoded_authstring) {
362 char decoded_authstring[1024];
369 CtdlDecodeBase64(decoded_authstring, encoded_authstring, strlen(encoded_authstring) );
370 safestrncpy(ident, decoded_authstring, sizeof ident);
371 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
372 len = safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
374 len = sizeof(pass) - 1;
376 SMTP->command_state = smtp_command;
378 if (!IsEmptyStr(ident)) {
379 result = CtdlLoginExistingUser(user, ident);
382 result = CtdlLoginExistingUser(NULL, user);
385 if (result == login_ok) {
386 if (CtdlTryPassword(pass, len) == pass_ok) {
387 smtp_auth_greeting();
391 cprintf("504 Authentication failed.\r\n");
396 * Attempt to perform authenticated SMTP
398 void smtp_auth(char *argbuf) {
399 char username_prompt[64];
401 char encoded_authstring[1024];
404 cprintf("504 Already logged in.\r\n");
408 extract_token(method, argbuf, 0, ' ', sizeof method);
410 if (!strncasecmp(method, "login", 5) ) {
411 if (strlen(argbuf) >= 7) {
412 smtp_get_user(&argbuf[6]);
415 CtdlEncodeBase64(username_prompt, "Username:", 9, 0);
416 cprintf("334 %s\r\n", username_prompt);
417 SMTP->command_state = smtp_user;
422 if (!strncasecmp(method, "plain", 5) ) {
423 if (num_tokens(argbuf, ' ') < 2) {
425 SMTP->command_state = smtp_plain;
429 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
431 smtp_try_plain(encoded_authstring);
435 if (strncasecmp(method, "login", 5) ) {
436 cprintf("504 Unknown authentication method.\r\n");
444 * Implements the RSET (reset state) command.
445 * Currently this just zeroes out the state buffer. If pointers to data
446 * allocated with malloc() are ever placed in the state buffer, we have to
447 * be sure to free() them first!
449 * Set do_response to nonzero to output the SMTP RSET response code.
451 void smtp_rset(int do_response) {
454 citsmtp *sSMTP = SMTP;
457 * Our entire SMTP state is discarded when a RSET command is issued,
458 * but we need to preserve this one little piece of information, so
459 * we save it for later.
461 is_lmtp = sSMTP->is_lmtp;
462 is_unfiltered = sSMTP->is_unfiltered;
464 memset(sSMTP, 0, sizeof(citsmtp));
467 * It is somewhat ambiguous whether we want to log out when a RSET
468 * command is issued. Here's the code to do it. It is commented out
469 * because some clients (such as Pine) issue RSET commands before
470 * each message, but still expect to be logged in.
472 * if (CC->logged_in) {
478 * Reinstate this little piece of information we saved (see above).
480 sSMTP->is_lmtp = is_lmtp;
481 sSMTP->is_unfiltered = is_unfiltered;
484 cprintf("250 Zap!\r\n");
489 * Clear out the portions of the state buffer that need to be cleared out
490 * after the DATA command finishes.
492 void smtp_data_clear(void) {
493 citsmtp *sSMTP = SMTP;
495 strcpy(sSMTP->from, "");
496 strcpy(sSMTP->recipients, "");
497 sSMTP->number_of_recipients = 0;
498 sSMTP->delivery_mode = 0;
499 sSMTP->message_originated_locally = 0;
502 const char *smtp_get_Recipients(void)
504 citsmtp *sSMTP = SMTP;
508 else return sSMTP->from;
512 * Implements the "MAIL FROM:" command
514 void smtp_mail(char *argbuf) {
518 citsmtp *sSMTP = SMTP;
520 if (!IsEmptyStr(sSMTP->from)) {
521 cprintf("503 Only one sender permitted\r\n");
525 if (strncasecmp(argbuf, "From:", 5)) {
526 cprintf("501 Syntax error\r\n");
530 strcpy(sSMTP->from, &argbuf[5]);
531 striplt(sSMTP->from);
532 if (haschar(sSMTP->from, '<') > 0) {
533 stripallbut(sSMTP->from, '<', '>');
536 /* We used to reject empty sender names, until it was brought to our
537 * attention that RFC1123 5.2.9 requires that this be allowed. So now
538 * we allow it, but replace the empty string with a fake
539 * address so we don't have to contend with the empty string causing
540 * other code to fail when it's expecting something there.
542 if (IsEmptyStr(sSMTP->from)) {
543 strcpy(sSMTP->from, "someone@example.com");
546 /* If this SMTP connection is from a logged-in user, force the 'from'
547 * to be the user's Internet e-mail address as Citadel knows it.
550 safestrncpy(sSMTP->from, CC->cs_inet_email, sizeof sSMTP->from);
551 cprintf("250 Sender ok <%s>\r\n", sSMTP->from);
552 sSMTP->message_originated_locally = 1;
556 else if (sSMTP->is_lmtp) {
557 /* Bypass forgery checking for LMTP */
560 /* Otherwise, make sure outsiders aren't trying to forge mail from
561 * this system (unless, of course, c_allow_spoofing is enabled)
563 else if (config.c_allow_spoofing == 0) {
564 process_rfc822_addr(sSMTP->from, user, node, name);
565 if (CtdlHostAlias(node) != hostalias_nomatch) {
566 cprintf("550 You must log in to send mail from %s\r\n", node);
567 strcpy(sSMTP->from, "");
572 cprintf("250 Sender ok\r\n");
578 * Implements the "RCPT To:" command
580 void smtp_rcpt(char *argbuf) {
582 char message_to_spammer[SIZ];
583 struct recptypes *valid = NULL;
584 citsmtp *sSMTP = SMTP;
586 if (IsEmptyStr(sSMTP->from)) {
587 cprintf("503 Need MAIL before RCPT\r\n");
591 if (strncasecmp(argbuf, "To:", 3)) {
592 cprintf("501 Syntax error\r\n");
596 if ( (sSMTP->is_msa) && (!CC->logged_in) ) {
597 cprintf("550 You must log in to send mail on this port.\r\n");
598 strcpy(sSMTP->from, "");
602 safestrncpy(recp, &argbuf[3], sizeof recp);
604 stripallbut(recp, '<', '>');
606 if ( (strlen(recp) + strlen(sSMTP->recipients) + 1 ) >= SIZ) {
607 cprintf("452 Too many recipients\r\n");
612 if ( (!CC->logged_in) /* Don't RBL authenticated users */
613 && (!sSMTP->is_lmtp) ) { /* Don't RBL LMTP clients */
614 if (config.c_rbl_at_greeting == 0) { /* Don't RBL again if we already did it */
615 if (rbl_check(message_to_spammer)) {
616 if (CtdlThreadCheckStop())
617 cprintf("421 %s\r\n", message_to_spammer);
619 cprintf("550 %s\r\n", message_to_spammer);
620 /* no need to free_recipients(valid), it's not allocated yet */
626 valid = validate_recipients(recp,
627 smtp_get_Recipients (),
628 (sSMTP->is_lmtp)? POST_LMTP:
629 (CC->logged_in)? POST_LOGGED_IN:
631 if (valid->num_error != 0) {
632 cprintf("550 %s\r\n", valid->errormsg);
633 free_recipients(valid);
637 if (valid->num_internet > 0) {
639 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
640 cprintf("551 <%s> - you do not have permission to send Internet mail\r\n", recp);
641 free_recipients(valid);
647 if (valid->num_internet > 0) {
648 if ( (sSMTP->message_originated_locally == 0)
649 && (sSMTP->is_lmtp == 0) ) {
650 cprintf("551 <%s> - relaying denied\r\n", recp);
651 free_recipients(valid);
656 cprintf("250 RCPT ok <%s>\r\n", recp);
657 if (!IsEmptyStr(sSMTP->recipients)) {
658 strcat(sSMTP->recipients, ",");
660 strcat(sSMTP->recipients, recp);
661 sSMTP->number_of_recipients += 1;
663 free_recipients(valid);
671 * Implements the DATA command
673 void smtp_data(void) {
675 char *defbody; //TODO: remove me
676 struct CtdlMessage *msg = NULL;
679 struct recptypes *valid;
683 citsmtp *sSMTP = SMTP;
685 if (IsEmptyStr(sSMTP->from)) {
686 cprintf("503 Need MAIL command first.\r\n");
690 if (sSMTP->number_of_recipients < 1) {
691 cprintf("503 Need RCPT command first.\r\n");
695 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
697 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
698 defbody = malloc(4096);
700 if (defbody != NULL) {
701 if (sSMTP->is_lmtp && (CC->cs_UDSclientUID != -1)) {
702 snprintf(defbody, 4096,
703 "Received: from %s (Citadel from userid %ld)\n"
706 (long int) CC->cs_UDSclientUID,
711 snprintf(defbody, 4096,
712 "Received: from %s (%s [%s])\n"
721 body = CtdlReadMessageBodyBuf(HKEY("."), config.c_maxmsglen, defbody, 1, NULL);
723 cprintf("550 Unable to save message: internal error.\r\n");
727 CtdlLogPrintf(CTDL_DEBUG, "Converting message...\n");
728 msg = convert_internet_message_buf(&body);
730 /* If the user is locally authenticated, FORCE the From: header to
731 * show up as the real sender. Yes, this violates the RFC standard,
732 * but IT MAKES SENSE. If you prefer strict RFC adherence over
733 * common sense, you can disable this in the configuration.
735 * We also set the "message room name" ('O' field) to MAILROOM
736 * (which is Mail> on most systems) to prevent it from getting set
737 * to something ugly like "0000058008.Sent Items>" when the message
738 * is read with a Citadel client.
740 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
741 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
742 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
743 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
744 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
745 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
746 msg->cm_fields['A'] = strdup(CC->user.fullname);
747 msg->cm_fields['N'] = strdup(config.c_nodename);
748 msg->cm_fields['H'] = strdup(config.c_humannode);
749 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
750 msg->cm_fields['O'] = strdup(MAILROOM);
753 /* Set the "envelope from" address */
754 if (msg->cm_fields['P'] != NULL) {
755 free(msg->cm_fields['P']);
757 msg->cm_fields['P'] = strdup(sSMTP->from);
759 /* Set the "envelope to" address */
760 if (msg->cm_fields['V'] != NULL) {
761 free(msg->cm_fields['V']);
763 msg->cm_fields['V'] = strdup(sSMTP->recipients);
765 /* Submit the message into the Citadel system. */
766 valid = validate_recipients(sSMTP->recipients,
767 smtp_get_Recipients (),
768 (sSMTP->is_lmtp)? POST_LMTP:
769 (CC->logged_in)? POST_LOGGED_IN:
772 /* If there are modules that want to scan this message before final
773 * submission (such as virus checkers or spam filters), call them now
774 * and give them an opportunity to reject the message.
776 if (sSMTP->is_unfiltered) {
780 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
783 if (scan_errors > 0) { /* We don't want this message! */
785 if (msg->cm_fields['0'] == NULL) {
786 msg->cm_fields['0'] = strdup("Message rejected by filter");
789 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
792 else { /* Ok, we'll accept this message. */
793 msgnum = CtdlSubmitMsg(msg, valid, "", 0);
795 sprintf(result, "250 Message accepted.\r\n");
798 sprintf(result, "550 Internal delivery error\r\n");
802 /* For SMTP and ESTMP, just print the result message. For LMTP, we
803 * have to print one result message for each recipient. Since there
804 * is nothing in Citadel which would cause different recipients to
805 * have different results, we can get away with just spitting out the
806 * same message once for each recipient.
808 if (sSMTP->is_lmtp) {
809 for (i=0; i<sSMTP->number_of_recipients; ++i) {
810 cprintf("%s", result);
814 cprintf("%s", result);
817 /* Write something to the syslog (which may or may not be where the
818 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
821 syslog((LOG_MAIL | LOG_INFO),
822 "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
825 sSMTP->number_of_recipients,
833 CtdlFreeMessage(msg);
834 free_recipients(valid);
835 smtp_data_clear(); /* clear out the buffers now */
840 * implements the STARTTLS command (Citadel API version)
842 void smtp_starttls(void)
844 char ok_response[SIZ];
845 char nosup_response[SIZ];
846 char error_response[SIZ];
849 "220 Begin TLS negotiation now\r\n");
850 sprintf(nosup_response,
851 "554 TLS not supported here\r\n");
852 sprintf(error_response,
853 "554 Internal error\r\n");
854 CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
861 * Main command loop for SMTP sessions.
863 void smtp_command_loop(void) {
865 citsmtp *sSMTP = SMTP;
868 CtdlLogPrintf(CTDL_EMERG, "Session SMTP data is null. WTF? We will crash now.\n");
872 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
873 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
874 CtdlLogPrintf(CTDL_CRIT, "Client disconnected: ending session.\n");
878 CtdlLogPrintf(CTDL_INFO, "SMTP server: %s\n", cmdbuf);
879 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
881 if (sSMTP->command_state == smtp_user) {
882 smtp_get_user(cmdbuf);
885 else if (sSMTP->command_state == smtp_password) {
886 smtp_get_pass(cmdbuf);
889 else if (sSMTP->command_state == smtp_plain) {
890 smtp_try_plain(cmdbuf);
893 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
894 smtp_auth(&cmdbuf[5]);
897 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
901 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
902 smtp_hello(&cmdbuf[5], 0);
905 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
906 smtp_hello(&cmdbuf[5], 1);
909 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
910 smtp_hello(&cmdbuf[5], 2);
913 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
917 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
918 smtp_mail(&cmdbuf[5]);
921 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
922 cprintf("250 NOOP\r\n");
925 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
926 cprintf("221 Goodbye...\r\n");
931 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
932 smtp_rcpt(&cmdbuf[5]);
935 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
939 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
944 cprintf("502 I'm afraid I can't do that.\r\n");
953 /*****************************************************************************/
954 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
955 /*****************************************************************************/
962 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
965 void smtp_try(const char *key, const char *addr, int *status,
966 char *dsn, size_t n, long msgnum, char *envelope_from)
973 char user[1024], node[1024], name[1024];
988 /* Parse out the host portion of the recipient address */
989 process_rfc822_addr(addr, user, node, name);
991 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Attempting delivery to <%s> @ <%s> (%s)\n",
994 /* Load the message out of the database */
995 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
996 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, ESC_DOT);
997 msg_size = StrLength(CC->redirect_buffer);
998 msgtext = SmashStrBuf(&CC->redirect_buffer);
1000 /* If no envelope_from is supplied, extract one from the message */
1001 if ( (envelope_from == NULL) || (IsEmptyStr(envelope_from)) ) {
1002 strcpy(mailfrom, "");
1006 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
1009 if (!strncasecmp(buf, "From:", 5)) {
1010 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
1012 for (i=0; mailfrom[i]; ++i) {
1013 if (!isprint(mailfrom[i])) {
1014 strcpy(&mailfrom[i], &mailfrom[i+1]);
1019 /* Strip out parenthesized names */
1022 for (i=0; mailfrom[i]; ++i) {
1023 if (mailfrom[i] == '(') lp = i;
1024 if (mailfrom[i] == ')') rp = i;
1026 if ((lp>0)&&(rp>lp)) {
1027 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
1030 /* Prefer brokketized names */
1033 for (i=0; mailfrom[i]; ++i) {
1034 if (mailfrom[i] == '<') lp = i;
1035 if (mailfrom[i] == '>') rp = i;
1037 if ( (lp>=0) && (rp>lp) ) {
1039 strcpy(mailfrom, &mailfrom[lp]);
1044 } while (scan_done == 0);
1045 if (IsEmptyStr(mailfrom)) strcpy(mailfrom, "someone@somewhere.org");
1046 stripallbut(mailfrom, '<', '>');
1047 envelope_from = mailfrom;
1050 /* Figure out what mail exchanger host we have to connect to */
1051 num_mxhosts = getmx(mxhosts, node);
1052 CtdlLogPrintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d [%s]\n", node, num_mxhosts, mxhosts);
1053 if (num_mxhosts < 1) {
1055 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1060 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1062 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1063 strcpy(mx_user, "");
1064 strcpy(mx_pass, "");
1065 if (num_tokens(buf, '@') > 1) {
1066 strcpy (mx_user, buf);
1067 endpart = strrchr(mx_user, '@');
1069 strcpy (mx_host, endpart + 1);
1070 endpart = strrchr(mx_user, ':');
1071 if (endpart != NULL) {
1072 strcpy(mx_pass, endpart+1);
1077 strcpy (mx_host, buf);
1078 endpart = strrchr(mx_host, ':');
1081 strcpy(mx_port, endpart + 1);
1084 strcpy(mx_port, "25");
1086 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connecting to %s : %s ...\n", mx_host, mx_port);
1087 sock = sock_connect(mx_host, mx_port, "tcp");
1088 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1091 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connected!\n");
1093 fdflags = fcntl(sock, F_GETFL);
1095 CtdlLogPrintf(CTDL_DEBUG,
1096 "unable to get SMTP-Client socket flags! %s \n",
1098 fdflags = fdflags | O_NONBLOCK;
1099 if (fcntl(sock, F_SETFL, fdflags) < 0)
1100 CtdlLogPrintf(CTDL_DEBUG,
1101 "unable to set SMTP-Client socket nonblocking flags! %s \n",
1106 snprintf(dsn, SIZ, "%s", strerror(errno));
1109 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
1115 *status = 4; /* dsn is already filled in */
1119 CCC->sReadBuf = NewStrBuf();
1120 CCC->sMigrateBuf = NewStrBuf();
1123 /* Process the SMTP greeting from the server */
1124 if (ml_sock_gets(&sock, buf, 5) < 0) {
1126 strcpy(dsn, "Connection broken during SMTP conversation");
1129 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1130 if (buf[0] != '2') {
1131 if (buf[0] == '4') {
1133 safestrncpy(dsn, &buf[4], 1023);
1138 safestrncpy(dsn, &buf[4], 1023);
1143 /* At this point we know we are talking to a real SMTP server */
1145 /* Do a EHLO command. If it fails, try the HELO command. */
1146 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1147 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1148 sock_write(&sock, buf, strlen(buf));
1149 if (ml_sock_gets(&sock, buf, 5) < 0) {
1151 strcpy(dsn, "Connection broken during SMTP HELO");
1154 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1155 if (buf[0] != '2') {
1156 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1157 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1158 sock_write(&sock, buf, strlen(buf));
1159 if (ml_sock_gets(&sock, buf, 5) < 0) {
1161 strcpy(dsn, "Connection broken during SMTP HELO");
1165 if (buf[0] != '2') {
1166 if (buf[0] == '4') {
1168 safestrncpy(dsn, &buf[4], 1023);
1173 safestrncpy(dsn, &buf[4], 1023);
1178 /* Do an AUTH command if necessary */
1179 if (!IsEmptyStr(mx_user)) {
1181 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
1182 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2, 0);
1183 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
1184 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1185 sock_write(&sock, buf, strlen(buf));
1186 if (ml_sock_gets(&sock, buf, 5) < 0) {
1188 strcpy(dsn, "Connection broken during SMTP AUTH");
1191 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1192 if (buf[0] != '2') {
1193 if (buf[0] == '4') {
1195 safestrncpy(dsn, &buf[4], 1023);
1200 safestrncpy(dsn, &buf[4], 1023);
1206 /* previous command succeeded, now try the MAIL FROM: command */
1207 snprintf(buf, sizeof buf, "MAIL FROM:<%s>\r\n", envelope_from);
1208 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1209 sock_write(&sock, buf, strlen(buf));
1210 if (ml_sock_gets(&sock, buf, 5) < 0) {
1212 strcpy(dsn, "Connection broken during SMTP MAIL");
1215 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1216 if (buf[0] != '2') {
1217 if (buf[0] == '4') {
1219 safestrncpy(dsn, &buf[4], 1023);
1224 safestrncpy(dsn, &buf[4], 1023);
1229 /* MAIL succeeded, now try the RCPT To: command */
1230 snprintf(buf, sizeof buf, "RCPT TO:<%s@%s>\r\n", user, node);
1231 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1232 sock_write(&sock, buf, strlen(buf));
1233 if (ml_sock_gets(&sock, buf, 5) < 0) {
1235 strcpy(dsn, "Connection broken during SMTP RCPT");
1238 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1239 if (buf[0] != '2') {
1240 if (buf[0] == '4') {
1242 safestrncpy(dsn, &buf[4], 1023);
1247 safestrncpy(dsn, &buf[4], 1023);
1252 /* RCPT succeeded, now try the DATA command */
1253 CtdlLogPrintf(CTDL_DEBUG, ">DATA\n");
1254 sock_write(&sock, "DATA\r\n", 6);
1255 if (ml_sock_gets(&sock, buf, 5) < 0) {
1257 strcpy(dsn, "Connection broken during SMTP DATA");
1260 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1261 if (buf[0] != '3') {
1262 if (buf[0] == '4') {
1264 safestrncpy(dsn, &buf[4], 1023);
1269 safestrncpy(dsn, &buf[4], 1023);
1274 /* If we reach this point, the server is expecting data.*/
1275 sock_write(&sock, msgtext, msg_size);
1276 if (msgtext[msg_size-1] != 10) {
1277 CtdlLogPrintf(CTDL_WARNING, "Possible problem: message did not "
1278 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1280 sock_write(&sock, "\r\n", 2);
1283 sock_write(&sock, ".\r\n", 3);
1285 if (ml_sock_gets(&sock, buf, 90) < 0) {
1287 strcpy(dsn, "Connection broken during SMTP message transmit");
1290 CtdlLogPrintf(CTDL_DEBUG, "%s\n", buf);
1291 if (buf[0] != '2') {
1292 if (buf[0] == '4') {
1294 safestrncpy(dsn, &buf[4], 1023);
1299 safestrncpy(dsn, &buf[4], 1023);
1305 safestrncpy(dsn, &buf[4], 1023);
1308 CtdlLogPrintf(CTDL_DEBUG, ">QUIT\n");
1309 sock_write(&sock, "QUIT\r\n", 6);
1310 ml_sock_gets(&sock, buf, 1);
1311 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1312 CtdlLogPrintf(CTDL_INFO, "SMTP client: delivery to <%s> @ <%s> (%s) succeeded\n",
1315 bail: free(msgtext);
1316 FreeStrBuf(&CCC->sReadBuf);
1317 FreeStrBuf(&CCC->sMigrateBuf);
1321 /* Write something to the syslog (which may or may not be where the
1322 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1324 if (enable_syslog) {
1325 syslog((LOG_MAIL | LOG_INFO),
1326 "%ld: to=<%s>, relay=%s, stat=%s",
1340 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1341 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1342 * a "bounce" message (delivery status notification).
1344 void smtp_do_bounce(char *instr) {
1352 char bounceto[1024];
1354 int num_bounces = 0;
1355 int bounce_this = 0;
1356 long bounce_msgid = (-1);
1357 time_t submitted = 0L;
1358 struct CtdlMessage *bmsg = NULL;
1360 struct recptypes *valid;
1361 int successful_bounce = 0;
1366 CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1367 strcpy(bounceto, "");
1368 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
1369 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
1370 lines = num_tokens(instr, '\n');
1372 /* See if it's time to give up on delivery of this message */
1373 for (i=0; i<lines; ++i) {
1374 extract_token(buf, instr, i, '\n', sizeof buf);
1375 extract_token(key, buf, 0, '|', sizeof key);
1376 extract_token(addr, buf, 1, '|', sizeof addr);
1377 if (!strcasecmp(key, "submitted")) {
1378 submitted = atol(addr);
1382 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1386 /* Start building our bounce message */
1388 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1389 if (bmsg == NULL) return;
1390 memset(bmsg, 0, sizeof(struct CtdlMessage));
1391 BounceMB = NewStrBufPlain(NULL, 1024);
1393 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1394 bmsg->cm_anon_type = MES_NORMAL;
1395 bmsg->cm_format_type = FMT_RFC822;
1396 bmsg->cm_fields['A'] = strdup("Citadel");
1397 bmsg->cm_fields['O'] = strdup(MAILROOM);
1398 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1399 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1400 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
1401 StrBufAppendBuf(BounceMB, boundary, 0);
1402 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
1403 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
1404 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
1405 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
1406 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1407 StrBufAppendBuf(BounceMB, boundary, 0);
1408 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1409 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
1411 if (give_up) StrBufAppendBufPlain(BounceMB, HKEY(
1412 "A message you sent could not be delivered to some or all of its recipients\n"
1413 "due to prolonged unavailability of its destination(s).\n"
1414 "Giving up on the following addresses:\n\n"
1417 else StrBufAppendBufPlain(BounceMB, HKEY(
1418 "A message you sent could not be delivered to some or all of its recipients.\n"
1419 "The following addresses were undeliverable:\n\n"
1423 * Now go through the instructions checking for stuff.
1425 for (i=0; i<lines; ++i) {
1428 extract_token(buf, instr, i, '\n', sizeof buf);
1429 extract_token(key, buf, 0, '|', sizeof key);
1430 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
1431 status = extract_int(buf, 2);
1432 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
1435 CtdlLogPrintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1436 key, addr, status, dsn);
1438 if (!strcasecmp(key, "bounceto")) {
1439 strcpy(bounceto, addr);
1442 if (!strcasecmp(key, "msgid")) {
1443 omsgid = atol(addr);
1446 if (!strcasecmp(key, "remote")) {
1447 if (status == 5) bounce_this = 1;
1448 if (give_up) bounce_this = 1;
1454 StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
1455 StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
1456 StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
1457 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1459 remove_token(instr, i, '\n');
1465 /* Attach the original message */
1467 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1468 StrBufAppendBuf(BounceMB, boundary, 0);
1469 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1470 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
1471 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
1472 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
1473 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1475 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
1476 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0);
1477 StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
1478 FreeStrBuf(&CC->redirect_buffer);
1481 /* Close the multipart MIME scope */
1482 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1483 StrBufAppendBuf(BounceMB, boundary, 0);
1484 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
1485 if (bmsg->cm_fields['A'] != NULL)
1486 free(bmsg->cm_fields['A']);
1487 bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB);
1488 /* Deliver the bounce if there's anything worth mentioning */
1489 CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1490 if (num_bounces > 0) {
1492 /* First try the user who sent the message */
1493 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1494 if (IsEmptyStr(bounceto)) {
1495 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
1496 bounce_msgid = (-1L);
1499 /* Can we deliver the bounce to the original sender? */
1500 valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
1501 if (valid != NULL) {
1502 if (valid->num_error == 0) {
1503 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
1504 successful_bounce = 1;
1508 /* If not, post it in the Aide> room */
1509 if (successful_bounce == 0) {
1510 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
1513 /* Free up the memory we used */
1514 if (valid != NULL) {
1515 free_recipients(valid);
1518 FreeStrBuf(&boundary);
1519 CtdlFreeMessage(bmsg);
1520 CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
1525 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1526 * set of delivery instructions for completed deliveries and remove them.
1528 * It returns the number of incomplete deliveries remaining.
1530 int smtp_purge_completed_deliveries(char *instr) {
1541 lines = num_tokens(instr, '\n');
1542 for (i=0; i<lines; ++i) {
1543 extract_token(buf, instr, i, '\n', sizeof buf);
1544 extract_token(key, buf, 0, '|', sizeof key);
1545 extract_token(addr, buf, 1, '|', sizeof addr);
1546 status = extract_int(buf, 2);
1547 extract_token(dsn, buf, 3, '|', sizeof dsn);
1551 if (!strcasecmp(key, "remote")) {
1552 if (status == 2) completed = 1;
1557 remove_token(instr, i, '\n');
1570 * Called by smtp_do_queue() to handle an individual message.
1572 void smtp_do_procmsg(long msgnum, void *userdata) {
1573 struct CtdlMessage *msg = NULL;
1575 char *results = NULL;
1583 char envelope_from[1024];
1584 long text_msgid = (-1);
1585 int incomplete_deliveries_remaining;
1586 time_t attempted = 0L;
1587 time_t last_attempted = 0L;
1588 time_t retry = SMTP_RETRY_INTERVAL;
1590 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: smtp_do_procmsg(%ld)\n", msgnum);
1591 strcpy(envelope_from, "");
1593 msg = CtdlFetchMessage(msgnum, 1);
1595 CtdlLogPrintf(CTDL_ERR, "SMTP client: tried %ld but no such message!\n", msgnum);
1599 instr = strdup(msg->cm_fields['M']);
1600 CtdlFreeMessage(msg);
1602 /* Strip out the headers amd any other non-instruction line */
1603 lines = num_tokens(instr, '\n');
1604 for (i=0; i<lines; ++i) {
1605 extract_token(buf, instr, i, '\n', sizeof buf);
1606 if (num_tokens(buf, '|') < 2) {
1607 remove_token(instr, i, '\n');
1613 /* Learn the message ID and find out about recent delivery attempts */
1614 lines = num_tokens(instr, '\n');
1615 for (i=0; i<lines; ++i) {
1616 extract_token(buf, instr, i, '\n', sizeof buf);
1617 extract_token(key, buf, 0, '|', sizeof key);
1618 if (!strcasecmp(key, "msgid")) {
1619 text_msgid = extract_long(buf, 1);
1621 if (!strcasecmp(key, "envelope_from")) {
1622 extract_token(envelope_from, buf, 1, '|', sizeof envelope_from);
1624 if (!strcasecmp(key, "retry")) {
1625 /* double the retry interval after each attempt */
1626 retry = extract_long(buf, 1) * 2L;
1627 if (retry > SMTP_RETRY_MAX) {
1628 retry = SMTP_RETRY_MAX;
1630 remove_token(instr, i, '\n');
1632 if (!strcasecmp(key, "attempted")) {
1633 attempted = extract_long(buf, 1);
1634 if (attempted > last_attempted)
1635 last_attempted = attempted;
1640 * Postpone delivery if we've already tried recently.
1642 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1643 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1650 * Bail out if there's no actual message associated with this
1652 if (text_msgid < 0L) {
1653 CtdlLogPrintf(CTDL_ERR, "SMTP client: no 'msgid' directive found!\n");
1658 /* Plow through the instructions looking for 'remote' directives and
1659 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1660 * were experienced and it's time to try again)
1662 lines = num_tokens(instr, '\n');
1663 for (i=0; i<lines; ++i) {
1664 extract_token(buf, instr, i, '\n', sizeof buf);
1665 extract_token(key, buf, 0, '|', sizeof key);
1666 extract_token(addr, buf, 1, '|', sizeof addr);
1667 status = extract_int(buf, 2);
1668 extract_token(dsn, buf, 3, '|', sizeof dsn);
1669 if ( (!strcasecmp(key, "remote"))
1670 && ((status==0)||(status==3)||(status==4)) ) {
1672 /* Remove this "remote" instruction from the set,
1673 * but replace the set's final newline if
1674 * remove_token() stripped it. It has to be there.
1676 remove_token(instr, i, '\n');
1677 if (instr[strlen(instr)-1] != '\n') {
1678 strcat(instr, "\n");
1683 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Trying <%s>\n", addr);
1684 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid, envelope_from);
1686 if (results == NULL) {
1687 results = malloc(1024);
1688 memset(results, 0, 1024);
1691 results = realloc(results, strlen(results) + 1024);
1693 snprintf(&results[strlen(results)], 1024,
1695 key, addr, status, dsn);
1700 if (results != NULL) {
1701 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1702 strcat(instr, results);
1707 /* Generate 'bounce' messages */
1708 smtp_do_bounce(instr);
1710 /* Go through the delivery list, deleting completed deliveries */
1711 incomplete_deliveries_remaining =
1712 smtp_purge_completed_deliveries(instr);
1716 * No delivery instructions remain, so delete both the instructions
1717 * message and the message message.
1719 if (incomplete_deliveries_remaining <= 0) {
1721 delmsgs[0] = msgnum;
1722 delmsgs[1] = text_msgid;
1723 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
1727 * Uncompleted delivery instructions remain, so delete the old
1728 * instructions and replace with the updated ones.
1730 if (incomplete_deliveries_remaining > 0) {
1731 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
1732 msg = malloc(sizeof(struct CtdlMessage));
1733 memset(msg, 0, sizeof(struct CtdlMessage));
1734 msg->cm_magic = CTDLMESSAGE_MAGIC;
1735 msg->cm_anon_type = MES_NORMAL;
1736 msg->cm_format_type = FMT_RFC822;
1737 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1738 snprintf(msg->cm_fields['M'],
1740 "Content-type: %s\n\n%s\n"
1743 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1744 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
1745 CtdlFreeMessage(msg);
1757 * Run through the queue sending out messages.
1759 void *smtp_do_queue(void *arg) {
1760 int num_processed = 0;
1761 struct CitContext smtp_queue_CC;
1763 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1764 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC );
1765 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1767 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1768 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1771 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1774 citthread_mutex_unlock (&smtp_send_lock);
1775 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
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 */