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
92 HashList *QItemHandlers = NULL;
94 citthread_mutex_t ActiveQItemsLock;
95 HashList *ActiveQItems = NULL;
97 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
99 /*****************************************************************************/
100 /* SMTP CLIENT (Queue Management) STUFF */
101 /*****************************************************************************/
103 #define MaxAttempts 15
104 typedef struct _delivery_attempt {
109 typedef struct _mailq_entry {
110 DeliveryAttempt Attempts[MaxAttempts];
113 StrBuf *StatusMessage;
118 void FreeMailQEntry(void *qv)
121 FreeStrBuf(&Q->Recipient);
122 FreeStrBuf(&Q->StatusMessage);
126 typedef struct queueitem {
130 HashList *MailQEntries;
131 MailQEntry *Current; /* copy of the currently parsed item in the MailQEntries list; if null add a new one. */
132 DeliveryAttempt LastAttempt;
133 long ActiveDeliveries;
134 StrBuf *EnvelopeFrom;
137 typedef void (*QItemHandler)(OneQueItem *Item, StrBuf *Line, const char **Pos);
139 /*****************************************************************************/
140 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
141 /*****************************************************************************/
143 typedef enum _eSMTP_C_States {
157 const long SMTP_C_ReadTimeouts[eMaxSMTPC] = {
158 90, /* Greeting... */
166 900, /* end of body... */
170 const long SMTP_C_SendTimeouts[eMaxSMTPC] = {
173 const char *ReadErrors[eMaxSMTPC] = {
174 "Connection broken during SMTP conversation",
175 "Connection broken during SMTP EHLO",
176 "Connection broken during SMTP HELO",
177 "Connection broken during SMTP AUTH",
178 "Connection broken during SMTP MAIL FROM",
179 "Connection broken during SMTP RCPT",
180 "Connection broken during SMTP DATA",
181 "Connection broken during SMTP message transmit",
182 ""/* quit reply, don't care. */
186 typedef struct _stmp_out_msg {
187 MailQEntry *MyQEntry;
192 eSMTP_C_States State;
210 /// char addr[SIZ]; -> MyQEntry->Recipient
211 /// char dsn[1024]; -> MyQEntry->StatusMessage
212 /// char envelope_from_buf[1024]; MyQItem->EnvelopeFrom
215 void DeleteSmtpOutMsg(void *v)
218 FreeStrBuf(&Msg->msgtext);
219 FreeAsyncIOContents(&Msg->IO);
223 eNextState SMTP_C_DispatchReadDone(void *Data);
224 eNextState SMTP_C_DispatchWriteDone(void *Data);
225 eNextState SMTP_C_Terminate(void *Data);
227 typedef eNextState (*SMTPReadHandler)(SmtpOutMsg *Msg);
228 typedef eNextState (*SMTPSendHandler)(SmtpOutMsg *Msg);
233 void FreeQueItem(OneQueItem **Item)
235 DeleteHash(&(*Item)->MailQEntries);
236 FreeStrBuf(&(*Item)->EnvelopeFrom);
237 FreeStrBuf(&(*Item)->BounceTo);
241 void HFreeQueItem(void *Item)
243 FreeQueItem((OneQueItem**)&Item);
247 /* inspect recipients with a status of:
248 * - 0 (no delivery yet attempted)
249 * - 3/4 (transient errors
250 * were experienced and it's time to try again)
252 int CountActiveQueueEntries(OneQueItem *MyQItem)
259 MyQItem->ActiveDeliveries = 0;
260 It = GetNewHashPos(MyQItem->MailQEntries, 0);
261 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
263 MailQEntry *ThisItem = vQE;
264 if ((ThisItem->Status == 0) ||
265 (ThisItem->Status == 3) ||
266 (ThisItem->Status == 4))
268 MyQItem->ActiveDeliveries++;
269 ThisItem->Active = 1;
272 ThisItem->Active = 0;
275 return MyQItem->ActiveDeliveries;
278 OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
281 const char *pLine = NULL;
286 Item = (OneQueItem*)malloc(sizeof(OneQueItem));
287 memset(Item, 0, sizeof(OneQueItem));
288 Item->LastAttempt.retry = SMTP_RETRY_INTERVAL;
289 Item->MessageID = -1;
290 Item->QueMsgID = QueMsgID;
292 citthread_mutex_lock(&ActiveQItemsLock);
293 if (GetHash(ActiveQItems,
294 IKEY(Item->QueMsgID),
297 /* WHOOPS. somebody else is already working on this. */
298 citthread_mutex_unlock(&ActiveQItemsLock);
303 /* mark our claim on this. */
305 IKEY(Item->QueMsgID),
308 citthread_mutex_unlock(&ActiveQItemsLock);
312 Line = NewStrBufPlain(NULL, 128);
313 while (pLine != StrBufNOTNULL) {
314 const char *pItemPart = NULL;
317 StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
318 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
319 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
322 H = (QItemHandler) vHandler;
323 H(Item, Line, &pItemPart);
331 StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
339 QMessage = NewStrBufPlain(NULL, SIZ);
340 StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
342 // "attempted|%ld\n" "retry|%ld\n",, (long)time(NULL), (long)retry );
343 StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
344 StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
346 if (StrLength(MyQItem->BounceTo) > 0) {
347 StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
348 StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
351 if (StrLength(MyQItem->EnvelopeFrom) > 0) {
352 StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
353 StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
356 It = GetNewHashPos(MyQItem->MailQEntries, 0);
357 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
359 MailQEntry *ThisItem = vQE;
362 if (!ThisItem->Active)
363 continue; /* skip already sent ones from the spoolfile. */
365 for (i=0; i < ThisItem->nAttempts; i++) {
366 StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
367 StrBufAppendPrintf(QMessage, "%ld",
368 ThisItem->Attempts[i].retry);
370 StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
371 StrBufAppendPrintf(QMessage, "%ld",
372 ThisItem->Attempts[i].when);
374 StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
375 StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
376 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
377 StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
378 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
379 StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
382 StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);
386 void FinalizeMessageSend(SmtpOutMsg *Msg)
388 int IDestructQueItem;
391 citthread_mutex_lock(&ActiveQItemsLock);
392 Msg->MyQItem->ActiveDeliveries--;
393 IDestructQueItem = Msg->MyQItem->ActiveDeliveries == 0;
394 citthread_mutex_unlock(&ActiveQItemsLock);
396 if (IDestructQueItem) {
400 nRemain = CountActiveQueueEntries(Msg->MyQItem);
403 MsgData = SerializeQueueItem(Msg->MyQItem);
405 * Uncompleted delivery instructions remain, so delete the old
406 * instructions and replace with the updated ones.
408 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, "");
410 /* Generate 'bounce' messages * /
411 smtp_do_bounce(instr); */
413 struct CtdlMessage *msg;
414 msg = malloc(sizeof(struct CtdlMessage));
415 memset(msg, 0, sizeof(struct CtdlMessage));
416 msg->cm_magic = CTDLMESSAGE_MAGIC;
417 msg->cm_anon_type = MES_NORMAL;
418 msg->cm_format_type = FMT_RFC822;
419 msg->cm_fields['M'] = SmashStrBuf(&MsgData);
421 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
422 CtdlFreeMessage(msg);
424 It = GetNewHashPos(Msg->MyQItem->MailQEntries, 0);
425 citthread_mutex_lock(&ActiveQItemsLock);
427 GetHashPosFromKey(ActiveQItems, IKEY(Msg->MyQItem->MessageID), It);
428 DeleteEntryFromHash(ActiveQItems, It);
430 citthread_mutex_unlock(&ActiveQItemsLock);
434 /// TODO : else free message...
435 DeleteSmtpOutMsg(Msg);
438 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
440 eReadState Finished = eBufferNotEmpty;
442 while (Finished == eBufferNotEmpty) {
443 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
446 case eMustReadMore: /// read new from socket...
449 case eBufferNotEmpty: /* shouldn't happen... */
450 case eReadSuccess: /// done for now...
451 if (StrLength(IO->IOBuf) < 4)
453 if (ChrPtr(IO->IOBuf)[3] == '-')
454 Finished = eBufferNotEmpty;
458 case eReadFail: /// WHUT?
467 * this one has to have the context for loading the message via the redirect buffer...
469 StrBuf *smtp_load_msg(OneQueItem *MyQItem)
474 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
475 CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
476 SendMsg = CCC->redirect_buffer;
477 CCC->redirect_buffer = NULL;
478 if ((StrLength(SendMsg) > 0) &&
479 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
480 CtdlLogPrintf(CTDL_WARNING,
481 "SMTP client[%ld]: Possible problem: message did not "
482 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
483 MsgCount, //yes uncool, but best choice here...
484 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
485 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
491 int smtp_resolve_recipients(SmtpOutMsg *SendMsg)
499 /* Parse out the host portion of the recipient address */
500 process_rfc822_addr(ChrPtr(SendMsg->MyQEntry->Recipient),
505 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
506 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
507 /* If no envelope_from is supplied, extract one from the message */
508 if ( (SendMsg->envelope_from == NULL) ||
509 (IsEmptyStr(SendMsg->envelope_from)) ) {
510 SendMsg->mailfrom[0] = '\0';
512 ptr = ChrPtr(SendMsg->msgtext);
514 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
517 if (!strncasecmp(buf, "From:", 5)) {
518 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
519 striplt(SendMsg->mailfrom);
520 for (i=0; SendMsg->mailfrom[i]; ++i) {
521 if (!isprint(SendMsg->mailfrom[i])) {
522 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
527 /* Strip out parenthesized names */
530 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
531 if (SendMsg->mailfrom[i] == '(') lp = i;
532 if (SendMsg->mailfrom[i] == ')') rp = i;
534 if ((lp>0)&&(rp>lp)) {
535 strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
538 /* Prefer brokketized names */
541 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
542 if (SendMsg->mailfrom[i] == '<') lp = i;
543 if (SendMsg->mailfrom[i] == '>') rp = i;
545 if ( (lp>=0) && (rp>lp) ) {
546 SendMsg->mailfrom[rp] = 0;
547 memmove(SendMsg->mailfrom,
548 &SendMsg->mailfrom[lp + 1],
554 } while (scan_done == 0);
555 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
556 stripallbut(SendMsg->mailfrom, '<', '>');
557 SendMsg->envelope_from = SendMsg->mailfrom;
563 void resolve_mx_hosts(SmtpOutMsg *SendMsg)
565 /// well this is blocking and sux, but libevent jsut supports async dns since v2
566 /* Figure out what mail exchanger host we have to connect to */
567 SendMsg->num_mxhosts = getmx(SendMsg->mxhosts, SendMsg->node);
568 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Number of MX hosts for <%s> is %d [%s]\n",
569 SendMsg->n, SendMsg->node, SendMsg->num_mxhosts, SendMsg->mxhosts);
570 if (SendMsg->num_mxhosts < 1) {
571 SendMsg->SMTPstatus = 5;
572 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
573 "No MX hosts found for <%s>", SendMsg->node);
574 return; ///////TODO: abort!
579 #define SMTP_ERROR(WHICH_ERR, ERRSTR) {SendMsg->SMTPstatus = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, HKEY(ERRSTR), 0); return eAbort; }
580 #define SMTP_VERROR(WHICH_ERR) { SendMsg->SMTPstatus = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, &ChrPtr(SendMsg->IO.IOBuf)[4], -1, 0); return eAbort; }
581 #define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(SendMsg->IO.IOBuf)[0] == WHICH_STATE)
583 #define SMTP_DBG_SEND() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: > %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
584 #define SMTP_DBG_READ() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: < %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
586 void connect_one_smtpsrv(SmtpOutMsg *SendMsg)
591 extract_token(buf, SendMsg->mxhosts, SendMsg->n_mx, '|', sizeof(buf));
592 strcpy(SendMsg->mx_user, "");
593 strcpy(SendMsg->mx_pass, "");
594 if (num_tokens(buf, '@') > 1) {
595 strcpy (SendMsg->mx_user, buf);
596 endpart = strrchr(SendMsg->mx_user, '@');
598 strcpy (SendMsg->mx_host, endpart + 1);
599 endpart = strrchr(SendMsg->mx_user, ':');
600 if (endpart != NULL) {
601 strcpy(SendMsg->mx_pass, endpart+1);
606 strcpy (SendMsg->mx_host, buf);
607 endpart = strrchr(SendMsg->mx_host, ':');
610 strcpy(SendMsg->mx_port, endpart + 1);
613 strcpy(SendMsg->mx_port, "25");
615 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connecting to %s : %s ...\n",
616 SendMsg->n, SendMsg->mx_host, SendMsg->mx_port);
621 int connect_one_smtpsrv_xamine_result(void *Ctx)
623 SmtpOutMsg *SendMsg = Ctx;
624 SendMsg->IO.SendBuf.fd =
625 SendMsg->IO.RecvBuf.fd =
626 SendMsg->IO.sock = sock_connect(SendMsg->mx_host, SendMsg->mx_port);
628 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
629 "Could not connect: %s", strerror(errno));
630 if (SendMsg->IO.sock >= 0)
632 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connected!\n", SendMsg->n);
634 fdflags = fcntl(SendMsg->IO.sock, F_GETFL);
636 CtdlLogPrintf(CTDL_DEBUG,
637 "SMTP client[%ld]: unable to get socket flags! %s \n",
638 SendMsg->n, strerror(errno));
639 fdflags = fdflags | O_NONBLOCK;
640 if (fcntl(SendMsg->IO.sock, F_SETFL, fdflags) < 0)
641 CtdlLogPrintf(CTDL_DEBUG,
642 "SMTP client[%ld]: unable to set socket nonblocking flags! %s \n",
643 SendMsg->n, strerror(errno));
645 if (SendMsg->IO.sock < 0) {
647 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
648 strerror(errno), -1);
651 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
652 "Unable to connect to %s : %s\n",
653 SendMsg->mx_host, SendMsg->mx_port);
656 /// hier: naechsten mx ausprobieren.
657 if (SendMsg->IO.sock < 0) {
658 SendMsg->SMTPstatus = 4; /* dsn is already filled in */
659 //// hier: abbrechen & bounce.
664 SendMsg->IO.SendBuf.Buf = NewStrBuf();
665 SendMsg->IO.RecvBuf.Buf = NewStrBuf();
666 SendMsg->IO.IOBuf = NewStrBuf();
667 InitEventIO(&SendMsg->IO, SendMsg,
668 SMTP_C_DispatchReadDone,
669 SMTP_C_DispatchWriteDone,
671 SMTP_C_ReadServerStatus,
676 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
678 /* Process the SMTP greeting from the server */
681 if (!SMTP_IS_STATE('2')) {
682 if (SMTP_IS_STATE('4'))
690 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
692 /* At this point we know we are talking to a real SMTP server */
694 /* Do a EHLO command. If it fails, try the HELO command. */
695 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
696 "EHLO %s\r\n", config.c_fqdn);
702 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
706 if (SMTP_IS_STATE('2')) {
708 if (IsEmptyStr(SendMsg->mx_user))
709 SendMsg->State ++; /* Skip auth... */
711 /* else we fall back to 'helo' */
715 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
717 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
718 "HELO %s\r\n", config.c_fqdn);
724 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
728 if (!SMTP_IS_STATE('2')) {
729 if (SMTP_IS_STATE('4'))
734 if (!IsEmptyStr(SendMsg->mx_user))
735 SendMsg->State ++; /* Skip auth... */
739 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
744 /* Do an AUTH command if necessary */
745 sprintf(buf, "%s%c%s%c%s",
746 SendMsg->mx_user, '\0',
747 SendMsg->mx_user, '\0',
749 CtdlEncodeBase64(encoded, buf,
750 strlen(SendMsg->mx_user) +
751 strlen(SendMsg->mx_user) +
752 strlen(SendMsg->mx_pass) + 2, 0);
753 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
754 "AUTH PLAIN %s\r\n", encoded);
760 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
762 /* Do an AUTH command if necessary */
766 if (!SMTP_IS_STATE('2')) {
767 if (SMTP_IS_STATE('4'))
775 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
777 /* previous command succeeded, now try the MAIL FROM: command */
778 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
779 "MAIL FROM:<%s>\r\n",
780 SendMsg->envelope_from);
786 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
790 if (!SMTP_IS_STATE('2')) {
791 if (SMTP_IS_STATE('4'))
800 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
802 /* MAIL succeeded, now try the RCPT To: command */
803 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
804 "RCPT TO:<%s@%s>\r\n",
812 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
816 if (!SMTP_IS_STATE('2')) {
817 if (SMTP_IS_STATE('4'))
825 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
827 /* RCPT succeeded, now try the DATA command */
828 StrBufPlain(SendMsg->IO.SendBuf.Buf,
835 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
839 if (!SMTP_IS_STATE('3')) {
840 if (SMTP_IS_STATE('4'))
848 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
851 /* If we reach this point, the server is expecting data.*/
853 Buf = SendMsg->IO.SendBuf.Buf;
854 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
855 SendMsg->msgtext = Buf;
856 //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
862 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
866 Buf = SendMsg->IO.SendBuf.Buf;
867 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
868 SendMsg->msgtext = Buf;
870 StrBufPlain(SendMsg->IO.SendBuf.Buf,
877 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
881 if (!SMTP_IS_STATE('2')) {
882 if (SMTP_IS_STATE('4'))
889 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
890 &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
891 StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
892 SendMsg->SMTPstatus = 2;
896 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
898 StrBufPlain(SendMsg->IO.SendBuf.Buf,
905 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
909 CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
910 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
911 return eTerminateConnection;
914 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
919 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
924 void smtp_try(OneQueItem *MyQItem,
925 MailQEntry *MyQEntry,
927 int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
929 SmtpOutMsg * SendMsg;
931 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
932 memset(SendMsg, 0, sizeof(SmtpOutMsg));
933 SendMsg->IO.sock = (-1);
934 SendMsg->n = MsgCount++;
935 SendMsg->MyQEntry = MyQEntry;
936 SendMsg->MyQItem = MyQItem;
937 SendMsg->msgtext = MsgText;
939 smtp_resolve_recipients(SendMsg);
940 resolve_mx_hosts(SendMsg);
941 connect_one_smtpsrv(SendMsg);
942 QueueEventContext(SendMsg,
944 connect_one_smtpsrv_xamine_result);
949 void NewMailQEntry(OneQueItem *Item)
951 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
952 memset(Item->Current, 0, sizeof(MailQEntry));
954 if (Item->MailQEntries == NULL)
955 Item->MailQEntries = NewHash(1, Flathash);
956 Item->Current->n = GetCount(Item->MailQEntries);
957 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
960 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
962 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
965 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
967 if (Item->EnvelopeFrom == NULL)
968 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
969 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
972 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
974 if (Item->BounceTo == NULL)
975 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
976 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
979 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
981 if (Item->Current == NULL)
983 if (Item->Current->Recipient == NULL)
984 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
985 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
986 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
987 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
991 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
993 if (Item->Current == NULL)
995 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
996 Item->Current->nAttempts++;
997 if (Item->Current->nAttempts > MaxAttempts) {
1001 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1004 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1006 if (Item->Current == NULL)
1007 NewMailQEntry(Item);
1008 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1009 Item->Current->nAttempts++;
1010 if (Item->Current->nAttempts > MaxAttempts) {
1015 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1016 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1018 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1019 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1020 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1021 Item->LastAttempt.retry = SMTP_RETRY_MAX;
1031 * Called by smtp_do_queue() to handle an individual message.
1033 void smtp_do_procmsg(long msgnum, void *userdata) {
1034 struct CtdlMessage *msg = NULL;
1037 OneQueItem *MyQItem;
1044 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: smtp_do_procmsg(%ld)\n", msgnum);
1045 ///strcpy(envelope_from, "");
1047 msg = CtdlFetchMessage(msgnum, 1);
1049 CtdlLogPrintf(CTDL_ERR, "SMTP client: tried %ld but no such message!\n", msgnum);
1053 pch = instr = msg->cm_fields['M'];
1055 /* Strip out the headers (no not amd any other non-instruction) line */
1056 while (pch != NULL) {
1057 pch = strchr(pch, '\n');
1058 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1063 PlainQItem = NewStrBufPlain(instr, -1);
1064 CtdlFreeMessage(msg);
1065 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1066 FreeStrBuf(&PlainQItem);
1068 if (MyQItem == NULL)
1069 return; /* s.b. else is already processing... */
1073 * Postpone delivery if we've already tried recently.
1075 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1076 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1078 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1079 citthread_mutex_lock(&ActiveQItemsLock);
1081 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1082 DeleteEntryFromHash(ActiveQItems, It);
1084 citthread_mutex_unlock(&ActiveQItemsLock);
1085 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1088 }// TODO: reenable me.*/
1091 * Bail out if there's no actual message associated with this
1093 if (MyQItem->MessageID < 0L) {
1094 CtdlLogPrintf(CTDL_ERR, "SMTP client: no 'msgid' directive found!\n");
1095 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1096 citthread_mutex_lock(&ActiveQItemsLock);
1098 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1099 DeleteEntryFromHash(ActiveQItems, It);
1101 citthread_mutex_unlock(&ActiveQItemsLock);
1103 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1107 CountActiveQueueEntries(MyQItem);
1108 if (MyQItem->ActiveDeliveries > 0)
1111 StrBuf *Msg = smtp_load_msg(MyQItem);
1112 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1113 while ((i <= MyQItem->ActiveDeliveries) &&
1114 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1116 MailQEntry *ThisItem = vQE;
1117 if (ThisItem->Active == 1) {
1118 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1119 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1127 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1128 citthread_mutex_lock(&ActiveQItemsLock);
1130 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1131 DeleteEntryFromHash(ActiveQItems, It);
1133 citthread_mutex_unlock(&ActiveQItemsLock);
1135 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1137 // TODO: bounce & delete?
1143 /*****************************************************************************/
1144 /* SMTP UTILITY COMMANDS */
1145 /*****************************************************************************/
1147 void cmd_smtp(char *argbuf) {
1154 if (CtdlAccessCheck(ac_aide)) return;
1156 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1158 if (!strcasecmp(cmd, "mx")) {
1159 extract_token(node, argbuf, 1, '|', sizeof node);
1160 num_mxhosts = getmx(buf, node);
1161 cprintf("%d %d MX hosts listed for %s\n",
1162 LISTING_FOLLOWS, num_mxhosts, node);
1163 for (i=0; i<num_mxhosts; ++i) {
1164 extract_token(node, buf, i, '|', sizeof node);
1165 cprintf("%s\n", node);
1171 else if (!strcasecmp(cmd, "runqueue")) {
1173 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1178 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1185 * smtp_queue_thread()
1187 * Run through the queue sending out messages.
1189 void *smtp_queue_thread(void *arg) {
1190 int num_processed = 0;
1191 struct CitContext smtp_queue_CC;
1193 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1194 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1195 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1197 while (!CtdlThreadCheckStop()) {
1199 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1201 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1202 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1205 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1207 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1208 CtdlThreadSleep(60);
1211 CtdlClearSystemContext();
1217 * Initialize the SMTP outbound queue
1219 void smtp_init_spoolout(void) {
1220 struct ctdlroom qrbuf;
1223 * Create the room. This will silently fail if the room already
1224 * exists, and that's perfectly ok, because we want it to exist.
1226 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1229 * Make sure it's set to be a "system room" so it doesn't show up
1230 * in the <K>nown rooms list for Aides.
1232 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1233 qrbuf.QRflags2 |= QR2_SYSTEM;
1234 CtdlPutRoomLock(&qrbuf);
1239 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1240 SMTPC_read_greeting,
1241 SMTPC_read_EHLO_reply,
1242 SMTPC_read_HELO_reply,
1243 SMTPC_read_auth_reply,
1244 SMTPC_read_FROM_reply,
1245 SMTPC_read_RCPT_reply,
1246 SMTPC_read_DATAcmd_reply,
1248 SMTPC_read_data_body_reply,
1249 SMTPC_read_QUIT_reply
1252 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1253 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1260 SMTPC_send_data_body,
1261 SMTPC_send_terminate_data_body,
1265 eNextState SMTP_C_Terminate(void *Data)
1267 SmtpOutMsg *pMsg = Data;
1268 FinalizeMessageSend(pMsg);
1271 eNextState SMTP_C_DispatchReadDone(void *Data)
1273 SmtpOutMsg *pMsg = Data;
1274 eNextState rc = ReadHandlers[pMsg->State](pMsg);
1279 eNextState SMTP_C_DispatchWriteDone(void *Data)
1281 SmtpOutMsg *pMsg = Data;
1282 return SendHandlers[pMsg->State](pMsg);
1288 CTDL_MODULE_INIT(smtp_eventclient)
1290 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1293 ActiveQItems = NewHash(1, Flathash);
1294 citthread_mutex_init(&ActiveQItemsLock, NULL);
1296 QItemHandlers = NewHash(0, NULL);
1298 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1299 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1300 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1301 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1302 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1303 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1304 ///submitted /TODO: flush qitemhandlers on exit
1307 smtp_init_spoolout();
1308 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1310 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1314 /* return our Subversion id for the Log */
1315 return "smtpeventclient";