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) do {\
567 SendMsg->MyQEntry->Status = WHICH_ERR; \
568 StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, HKEY(ERRSTR), 0); \
572 #define SMTP_VERROR(WHICH_ERR) do {\
573 SendMsg->MyQEntry->Status = WHICH_ERR; \
574 StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, &ChrPtr(SendMsg->IO.IOBuf)[4], -1, 0); \
578 #define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(SendMsg->IO.IOBuf)[0] == WHICH_STATE)
580 #define SMTP_DBG_SEND() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: > %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
581 #define SMTP_DBG_READ() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: < %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
583 void get_one_mx_host_name_done(void *Ctx,
586 struct hostent *hostent)
589 SmtpOutMsg *SendMsg = IO->Data;
590 if ((status == ARES_SUCCESS) && (hostent != NULL) ) {
591 CtdlLogPrintf(CTDL_DEBUG,
592 "SMTP client[%ld]: connecting to %s : %d ...\n",
598 SendMsg->IO.HEnt = hostent;
599 InitEventIO(IO, SendMsg,
600 SMTP_C_DispatchReadDone,
601 SMTP_C_DispatchWriteDone,
605 SMTP_C_ReadServerStatus,
611 const unsigned short DefaultMXPort = 25;
612 void connect_one_smtpsrv(SmtpOutMsg *SendMsg)
617 SendMsg->IO.dport = DefaultMXPort;
621 *SendMsg->mx_user = '\0';
622 *SendMsg->mx_pass = '\0';
623 if (num_tokens(buf, '@') > 1) {
624 strcpy (SendMsg->mx_user, buf);
625 endpart = strrchr(SendMsg->mx_user, '@');
627 strcpy (SendMsg->mx_host, endpart + 1);
628 endpart = strrchr(SendMsg->mx_user, ':');
629 if (endpart != NULL) {
630 strcpy(SendMsg->mx_pass, endpart+1);
634 endpart = strrchr(SendMsg->mx_host, ':');
637 strcpy(SendMsg->mx_port, endpart + 1);
642 SendMsg->mx_host = SendMsg->CurrMX->host;
643 SendMsg->CurrMX = SendMsg->CurrMX->next;
645 CtdlLogPrintf(CTDL_DEBUG,
646 "SMTP client[%ld]: looking up %s : %d ...\n",
650 ares_gethostbyname(SendMsg->IO.DNSChannel,
652 AF_INET6, /* it falls back to ipv4 in doubt... */
653 get_one_mx_host_name_done,
658 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
660 /* Process the SMTP greeting from the server */
663 if (!SMTP_IS_STATE('2')) {
664 if (SMTP_IS_STATE('4'))
672 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
674 /* At this point we know we are talking to a real SMTP server */
676 /* Do a EHLO command. If it fails, try the HELO command. */
677 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
678 "EHLO %s\r\n", config.c_fqdn);
684 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
688 if (SMTP_IS_STATE('2')) {
690 if (IsEmptyStr(SendMsg->mx_user))
691 SendMsg->State ++; /* Skip auth... */
693 /* else we fall back to 'helo' */
697 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
699 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
700 "HELO %s\r\n", config.c_fqdn);
706 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
710 if (!SMTP_IS_STATE('2')) {
711 if (SMTP_IS_STATE('4'))
716 if (!IsEmptyStr(SendMsg->mx_user))
717 SendMsg->State ++; /* Skip auth... */
721 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
726 /* Do an AUTH command if necessary */
727 sprintf(buf, "%s%c%s%c%s",
728 SendMsg->mx_user, '\0',
729 SendMsg->mx_user, '\0',
731 CtdlEncodeBase64(encoded, buf,
732 strlen(SendMsg->mx_user) +
733 strlen(SendMsg->mx_user) +
734 strlen(SendMsg->mx_pass) + 2, 0);
735 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
736 "AUTH PLAIN %s\r\n", encoded);
742 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
744 /* Do an AUTH command if necessary */
748 if (!SMTP_IS_STATE('2')) {
749 if (SMTP_IS_STATE('4'))
757 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
759 /* previous command succeeded, now try the MAIL FROM: command */
760 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
761 "MAIL FROM:<%s>\r\n",
762 SendMsg->envelope_from);
768 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
772 if (!SMTP_IS_STATE('2')) {
773 if (SMTP_IS_STATE('4'))
782 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
784 /* MAIL succeeded, now try the RCPT To: command */
785 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
786 "RCPT TO:<%s@%s>\r\n",
794 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
798 if (!SMTP_IS_STATE('2')) {
799 if (SMTP_IS_STATE('4'))
807 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
809 /* RCPT succeeded, now try the DATA command */
810 StrBufPlain(SendMsg->IO.SendBuf.Buf,
817 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
821 if (!SMTP_IS_STATE('3')) {
822 if (SMTP_IS_STATE('4'))
830 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
833 /* If we reach this point, the server is expecting data.*/
835 Buf = SendMsg->IO.SendBuf.Buf;
836 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
837 SendMsg->msgtext = Buf;
838 //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
844 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
848 Buf = SendMsg->IO.SendBuf.Buf;
849 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
850 SendMsg->msgtext = Buf;
852 StrBufPlain(SendMsg->IO.SendBuf.Buf,
859 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
863 if (!SMTP_IS_STATE('2')) {
864 if (SMTP_IS_STATE('4'))
871 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
872 &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
873 StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
874 SendMsg->MyQEntry->Status = 2;
878 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
880 StrBufPlain(SendMsg->IO.SendBuf.Buf,
887 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
891 CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
892 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
893 return eTerminateConnection;
896 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
901 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
906 eNextState smtp_resolve_mx_done(void *data)
909 SmtpOutMsg * SendMsg = IO->Data;
911 SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
912 SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
913 SendMsg->IO.IOBuf = NewStrBuf();
914 SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
916 //// connect_one_smtpsrv_xamine_result
917 SendMsg->CurrMX = SendMsg->AllMX = IO->VParsedDNSReply;
918 //// TODO: should we remove the current ares context???
919 connect_one_smtpsrv(SendMsg);
925 int resolve_mx_records(void *Ctx)
927 SmtpOutMsg * SendMsg = Ctx;
929 if (!QueueQuery(ns_t_mx,
932 smtp_resolve_mx_done))
934 SendMsg->MyQEntry->Status = 5;
935 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
936 "No MX hosts found for <%s>", SendMsg->node);
937 return 0; ///////TODO: abort!
942 void smtp_try(OneQueItem *MyQItem,
943 MailQEntry *MyQEntry,
945 int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
947 SmtpOutMsg * SendMsg;
949 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
950 memset(SendMsg, 0, sizeof(SmtpOutMsg));
951 SendMsg->IO.sock = (-1);
952 SendMsg->n = MsgCount++;
953 SendMsg->MyQEntry = MyQEntry;
954 SendMsg->MyQItem = MyQItem;
955 SendMsg->IO.Data = SendMsg;
957 SendMsg->msgtext = MsgText;
959 SendMsg->msgtext = NewStrBufDup(MsgText);
961 smtp_resolve_recipients(SendMsg);
963 QueueEventContext(SendMsg,
970 void NewMailQEntry(OneQueItem *Item)
972 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
973 memset(Item->Current, 0, sizeof(MailQEntry));
975 if (Item->MailQEntries == NULL)
976 Item->MailQEntries = NewHash(1, Flathash);
977 Item->Current->n = GetCount(Item->MailQEntries);
978 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
981 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
983 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
986 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
988 if (Item->EnvelopeFrom == NULL)
989 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
990 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
993 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
995 if (Item->BounceTo == NULL)
996 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
997 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
1000 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
1002 if (Item->Current == NULL)
1003 NewMailQEntry(Item);
1004 if (Item->Current->Recipient == NULL)
1005 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
1006 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
1007 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
1008 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
1009 Item->Current = NULL; // TODO: is this always right?
1013 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
1015 if (Item->Current == NULL)
1016 NewMailQEntry(Item);
1017 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
1018 Item->Current->nAttempts++;
1019 if (Item->Current->nAttempts > MaxAttempts) {
1023 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1026 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1028 if (Item->Current == NULL)
1029 NewMailQEntry(Item);
1030 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1031 Item->Current->nAttempts++;
1032 if (Item->Current->nAttempts > MaxAttempts) {
1037 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1038 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1040 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1041 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1042 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1043 Item->LastAttempt.retry = SMTP_RETRY_MAX;
1053 * Called by smtp_do_queue() to handle an individual message.
1055 void smtp_do_procmsg(long msgnum, void *userdata) {
1056 struct CtdlMessage *msg = NULL;
1059 OneQueItem *MyQItem;
1066 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
1067 ///strcpy(envelope_from, "");
1069 msg = CtdlFetchMessage(msgnum, 1);
1071 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
1075 pch = instr = msg->cm_fields['M'];
1077 /* Strip out the headers (no not amd any other non-instruction) line */
1078 while (pch != NULL) {
1079 pch = strchr(pch, '\n');
1080 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1085 PlainQItem = NewStrBufPlain(instr, -1);
1086 CtdlFreeMessage(msg);
1087 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1088 FreeStrBuf(&PlainQItem);
1090 if (MyQItem == NULL) {
1091 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
1092 return; /* s.b. else is already processing... */
1096 * Postpone delivery if we've already tried recently.
1098 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1099 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1101 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1102 citthread_mutex_lock(&ActiveQItemsLock);
1104 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1105 DeleteEntryFromHash(ActiveQItems, It);
1107 citthread_mutex_unlock(&ActiveQItemsLock);
1108 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1111 }// TODO: reenable me.*/
1114 * Bail out if there's no actual message associated with this
1116 if (MyQItem->MessageID < 0L) {
1117 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
1118 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1119 citthread_mutex_lock(&ActiveQItemsLock);
1121 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1122 DeleteEntryFromHash(ActiveQItems, It);
1124 citthread_mutex_unlock(&ActiveQItemsLock);
1126 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1130 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1131 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
1133 MailQEntry *ThisItem = vQE;
1134 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
1138 CountActiveQueueEntries(MyQItem);
1139 if (MyQItem->ActiveDeliveries > 0)
1142 StrBuf *Msg = smtp_load_msg(MyQItem);
1143 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1144 while ((i <= MyQItem->ActiveDeliveries) &&
1145 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1147 MailQEntry *ThisItem = vQE;
1148 if (ThisItem->Active == 1) {
1149 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1150 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1158 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1159 citthread_mutex_lock(&ActiveQItemsLock);
1161 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1162 DeleteEntryFromHash(ActiveQItems, It);
1164 citthread_mutex_unlock(&ActiveQItemsLock);
1166 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1168 // TODO: bounce & delete?
1174 /*****************************************************************************/
1175 /* SMTP UTILITY COMMANDS */
1176 /*****************************************************************************/
1178 void cmd_smtp(char *argbuf) {
1185 if (CtdlAccessCheck(ac_aide)) return;
1187 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1189 if (!strcasecmp(cmd, "mx")) {
1190 extract_token(node, argbuf, 1, '|', sizeof node);
1191 num_mxhosts = getmx(buf, node);
1192 cprintf("%d %d MX hosts listed for %s\n",
1193 LISTING_FOLLOWS, num_mxhosts, node);
1194 for (i=0; i<num_mxhosts; ++i) {
1195 extract_token(node, buf, i, '|', sizeof node);
1196 cprintf("%s\n", node);
1202 else if (!strcasecmp(cmd, "runqueue")) {
1204 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1209 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1216 * smtp_queue_thread()
1218 * Run through the queue sending out messages.
1220 void *smtp_queue_thread(void *arg) {
1221 int num_processed = 0;
1222 struct CitContext smtp_queue_CC;
1224 CtdlThreadSleep(10);
1226 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1227 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1228 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1230 while (!CtdlThreadCheckStop()) {
1232 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1234 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1235 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1238 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1240 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1241 CtdlThreadSleep(60);
1244 CtdlClearSystemContext();
1250 * Initialize the SMTP outbound queue
1252 void smtp_init_spoolout(void) {
1253 struct ctdlroom qrbuf;
1256 * Create the room. This will silently fail if the room already
1257 * exists, and that's perfectly ok, because we want it to exist.
1259 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1262 * Make sure it's set to be a "system room" so it doesn't show up
1263 * in the <K>nown rooms list for Aides.
1265 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1266 qrbuf.QRflags2 |= QR2_SYSTEM;
1267 CtdlPutRoomLock(&qrbuf);
1272 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1273 SMTPC_read_greeting,
1274 SMTPC_read_EHLO_reply,
1275 SMTPC_read_HELO_reply,
1276 SMTPC_read_auth_reply,
1277 SMTPC_read_FROM_reply,
1278 SMTPC_read_RCPT_reply,
1279 SMTPC_read_DATAcmd_reply,
1281 SMTPC_read_data_body_reply,
1282 SMTPC_read_QUIT_reply
1285 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1286 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1293 SMTPC_send_data_body,
1294 SMTPC_send_terminate_data_body,
1298 eNextState SMTP_C_Terminate(void *Data)
1300 SmtpOutMsg *pMsg = Data;
1301 FinalizeMessageSend(pMsg);
1305 eNextState SMTP_C_Timeout(void *Data)
1307 SmtpOutMsg *pMsg = Data;
1308 FinalizeMessageSend(pMsg);
1312 eNextState SMTP_C_ConnFail(void *Data)
1314 SmtpOutMsg *pMsg = Data;
1315 FinalizeMessageSend(pMsg);
1319 eNextState SMTP_C_DispatchReadDone(void *Data)
1321 SmtpOutMsg *pMsg = Data;
1322 eNextState rc = ReadHandlers[pMsg->State](pMsg);
1327 eNextState SMTP_C_DispatchWriteDone(void *Data)
1329 SmtpOutMsg *pMsg = Data;
1330 return SendHandlers[pMsg->State](pMsg);
1334 void smtp_evc_cleanup(void)
1336 DeleteHash(&QItemHandlers);
1337 DeleteHash(&ActiveQItems);
1341 CTDL_MODULE_INIT(smtp_eventclient)
1343 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1346 ActiveQItems = NewHash(1, Flathash);
1347 citthread_mutex_init(&ActiveQItemsLock, NULL);
1349 QItemHandlers = NewHash(0, NULL);
1351 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1352 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1353 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1354 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1355 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1356 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1357 ///submitted /TODO: flush qitemhandlers on exit
1360 smtp_init_spoolout();
1362 CtdlRegisterCleanupHook(smtp_evc_cleanup);
1363 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1365 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1369 /* return our Subversion id for the Log */
1370 return "smtpeventclient";