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>
66 #include <sys/socket.h>
67 #include <netinet/in.h>
68 #include <arpa/inet.h>
69 #include <libcitadel.h>
72 #include "citserver.h"
79 #include "internet_addressing.h"
82 #include "clientsocket.h"
83 #include "locate_host.h"
84 #include "citadel_dirs.h"
86 #include "ctdl_module.h"
88 #include "smtp_util.h"
89 #include "event_client.h"
91 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
93 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
95 /*****************************************************************************/
96 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
97 /*****************************************************************************/
100 typedef enum _eSMTP_C_States {
114 const long SMTP_C_ReadTimeouts[eMaxSMTPC] = {
115 90, /* Greeting... */
123 900, /* end of body... */
127 const long SMTP_C_SendTimeouts[eMaxSMTPC] = {
130 const char *ReadErrors[eMaxSMTPC] = {
131 "Connection broken during SMTP conversation",
132 "Connection broken during SMTP EHLO",
133 "Connection broken during SMTP HELO",
134 "Connection broken during SMTP AUTH",
135 "Connection broken during SMTP MAIL FROM",
136 "Connection broken during SMTP RCPT",
137 "Connection broken during SMTP DATA",
138 "Connection broken during SMTP message transmit",
139 ""/* quit reply, don't care. */
143 typedef struct _stmp_out_msg {
147 eSMTP_C_States State;
167 char envelope_from_buf[1024];
171 eNextState SMTP_C_DispatchReadDone(void *Data);
172 eNextState SMTP_C_DispatchWriteDone(void *Data);
175 typedef eNextState (*SMTPReadHandler)(SmtpOutMsg *Msg);
176 typedef eNextState (*SMTPSendHandler)(SmtpOutMsg *Msg);
179 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
181 eReadState Finished = eBufferNotEmpty;
183 while (Finished == eBufferNotEmpty) {
184 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
187 case eMustReadMore: /// read new from socket...
190 case eBufferNotEmpty: /* shouldn't happen... */
191 case eReadSuccess: /// done for now...
192 if (StrLength(IO->IOBuf) < 4)
194 if (ChrPtr(IO->IOBuf)[3] == '-')
195 Finished = eBufferNotEmpty;
199 case eReadFail: /// WHUT?
211 * this one has to have the context for loading the message via the redirect buffer...
213 SmtpOutMsg *smtp_load_msg(long msgnum, const char *addr, char *envelope_from)
218 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
220 memset(SendMsg, 0, sizeof(SmtpOutMsg));
221 SendMsg->IO.sock = (-1);
223 SendMsg->n = MsgCount++;
224 /* Load the message out of the database */
225 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
226 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
227 SendMsg->msgtext = CCC->redirect_buffer;
228 CCC->redirect_buffer = NULL;
229 if ((StrLength(SendMsg->msgtext) > 0) &&
230 ChrPtr(SendMsg->msgtext)[StrLength(SendMsg->msgtext) - 1] != '\n') {
231 CtdlLogPrintf(CTDL_WARNING,
232 "SMTP client[%ld]: Possible problem: message did not "
233 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
235 ChrPtr(SendMsg->msgtext)[StrLength(SendMsg->msgtext) - 1] );
236 StrBufAppendBufPlain(SendMsg->msgtext, HKEY("\r\n"), 0);
239 safestrncpy(SendMsg->addr, addr, SIZ);
240 safestrncpy(SendMsg->envelope_from_buf, envelope_from, 1024);
246 int smtp_resolve_recipients(SmtpOutMsg *SendMsg)
254 /* Parse out the host portion of the recipient address */
255 process_rfc822_addr(SendMsg->addr, SendMsg->user, SendMsg->node, SendMsg->name);
257 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
258 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
259 /* If no envelope_from is supplied, extract one from the message */
260 if ( (SendMsg->envelope_from == NULL) ||
261 (IsEmptyStr(SendMsg->envelope_from)) ) {
262 SendMsg->mailfrom[0] = '\0';
264 ptr = ChrPtr(SendMsg->msgtext);
266 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
269 if (!strncasecmp(buf, "From:", 5)) {
270 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
271 striplt(SendMsg->mailfrom);
272 for (i=0; SendMsg->mailfrom[i]; ++i) {
273 if (!isprint(SendMsg->mailfrom[i])) {
274 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
279 /* Strip out parenthesized names */
282 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
283 if (SendMsg->mailfrom[i] == '(') lp = i;
284 if (SendMsg->mailfrom[i] == ')') rp = i;
286 if ((lp>0)&&(rp>lp)) {
287 strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
290 /* Prefer brokketized names */
293 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
294 if (SendMsg->mailfrom[i] == '<') lp = i;
295 if (SendMsg->mailfrom[i] == '>') rp = i;
297 if ( (lp>=0) && (rp>lp) ) {
298 SendMsg->mailfrom[rp] = 0;
299 memmove(SendMsg->mailfrom,
300 &SendMsg->mailfrom[lp + 1],
306 } while (scan_done == 0);
307 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
308 stripallbut(SendMsg->mailfrom, '<', '>');
309 SendMsg->envelope_from = SendMsg->mailfrom;
315 void resolve_mx_hosts(SmtpOutMsg *SendMsg)
317 /// well this is blocking and sux, but libevent jsut supports async dns since v2
318 /* Figure out what mail exchanger host we have to connect to */
319 SendMsg->num_mxhosts = getmx(SendMsg->mxhosts, SendMsg->node);
320 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Number of MX hosts for <%s> is %d [%s]\n",
321 SendMsg->n, SendMsg->node, SendMsg->num_mxhosts, SendMsg->mxhosts);
322 if (SendMsg->num_mxhosts < 1) {
323 SendMsg->SMTPstatus = 5;
324 snprintf(SendMsg->dsn, SIZ, "No MX hosts found for <%s>", SendMsg->node);
325 return; ///////TODO: abort!
330 #define SMTP_ERROR(WHICH_ERR, ERRSTR) {SendMsg->SMTPstatus = WHICH_ERR; memcpy(SendMsg->dsn, HKEY(ERRSTR) + 1); return eAbort; }
331 #define SMTP_VERROR(WHICH_ERR) { SendMsg->SMTPstatus = WHICH_ERR; safestrncpy(SendMsg->dsn, &ChrPtr(SendMsg->IO.IOBuf)[4], sizeof(SendMsg->dsn)); return eAbort; }
332 #define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(SendMsg->IO.IOBuf)[0] == WHICH_STATE)
334 #define SMTP_DBG_SEND() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: > %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
335 #define SMTP_DBG_READ() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: < %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
337 void connect_one_smtpsrv(SmtpOutMsg *SendMsg)
342 extract_token(buf, SendMsg->mxhosts, SendMsg->n_mx, '|', sizeof(buf));
343 strcpy(SendMsg->mx_user, "");
344 strcpy(SendMsg->mx_pass, "");
345 if (num_tokens(buf, '@') > 1) {
346 strcpy (SendMsg->mx_user, buf);
347 endpart = strrchr(SendMsg->mx_user, '@');
349 strcpy (SendMsg->mx_host, endpart + 1);
350 endpart = strrchr(SendMsg->mx_user, ':');
351 if (endpart != NULL) {
352 strcpy(SendMsg->mx_pass, endpart+1);
357 strcpy (SendMsg->mx_host, buf);
358 endpart = strrchr(SendMsg->mx_host, ':');
361 strcpy(SendMsg->mx_port, endpart + 1);
364 strcpy(SendMsg->mx_port, "25");
366 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connecting to %s : %s ...\n",
367 SendMsg->n, SendMsg->mx_host, SendMsg->mx_port);
372 int connect_one_smtpsrv_xamine_result(void *Ctx)
374 SmtpOutMsg *SendMsg = Ctx;
375 SendMsg->IO.SendBuf.fd =
376 SendMsg->IO.RecvBuf.fd =
377 SendMsg->IO.sock = sock_connect(SendMsg->mx_host, SendMsg->mx_port);
379 snprintf(SendMsg->dsn, SIZ, "Could not connect: %s", strerror(errno));
380 if (SendMsg->IO.sock >= 0)
382 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connected!\n", SendMsg->n);
384 fdflags = fcntl(SendMsg->IO.sock, F_GETFL);
386 CtdlLogPrintf(CTDL_DEBUG,
387 "SMTP client[%ld]: unable to get socket flags! %s \n",
388 SendMsg->n, strerror(errno));
389 fdflags = fdflags | O_NONBLOCK;
390 if (fcntl(SendMsg->IO.sock, F_SETFL, fdflags) < 0)
391 CtdlLogPrintf(CTDL_DEBUG,
392 "SMTP client[%ld]: unable to set socket nonblocking flags! %s \n",
393 SendMsg->n, strerror(errno));
395 if (SendMsg->IO.sock < 0) {
397 snprintf(SendMsg->dsn, SIZ, "%s", strerror(errno));
400 snprintf(SendMsg->dsn, SIZ, "Unable to connect to %s : %s\n",
401 SendMsg->mx_host, SendMsg->mx_port);
404 /// hier: naechsten mx ausprobieren.
405 if (SendMsg->IO.sock < 0) {
406 SendMsg->SMTPstatus = 4; /* dsn is already filled in */
407 //// hier: abbrechen & bounce.
410 SendMsg->IO.SendBuf.Buf = NewStrBuf();
411 SendMsg->IO.RecvBuf.Buf = NewStrBuf();
412 SendMsg->IO.IOBuf = NewStrBuf();
413 InitEventIO(&SendMsg->IO, SendMsg,
414 SMTP_C_DispatchReadDone,
415 SMTP_C_DispatchWriteDone,
416 SMTP_C_ReadServerStatus,
421 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
423 /* Process the SMTP greeting from the server */
426 if (!SMTP_IS_STATE('2')) {
427 if (SMTP_IS_STATE('4'))
435 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
437 /* At this point we know we are talking to a real SMTP server */
439 /* Do a EHLO command. If it fails, try the HELO command. */
440 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
441 "EHLO %s\r\n", config.c_fqdn);
447 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
451 if (SMTP_IS_STATE('2')) {
453 if (IsEmptyStr(SendMsg->mx_user))
454 SendMsg->State ++; /* Skip auth... */
456 /* else we fall back to 'helo' */
460 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
462 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
463 "HELO %s\r\n", config.c_fqdn);
469 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
473 if (!SMTP_IS_STATE('2')) {
474 if (SMTP_IS_STATE('4'))
479 if (!IsEmptyStr(SendMsg->mx_user))
480 SendMsg->State ++; /* Skip auth... */
484 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
489 /* Do an AUTH command if necessary */
490 sprintf(buf, "%s%c%s%c%s",
491 SendMsg->mx_user, '\0',
492 SendMsg->mx_user, '\0',
494 CtdlEncodeBase64(encoded, buf,
495 strlen(SendMsg->mx_user) +
496 strlen(SendMsg->mx_user) +
497 strlen(SendMsg->mx_pass) + 2, 0);
498 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
499 "AUTH PLAIN %s\r\n", encoded);
505 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
507 /* Do an AUTH command if necessary */
511 if (!SMTP_IS_STATE('2')) {
512 if (SMTP_IS_STATE('4'))
520 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
522 /* previous command succeeded, now try the MAIL FROM: command */
523 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
524 "MAIL FROM:<%s>\r\n",
525 SendMsg->envelope_from);
531 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
535 if (!SMTP_IS_STATE('2')) {
536 if (SMTP_IS_STATE('4'))
545 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
547 /* MAIL succeeded, now try the RCPT To: command */
548 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
549 "RCPT TO:<%s@%s>\r\n",
557 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
561 if (!SMTP_IS_STATE('2')) {
562 if (SMTP_IS_STATE('4'))
570 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
572 /* RCPT succeeded, now try the DATA command */
573 StrBufPlain(SendMsg->IO.SendBuf.Buf,
580 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
584 if (!SMTP_IS_STATE('3')) {
585 if (SMTP_IS_STATE('4'))
593 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
596 /* If we reach this point, the server is expecting data.*/
598 Buf = SendMsg->IO.SendBuf.Buf;
599 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
600 SendMsg->msgtext = Buf;
601 //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
607 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
611 Buf = SendMsg->IO.SendBuf.Buf;
612 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
613 SendMsg->msgtext = Buf;
615 StrBufPlain(SendMsg->IO.SendBuf.Buf,
622 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
626 if (!SMTP_IS_STATE('2')) {
627 if (SMTP_IS_STATE('4'))
634 safestrncpy(SendMsg->dsn, &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4], 1023);
635 SendMsg->SMTPstatus = 2;
639 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
641 StrBufPlain(SendMsg->IO.SendBuf.Buf,
648 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
652 CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
653 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
657 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
662 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
668 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
669 * instructions for "5" codes (permanent fatal errors) and produce/deliver
670 * a "bounce" message (delivery status notification).
672 void smtp_do_bounce(char *instr) {
684 long bounce_msgid = (-1);
685 time_t submitted = 0L;
686 struct CtdlMessage *bmsg = NULL;
688 struct recptypes *valid;
689 int successful_bounce = 0;
694 CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
695 strcpy(bounceto, "");
696 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
697 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
698 lines = num_tokens(instr, '\n');
700 /* See if it's time to give up on delivery of this message */
701 for (i=0; i<lines; ++i) {
702 extract_token(buf, instr, i, '\n', sizeof buf);
703 extract_token(key, buf, 0, '|', sizeof key);
704 extract_token(addr, buf, 1, '|', sizeof addr);
705 if (!strcasecmp(key, "submitted")) {
706 submitted = atol(addr);
710 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
714 /* Start building our bounce message */
716 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
717 if (bmsg == NULL) return;
718 memset(bmsg, 0, sizeof(struct CtdlMessage));
719 BounceMB = NewStrBufPlain(NULL, 1024);
721 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
722 bmsg->cm_anon_type = MES_NORMAL;
723 bmsg->cm_format_type = FMT_RFC822;
724 bmsg->cm_fields['A'] = strdup("Citadel");
725 bmsg->cm_fields['O'] = strdup(MAILROOM);
726 bmsg->cm_fields['N'] = strdup(config.c_nodename);
727 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
728 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
729 StrBufAppendBuf(BounceMB, boundary, 0);
730 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
731 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
732 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
733 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
734 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
735 StrBufAppendBuf(BounceMB, boundary, 0);
736 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
737 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
739 if (give_up) StrBufAppendBufPlain(BounceMB, HKEY(
740 "A message you sent could not be delivered to some or all of its recipients\n"
741 "due to prolonged unavailability of its destination(s).\n"
742 "Giving up on the following addresses:\n\n"
745 else StrBufAppendBufPlain(BounceMB, HKEY(
746 "A message you sent could not be delivered to some or all of its recipients.\n"
747 "The following addresses were undeliverable:\n\n"
751 * Now go through the instructions checking for stuff.
753 for (i=0; i<lines; ++i) {
756 extract_token(buf, instr, i, '\n', sizeof buf);
757 extract_token(key, buf, 0, '|', sizeof key);
758 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
759 status = extract_int(buf, 2);
760 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
763 CtdlLogPrintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
764 key, addr, status, dsn);
766 if (!strcasecmp(key, "bounceto")) {
767 strcpy(bounceto, addr);
770 if (!strcasecmp(key, "msgid")) {
774 if (!strcasecmp(key, "remote")) {
775 if (status == 5) bounce_this = 1;
776 if (give_up) bounce_this = 1;
782 StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
783 StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
784 StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
785 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
787 remove_token(instr, i, '\n');
793 /* Attach the original message */
795 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
796 StrBufAppendBuf(BounceMB, boundary, 0);
797 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
798 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
799 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
800 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
801 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
803 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
804 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0);
805 StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
806 FreeStrBuf(&CC->redirect_buffer);
809 /* Close the multipart MIME scope */
810 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
811 StrBufAppendBuf(BounceMB, boundary, 0);
812 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
813 if (bmsg->cm_fields['A'] != NULL)
814 free(bmsg->cm_fields['A']);
815 bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB);
816 /* Deliver the bounce if there's anything worth mentioning */
817 CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
818 if (num_bounces > 0) {
820 /* First try the user who sent the message */
821 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
822 if (IsEmptyStr(bounceto)) {
823 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
824 bounce_msgid = (-1L);
827 /* Can we deliver the bounce to the original sender? */
828 valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
830 if (valid->num_error == 0) {
831 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
832 successful_bounce = 1;
836 /* If not, post it in the Aide> room */
837 if (successful_bounce == 0) {
838 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
841 /* Free up the memory we used */
843 free_recipients(valid);
846 FreeStrBuf(&boundary);
847 CtdlFreeMessage(bmsg);
848 CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
853 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
854 * set of delivery instructions for completed deliveries and remove them.
856 * It returns the number of incomplete deliveries remaining.
858 int smtp_purge_completed_deliveries(char *instr) {
869 lines = num_tokens(instr, '\n');
870 for (i=0; i<lines; ++i) {
871 extract_token(buf, instr, i, '\n', sizeof buf);
872 extract_token(key, buf, 0, '|', sizeof key);
873 extract_token(addr, buf, 1, '|', sizeof addr);
874 status = extract_int(buf, 2);
875 extract_token(dsn, buf, 3, '|', sizeof dsn);
879 if (!strcasecmp(key, "remote")) {
880 if (status == 2) completed = 1;
885 remove_token(instr, i, '\n');
894 void smtp_try(const char *key, const char *addr, int *status,
895 char *dsn, size_t n, long msgnum, char *envelope_from)
897 SmtpOutMsg * SmtpC = smtp_load_msg(msgnum, addr, envelope_from);
898 smtp_resolve_recipients(SmtpC);
899 resolve_mx_hosts(SmtpC);
900 connect_one_smtpsrv(SmtpC);
901 QueueEventContext(SmtpC, connect_one_smtpsrv_xamine_result);
907 * Called by smtp_do_queue() to handle an individual message.
909 void smtp_do_procmsg(long msgnum, void *userdata) {
910 struct CtdlMessage *msg = NULL;
912 char *results = NULL;
920 char envelope_from[1024];
921 long text_msgid = (-1);
922 int incomplete_deliveries_remaining;
923 time_t attempted = 0L;
924 time_t last_attempted = 0L;
925 time_t retry = SMTP_RETRY_INTERVAL;
927 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: smtp_do_procmsg(%ld)\n", msgnum);
928 strcpy(envelope_from, "");
930 msg = CtdlFetchMessage(msgnum, 1);
932 CtdlLogPrintf(CTDL_ERR, "SMTP client: tried %ld but no such message!\n", msgnum);
936 instr = strdup(msg->cm_fields['M']);
937 CtdlFreeMessage(msg);
939 /* Strip out the headers amd any other non-instruction line */
940 lines = num_tokens(instr, '\n');
941 for (i=0; i<lines; ++i) {
942 extract_token(buf, instr, i, '\n', sizeof buf);
943 if (num_tokens(buf, '|') < 2) {
944 remove_token(instr, i, '\n');
950 /* Learn the message ID and find out about recent delivery attempts */
951 lines = num_tokens(instr, '\n');
952 for (i=0; i<lines; ++i) {
953 extract_token(buf, instr, i, '\n', sizeof buf);
954 extract_token(key, buf, 0, '|', sizeof key);
955 if (!strcasecmp(key, "msgid")) {
956 text_msgid = extract_long(buf, 1);
958 if (!strcasecmp(key, "envelope_from")) {
959 extract_token(envelope_from, buf, 1, '|', sizeof envelope_from);
961 if (!strcasecmp(key, "retry")) {
962 /* double the retry interval after each attempt */
963 retry = extract_long(buf, 1) * 2L;
964 if (retry > SMTP_RETRY_MAX) {
965 retry = SMTP_RETRY_MAX;
967 remove_token(instr, i, '\n');
969 if (!strcasecmp(key, "attempted")) {
970 attempted = extract_long(buf, 1);
971 if (attempted > last_attempted)
972 last_attempted = attempted;
977 * Postpone delivery if we've already tried recently.
979 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
980 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
987 * Bail out if there's no actual message associated with this
989 if (text_msgid < 0L) {
990 CtdlLogPrintf(CTDL_ERR, "SMTP client: no 'msgid' directive found!\n");
995 /* Plow through the instructions looking for 'remote' directives and
996 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
997 * were experienced and it's time to try again)
999 lines = num_tokens(instr, '\n');
1000 for (i=0; i<lines; ++i) {
1001 extract_token(buf, instr, i, '\n', sizeof buf);
1002 extract_token(key, buf, 0, '|', sizeof key);
1003 extract_token(addr, buf, 1, '|', sizeof addr);
1004 status = extract_int(buf, 2);
1005 extract_token(dsn, buf, 3, '|', sizeof dsn);
1006 if ( (!strcasecmp(key, "remote"))
1007 && ((status==0)||(status==3)||(status==4)) ) {
1009 /* Remove this "remote" instruction from the set,
1010 * but replace the set's final newline if
1011 * remove_token() stripped it. It has to be there.
1013 remove_token(instr, i, '\n');
1014 if (instr[strlen(instr)-1] != '\n') {
1015 strcat(instr, "\n");
1020 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Trying <%s>\n", addr);
1021 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid, envelope_from);
1023 if (results == NULL) {
1024 results = malloc(1024);
1025 memset(results, 0, 1024);
1028 results = realloc(results, strlen(results) + 1024);
1030 snprintf(&results[strlen(results)], 1024,
1032 key, addr, status, dsn);
1037 if (results != NULL) {
1038 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1039 strcat(instr, results);
1044 /* Generate 'bounce' messages */
1045 smtp_do_bounce(instr);
1047 /* Go through the delivery list, deleting completed deliveries */
1048 incomplete_deliveries_remaining =
1049 smtp_purge_completed_deliveries(instr);
1053 * No delivery instructions remain, so delete both the instructions
1054 * message and the message message.
1056 if (incomplete_deliveries_remaining <= 0) {
1058 delmsgs[0] = msgnum;
1059 delmsgs[1] = text_msgid;
1060 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
1064 * Uncompleted delivery instructions remain, so delete the old
1065 * instructions and replace with the updated ones.
1067 if (incomplete_deliveries_remaining > 0) {
1068 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
1069 msg = malloc(sizeof(struct CtdlMessage));
1070 memset(msg, 0, sizeof(struct CtdlMessage));
1071 msg->cm_magic = CTDLMESSAGE_MAGIC;
1072 msg->cm_anon_type = MES_NORMAL;
1073 msg->cm_format_type = FMT_RFC822;
1074 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1075 snprintf(msg->cm_fields['M'],
1077 "Content-type: %s\n\n%s\n"
1080 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1081 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
1082 CtdlFreeMessage(msg);
1089 /*****************************************************************************/
1090 /* SMTP UTILITY COMMANDS */
1091 /*****************************************************************************/
1093 void cmd_smtp(char *argbuf) {
1100 if (CtdlAccessCheck(ac_aide)) return;
1102 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1104 if (!strcasecmp(cmd, "mx")) {
1105 extract_token(node, argbuf, 1, '|', sizeof node);
1106 num_mxhosts = getmx(buf, node);
1107 cprintf("%d %d MX hosts listed for %s\n",
1108 LISTING_FOLLOWS, num_mxhosts, node);
1109 for (i=0; i<num_mxhosts; ++i) {
1110 extract_token(node, buf, i, '|', sizeof node);
1111 cprintf("%s\n", node);
1117 else if (!strcasecmp(cmd, "runqueue")) {
1119 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1124 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1131 * smtp_queue_thread()
1133 * Run through the queue sending out messages.
1135 void *smtp_queue_thread(void *arg) {
1136 int num_processed = 0;
1137 struct CitContext smtp_queue_CC;
1139 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1140 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1141 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1143 while (!CtdlThreadCheckStop()) {
1145 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1147 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1148 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1151 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1153 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1154 CtdlThreadSleep(60);
1157 CtdlClearSystemContext();
1163 * Initialize the SMTP outbound queue
1165 void smtp_init_spoolout(void) {
1166 struct ctdlroom qrbuf;
1169 * Create the room. This will silently fail if the room already
1170 * exists, and that's perfectly ok, because we want it to exist.
1172 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1175 * Make sure it's set to be a "system room" so it doesn't show up
1176 * in the <K>nown rooms list for Aides.
1178 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1179 qrbuf.QRflags2 |= QR2_SYSTEM;
1180 CtdlPutRoomLock(&qrbuf);
1185 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1186 SMTPC_read_greeting,
1187 SMTPC_read_EHLO_reply,
1188 SMTPC_read_HELO_reply,
1189 SMTPC_read_auth_reply,
1190 SMTPC_read_FROM_reply,
1191 SMTPC_read_RCPT_reply,
1192 SMTPC_read_DATAcmd_reply,
1194 SMTPC_read_data_body_reply,
1195 SMTPC_read_QUIT_reply
1198 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1199 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1206 SMTPC_send_data_body,
1207 SMTPC_send_terminate_data_body,
1211 eNextState SMTP_C_DispatchReadDone(void *Data)
1213 SmtpOutMsg *pMsg = Data;
1214 eNextState rc = ReadHandlers[pMsg->State](pMsg);
1219 eNextState SMTP_C_DispatchWriteDone(void *Data)
1221 SmtpOutMsg *pMsg = Data;
1222 return SendHandlers[pMsg->State](pMsg);
1228 CTDL_MODULE_INIT(smtp_eventclient)
1230 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1233 smtp_init_spoolout();
1234 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1236 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1240 /* return our Subversion id for the Log */
1241 return "smtpeventclient";