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) ) {
581 CtdlLogPrintf(CTDL_DEBUG,
582 "SMTP client[%ld]: connecting to %s : %d ...\n",
588 SendMsg->IO.HEnt = hostent;
589 InitEventIO(IO, SendMsg,
590 SMTP_C_DispatchReadDone,
591 SMTP_C_DispatchWriteDone,
595 SMTP_C_ReadServerStatus,
601 const unsigned short DefaultMXPort = 25;
602 void connect_one_smtpsrv(SmtpOutMsg *SendMsg)
607 SendMsg->IO.dport = DefaultMXPort;
611 *SendMsg->mx_user = '\0';
612 *SendMsg->mx_pass = '\0';
613 if (num_tokens(buf, '@') > 1) {
614 strcpy (SendMsg->mx_user, buf);
615 endpart = strrchr(SendMsg->mx_user, '@');
617 strcpy (SendMsg->mx_host, endpart + 1);
618 endpart = strrchr(SendMsg->mx_user, ':');
619 if (endpart != NULL) {
620 strcpy(SendMsg->mx_pass, endpart+1);
624 endpart = strrchr(SendMsg->mx_host, ':');
627 strcpy(SendMsg->mx_port, endpart + 1);
632 SendMsg->mx_host = SendMsg->CurrMX->host;
633 SendMsg->CurrMX = SendMsg->CurrMX->next;
635 CtdlLogPrintf(CTDL_DEBUG,
636 "SMTP client[%ld]: looking up %s : %d ...\n",
640 ares_gethostbyname(SendMsg->IO.DNSChannel,
642 AF_INET6, /* it falls back to ipv4 in doubt... */
643 get_one_mx_host_name_done,
648 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
650 /* Process the SMTP greeting from the server */
653 if (!SMTP_IS_STATE('2')) {
654 if (SMTP_IS_STATE('4'))
662 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
664 /* At this point we know we are talking to a real SMTP server */
666 /* Do a EHLO command. If it fails, try the HELO command. */
667 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
668 "EHLO %s\r\n", config.c_fqdn);
674 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
678 if (SMTP_IS_STATE('2')) {
680 if (IsEmptyStr(SendMsg->mx_user))
681 SendMsg->State ++; /* Skip auth... */
683 /* else we fall back to 'helo' */
687 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
689 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
690 "HELO %s\r\n", config.c_fqdn);
696 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
700 if (!SMTP_IS_STATE('2')) {
701 if (SMTP_IS_STATE('4'))
706 if (!IsEmptyStr(SendMsg->mx_user))
707 SendMsg->State ++; /* Skip auth... */
711 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
716 /* Do an AUTH command if necessary */
717 sprintf(buf, "%s%c%s%c%s",
718 SendMsg->mx_user, '\0',
719 SendMsg->mx_user, '\0',
721 CtdlEncodeBase64(encoded, buf,
722 strlen(SendMsg->mx_user) +
723 strlen(SendMsg->mx_user) +
724 strlen(SendMsg->mx_pass) + 2, 0);
725 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
726 "AUTH PLAIN %s\r\n", encoded);
732 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
734 /* Do an AUTH command if necessary */
738 if (!SMTP_IS_STATE('2')) {
739 if (SMTP_IS_STATE('4'))
747 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
749 /* previous command succeeded, now try the MAIL FROM: command */
750 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
751 "MAIL FROM:<%s>\r\n",
752 SendMsg->envelope_from);
758 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
762 if (!SMTP_IS_STATE('2')) {
763 if (SMTP_IS_STATE('4'))
772 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
774 /* MAIL succeeded, now try the RCPT To: command */
775 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
776 "RCPT TO:<%s@%s>\r\n",
784 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
788 if (!SMTP_IS_STATE('2')) {
789 if (SMTP_IS_STATE('4'))
797 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
799 /* RCPT succeeded, now try the DATA command */
800 StrBufPlain(SendMsg->IO.SendBuf.Buf,
807 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
811 if (!SMTP_IS_STATE('3')) {
812 if (SMTP_IS_STATE('4'))
820 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
823 /* If we reach this point, the server is expecting data.*/
825 Buf = SendMsg->IO.SendBuf.Buf;
826 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
827 SendMsg->msgtext = Buf;
828 //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
834 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
838 Buf = SendMsg->IO.SendBuf.Buf;
839 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
840 SendMsg->msgtext = Buf;
842 StrBufPlain(SendMsg->IO.SendBuf.Buf,
849 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
853 if (!SMTP_IS_STATE('2')) {
854 if (SMTP_IS_STATE('4'))
861 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
862 &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
863 StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
864 SendMsg->MyQEntry->Status = 2;
868 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
870 StrBufPlain(SendMsg->IO.SendBuf.Buf,
877 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
881 CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
882 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
883 return eTerminateConnection;
886 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
891 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
896 eNextState smtp_resolve_mx_done(void *data)
899 SmtpOutMsg * SendMsg = IO->Data;
901 SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
902 SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
903 SendMsg->IO.IOBuf = NewStrBuf();
904 SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
906 //// connect_one_smtpsrv_xamine_result
907 SendMsg->CurrMX = SendMsg->AllMX = IO->VParsedDNSReply;
908 //// TODO: should we remove the current ares context???
909 connect_one_smtpsrv(SendMsg);
915 int resolve_mx_records(void *Ctx)
917 SmtpOutMsg * SendMsg = Ctx;
919 if (!QueueQuery(ns_t_mx,
922 smtp_resolve_mx_done))
924 SendMsg->MyQEntry->Status = 5;
925 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
926 "No MX hosts found for <%s>", SendMsg->node);
927 return 0; ///////TODO: abort!
932 void smtp_try(OneQueItem *MyQItem,
933 MailQEntry *MyQEntry,
935 int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
937 SmtpOutMsg * SendMsg;
939 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
940 memset(SendMsg, 0, sizeof(SmtpOutMsg));
941 SendMsg->IO.sock = (-1);
942 SendMsg->n = MsgCount++;
943 SendMsg->MyQEntry = MyQEntry;
944 SendMsg->MyQItem = MyQItem;
945 SendMsg->IO.Data = SendMsg;
947 SendMsg->msgtext = MsgText;
949 SendMsg->msgtext = NewStrBufDup(MsgText);
951 smtp_resolve_recipients(SendMsg);
953 QueueEventContext(SendMsg,
960 void NewMailQEntry(OneQueItem *Item)
962 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
963 memset(Item->Current, 0, sizeof(MailQEntry));
965 if (Item->MailQEntries == NULL)
966 Item->MailQEntries = NewHash(1, Flathash);
967 Item->Current->n = GetCount(Item->MailQEntries);
968 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
971 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
973 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
976 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
978 if (Item->EnvelopeFrom == NULL)
979 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
980 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
983 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
985 if (Item->BounceTo == NULL)
986 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
987 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
990 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
992 if (Item->Current == NULL)
994 if (Item->Current->Recipient == NULL)
995 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
996 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
997 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
998 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
999 Item->Current = NULL; // TODO: is this always right?
1003 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
1005 if (Item->Current == NULL)
1006 NewMailQEntry(Item);
1007 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
1008 Item->Current->nAttempts++;
1009 if (Item->Current->nAttempts > MaxAttempts) {
1013 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1016 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1018 if (Item->Current == NULL)
1019 NewMailQEntry(Item);
1020 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1021 Item->Current->nAttempts++;
1022 if (Item->Current->nAttempts > MaxAttempts) {
1027 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1028 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1030 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1031 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1032 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1033 Item->LastAttempt.retry = SMTP_RETRY_MAX;
1043 * Called by smtp_do_queue() to handle an individual message.
1045 void smtp_do_procmsg(long msgnum, void *userdata) {
1046 struct CtdlMessage *msg = NULL;
1049 OneQueItem *MyQItem;
1056 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
1057 ///strcpy(envelope_from, "");
1059 msg = CtdlFetchMessage(msgnum, 1);
1061 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
1065 pch = instr = msg->cm_fields['M'];
1067 /* Strip out the headers (no not amd any other non-instruction) line */
1068 while (pch != NULL) {
1069 pch = strchr(pch, '\n');
1070 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1075 PlainQItem = NewStrBufPlain(instr, -1);
1076 CtdlFreeMessage(msg);
1077 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1078 FreeStrBuf(&PlainQItem);
1080 if (MyQItem == NULL) {
1081 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
1082 return; /* s.b. else is already processing... */
1086 * Postpone delivery if we've already tried recently.
1088 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1089 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1091 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1092 citthread_mutex_lock(&ActiveQItemsLock);
1094 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1095 DeleteEntryFromHash(ActiveQItems, It);
1097 citthread_mutex_unlock(&ActiveQItemsLock);
1098 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1101 }// TODO: reenable me.*/
1104 * Bail out if there's no actual message associated with this
1106 if (MyQItem->MessageID < 0L) {
1107 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
1108 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1109 citthread_mutex_lock(&ActiveQItemsLock);
1111 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1112 DeleteEntryFromHash(ActiveQItems, It);
1114 citthread_mutex_unlock(&ActiveQItemsLock);
1116 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1120 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1121 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
1123 MailQEntry *ThisItem = vQE;
1124 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
1128 CountActiveQueueEntries(MyQItem);
1129 if (MyQItem->ActiveDeliveries > 0)
1132 StrBuf *Msg = smtp_load_msg(MyQItem);
1133 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1134 while ((i <= MyQItem->ActiveDeliveries) &&
1135 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1137 MailQEntry *ThisItem = vQE;
1138 if (ThisItem->Active == 1) {
1139 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1140 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1148 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1149 citthread_mutex_lock(&ActiveQItemsLock);
1151 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1152 DeleteEntryFromHash(ActiveQItems, It);
1154 citthread_mutex_unlock(&ActiveQItemsLock);
1156 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1158 // TODO: bounce & delete?
1164 /*****************************************************************************/
1165 /* SMTP UTILITY COMMANDS */
1166 /*****************************************************************************/
1168 void cmd_smtp(char *argbuf) {
1175 if (CtdlAccessCheck(ac_aide)) return;
1177 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1179 if (!strcasecmp(cmd, "mx")) {
1180 extract_token(node, argbuf, 1, '|', sizeof node);
1181 num_mxhosts = getmx(buf, node);
1182 cprintf("%d %d MX hosts listed for %s\n",
1183 LISTING_FOLLOWS, num_mxhosts, node);
1184 for (i=0; i<num_mxhosts; ++i) {
1185 extract_token(node, buf, i, '|', sizeof node);
1186 cprintf("%s\n", node);
1192 else if (!strcasecmp(cmd, "runqueue")) {
1194 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1199 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1206 * smtp_queue_thread()
1208 * Run through the queue sending out messages.
1210 void *smtp_queue_thread(void *arg) {
1211 int num_processed = 0;
1212 struct CitContext smtp_queue_CC;
1214 CtdlThreadSleep(10);
1216 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1217 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1218 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1220 while (!CtdlThreadCheckStop()) {
1222 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1224 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1225 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1228 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1230 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1231 CtdlThreadSleep(60);
1234 CtdlClearSystemContext();
1240 * Initialize the SMTP outbound queue
1242 void smtp_init_spoolout(void) {
1243 struct ctdlroom qrbuf;
1246 * Create the room. This will silently fail if the room already
1247 * exists, and that's perfectly ok, because we want it to exist.
1249 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1252 * Make sure it's set to be a "system room" so it doesn't show up
1253 * in the <K>nown rooms list for Aides.
1255 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1256 qrbuf.QRflags2 |= QR2_SYSTEM;
1257 CtdlPutRoomLock(&qrbuf);
1262 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1263 SMTPC_read_greeting,
1264 SMTPC_read_EHLO_reply,
1265 SMTPC_read_HELO_reply,
1266 SMTPC_read_auth_reply,
1267 SMTPC_read_FROM_reply,
1268 SMTPC_read_RCPT_reply,
1269 SMTPC_read_DATAcmd_reply,
1271 SMTPC_read_data_body_reply,
1272 SMTPC_read_QUIT_reply
1275 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1276 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1283 SMTPC_send_data_body,
1284 SMTPC_send_terminate_data_body,
1288 eNextState SMTP_C_Terminate(void *Data)
1290 SmtpOutMsg *pMsg = Data;
1291 FinalizeMessageSend(pMsg);
1295 eNextState SMTP_C_Timeout(void *Data)
1297 SmtpOutMsg *pMsg = Data;
1298 FinalizeMessageSend(pMsg);
1302 eNextState SMTP_C_ConnFail(void *Data)
1304 SmtpOutMsg *pMsg = Data;
1305 FinalizeMessageSend(pMsg);
1309 eNextState SMTP_C_DispatchReadDone(void *Data)
1311 SmtpOutMsg *pMsg = Data;
1312 eNextState rc = ReadHandlers[pMsg->State](pMsg);
1317 eNextState SMTP_C_DispatchWriteDone(void *Data)
1319 SmtpOutMsg *pMsg = Data;
1320 return SendHandlers[pMsg->State](pMsg);
1324 void smtp_evc_cleanup(void)
1326 DeleteHash(&QItemHandlers);
1327 DeleteHash(&ActiveQItems);
1331 CTDL_MODULE_INIT(smtp_eventclient)
1333 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1336 ActiveQItems = NewHash(1, Flathash);
1337 citthread_mutex_init(&ActiveQItemsLock, NULL);
1339 QItemHandlers = NewHash(0, NULL);
1341 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1342 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1343 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1344 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1345 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1346 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1347 ///submitted /TODO: flush qitemhandlers on exit
1350 smtp_init_spoolout();
1352 CtdlRegisterCleanupHook(smtp_evc_cleanup);
1353 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1355 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1359 /* return our Subversion id for the Log */
1360 return "smtpeventclient";