2 * This module is an SMTP and ESMTP implementation for the Citadel system.
3 * It is compliant with all of the following:
5 * RFC 821 - Simple Mail Transfer Protocol
6 * RFC 876 - Survey of SMTP Implementations
7 * RFC 1047 - Duplicate messages and SMTP
8 * RFC 1652 - 8 bit MIME
9 * RFC 1869 - Extended Simple Mail Transfer Protocol
10 * RFC 1870 - SMTP Service Extension for Message Size Declaration
11 * RFC 2033 - Local Mail Transfer Protocol
12 * RFC 2197 - SMTP Service Extension for Command Pipelining
13 * RFC 2476 - Message Submission
14 * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
15 * RFC 2554 - SMTP Service Extension for Authentication
16 * RFC 2821 - Simple Mail Transfer Protocol
17 * RFC 2822 - Internet Message Format
18 * RFC 2920 - SMTP Service Extension for Command Pipelining
20 * The VRFY and EXPN commands have been removed from this implementation
21 * because nobody uses these commands anymore, except for spammers.
23 * Copyright (c) 1998-2009 by the citadel.org team
25 * This program is free software; you can redistribute it and/or modify
26 * it under the terms of the GNU General Public License as published by
27 * the Free Software Foundation; either version 3 of the License, or
28 * (at your option) any later version.
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 * GNU General Public License for more details.
35 * You should have received a copy of the GNU General Public License
36 * along with this program; if not, write to the Free Software
37 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
49 #include <sys/types.h>
52 #if TIME_WITH_SYS_TIME
53 # include <sys/time.h>
57 # include <sys/time.h>
67 #include <sys/socket.h>
68 #include <netinet/in.h>
69 #include <arpa/inet.h>
70 #include <libcitadel.h>
73 #include "citserver.h"
80 #include "internet_addressing.h"
83 #include "clientsocket.h"
84 #include "locate_host.h"
85 #include "citadel_dirs.h"
94 #include "ctdl_module.h"
98 typedef struct _citsmtp { /* Information about the current session */
102 char recipients[SIZ];
103 int number_of_recipients;
105 int message_originated_locally;
111 enum { /* Command states for login authentication */
118 #define SMTP ((citsmtp *)CC->session_specific_data)
121 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
124 /*****************************************************************************/
125 /* SMTP SERVER (INBOUND) STUFF */
126 /*****************************************************************************/
130 * Here's where our SMTP session begins its happy day.
132 void smtp_greeting(int is_msa)
135 char message_to_spammer[1024];
137 strcpy(CC->cs_clientname, "SMTP session");
138 CC->internal_pgm = 1;
139 CC->cs_flags |= CS_STEALTH;
140 CC->session_specific_data = malloc(sizeof(citsmtp));
141 memset(SMTP, 0, sizeof(citsmtp));
143 sSMTP->is_msa = is_msa;
145 /* If this config option is set, reject connections from problem
146 * addresses immediately instead of after they execute a RCPT
148 if ( (config.c_rbl_at_greeting) && (sSMTP->is_msa == 0) ) {
149 if (rbl_check(message_to_spammer)) {
150 if (CtdlThreadCheckStop())
151 cprintf("421 %s\r\n", message_to_spammer);
153 cprintf("550 %s\r\n", message_to_spammer);
155 /* no need to free_recipients(valid), it's not allocated yet */
160 /* Otherwise we're either clean or we check later. */
162 if (CC->nologin==1) {
163 cprintf("500 Too many users are already online (maximum is %d)\r\n",
167 /* no need to free_recipients(valid), it's not allocated yet */
171 /* Note: the FQDN *must* appear as the first thing after the 220 code.
172 * Some clients (including citmail.c) depend on it being there.
174 cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
179 * SMTPS is just like SMTP, except it goes crypto right away.
181 void smtps_greeting(void) {
182 CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
184 if (!CC->redirect_ssl) CC->kill_me = 1; /* kill session if no crypto */
191 * SMTP MSA port requires authentication.
193 void smtp_msa_greeting(void) {
199 * LMTP is like SMTP but with some extra bonus footage added.
201 void lmtp_greeting(void) {
211 * Generic SMTP MTA greeting
213 void smtp_mta_greeting(void) {
219 * We also have an unfiltered LMTP socket that bypasses spam filters.
221 void lmtp_unfiltered_greeting(void) {
227 sSMTP->is_unfiltered = 1;
232 * Login greeting common to all auth methods
234 void smtp_auth_greeting(void) {
235 cprintf("235 Hello, %s\r\n", CC->user.fullname);
236 CtdlLogPrintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
237 CC->internal_pgm = 0;
238 CC->cs_flags &= ~CS_STEALTH;
243 * Implement HELO and EHLO commands.
245 * which_command: 0=HELO, 1=EHLO, 2=LHLO
247 void smtp_hello(char *argbuf, int which_command) {
248 citsmtp *sSMTP = SMTP;
250 safestrncpy(sSMTP->helo_node, argbuf, sizeof sSMTP->helo_node);
252 if ( (which_command != 2) && (sSMTP->is_lmtp) ) {
253 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
257 if ( (which_command == 2) && (sSMTP->is_lmtp == 0) ) {
258 cprintf("500 LHLO is only allowed when running LMTP\r\n");
262 if (which_command == 0) {
263 cprintf("250 Hello %s (%s [%s])\r\n",
270 if (which_command == 1) {
271 cprintf("250-Hello %s (%s [%s])\r\n",
278 cprintf("250-Greetings and joyous salutations.\r\n");
280 cprintf("250-HELP\r\n");
281 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
285 * Offer TLS, but only if TLS is not already active.
286 * Furthermore, only offer TLS when running on
287 * the SMTP-MSA port, not on the SMTP-MTA port, due to
288 * questionable reliability of TLS in certain sending MTA's.
290 if ( (!CC->redirect_ssl) && (sSMTP->is_msa) ) {
291 cprintf("250-STARTTLS\r\n");
293 #endif /* HAVE_OPENSSL */
295 cprintf("250-AUTH LOGIN PLAIN\r\n"
296 "250-AUTH=LOGIN PLAIN\r\n"
305 * Implement HELP command.
307 void smtp_help(void) {
308 cprintf("214 RTFM http://www.ietf.org/rfc/rfc2821.txt\r\n");
315 void smtp_get_user(char *argbuf) {
318 citsmtp *sSMTP = SMTP;
320 CtdlDecodeBase64(username, argbuf, SIZ);
321 /* CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", username); */
322 if (CtdlLoginExistingUser(NULL, username) == login_ok) {
323 CtdlEncodeBase64(buf, "Password:", 9, 0);
324 cprintf("334 %s\r\n", buf);
325 sSMTP->command_state = smtp_password;
328 cprintf("500 No such user.\r\n");
329 sSMTP->command_state = smtp_command;
337 void smtp_get_pass(char *argbuf) {
341 memset(password, 0, sizeof(password));
342 len = CtdlDecodeBase64(password, argbuf, SIZ);
343 /* CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", password); */
344 if (CtdlTryPassword(password, len) == pass_ok) {
345 smtp_auth_greeting();
348 cprintf("535 Authentication failed.\r\n");
350 SMTP->command_state = smtp_command;
355 * Back end for PLAIN auth method (either inline or multistate)
357 void smtp_try_plain(char *encoded_authstring) {
358 char decoded_authstring[1024];
365 CtdlDecodeBase64(decoded_authstring, encoded_authstring, strlen(encoded_authstring) );
366 safestrncpy(ident, decoded_authstring, sizeof ident);
367 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
368 len = safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
370 len = sizeof(pass) - 1;
372 SMTP->command_state = smtp_command;
374 if (!IsEmptyStr(ident)) {
375 result = CtdlLoginExistingUser(user, ident);
378 result = CtdlLoginExistingUser(NULL, user);
381 if (result == login_ok) {
382 if (CtdlTryPassword(pass, len) == pass_ok) {
383 smtp_auth_greeting();
387 cprintf("504 Authentication failed.\r\n");
392 * Attempt to perform authenticated SMTP
394 void smtp_auth(char *argbuf) {
395 char username_prompt[64];
397 char encoded_authstring[1024];
400 cprintf("504 Already logged in.\r\n");
404 extract_token(method, argbuf, 0, ' ', sizeof method);
406 if (!strncasecmp(method, "login", 5) ) {
407 if (strlen(argbuf) >= 7) {
408 smtp_get_user(&argbuf[6]);
411 CtdlEncodeBase64(username_prompt, "Username:", 9, 0);
412 cprintf("334 %s\r\n", username_prompt);
413 SMTP->command_state = smtp_user;
418 if (!strncasecmp(method, "plain", 5) ) {
419 if (num_tokens(argbuf, ' ') < 2) {
421 SMTP->command_state = smtp_plain;
425 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
427 smtp_try_plain(encoded_authstring);
431 if (strncasecmp(method, "login", 5) ) {
432 cprintf("504 Unknown authentication method.\r\n");
440 * Implements the RSET (reset state) command.
441 * Currently this just zeroes out the state buffer. If pointers to data
442 * allocated with malloc() are ever placed in the state buffer, we have to
443 * be sure to free() them first!
445 * Set do_response to nonzero to output the SMTP RSET response code.
447 void smtp_rset(int do_response) {
450 citsmtp *sSMTP = SMTP;
453 * Our entire SMTP state is discarded when a RSET command is issued,
454 * but we need to preserve this one little piece of information, so
455 * we save it for later.
457 is_lmtp = sSMTP->is_lmtp;
458 is_unfiltered = sSMTP->is_unfiltered;
460 memset(sSMTP, 0, sizeof(citsmtp));
463 * It is somewhat ambiguous whether we want to log out when a RSET
464 * command is issued. Here's the code to do it. It is commented out
465 * because some clients (such as Pine) issue RSET commands before
466 * each message, but still expect to be logged in.
468 * if (CC->logged_in) {
474 * Reinstate this little piece of information we saved (see above).
476 sSMTP->is_lmtp = is_lmtp;
477 sSMTP->is_unfiltered = is_unfiltered;
480 cprintf("250 Zap!\r\n");
485 * Clear out the portions of the state buffer that need to be cleared out
486 * after the DATA command finishes.
488 void smtp_data_clear(void) {
489 citsmtp *sSMTP = SMTP;
491 strcpy(sSMTP->from, "");
492 strcpy(sSMTP->recipients, "");
493 sSMTP->number_of_recipients = 0;
494 sSMTP->delivery_mode = 0;
495 sSMTP->message_originated_locally = 0;
498 const char *smtp_get_Recipients(void)
500 citsmtp *sSMTP = SMTP;
504 else return sSMTP->from;
508 * Implements the "MAIL FROM:" command
510 void smtp_mail(char *argbuf) {
514 citsmtp *sSMTP = SMTP;
516 if (!IsEmptyStr(sSMTP->from)) {
517 cprintf("503 Only one sender permitted\r\n");
521 if (strncasecmp(argbuf, "From:", 5)) {
522 cprintf("501 Syntax error\r\n");
526 strcpy(sSMTP->from, &argbuf[5]);
527 striplt(sSMTP->from);
528 if (haschar(sSMTP->from, '<') > 0) {
529 stripallbut(sSMTP->from, '<', '>');
532 /* We used to reject empty sender names, until it was brought to our
533 * attention that RFC1123 5.2.9 requires that this be allowed. So now
534 * we allow it, but replace the empty string with a fake
535 * address so we don't have to contend with the empty string causing
536 * other code to fail when it's expecting something there.
538 if (IsEmptyStr(sSMTP->from)) {
539 strcpy(sSMTP->from, "someone@example.com");
542 /* If this SMTP connection is from a logged-in user, force the 'from'
543 * to be the user's Internet e-mail address as Citadel knows it.
546 safestrncpy(sSMTP->from, CC->cs_inet_email, sizeof sSMTP->from);
547 cprintf("250 Sender ok <%s>\r\n", sSMTP->from);
548 sSMTP->message_originated_locally = 1;
552 else if (sSMTP->is_lmtp) {
553 /* Bypass forgery checking for LMTP */
556 /* Otherwise, make sure outsiders aren't trying to forge mail from
557 * this system (unless, of course, c_allow_spoofing is enabled)
559 else if (config.c_allow_spoofing == 0) {
560 process_rfc822_addr(sSMTP->from, user, node, name);
561 if (CtdlHostAlias(node) != hostalias_nomatch) {
562 cprintf("550 You must log in to send mail from %s\r\n", node);
563 strcpy(sSMTP->from, "");
568 cprintf("250 Sender ok\r\n");
574 * Implements the "RCPT To:" command
576 void smtp_rcpt(char *argbuf) {
578 char message_to_spammer[SIZ];
579 struct recptypes *valid = NULL;
580 citsmtp *sSMTP = SMTP;
582 if (IsEmptyStr(sSMTP->from)) {
583 cprintf("503 Need MAIL before RCPT\r\n");
587 if (strncasecmp(argbuf, "To:", 3)) {
588 cprintf("501 Syntax error\r\n");
592 if ( (sSMTP->is_msa) && (!CC->logged_in) ) {
593 cprintf("550 You must log in to send mail on this port.\r\n");
594 strcpy(sSMTP->from, "");
598 safestrncpy(recp, &argbuf[3], sizeof recp);
600 stripallbut(recp, '<', '>');
602 if ( (strlen(recp) + strlen(sSMTP->recipients) + 1 ) >= SIZ) {
603 cprintf("452 Too many recipients\r\n");
608 if ( (!CC->logged_in) /* Don't RBL authenticated users */
609 && (!sSMTP->is_lmtp) ) { /* Don't RBL LMTP clients */
610 if (config.c_rbl_at_greeting == 0) { /* Don't RBL again if we already did it */
611 if (rbl_check(message_to_spammer)) {
612 if (CtdlThreadCheckStop())
613 cprintf("421 %s\r\n", message_to_spammer);
615 cprintf("550 %s\r\n", message_to_spammer);
616 /* no need to free_recipients(valid), it's not allocated yet */
622 valid = validate_recipients(recp,
623 smtp_get_Recipients (),
624 (sSMTP->is_lmtp)? POST_LMTP:
625 (CC->logged_in)? POST_LOGGED_IN:
627 if (valid->num_error != 0) {
628 cprintf("550 %s\r\n", valid->errormsg);
629 free_recipients(valid);
633 if (valid->num_internet > 0) {
635 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
636 cprintf("551 <%s> - you do not have permission to send Internet mail\r\n", recp);
637 free_recipients(valid);
643 if (valid->num_internet > 0) {
644 if ( (sSMTP->message_originated_locally == 0)
645 && (sSMTP->is_lmtp == 0) ) {
646 cprintf("551 <%s> - relaying denied\r\n", recp);
647 free_recipients(valid);
652 cprintf("250 RCPT ok <%s>\r\n", recp);
653 if (!IsEmptyStr(sSMTP->recipients)) {
654 strcat(sSMTP->recipients, ",");
656 strcat(sSMTP->recipients, recp);
657 sSMTP->number_of_recipients += 1;
659 free_recipients(valid);
667 * Implements the DATA command
669 void smtp_data(void) {
671 char *defbody; //TODO: remove me
672 struct CtdlMessage *msg = NULL;
675 struct recptypes *valid;
679 citsmtp *sSMTP = SMTP;
681 if (IsEmptyStr(sSMTP->from)) {
682 cprintf("503 Need MAIL command first.\r\n");
686 if (sSMTP->number_of_recipients < 1) {
687 cprintf("503 Need RCPT command first.\r\n");
691 cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
693 datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
694 defbody = malloc(4096);
696 if (defbody != NULL) {
697 if (sSMTP->is_lmtp && (CC->cs_UDSclientUID != -1)) {
698 snprintf(defbody, 4096,
699 "Received: from %s (Citadel from userid %ld)\n"
702 (long int) CC->cs_UDSclientUID,
707 snprintf(defbody, 4096,
708 "Received: from %s (%s [%s])\n"
717 body = CtdlReadMessageBodyBuf(HKEY("."), config.c_maxmsglen, defbody, 1, NULL);
719 cprintf("550 Unable to save message: internal error.\r\n");
723 CtdlLogPrintf(CTDL_DEBUG, "Converting message...\n");
724 msg = convert_internet_message_buf(&body);
726 /* If the user is locally authenticated, FORCE the From: header to
727 * show up as the real sender. Yes, this violates the RFC standard,
728 * but IT MAKES SENSE. If you prefer strict RFC adherence over
729 * common sense, you can disable this in the configuration.
731 * We also set the "message room name" ('O' field) to MAILROOM
732 * (which is Mail> on most systems) to prevent it from getting set
733 * to something ugly like "0000058008.Sent Items>" when the message
734 * is read with a Citadel client.
736 if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
737 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
738 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
739 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
740 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
741 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
742 msg->cm_fields['A'] = strdup(CC->user.fullname);
743 msg->cm_fields['N'] = strdup(config.c_nodename);
744 msg->cm_fields['H'] = strdup(config.c_humannode);
745 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
746 msg->cm_fields['O'] = strdup(MAILROOM);
749 /* Set the "envelope from" address */
750 if (msg->cm_fields['P'] != NULL) {
751 free(msg->cm_fields['P']);
753 msg->cm_fields['P'] = strdup(sSMTP->from);
755 /* Set the "envelope to" address */
756 if (msg->cm_fields['V'] != NULL) {
757 free(msg->cm_fields['V']);
759 msg->cm_fields['V'] = strdup(sSMTP->recipients);
761 /* Submit the message into the Citadel system. */
762 valid = validate_recipients(sSMTP->recipients,
763 smtp_get_Recipients (),
764 (sSMTP->is_lmtp)? POST_LMTP:
765 (CC->logged_in)? POST_LOGGED_IN:
768 /* If there are modules that want to scan this message before final
769 * submission (such as virus checkers or spam filters), call them now
770 * and give them an opportunity to reject the message.
772 if (sSMTP->is_unfiltered) {
776 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
779 if (scan_errors > 0) { /* We don't want this message! */
781 if (msg->cm_fields['0'] == NULL) {
782 msg->cm_fields['0'] = strdup("Message rejected by filter");
785 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
788 else { /* Ok, we'll accept this message. */
789 msgnum = CtdlSubmitMsg(msg, valid, "", 0);
791 sprintf(result, "250 Message accepted.\r\n");
794 sprintf(result, "550 Internal delivery error\r\n");
798 /* For SMTP and ESTMP, just print the result message. For LMTP, we
799 * have to print one result message for each recipient. Since there
800 * is nothing in Citadel which would cause different recipients to
801 * have different results, we can get away with just spitting out the
802 * same message once for each recipient.
804 if (sSMTP->is_lmtp) {
805 for (i=0; i<sSMTP->number_of_recipients; ++i) {
806 cprintf("%s", result);
810 cprintf("%s", result);
813 /* Write something to the syslog (which may or may not be where the
814 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
817 syslog((LOG_MAIL | LOG_INFO),
818 "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
821 sSMTP->number_of_recipients,
829 CtdlFreeMessage(msg);
830 free_recipients(valid);
831 smtp_data_clear(); /* clear out the buffers now */
836 * implements the STARTTLS command (Citadel API version)
838 void smtp_starttls(void)
840 char ok_response[SIZ];
841 char nosup_response[SIZ];
842 char error_response[SIZ];
845 "220 Begin TLS negotiation now\r\n");
846 sprintf(nosup_response,
847 "554 TLS not supported here\r\n");
848 sprintf(error_response,
849 "554 Internal error\r\n");
850 CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
857 * Main command loop for SMTP sessions.
859 void smtp_command_loop(void) {
861 citsmtp *sSMTP = SMTP;
864 CtdlLogPrintf(CTDL_EMERG, "Session SMTP data is null. WTF? We will crash now.\n");
868 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
869 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
870 CtdlLogPrintf(CTDL_CRIT, "Client disconnected: ending session.\n");
874 CtdlLogPrintf(CTDL_INFO, "SMTP server: %s\n", cmdbuf);
875 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
877 if (sSMTP->command_state == smtp_user) {
878 smtp_get_user(cmdbuf);
881 else if (sSMTP->command_state == smtp_password) {
882 smtp_get_pass(cmdbuf);
885 else if (sSMTP->command_state == smtp_plain) {
886 smtp_try_plain(cmdbuf);
889 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
890 smtp_auth(&cmdbuf[5]);
893 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
897 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
898 smtp_hello(&cmdbuf[5], 0);
901 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
902 smtp_hello(&cmdbuf[5], 1);
905 else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
906 smtp_hello(&cmdbuf[5], 2);
909 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
913 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
914 smtp_mail(&cmdbuf[5]);
917 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
918 cprintf("250 NOOP\r\n");
921 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
922 cprintf("221 Goodbye...\r\n");
927 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
928 smtp_rcpt(&cmdbuf[5]);
931 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
935 else if (!strcasecmp(cmdbuf, "STARTTLS")) {
940 cprintf("502 I'm afraid I can't do that.\r\n");
949 /*****************************************************************************/
950 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
951 /*****************************************************************************/
958 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
961 void smtp_try(const char *key, const char *addr, int *status,
962 char *dsn, size_t n, long msgnum, char *envelope_from)
969 char user[1024], node[1024], name[1024];
984 /* Parse out the host portion of the recipient address */
985 process_rfc822_addr(addr, user, node, name);
987 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Attempting delivery to <%s> @ <%s> (%s)\n",
990 /* Load the message out of the database */
991 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
992 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
993 msg_size = StrLength(CC->redirect_buffer);
994 msgtext = SmashStrBuf(&CC->redirect_buffer);
996 /* If no envelope_from is supplied, extract one from the message */
997 if ( (envelope_from == NULL) || (IsEmptyStr(envelope_from)) ) {
998 strcpy(mailfrom, "");
1002 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
1005 if (!strncasecmp(buf, "From:", 5)) {
1006 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
1008 for (i=0; mailfrom[i]; ++i) {
1009 if (!isprint(mailfrom[i])) {
1010 strcpy(&mailfrom[i], &mailfrom[i+1]);
1015 /* Strip out parenthesized names */
1018 for (i=0; mailfrom[i]; ++i) {
1019 if (mailfrom[i] == '(') lp = i;
1020 if (mailfrom[i] == ')') rp = i;
1022 if ((lp>0)&&(rp>lp)) {
1023 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
1026 /* Prefer brokketized names */
1029 for (i=0; mailfrom[i]; ++i) {
1030 if (mailfrom[i] == '<') lp = i;
1031 if (mailfrom[i] == '>') rp = i;
1033 if ( (lp>=0) && (rp>lp) ) {
1035 strcpy(mailfrom, &mailfrom[lp]);
1040 } while (scan_done == 0);
1041 if (IsEmptyStr(mailfrom)) strcpy(mailfrom, "someone@somewhere.org");
1042 stripallbut(mailfrom, '<', '>');
1043 envelope_from = mailfrom;
1046 /* Figure out what mail exchanger host we have to connect to */
1047 num_mxhosts = getmx(mxhosts, node);
1048 CtdlLogPrintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d [%s]\n", node, num_mxhosts, mxhosts);
1049 if (num_mxhosts < 1) {
1051 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1056 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1058 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1059 strcpy(mx_user, "");
1060 strcpy(mx_pass, "");
1061 if (num_tokens(buf, '@') > 1) {
1062 strcpy (mx_user, buf);
1063 endpart = strrchr(mx_user, '@');
1065 strcpy (mx_host, endpart + 1);
1066 endpart = strrchr(mx_user, ':');
1067 if (endpart != NULL) {
1068 strcpy(mx_pass, endpart+1);
1073 strcpy (mx_host, buf);
1074 endpart = strrchr(mx_host, ':');
1077 strcpy(mx_port, endpart + 1);
1080 strcpy(mx_port, "25");
1082 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connecting to %s : %s ...\n", mx_host, mx_port);
1083 sock = sock_connect(mx_host, mx_port);
1084 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1087 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connected!\n");
1089 fdflags = fcntl(sock, F_GETFL);
1091 CtdlLogPrintf(CTDL_DEBUG,
1092 "unable to get SMTP-Client socket flags! %s \n",
1094 fdflags = fdflags | O_NONBLOCK;
1095 if (fcntl(sock, F_SETFL, fdflags) < 0)
1096 CtdlLogPrintf(CTDL_DEBUG,
1097 "unable to set SMTP-Client socket nonblocking flags! %s \n",
1102 snprintf(dsn, SIZ, "%s", strerror(errno));
1105 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
1111 *status = 4; /* dsn is already filled in */
1115 CCC->sReadBuf = NewStrBuf();
1116 CCC->sMigrateBuf = NewStrBuf();
1119 /* Process the SMTP greeting from the server */
1120 if (ml_sock_gets(&sock, buf, 90) < 0) {
1122 strcpy(dsn, "Connection broken during SMTP conversation");
1125 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1126 if (buf[0] != '2') {
1127 if (buf[0] == '4') {
1129 safestrncpy(dsn, &buf[4], 1023);
1134 safestrncpy(dsn, &buf[4], 1023);
1139 /* At this point we know we are talking to a real SMTP server */
1141 /* Do a EHLO command. If it fails, try the HELO command. */
1142 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1143 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1144 sock_write(&sock, buf, strlen(buf));
1145 if (ml_sock_gets(&sock, buf, 30) < 0) {
1147 strcpy(dsn, "Connection broken during SMTP HELO");
1150 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1151 if (buf[0] != '2') {
1152 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1153 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1154 sock_write(&sock, buf, strlen(buf));
1155 if (ml_sock_gets(&sock, buf, 30) < 0) {
1157 strcpy(dsn, "Connection broken during SMTP HELO");
1161 if (buf[0] != '2') {
1162 if (buf[0] == '4') {
1164 safestrncpy(dsn, &buf[4], 1023);
1169 safestrncpy(dsn, &buf[4], 1023);
1174 /* Do an AUTH command if necessary */
1175 if (!IsEmptyStr(mx_user)) {
1177 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
1178 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2, 0);
1179 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
1180 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1181 sock_write(&sock, buf, strlen(buf));
1182 if (ml_sock_gets(&sock, buf, 30) < 0) {
1184 strcpy(dsn, "Connection broken during SMTP AUTH");
1187 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1188 if (buf[0] != '2') {
1189 if (buf[0] == '4') {
1191 safestrncpy(dsn, &buf[4], 1023);
1196 safestrncpy(dsn, &buf[4], 1023);
1202 /* previous command succeeded, now try the MAIL FROM: command */
1203 snprintf(buf, sizeof buf, "MAIL FROM:<%s>\r\n", envelope_from);
1204 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1205 sock_write(&sock, buf, strlen(buf));
1206 if (ml_sock_gets(&sock, buf, 30) < 0) {
1208 strcpy(dsn, "Connection broken during SMTP MAIL");
1211 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1212 if (buf[0] != '2') {
1213 if (buf[0] == '4') {
1215 safestrncpy(dsn, &buf[4], 1023);
1220 safestrncpy(dsn, &buf[4], 1023);
1225 /* MAIL succeeded, now try the RCPT To: command */
1226 snprintf(buf, sizeof buf, "RCPT TO:<%s@%s>\r\n", user, node);
1227 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1228 sock_write(&sock, buf, strlen(buf));
1229 if (ml_sock_gets(&sock, buf, 30) < 0) {
1231 strcpy(dsn, "Connection broken during SMTP RCPT");
1234 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1235 if (buf[0] != '2') {
1236 if (buf[0] == '4') {
1238 safestrncpy(dsn, &buf[4], 1023);
1243 safestrncpy(dsn, &buf[4], 1023);
1248 /* RCPT succeeded, now try the DATA command */
1249 CtdlLogPrintf(CTDL_DEBUG, ">DATA\n");
1250 sock_write(&sock, "DATA\r\n", 6);
1251 if (ml_sock_gets(&sock, buf, 30) < 0) {
1253 strcpy(dsn, "Connection broken during SMTP DATA");
1256 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1257 if (buf[0] != '3') {
1258 if (buf[0] == '4') {
1260 safestrncpy(dsn, &buf[4], 1023);
1265 safestrncpy(dsn, &buf[4], 1023);
1270 /* If we reach this point, the server is expecting data.*/
1271 sock_write(&sock, msgtext, msg_size);
1272 if (msgtext[msg_size-1] != 10) {
1273 CtdlLogPrintf(CTDL_WARNING, "Possible problem: message did not "
1274 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1276 sock_write(&sock, "\r\n", 2);
1279 sock_write(&sock, ".\r\n", 3);
1281 if (ml_sock_gets(&sock, buf, 90) < 0) {
1283 strcpy(dsn, "Connection broken during SMTP message transmit");
1286 CtdlLogPrintf(CTDL_DEBUG, "%s\n", buf);
1287 if (buf[0] != '2') {
1288 if (buf[0] == '4') {
1290 safestrncpy(dsn, &buf[4], 1023);
1295 safestrncpy(dsn, &buf[4], 1023);
1301 safestrncpy(dsn, &buf[4], 1023);
1304 CtdlLogPrintf(CTDL_DEBUG, ">QUIT\n");
1305 sock_write(&sock, "QUIT\r\n", 6);
1306 ml_sock_gets(&sock, buf, 30);
1307 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1308 CtdlLogPrintf(CTDL_INFO, "SMTP client: delivery to <%s> @ <%s> (%s) succeeded\n",
1311 bail: free(msgtext);
1312 FreeStrBuf(&CCC->sReadBuf);
1313 FreeStrBuf(&CCC->sMigrateBuf);
1317 /* Write something to the syslog (which may or may not be where the
1318 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1320 if (enable_syslog) {
1321 syslog((LOG_MAIL | LOG_INFO),
1322 "%ld: to=<%s>, relay=%s, stat=%s",
1336 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1337 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1338 * a "bounce" message (delivery status notification).
1340 void smtp_do_bounce(char *instr) {
1348 char bounceto[1024];
1350 int num_bounces = 0;
1351 int bounce_this = 0;
1352 long bounce_msgid = (-1);
1353 time_t submitted = 0L;
1354 struct CtdlMessage *bmsg = NULL;
1356 struct recptypes *valid;
1357 int successful_bounce = 0;
1362 CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1363 strcpy(bounceto, "");
1364 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
1365 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
1366 lines = num_tokens(instr, '\n');
1368 /* See if it's time to give up on delivery of this message */
1369 for (i=0; i<lines; ++i) {
1370 extract_token(buf, instr, i, '\n', sizeof buf);
1371 extract_token(key, buf, 0, '|', sizeof key);
1372 extract_token(addr, buf, 1, '|', sizeof addr);
1373 if (!strcasecmp(key, "submitted")) {
1374 submitted = atol(addr);
1378 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1382 /* Start building our bounce message */
1384 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1385 if (bmsg == NULL) return;
1386 memset(bmsg, 0, sizeof(struct CtdlMessage));
1387 BounceMB = NewStrBufPlain(NULL, 1024);
1389 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1390 bmsg->cm_anon_type = MES_NORMAL;
1391 bmsg->cm_format_type = FMT_RFC822;
1392 bmsg->cm_fields['A'] = strdup("Citadel");
1393 bmsg->cm_fields['O'] = strdup(MAILROOM);
1394 bmsg->cm_fields['N'] = strdup(config.c_nodename);
1395 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1396 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
1397 StrBufAppendBuf(BounceMB, boundary, 0);
1398 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
1399 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
1400 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
1401 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
1402 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1403 StrBufAppendBuf(BounceMB, boundary, 0);
1404 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1405 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
1407 if (give_up) StrBufAppendBufPlain(BounceMB, HKEY(
1408 "A message you sent could not be delivered to some or all of its recipients\n"
1409 "due to prolonged unavailability of its destination(s).\n"
1410 "Giving up on the following addresses:\n\n"
1413 else StrBufAppendBufPlain(BounceMB, HKEY(
1414 "A message you sent could not be delivered to some or all of its recipients.\n"
1415 "The following addresses were undeliverable:\n\n"
1419 * Now go through the instructions checking for stuff.
1421 for (i=0; i<lines; ++i) {
1424 extract_token(buf, instr, i, '\n', sizeof buf);
1425 extract_token(key, buf, 0, '|', sizeof key);
1426 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
1427 status = extract_int(buf, 2);
1428 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
1431 CtdlLogPrintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1432 key, addr, status, dsn);
1434 if (!strcasecmp(key, "bounceto")) {
1435 strcpy(bounceto, addr);
1438 if (!strcasecmp(key, "msgid")) {
1439 omsgid = atol(addr);
1442 if (!strcasecmp(key, "remote")) {
1443 if (status == 5) bounce_this = 1;
1444 if (give_up) bounce_this = 1;
1450 StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
1451 StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
1452 StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
1453 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1455 remove_token(instr, i, '\n');
1461 /* Attach the original message */
1463 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1464 StrBufAppendBuf(BounceMB, boundary, 0);
1465 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1466 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
1467 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
1468 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
1469 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1471 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
1472 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0);
1473 StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
1474 FreeStrBuf(&CC->redirect_buffer);
1477 /* Close the multipart MIME scope */
1478 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1479 StrBufAppendBuf(BounceMB, boundary, 0);
1480 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
1481 if (bmsg->cm_fields['A'] != NULL)
1482 free(bmsg->cm_fields['A']);
1483 bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB);
1484 /* Deliver the bounce if there's anything worth mentioning */
1485 CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1486 if (num_bounces > 0) {
1488 /* First try the user who sent the message */
1489 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1490 if (IsEmptyStr(bounceto)) {
1491 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
1492 bounce_msgid = (-1L);
1495 /* Can we deliver the bounce to the original sender? */
1496 valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
1497 if (valid != NULL) {
1498 if (valid->num_error == 0) {
1499 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
1500 successful_bounce = 1;
1504 /* If not, post it in the Aide> room */
1505 if (successful_bounce == 0) {
1506 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
1509 /* Free up the memory we used */
1510 if (valid != NULL) {
1511 free_recipients(valid);
1514 FreeStrBuf(&boundary);
1515 CtdlFreeMessage(bmsg);
1516 CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
1521 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1522 * set of delivery instructions for completed deliveries and remove them.
1524 * It returns the number of incomplete deliveries remaining.
1526 int smtp_purge_completed_deliveries(char *instr) {
1537 lines = num_tokens(instr, '\n');
1538 for (i=0; i<lines; ++i) {
1539 extract_token(buf, instr, i, '\n', sizeof buf);
1540 extract_token(key, buf, 0, '|', sizeof key);
1541 extract_token(addr, buf, 1, '|', sizeof addr);
1542 status = extract_int(buf, 2);
1543 extract_token(dsn, buf, 3, '|', sizeof dsn);
1547 if (!strcasecmp(key, "remote")) {
1548 if (status == 2) completed = 1;
1553 remove_token(instr, i, '\n');
1566 * Called by smtp_do_queue() to handle an individual message.
1568 void smtp_do_procmsg(long msgnum, void *userdata) {
1569 struct CtdlMessage *msg = NULL;
1571 char *results = NULL;
1579 char envelope_from[1024];
1580 long text_msgid = (-1);
1581 int incomplete_deliveries_remaining;
1582 time_t attempted = 0L;
1583 time_t last_attempted = 0L;
1584 time_t retry = SMTP_RETRY_INTERVAL;
1586 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: smtp_do_procmsg(%ld)\n", msgnum);
1587 strcpy(envelope_from, "");
1589 msg = CtdlFetchMessage(msgnum, 1);
1591 CtdlLogPrintf(CTDL_ERR, "SMTP client: tried %ld but no such message!\n", msgnum);
1595 instr = strdup(msg->cm_fields['M']);
1596 CtdlFreeMessage(msg);
1598 /* Strip out the headers amd any other non-instruction line */
1599 lines = num_tokens(instr, '\n');
1600 for (i=0; i<lines; ++i) {
1601 extract_token(buf, instr, i, '\n', sizeof buf);
1602 if (num_tokens(buf, '|') < 2) {
1603 remove_token(instr, i, '\n');
1609 /* Learn the message ID and find out about recent delivery attempts */
1610 lines = num_tokens(instr, '\n');
1611 for (i=0; i<lines; ++i) {
1612 extract_token(buf, instr, i, '\n', sizeof buf);
1613 extract_token(key, buf, 0, '|', sizeof key);
1614 if (!strcasecmp(key, "msgid")) {
1615 text_msgid = extract_long(buf, 1);
1617 if (!strcasecmp(key, "envelope_from")) {
1618 extract_token(envelope_from, buf, 1, '|', sizeof envelope_from);
1620 if (!strcasecmp(key, "retry")) {
1621 /* double the retry interval after each attempt */
1622 retry = extract_long(buf, 1) * 2L;
1623 if (retry > SMTP_RETRY_MAX) {
1624 retry = SMTP_RETRY_MAX;
1626 remove_token(instr, i, '\n');
1628 if (!strcasecmp(key, "attempted")) {
1629 attempted = extract_long(buf, 1);
1630 if (attempted > last_attempted)
1631 last_attempted = attempted;
1636 * Postpone delivery if we've already tried recently.
1638 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1639 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1646 * Bail out if there's no actual message associated with this
1648 if (text_msgid < 0L) {
1649 CtdlLogPrintf(CTDL_ERR, "SMTP client: no 'msgid' directive found!\n");
1654 /* Plow through the instructions looking for 'remote' directives and
1655 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1656 * were experienced and it's time to try again)
1658 lines = num_tokens(instr, '\n');
1659 for (i=0; i<lines; ++i) {
1660 extract_token(buf, instr, i, '\n', sizeof buf);
1661 extract_token(key, buf, 0, '|', sizeof key);
1662 extract_token(addr, buf, 1, '|', sizeof addr);
1663 status = extract_int(buf, 2);
1664 extract_token(dsn, buf, 3, '|', sizeof dsn);
1665 if ( (!strcasecmp(key, "remote"))
1666 && ((status==0)||(status==3)||(status==4)) ) {
1668 /* Remove this "remote" instruction from the set,
1669 * but replace the set's final newline if
1670 * remove_token() stripped it. It has to be there.
1672 remove_token(instr, i, '\n');
1673 if (instr[strlen(instr)-1] != '\n') {
1674 strcat(instr, "\n");
1679 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Trying <%s>\n", addr);
1680 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid, envelope_from);
1682 if (results == NULL) {
1683 results = malloc(1024);
1684 memset(results, 0, 1024);
1687 results = realloc(results, strlen(results) + 1024);
1689 snprintf(&results[strlen(results)], 1024,
1691 key, addr, status, dsn);
1696 if (results != NULL) {
1697 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1698 strcat(instr, results);
1703 /* Generate 'bounce' messages */
1704 smtp_do_bounce(instr);
1706 /* Go through the delivery list, deleting completed deliveries */
1707 incomplete_deliveries_remaining =
1708 smtp_purge_completed_deliveries(instr);
1712 * No delivery instructions remain, so delete both the instructions
1713 * message and the message message.
1715 if (incomplete_deliveries_remaining <= 0) {
1717 delmsgs[0] = msgnum;
1718 delmsgs[1] = text_msgid;
1719 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
1723 * Uncompleted delivery instructions remain, so delete the old
1724 * instructions and replace with the updated ones.
1726 if (incomplete_deliveries_remaining > 0) {
1727 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
1728 msg = malloc(sizeof(struct CtdlMessage));
1729 memset(msg, 0, sizeof(struct CtdlMessage));
1730 msg->cm_magic = CTDLMESSAGE_MAGIC;
1731 msg->cm_anon_type = MES_NORMAL;
1732 msg->cm_format_type = FMT_RFC822;
1733 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1734 snprintf(msg->cm_fields['M'],
1736 "Content-type: %s\n\n%s\n"
1739 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1740 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
1741 CtdlFreeMessage(msg);
1751 * smtp_queue_thread()
1753 * Run through the queue sending out messages.
1755 void *smtp_queue_thread(void *arg) {
1756 int num_processed = 0;
1757 struct CitContext smtp_queue_CC;
1759 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1760 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1761 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1763 while (!CtdlThreadCheckStop()) {
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);
1773 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1774 CtdlThreadSleep(60);
1777 CtdlClearSystemContext();
1783 /*****************************************************************************/
1784 /* SMTP UTILITY COMMANDS */
1785 /*****************************************************************************/
1787 void cmd_smtp(char *argbuf) {
1794 if (CtdlAccessCheck(ac_aide)) return;
1796 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1798 if (!strcasecmp(cmd, "mx")) {
1799 extract_token(node, argbuf, 1, '|', sizeof node);
1800 num_mxhosts = getmx(buf, node);
1801 cprintf("%d %d MX hosts listed for %s\n",
1802 LISTING_FOLLOWS, num_mxhosts, node);
1803 for (i=0; i<num_mxhosts; ++i) {
1804 extract_token(node, buf, i, '|', sizeof node);
1805 cprintf("%s\n", node);
1811 else if (!strcasecmp(cmd, "runqueue")) {
1813 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1818 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1825 * Initialize the SMTP outbound queue
1827 void smtp_init_spoolout(void) {
1828 struct ctdlroom qrbuf;
1831 * Create the room. This will silently fail if the room already
1832 * exists, and that's perfectly ok, because we want it to exist.
1834 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1837 * Make sure it's set to be a "system room" so it doesn't show up
1838 * in the <K>nown rooms list for Aides.
1840 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1841 qrbuf.QRflags2 |= QR2_SYSTEM;
1842 CtdlPutRoomLock(&qrbuf);
1849 /*****************************************************************************/
1850 /* MODULE INITIALIZATION STUFF */
1851 /*****************************************************************************/
1853 * This cleanup function blows away the temporary memory used by
1856 void smtp_cleanup_function(void) {
1858 /* Don't do this stuff if this is not an SMTP session! */
1859 if (CC->h_command_function != smtp_command_loop) return;
1861 CtdlLogPrintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1867 const char *CitadelServiceSMTP_MTA="SMTP-MTA";
1868 const char *CitadelServiceSMTPS_MTA="SMTPs-MTA";
1869 const char *CitadelServiceSMTP_MSA="SMTP-MSA";
1870 const char *CitadelServiceSMTP_LMTP="LMTP";
1871 const char *CitadelServiceSMTP_LMTP_UNF="LMTP-UnF";
1873 CTDL_MODULE_INIT(smtp)
1877 CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */
1882 CitadelServiceSMTP_MTA);
1885 CtdlRegisterServiceHook(config.c_smtps_port,
1890 CitadelServiceSMTPS_MTA);
1893 CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */
1898 CitadelServiceSMTP_MSA);
1900 CtdlRegisterServiceHook(0, /* local LMTP */
1905 CitadelServiceSMTP_LMTP);
1907 CtdlRegisterServiceHook(0, /* local LMTP */
1908 file_lmtp_unfiltered_socket,
1909 lmtp_unfiltered_greeting,
1912 CitadelServiceSMTP_LMTP_UNF);
1914 smtp_init_spoolout();
1915 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1916 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1917 CtdlThreadCreate("SMTP Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1920 /* return our Subversion id for the Log */