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;
194 struct ares_mx_reply *AllMX;
195 struct ares_mx_reply *CurrMX;
199 struct hostent *OneMX;
210 void DeleteSmtpOutMsg(void *v)
214 ares_free_data(Msg->AllMX);
215 FreeStrBuf(&Msg->msgtext);
216 FreeAsyncIOContents(&Msg->IO);
220 eNextState SMTP_C_Timeout(void *Data);
221 eNextState SMTP_C_ConnFail(void *Data);
222 eNextState SMTP_C_DispatchReadDone(void *Data);
223 eNextState SMTP_C_DispatchWriteDone(void *Data);
224 eNextState SMTP_C_Terminate(void *Data);
225 eNextState SMTP_C_MXLookup(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 if (StrLength(Line) == 0) continue;
319 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
320 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
323 H = (QItemHandler) vHandler;
324 H(Item, Line, &pItemPart);
332 StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
340 QMessage = NewStrBufPlain(NULL, SIZ);
341 StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
343 // "attempted|%ld\n" "retry|%ld\n",, (long)time(NULL), (long)retry );
344 StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
345 StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
347 if (StrLength(MyQItem->BounceTo) > 0) {
348 StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
349 StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
352 if (StrLength(MyQItem->EnvelopeFrom) > 0) {
353 StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
354 StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
357 It = GetNewHashPos(MyQItem->MailQEntries, 0);
358 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
360 MailQEntry *ThisItem = vQE;
363 if (!ThisItem->Active)
364 continue; /* skip already sent ones from the spoolfile. */
366 for (i=0; i < ThisItem->nAttempts; i++) {
367 StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
368 StrBufAppendPrintf(QMessage, "%ld",
369 ThisItem->Attempts[i].retry);
371 StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
372 StrBufAppendPrintf(QMessage, "%ld",
373 ThisItem->Attempts[i].when);
375 StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
376 StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
377 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
378 StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
379 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
380 StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
383 StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);
387 void FinalizeMessageSend(SmtpOutMsg *Msg)
389 int IDestructQueItem;
392 citthread_mutex_lock(&ActiveQItemsLock);
393 Msg->MyQItem->ActiveDeliveries--;
394 IDestructQueItem = Msg->MyQItem->ActiveDeliveries == 0;
395 citthread_mutex_unlock(&ActiveQItemsLock);
397 if (IDestructQueItem) {
401 nRemain = CountActiveQueueEntries(Msg->MyQItem);
404 MsgData = SerializeQueueItem(Msg->MyQItem);
406 * Uncompleted delivery instructions remain, so delete the old
407 * instructions and replace with the updated ones.
409 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, "");
411 /* Generate 'bounce' messages * /
412 smtp_do_bounce(instr); */
414 struct CtdlMessage *msg;
415 msg = malloc(sizeof(struct CtdlMessage));
416 memset(msg, 0, sizeof(struct CtdlMessage));
417 msg->cm_magic = CTDLMESSAGE_MAGIC;
418 msg->cm_anon_type = MES_NORMAL;
419 msg->cm_format_type = FMT_RFC822;
420 msg->cm_fields['M'] = SmashStrBuf(&MsgData);
422 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
423 CtdlFreeMessage(msg);
425 It = GetNewHashPos(Msg->MyQItem->MailQEntries, 0);
426 citthread_mutex_lock(&ActiveQItemsLock);
428 GetHashPosFromKey(ActiveQItems, IKEY(Msg->MyQItem->MessageID), It);
429 DeleteEntryFromHash(ActiveQItems, It);
431 citthread_mutex_unlock(&ActiveQItemsLock);
435 /// TODO : else free message...
437 DeleteSmtpOutMsg(Msg);
440 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
442 eReadState Finished = eBufferNotEmpty;
444 while (Finished == eBufferNotEmpty) {
445 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
448 case eMustReadMore: /// read new from socket...
451 case eBufferNotEmpty: /* shouldn't happen... */
452 case eReadSuccess: /// done for now...
453 if (StrLength(IO->IOBuf) < 4)
455 if (ChrPtr(IO->IOBuf)[3] == '-')
456 Finished = eBufferNotEmpty;
460 case eReadFail: /// WHUT?
469 * this one has to have the context for loading the message via the redirect buffer...
471 StrBuf *smtp_load_msg(OneQueItem *MyQItem)
476 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
477 CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
478 SendMsg = CCC->redirect_buffer;
479 CCC->redirect_buffer = NULL;
480 if ((StrLength(SendMsg) > 0) &&
481 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
482 CtdlLogPrintf(CTDL_WARNING,
483 "SMTP client[%ld]: Possible problem: message did not "
484 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
485 MsgCount, //yes uncool, but best choice here...
486 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
487 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
493 int smtp_resolve_recipients(SmtpOutMsg *SendMsg)
501 /* Parse out the host portion of the recipient address */
502 process_rfc822_addr(ChrPtr(SendMsg->MyQEntry->Recipient),
507 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
508 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
509 /* If no envelope_from is supplied, extract one from the message */
510 if ( (SendMsg->envelope_from == NULL) ||
511 (IsEmptyStr(SendMsg->envelope_from)) ) {
512 SendMsg->mailfrom[0] = '\0';
514 ptr = ChrPtr(SendMsg->msgtext);
516 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
519 if (!strncasecmp(buf, "From:", 5)) {
520 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
521 striplt(SendMsg->mailfrom);
522 for (i=0; SendMsg->mailfrom[i]; ++i) {
523 if (!isprint(SendMsg->mailfrom[i])) {
524 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
529 /* Strip out parenthesized names */
532 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
533 if (SendMsg->mailfrom[i] == '(') lp = i;
534 if (SendMsg->mailfrom[i] == ')') rp = i;
536 if ((lp>0)&&(rp>lp)) {
537 strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
540 /* Prefer brokketized names */
543 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
544 if (SendMsg->mailfrom[i] == '<') lp = i;
545 if (SendMsg->mailfrom[i] == '>') rp = i;
547 if ( (lp>=0) && (rp>lp) ) {
548 SendMsg->mailfrom[rp] = 0;
549 memmove(SendMsg->mailfrom,
550 &SendMsg->mailfrom[lp + 1],
556 } while (scan_done == 0);
557 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
558 stripallbut(SendMsg->mailfrom, '<', '>');
559 SendMsg->envelope_from = SendMsg->mailfrom;
566 #define SMTP_ERROR(WHICH_ERR, ERRSTR) {SendMsg->MyQEntry->Status = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, HKEY(ERRSTR), 0); return eAbort; }
567 #define SMTP_VERROR(WHICH_ERR) { SendMsg->MyQEntry->Status = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, &ChrPtr(SendMsg->IO.IOBuf)[4], -1, 0); return eAbort; }
568 #define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(SendMsg->IO.IOBuf)[0] == WHICH_STATE)
570 #define SMTP_DBG_SEND() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: > %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
571 #define SMTP_DBG_READ() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: < %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
573 void get_one_mx_host_name_done(void *Ctx,
576 struct hostent *hostent)
579 SmtpOutMsg *SendMsg = IO->Data;
580 if ((status == ARES_SUCCESS) && (hostent != NULL) ) {
582 SendMsg->IO.HEnt = hostent;
583 InitEventIO(IO, SendMsg,
584 SMTP_C_DispatchReadDone,
585 SMTP_C_DispatchWriteDone,
589 SMTP_C_ReadServerStatus,
595 const unsigned short DefaultMXPort = 25;
596 void connect_one_smtpsrv(SmtpOutMsg *SendMsg)
601 SendMsg->IO.dport = DefaultMXPort;
605 *SendMsg->mx_user = '\0';
606 *SendMsg->mx_pass = '\0';
607 if (num_tokens(buf, '@') > 1) {
608 strcpy (SendMsg->mx_user, buf);
609 endpart = strrchr(SendMsg->mx_user, '@');
611 strcpy (SendMsg->mx_host, endpart + 1);
612 endpart = strrchr(SendMsg->mx_user, ':');
613 if (endpart != NULL) {
614 strcpy(SendMsg->mx_pass, endpart+1);
618 endpart = strrchr(SendMsg->mx_host, ':');
621 strcpy(SendMsg->mx_port, endpart + 1);
626 SendMsg->mx_host = SendMsg->CurrMX->host;
627 SendMsg->CurrMX = SendMsg->CurrMX->next;
629 CtdlLogPrintf(CTDL_DEBUG,
630 "SMTP client[%ld]: connecting to %s : %d ...\n",
635 ares_gethostbyname(SendMsg->IO.DNSChannel,
637 AF_INET6, /* it falls back to ipv4 in doubt... */
638 get_one_mx_host_name_done,
641 if (!QueueQuery(ns_t_a,
644 connect_one_smtpsrv_xamine_result))
652 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
654 /* Process the SMTP greeting from the server */
657 if (!SMTP_IS_STATE('2')) {
658 if (SMTP_IS_STATE('4'))
666 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
668 /* At this point we know we are talking to a real SMTP server */
670 /* Do a EHLO command. If it fails, try the HELO command. */
671 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
672 "EHLO %s\r\n", config.c_fqdn);
678 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
682 if (SMTP_IS_STATE('2')) {
684 if (IsEmptyStr(SendMsg->mx_user))
685 SendMsg->State ++; /* Skip auth... */
687 /* else we fall back to 'helo' */
691 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
693 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
694 "HELO %s\r\n", config.c_fqdn);
700 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
704 if (!SMTP_IS_STATE('2')) {
705 if (SMTP_IS_STATE('4'))
710 if (!IsEmptyStr(SendMsg->mx_user))
711 SendMsg->State ++; /* Skip auth... */
715 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
720 /* Do an AUTH command if necessary */
721 sprintf(buf, "%s%c%s%c%s",
722 SendMsg->mx_user, '\0',
723 SendMsg->mx_user, '\0',
725 CtdlEncodeBase64(encoded, buf,
726 strlen(SendMsg->mx_user) +
727 strlen(SendMsg->mx_user) +
728 strlen(SendMsg->mx_pass) + 2, 0);
729 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
730 "AUTH PLAIN %s\r\n", encoded);
736 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
738 /* Do an AUTH command if necessary */
742 if (!SMTP_IS_STATE('2')) {
743 if (SMTP_IS_STATE('4'))
751 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
753 /* previous command succeeded, now try the MAIL FROM: command */
754 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
755 "MAIL FROM:<%s>\r\n",
756 SendMsg->envelope_from);
762 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
766 if (!SMTP_IS_STATE('2')) {
767 if (SMTP_IS_STATE('4'))
776 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
778 /* MAIL succeeded, now try the RCPT To: command */
779 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
780 "RCPT TO:<%s@%s>\r\n",
788 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
792 if (!SMTP_IS_STATE('2')) {
793 if (SMTP_IS_STATE('4'))
801 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
803 /* RCPT succeeded, now try the DATA command */
804 StrBufPlain(SendMsg->IO.SendBuf.Buf,
811 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
815 if (!SMTP_IS_STATE('3')) {
816 if (SMTP_IS_STATE('4'))
824 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
827 /* If we reach this point, the server is expecting data.*/
829 Buf = SendMsg->IO.SendBuf.Buf;
830 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
831 SendMsg->msgtext = Buf;
832 //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
838 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
842 Buf = SendMsg->IO.SendBuf.Buf;
843 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
844 SendMsg->msgtext = Buf;
846 StrBufPlain(SendMsg->IO.SendBuf.Buf,
853 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
857 if (!SMTP_IS_STATE('2')) {
858 if (SMTP_IS_STATE('4'))
865 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
866 &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
867 StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
868 SendMsg->MyQEntry->Status = 2;
872 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
874 StrBufPlain(SendMsg->IO.SendBuf.Buf,
881 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
885 CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
886 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
887 return eTerminateConnection;
890 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
895 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
900 eNextState smtp_resolve_mx_done(void *data)
903 SmtpOutMsg * SendMsg = IO->Data;
905 SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
906 SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
907 SendMsg->IO.IOBuf = NewStrBuf();
908 SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
910 //// connect_one_smtpsrv_xamine_result
911 SendMsg->CurrMX = SendMsg->AllMX = IO->VParsedDNSReply;
912 //// TODO: should we remove the current ares context???
913 connect_one_smtpsrv(SendMsg);
919 int resolve_mx_records(void *Ctx)
921 SmtpOutMsg * SendMsg = Ctx;
923 if (!QueueQuery(ns_t_mx,
926 smtp_resolve_mx_done))
928 SendMsg->MyQEntry->Status = 5;
929 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
930 "No MX hosts found for <%s>", SendMsg->node);
931 return 0; ///////TODO: abort!
936 void smtp_try(OneQueItem *MyQItem,
937 MailQEntry *MyQEntry,
939 int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
941 SmtpOutMsg * SendMsg;
943 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
944 memset(SendMsg, 0, sizeof(SmtpOutMsg));
945 SendMsg->IO.sock = (-1);
946 SendMsg->n = MsgCount++;
947 SendMsg->MyQEntry = MyQEntry;
948 SendMsg->MyQItem = MyQItem;
949 SendMsg->IO.Data = SendMsg;
951 SendMsg->msgtext = MsgText;
953 SendMsg->msgtext = NewStrBufDup(MsgText);
955 smtp_resolve_recipients(SendMsg);
957 QueueEventContext(SendMsg,
964 void NewMailQEntry(OneQueItem *Item)
966 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
967 memset(Item->Current, 0, sizeof(MailQEntry));
969 if (Item->MailQEntries == NULL)
970 Item->MailQEntries = NewHash(1, Flathash);
971 Item->Current->n = GetCount(Item->MailQEntries);
972 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
975 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
977 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
980 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
982 if (Item->EnvelopeFrom == NULL)
983 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
984 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
987 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
989 if (Item->BounceTo == NULL)
990 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
991 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
994 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
996 if (Item->Current == NULL)
998 if (Item->Current->Recipient == NULL)
999 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
1000 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
1001 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
1002 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
1003 Item->Current = NULL; // TODO: is this always right?
1007 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
1009 if (Item->Current == NULL)
1010 NewMailQEntry(Item);
1011 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
1012 Item->Current->nAttempts++;
1013 if (Item->Current->nAttempts > MaxAttempts) {
1017 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1020 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1022 if (Item->Current == NULL)
1023 NewMailQEntry(Item);
1024 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1025 Item->Current->nAttempts++;
1026 if (Item->Current->nAttempts > MaxAttempts) {
1031 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1032 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1034 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1035 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1036 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1037 Item->LastAttempt.retry = SMTP_RETRY_MAX;
1047 * Called by smtp_do_queue() to handle an individual message.
1049 void smtp_do_procmsg(long msgnum, void *userdata) {
1050 struct CtdlMessage *msg = NULL;
1053 OneQueItem *MyQItem;
1060 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
1061 ///strcpy(envelope_from, "");
1063 msg = CtdlFetchMessage(msgnum, 1);
1065 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
1069 pch = instr = msg->cm_fields['M'];
1071 /* Strip out the headers (no not amd any other non-instruction) line */
1072 while (pch != NULL) {
1073 pch = strchr(pch, '\n');
1074 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1079 PlainQItem = NewStrBufPlain(instr, -1);
1080 CtdlFreeMessage(msg);
1081 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1082 FreeStrBuf(&PlainQItem);
1084 if (MyQItem == NULL) {
1085 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
1086 return; /* s.b. else is already processing... */
1090 * Postpone delivery if we've already tried recently.
1092 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1093 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\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);
1102 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1105 }// TODO: reenable me.*/
1108 * Bail out if there's no actual message associated with this
1110 if (MyQItem->MessageID < 0L) {
1111 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
1112 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1113 citthread_mutex_lock(&ActiveQItemsLock);
1115 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1116 DeleteEntryFromHash(ActiveQItems, It);
1118 citthread_mutex_unlock(&ActiveQItemsLock);
1120 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1124 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1125 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
1127 MailQEntry *ThisItem = vQE;
1128 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
1132 CountActiveQueueEntries(MyQItem);
1133 if (MyQItem->ActiveDeliveries > 0)
1136 StrBuf *Msg = smtp_load_msg(MyQItem);
1137 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1138 while ((i <= MyQItem->ActiveDeliveries) &&
1139 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1141 MailQEntry *ThisItem = vQE;
1142 if (ThisItem->Active == 1) {
1143 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1144 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1152 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1153 citthread_mutex_lock(&ActiveQItemsLock);
1155 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1156 DeleteEntryFromHash(ActiveQItems, It);
1158 citthread_mutex_unlock(&ActiveQItemsLock);
1160 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1162 // TODO: bounce & delete?
1168 /*****************************************************************************/
1169 /* SMTP UTILITY COMMANDS */
1170 /*****************************************************************************/
1172 void cmd_smtp(char *argbuf) {
1179 if (CtdlAccessCheck(ac_aide)) return;
1181 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1183 if (!strcasecmp(cmd, "mx")) {
1184 extract_token(node, argbuf, 1, '|', sizeof node);
1185 num_mxhosts = getmx(buf, node);
1186 cprintf("%d %d MX hosts listed for %s\n",
1187 LISTING_FOLLOWS, num_mxhosts, node);
1188 for (i=0; i<num_mxhosts; ++i) {
1189 extract_token(node, buf, i, '|', sizeof node);
1190 cprintf("%s\n", node);
1196 else if (!strcasecmp(cmd, "runqueue")) {
1198 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1203 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1210 * smtp_queue_thread()
1212 * Run through the queue sending out messages.
1214 void *smtp_queue_thread(void *arg) {
1215 int num_processed = 0;
1216 struct CitContext smtp_queue_CC;
1218 CtdlThreadSleep(10);
1220 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1221 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1222 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1224 while (!CtdlThreadCheckStop()) {
1226 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1228 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1229 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1232 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1234 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1235 CtdlThreadSleep(60);
1238 CtdlClearSystemContext();
1244 * Initialize the SMTP outbound queue
1246 void smtp_init_spoolout(void) {
1247 struct ctdlroom qrbuf;
1250 * Create the room. This will silently fail if the room already
1251 * exists, and that's perfectly ok, because we want it to exist.
1253 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1256 * Make sure it's set to be a "system room" so it doesn't show up
1257 * in the <K>nown rooms list for Aides.
1259 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1260 qrbuf.QRflags2 |= QR2_SYSTEM;
1261 CtdlPutRoomLock(&qrbuf);
1266 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1267 SMTPC_read_greeting,
1268 SMTPC_read_EHLO_reply,
1269 SMTPC_read_HELO_reply,
1270 SMTPC_read_auth_reply,
1271 SMTPC_read_FROM_reply,
1272 SMTPC_read_RCPT_reply,
1273 SMTPC_read_DATAcmd_reply,
1275 SMTPC_read_data_body_reply,
1276 SMTPC_read_QUIT_reply
1279 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1280 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1287 SMTPC_send_data_body,
1288 SMTPC_send_terminate_data_body,
1292 eNextState SMTP_C_Terminate(void *Data)
1294 SmtpOutMsg *pMsg = Data;
1295 FinalizeMessageSend(pMsg);
1299 eNextState SMTP_C_Timeout(void *Data)
1301 SmtpOutMsg *pMsg = Data;
1302 FinalizeMessageSend(pMsg);
1306 eNextState SMTP_C_ConnFail(void *Data)
1308 SmtpOutMsg *pMsg = Data;
1309 FinalizeMessageSend(pMsg);
1313 eNextState SMTP_C_DispatchReadDone(void *Data)
1315 SmtpOutMsg *pMsg = Data;
1316 eNextState rc = ReadHandlers[pMsg->State](pMsg);
1321 eNextState SMTP_C_DispatchWriteDone(void *Data)
1323 SmtpOutMsg *pMsg = Data;
1324 return SendHandlers[pMsg->State](pMsg);
1328 void smtp_evc_cleanup(void)
1330 DeleteHash(&QItemHandlers);
1331 DeleteHash(&ActiveQItems);
1335 CTDL_MODULE_INIT(smtp_eventclient)
1337 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1340 ActiveQItems = NewHash(1, Flathash);
1341 citthread_mutex_init(&ActiveQItemsLock, NULL);
1343 QItemHandlers = NewHash(0, NULL);
1345 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1346 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1347 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1348 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1349 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1350 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1351 ///submitted /TODO: flush qitemhandlers on exit
1354 smtp_init_spoolout();
1356 CtdlRegisterCleanupHook(smtp_evc_cleanup);
1357 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1359 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1363 /* return our Subversion id for the Log */
1364 return "smtpeventclient";