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, 90) < 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, 30) < 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, 30) < 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, 30) < 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, 30) < 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, 30) < 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, 30) < 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_timeout(&sock,
1278 (msg_size / 128) + 50);
1279 if (msgtext[msg_size-1] != 10) {
1280 CtdlLogPrintf(CTDL_WARNING, "Possible problem: message did not "
1281 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1283 sock_write(&sock, "\r\n", 2);
1286 sock_write(&sock, ".\r\n", 3);
1288 if (ml_sock_gets(&sock, buf, 90) < 0) {
1290 strcpy(dsn, "Connection broken during SMTP message transmit");
1293 CtdlLogPrintf(CTDL_DEBUG, "%s\n", buf);
1294 if (buf[0] != '2') {
1295 if (buf[0] == '4') {
1297 safestrncpy(dsn, &buf[4], 1023);
1302 safestrncpy(dsn, &buf[4], 1023);
1308 safestrncpy(dsn, &buf[4], 1023);
1311 CtdlLogPrintf(CTDL_DEBUG, ">QUIT\n");
1312 sock_write(&sock, "QUIT\r\n", 6);
1313 ml_sock_gets(&sock, buf, 30);
1314 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1315 CtdlLogPrintf(CTDL_INFO, "SMTP client: delivery to <%s> @ <%s> (%s) succeeded\n",
1318 bail: free(msgtext);
1319 FreeStrBuf(&CCC->sReadBuf);
1320 FreeStrBuf(&CCC->sMigrateBuf);
1324 /* Write something to the syslog (which may or may not be where the
1325 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1327 if (enable_syslog) {
1328 syslog((LOG_MAIL | LOG_INFO),
1329 "%ld: to=<%s>, relay=%s, stat=%s",
1343 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1344 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1345 * a "bounce" message (delivery status notification).
1347 void smtp_do_bounce(char *instr) {
1355 char bounceto[1024];
1357 int num_bounces = 0;
1358 int bounce_this = 0;
1359 long bounce_msgid = (-1);
1360 time_t submitted = 0L;
1361 struct CtdlMessage *bmsg = NULL;
1363 struct recptypes *valid;
1364 int successful_bounce = 0;
1369 CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1370 strcpy(bounceto, "");
1371 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
1372 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
1373 lines = num_tokens(instr, '\n');
1375 /* See if it's time to give up on delivery of this message */
1376 for (i=0; i<lines; ++i) {
1377 extract_token(buf, instr, i, '\n', sizeof buf);
1378 extract_token(key, buf, 0, '|', sizeof key);
1379 extract_token(addr, buf, 1, '|', sizeof addr);
1380 if (!strcasecmp(key, "submitted")) {
1381 submitted = atol(addr);
1385 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1389 /* Start building our bounce message */
1391 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1392 if (bmsg == NULL) return;
1393 memset(bmsg, 0, sizeof(struct CtdlMessage));
1394 BounceMB = NewStrBufPlain(NULL, 1024);
1396 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1397 bmsg->cm_anon_type = MES_NORMAL;
1398 bmsg->cm_format_type = FMT_RFC822;
1399 bmsg->cm_fields['A'] = strdup("Citadel");
1400 bmsg->cm_fields['O'] = strdup(MAILROOM);
1401 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1402 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1403 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
1404 StrBufAppendBuf(BounceMB, boundary, 0);
1405 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
1406 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
1407 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
1408 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
1409 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1410 StrBufAppendBuf(BounceMB, boundary, 0);
1411 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1412 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
1414 if (give_up) StrBufAppendBufPlain(BounceMB, HKEY(
1415 "A message you sent could not be delivered to some or all of its recipients\n"
1416 "due to prolonged unavailability of its destination(s).\n"
1417 "Giving up on the following addresses:\n\n"
1420 else StrBufAppendBufPlain(BounceMB, HKEY(
1421 "A message you sent could not be delivered to some or all of its recipients.\n"
1422 "The following addresses were undeliverable:\n\n"
1426 * Now go through the instructions checking for stuff.
1428 for (i=0; i<lines; ++i) {
1431 extract_token(buf, instr, i, '\n', sizeof buf);
1432 extract_token(key, buf, 0, '|', sizeof key);
1433 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
1434 status = extract_int(buf, 2);
1435 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
1438 CtdlLogPrintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1439 key, addr, status, dsn);
1441 if (!strcasecmp(key, "bounceto")) {
1442 strcpy(bounceto, addr);
1445 if (!strcasecmp(key, "msgid")) {
1446 omsgid = atol(addr);
1449 if (!strcasecmp(key, "remote")) {
1450 if (status == 5) bounce_this = 1;
1451 if (give_up) bounce_this = 1;
1457 StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
1458 StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
1459 StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
1460 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1462 remove_token(instr, i, '\n');
1468 /* Attach the original message */
1470 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1471 StrBufAppendBuf(BounceMB, boundary, 0);
1472 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1473 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
1474 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
1475 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
1476 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1478 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
1479 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0);
1480 StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
1481 FreeStrBuf(&CC->redirect_buffer);
1484 /* Close the multipart MIME scope */
1485 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1486 StrBufAppendBuf(BounceMB, boundary, 0);
1487 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
1488 if (bmsg->cm_fields['A'] != NULL)
1489 free(bmsg->cm_fields['A']);
1490 bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB);
1491 /* Deliver the bounce if there's anything worth mentioning */
1492 CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1493 if (num_bounces > 0) {
1495 /* First try the user who sent the message */
1496 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1497 if (IsEmptyStr(bounceto)) {
1498 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
1499 bounce_msgid = (-1L);
1502 /* Can we deliver the bounce to the original sender? */
1503 valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
1504 if (valid != NULL) {
1505 if (valid->num_error == 0) {
1506 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
1507 successful_bounce = 1;
1511 /* If not, post it in the Aide> room */
1512 if (successful_bounce == 0) {
1513 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
1516 /* Free up the memory we used */
1517 if (valid != NULL) {
1518 free_recipients(valid);
1521 FreeStrBuf(&boundary);
1522 CtdlFreeMessage(bmsg);
1523 CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
1528 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1529 * set of delivery instructions for completed deliveries and remove them.
1531 * It returns the number of incomplete deliveries remaining.
1533 int smtp_purge_completed_deliveries(char *instr) {
1544 lines = num_tokens(instr, '\n');
1545 for (i=0; i<lines; ++i) {
1546 extract_token(buf, instr, i, '\n', sizeof buf);
1547 extract_token(key, buf, 0, '|', sizeof key);
1548 extract_token(addr, buf, 1, '|', sizeof addr);
1549 status = extract_int(buf, 2);
1550 extract_token(dsn, buf, 3, '|', sizeof dsn);
1554 if (!strcasecmp(key, "remote")) {
1555 if (status == 2) completed = 1;
1560 remove_token(instr, i, '\n');
1573 * Called by smtp_do_queue() to handle an individual message.
1575 void smtp_do_procmsg(long msgnum, void *userdata) {
1576 struct CtdlMessage *msg = NULL;
1578 char *results = NULL;
1586 char envelope_from[1024];
1587 long text_msgid = (-1);
1588 int incomplete_deliveries_remaining;
1589 time_t attempted = 0L;
1590 time_t last_attempted = 0L;
1591 time_t retry = SMTP_RETRY_INTERVAL;
1593 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: smtp_do_procmsg(%ld)\n", msgnum);
1594 strcpy(envelope_from, "");
1596 msg = CtdlFetchMessage(msgnum, 1);
1598 CtdlLogPrintf(CTDL_ERR, "SMTP client: tried %ld but no such message!\n", msgnum);
1602 instr = strdup(msg->cm_fields['M']);
1603 CtdlFreeMessage(msg);
1605 /* Strip out the headers amd any other non-instruction line */
1606 lines = num_tokens(instr, '\n');
1607 for (i=0; i<lines; ++i) {
1608 extract_token(buf, instr, i, '\n', sizeof buf);
1609 if (num_tokens(buf, '|') < 2) {
1610 remove_token(instr, i, '\n');
1616 /* Learn the message ID and find out about recent delivery attempts */
1617 lines = num_tokens(instr, '\n');
1618 for (i=0; i<lines; ++i) {
1619 extract_token(buf, instr, i, '\n', sizeof buf);
1620 extract_token(key, buf, 0, '|', sizeof key);
1621 if (!strcasecmp(key, "msgid")) {
1622 text_msgid = extract_long(buf, 1);
1624 if (!strcasecmp(key, "envelope_from")) {
1625 extract_token(envelope_from, buf, 1, '|', sizeof envelope_from);
1627 if (!strcasecmp(key, "retry")) {
1628 /* double the retry interval after each attempt */
1629 retry = extract_long(buf, 1) * 2L;
1630 if (retry > SMTP_RETRY_MAX) {
1631 retry = SMTP_RETRY_MAX;
1633 remove_token(instr, i, '\n');
1635 if (!strcasecmp(key, "attempted")) {
1636 attempted = extract_long(buf, 1);
1637 if (attempted > last_attempted)
1638 last_attempted = attempted;
1643 * Postpone delivery if we've already tried recently.
1645 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1646 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1653 * Bail out if there's no actual message associated with this
1655 if (text_msgid < 0L) {
1656 CtdlLogPrintf(CTDL_ERR, "SMTP client: no 'msgid' directive found!\n");
1661 /* Plow through the instructions looking for 'remote' directives and
1662 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1663 * were experienced and it's time to try again)
1665 lines = num_tokens(instr, '\n');
1666 for (i=0; i<lines; ++i) {
1667 extract_token(buf, instr, i, '\n', sizeof buf);
1668 extract_token(key, buf, 0, '|', sizeof key);
1669 extract_token(addr, buf, 1, '|', sizeof addr);
1670 status = extract_int(buf, 2);
1671 extract_token(dsn, buf, 3, '|', sizeof dsn);
1672 if ( (!strcasecmp(key, "remote"))
1673 && ((status==0)||(status==3)||(status==4)) ) {
1675 /* Remove this "remote" instruction from the set,
1676 * but replace the set's final newline if
1677 * remove_token() stripped it. It has to be there.
1679 remove_token(instr, i, '\n');
1680 if (instr[strlen(instr)-1] != '\n') {
1681 strcat(instr, "\n");
1686 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Trying <%s>\n", addr);
1687 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid, envelope_from);
1689 if (results == NULL) {
1690 results = malloc(1024);
1691 memset(results, 0, 1024);
1694 results = realloc(results, strlen(results) + 1024);
1696 snprintf(&results[strlen(results)], 1024,
1698 key, addr, status, dsn);
1703 if (results != NULL) {
1704 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1705 strcat(instr, results);
1710 /* Generate 'bounce' messages */
1711 smtp_do_bounce(instr);
1713 /* Go through the delivery list, deleting completed deliveries */
1714 incomplete_deliveries_remaining =
1715 smtp_purge_completed_deliveries(instr);
1719 * No delivery instructions remain, so delete both the instructions
1720 * message and the message message.
1722 if (incomplete_deliveries_remaining <= 0) {
1724 delmsgs[0] = msgnum;
1725 delmsgs[1] = text_msgid;
1726 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
1730 * Uncompleted delivery instructions remain, so delete the old
1731 * instructions and replace with the updated ones.
1733 if (incomplete_deliveries_remaining > 0) {
1734 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
1735 msg = malloc(sizeof(struct CtdlMessage));
1736 memset(msg, 0, sizeof(struct CtdlMessage));
1737 msg->cm_magic = CTDLMESSAGE_MAGIC;
1738 msg->cm_anon_type = MES_NORMAL;
1739 msg->cm_format_type = FMT_RFC822;
1740 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1741 snprintf(msg->cm_fields['M'],
1743 "Content-type: %s\n\n%s\n"
1746 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1747 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
1748 CtdlFreeMessage(msg);
1760 * Run through the queue sending out messages.
1762 void *smtp_do_queue(void *arg) {
1763 int num_processed = 0;
1764 struct CitContext smtp_queue_CC;
1766 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1767 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC );
1768 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1770 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1771 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1774 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1777 citthread_mutex_unlock (&smtp_send_lock);
1778 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1780 CtdlClearSystemContext();
1789 * Create a thread to run the SMTP queue
1791 * This was created as a response to a situation seen on Uncensored where a bad remote was holding
1792 * up SMTP sending for long times.
1793 * Converting to a thread does not fix the problem caused by the bad remote but it does prevent
1794 * the SMTP sending from stopping housekeeping and the EVT_TIMER event system which in turn prevented
1795 * other things from happening.
1797 void smtp_queue_thread (void)
1799 if (citthread_mutex_trylock (&smtp_send_lock)) {
1800 CtdlLogPrintf(CTDL_DEBUG, "SMTP queue run already in progress\n");
1803 CtdlThreadCreate("SMTP Send", CTDLTHREAD_BIGSTACK, smtp_do_queue, NULL);
1809 void smtp_server_going_down (void)
1811 CtdlLogPrintf(CTDL_DEBUG, "SMTP module clean up for shutdown.\n");
1813 citthread_mutex_destroy (&smtp_send_lock);
1818 /*****************************************************************************/
1819 /* SMTP UTILITY COMMANDS */
1820 /*****************************************************************************/
1822 void cmd_smtp(char *argbuf) {
1829 if (CtdlAccessCheck(ac_aide)) return;
1831 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1833 if (!strcasecmp(cmd, "mx")) {
1834 extract_token(node, argbuf, 1, '|', sizeof node);
1835 num_mxhosts = getmx(buf, node);
1836 cprintf("%d %d MX hosts listed for %s\n",
1837 LISTING_FOLLOWS, num_mxhosts, node);
1838 for (i=0; i<num_mxhosts; ++i) {
1839 extract_token(node, buf, i, '|', sizeof node);
1840 cprintf("%s\n", node);
1846 else if (!strcasecmp(cmd, "runqueue")) {
1848 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1853 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1860 * Initialize the SMTP outbound queue
1862 void smtp_init_spoolout(void) {
1863 struct ctdlroom qrbuf;
1866 * Create the room. This will silently fail if the room already
1867 * exists, and that's perfectly ok, because we want it to exist.
1869 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1872 * Make sure it's set to be a "system room" so it doesn't show up
1873 * in the <K>nown rooms list for Aides.
1875 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1876 qrbuf.QRflags2 |= QR2_SYSTEM;
1877 CtdlPutRoomLock(&qrbuf);
1884 /*****************************************************************************/
1885 /* MODULE INITIALIZATION STUFF */
1886 /*****************************************************************************/
1888 * This cleanup function blows away the temporary memory used by
1891 void smtp_cleanup_function(void) {
1893 /* Don't do this stuff if this is not an SMTP session! */
1894 if (CC->h_command_function != smtp_command_loop) return;
1896 CtdlLogPrintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1902 const char *CitadelServiceSMTP_MTA="SMTP-MTA";
1903 const char *CitadelServiceSMTPS_MTA="SMTPs-MTA";
1904 const char *CitadelServiceSMTP_MSA="SMTP-MSA";
1905 const char *CitadelServiceSMTP_LMTP="LMTP";
1906 const char *CitadelServiceSMTP_LMTP_UNF="LMTP-UnF";
1908 CTDL_MODULE_INIT(smtp)
1912 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1917 CitadelServiceSMTP_MTA);
1920 CtdlRegisterServiceHook(config.c_smtps_port,
1925 CitadelServiceSMTPS_MTA);
1928 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1933 CitadelServiceSMTP_MSA);
1935 CtdlRegisterServiceHook(0, /* local LMTP */
1940 CitadelServiceSMTP_LMTP);
1942 CtdlRegisterServiceHook(0, /* local LMTP */
1943 file_lmtp_unfiltered_socket,
1944 lmtp_unfiltered_greeting,
1947 CitadelServiceSMTP_LMTP_UNF);
1949 smtp_init_spoolout();
1950 CtdlRegisterSessionHook(smtp_queue_thread, EVT_TIMER);
1951 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1952 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1953 CtdlRegisterCleanupHook (smtp_server_going_down);
1954 citthread_mutex_init (&smtp_send_lock, NULL);
1957 /* return our Subversion id for the Log */