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;
211 void DeleteSmtpOutMsg(void *v)
214 FreeStrBuf(&Msg->msgtext);
215 FreeAsyncIOContents(&Msg->IO);
219 eNextState SMTP_C_Timeout(void *Data);
220 eNextState SMTP_C_ConnFail(void *Data);
221 eNextState SMTP_C_DispatchReadDone(void *Data);
222 eNextState SMTP_C_DispatchWriteDone(void *Data);
223 eNextState SMTP_C_Terminate(void *Data);
224 eNextState SMTP_C_MXLookup(void *Data);
226 typedef eNextState (*SMTPReadHandler)(SmtpOutMsg *Msg);
227 typedef eNextState (*SMTPSendHandler)(SmtpOutMsg *Msg);
232 void FreeQueItem(OneQueItem **Item)
234 DeleteHash(&(*Item)->MailQEntries);
235 FreeStrBuf(&(*Item)->EnvelopeFrom);
236 FreeStrBuf(&(*Item)->BounceTo);
240 void HFreeQueItem(void *Item)
242 FreeQueItem((OneQueItem**)&Item);
246 /* inspect recipients with a status of:
247 * - 0 (no delivery yet attempted)
248 * - 3/4 (transient errors
249 * were experienced and it's time to try again)
251 int CountActiveQueueEntries(OneQueItem *MyQItem)
258 MyQItem->ActiveDeliveries = 0;
259 It = GetNewHashPos(MyQItem->MailQEntries, 0);
260 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
262 MailQEntry *ThisItem = vQE;
263 if ((ThisItem->Status == 0) ||
264 (ThisItem->Status == 3) ||
265 (ThisItem->Status == 4))
267 MyQItem->ActiveDeliveries++;
268 ThisItem->Active = 1;
271 ThisItem->Active = 0;
274 return MyQItem->ActiveDeliveries;
277 OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
280 const char *pLine = NULL;
285 Item = (OneQueItem*)malloc(sizeof(OneQueItem));
286 memset(Item, 0, sizeof(OneQueItem));
287 Item->LastAttempt.retry = SMTP_RETRY_INTERVAL;
288 Item->MessageID = -1;
289 Item->QueMsgID = QueMsgID;
291 citthread_mutex_lock(&ActiveQItemsLock);
292 if (GetHash(ActiveQItems,
293 IKEY(Item->QueMsgID),
296 /* WHOOPS. somebody else is already working on this. */
297 citthread_mutex_unlock(&ActiveQItemsLock);
302 /* mark our claim on this. */
304 IKEY(Item->QueMsgID),
307 citthread_mutex_unlock(&ActiveQItemsLock);
311 Line = NewStrBufPlain(NULL, 128);
312 while (pLine != StrBufNOTNULL) {
313 const char *pItemPart = NULL;
316 StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
317 if (StrLength(Line) == 0) continue;
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...
436 DeleteSmtpOutMsg(Msg);
439 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
441 eReadState Finished = eBufferNotEmpty;
443 while (Finished == eBufferNotEmpty) {
444 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
447 case eMustReadMore: /// read new from socket...
450 case eBufferNotEmpty: /* shouldn't happen... */
451 case eReadSuccess: /// done for now...
452 if (StrLength(IO->IOBuf) < 4)
454 if (ChrPtr(IO->IOBuf)[3] == '-')
455 Finished = eBufferNotEmpty;
459 case eReadFail: /// WHUT?
468 * this one has to have the context for loading the message via the redirect buffer...
470 StrBuf *smtp_load_msg(OneQueItem *MyQItem)
475 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
476 CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
477 SendMsg = CCC->redirect_buffer;
478 CCC->redirect_buffer = NULL;
479 if ((StrLength(SendMsg) > 0) &&
480 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
481 CtdlLogPrintf(CTDL_WARNING,
482 "SMTP client[%ld]: Possible problem: message did not "
483 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
484 MsgCount, //yes uncool, but best choice here...
485 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
486 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
492 int smtp_resolve_recipients(SmtpOutMsg *SendMsg)
500 /* Parse out the host portion of the recipient address */
501 process_rfc822_addr(ChrPtr(SendMsg->MyQEntry->Recipient),
506 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
507 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
508 /* If no envelope_from is supplied, extract one from the message */
509 if ( (SendMsg->envelope_from == NULL) ||
510 (IsEmptyStr(SendMsg->envelope_from)) ) {
511 SendMsg->mailfrom[0] = '\0';
513 ptr = ChrPtr(SendMsg->msgtext);
515 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
518 if (!strncasecmp(buf, "From:", 5)) {
519 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
520 striplt(SendMsg->mailfrom);
521 for (i=0; SendMsg->mailfrom[i]; ++i) {
522 if (!isprint(SendMsg->mailfrom[i])) {
523 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
528 /* Strip out parenthesized names */
531 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
532 if (SendMsg->mailfrom[i] == '(') lp = i;
533 if (SendMsg->mailfrom[i] == ')') rp = i;
535 if ((lp>0)&&(rp>lp)) {
536 strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
539 /* Prefer brokketized names */
542 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
543 if (SendMsg->mailfrom[i] == '<') lp = i;
544 if (SendMsg->mailfrom[i] == '>') rp = i;
546 if ( (lp>=0) && (rp>lp) ) {
547 SendMsg->mailfrom[rp] = 0;
548 memmove(SendMsg->mailfrom,
549 &SendMsg->mailfrom[lp + 1],
555 } while (scan_done == 0);
556 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
557 stripallbut(SendMsg->mailfrom, '<', '>');
558 SendMsg->envelope_from = SendMsg->mailfrom;
565 #define SMTP_ERROR(WHICH_ERR, ERRSTR) {SendMsg->MyQEntry->Status = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, HKEY(ERRSTR), 0); return eAbort; }
566 #define SMTP_VERROR(WHICH_ERR) { SendMsg->MyQEntry->Status = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, &ChrPtr(SendMsg->IO.IOBuf)[4], -1, 0); return eAbort; }
567 #define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(SendMsg->IO.IOBuf)[0] == WHICH_STATE)
569 #define SMTP_DBG_SEND() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: > %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
570 #define SMTP_DBG_READ() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: < %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
573 void connect_one_smtpsrv_xamine_result(void *Ctx,
576 struct hostent *hostent)
578 SmtpOutMsg *SendMsg = Ctx;
580 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connecting [%s:%s]!\n",
581 SendMsg->n, SendMsg->mx_host, SendMsg->mx_port);
583 SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
584 SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
585 SendMsg->IO.IOBuf = NewStrBuf();
586 SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
589 SendMsg->IO.SendBuf.fd =
590 SendMsg->IO.RecvBuf.fd =
591 SendMsg->IO.sock = sock_connect(SendMsg->mx_host, SendMsg->mx_port);
593 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
594 "Could not connect: %s", strerror(errno));
597 if (SendMsg->IO.sock < 0) {
599 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
600 strerror(errno), -1);
603 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
604 "Unable to connect to %s : %s\n",
605 SendMsg->mx_host, SendMsg->mx_port);
608 /// hier: naechsten mx ausprobieren.
609 if (SendMsg->IO.sock < 0) {
610 SendMsg->MyQEntry->Status = 4; /* dsn is already filled in * /
611 //// hier: abbrechen & bounce.
616 InitEventIO(&SendMsg->IO, SendMsg,
617 SMTP_C_DispatchReadDone,
618 SMTP_C_DispatchWriteDone,
623 SMTP_C_ReadServerStatus,
630 void get_one_mx_host_name_done(void *Ctx,
633 struct hostent *hostent)
636 SmtpOutMsg *SendMsg = IO->Data;
637 if ((status == ARES_SUCCESS) && (hostent != NULL) ) {
639 SendMsg->IO.HEnt = hostent;
640 InitEventIO(IO, SendMsg,
641 SMTP_C_DispatchReadDone,
642 SMTP_C_DispatchWriteDone,
646 SMTP_C_ReadServerStatus,
652 const char *DefaultMXPort = "25";
653 void connect_one_smtpsrv(SmtpOutMsg *SendMsg)
658 SendMsg->mx_port = DefaultMXPort;
662 *SendMsg->mx_user = '\0';
663 *SendMsg->mx_pass = '\0';
664 if (num_tokens(buf, '@') > 1) {
665 strcpy (SendMsg->mx_user, buf);
666 endpart = strrchr(SendMsg->mx_user, '@');
668 strcpy (SendMsg->mx_host, endpart + 1);
669 endpart = strrchr(SendMsg->mx_user, ':');
670 if (endpart != NULL) {
671 strcpy(SendMsg->mx_pass, endpart+1);
675 endpart = strrchr(SendMsg->mx_host, ':');
678 strcpy(SendMsg->mx_port, endpart + 1);
683 SendMsg->mx_host = SendMsg->CurrMX->host;
684 SendMsg->CurrMX = SendMsg->CurrMX->next;
686 CtdlLogPrintf(CTDL_DEBUG,
687 "SMTP client[%ld]: connecting to %s : %s ...\n",
692 ares_gethostbyname(SendMsg->IO.DNSChannel,
694 AF_INET6, /* it falls back to ipv4 in doubt... */
695 get_one_mx_host_name_done,
698 if (!QueueQuery(ns_t_a,
701 connect_one_smtpsrv_xamine_result))
709 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
711 /* Process the SMTP greeting from the server */
714 if (!SMTP_IS_STATE('2')) {
715 if (SMTP_IS_STATE('4'))
723 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
725 /* At this point we know we are talking to a real SMTP server */
727 /* Do a EHLO command. If it fails, try the HELO command. */
728 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
729 "EHLO %s\r\n", config.c_fqdn);
735 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
739 if (SMTP_IS_STATE('2')) {
741 if (IsEmptyStr(SendMsg->mx_user))
742 SendMsg->State ++; /* Skip auth... */
744 /* else we fall back to 'helo' */
748 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
750 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
751 "HELO %s\r\n", config.c_fqdn);
757 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
761 if (!SMTP_IS_STATE('2')) {
762 if (SMTP_IS_STATE('4'))
767 if (!IsEmptyStr(SendMsg->mx_user))
768 SendMsg->State ++; /* Skip auth... */
772 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
777 /* Do an AUTH command if necessary */
778 sprintf(buf, "%s%c%s%c%s",
779 SendMsg->mx_user, '\0',
780 SendMsg->mx_user, '\0',
782 CtdlEncodeBase64(encoded, buf,
783 strlen(SendMsg->mx_user) +
784 strlen(SendMsg->mx_user) +
785 strlen(SendMsg->mx_pass) + 2, 0);
786 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
787 "AUTH PLAIN %s\r\n", encoded);
793 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
795 /* Do an AUTH command if necessary */
799 if (!SMTP_IS_STATE('2')) {
800 if (SMTP_IS_STATE('4'))
808 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
810 /* previous command succeeded, now try the MAIL FROM: command */
811 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
812 "MAIL FROM:<%s>\r\n",
813 SendMsg->envelope_from);
819 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
823 if (!SMTP_IS_STATE('2')) {
824 if (SMTP_IS_STATE('4'))
833 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
835 /* MAIL succeeded, now try the RCPT To: command */
836 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
837 "RCPT TO:<%s@%s>\r\n",
845 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
849 if (!SMTP_IS_STATE('2')) {
850 if (SMTP_IS_STATE('4'))
858 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
860 /* RCPT succeeded, now try the DATA command */
861 StrBufPlain(SendMsg->IO.SendBuf.Buf,
868 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
872 if (!SMTP_IS_STATE('3')) {
873 if (SMTP_IS_STATE('4'))
881 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
884 /* If we reach this point, the server is expecting data.*/
886 Buf = SendMsg->IO.SendBuf.Buf;
887 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
888 SendMsg->msgtext = Buf;
889 //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
895 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
899 Buf = SendMsg->IO.SendBuf.Buf;
900 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
901 SendMsg->msgtext = Buf;
903 StrBufPlain(SendMsg->IO.SendBuf.Buf,
910 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
914 if (!SMTP_IS_STATE('2')) {
915 if (SMTP_IS_STATE('4'))
922 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
923 &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
924 StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
925 SendMsg->MyQEntry->Status = 2;
929 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
931 StrBufPlain(SendMsg->IO.SendBuf.Buf,
938 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
942 CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
943 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
944 return eTerminateConnection;
947 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
952 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
957 eNextState smtp_resolve_mx_done(void *data)
960 SmtpOutMsg * SendMsg = IO->Data;
962 SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
963 SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
964 SendMsg->IO.IOBuf = NewStrBuf();
965 SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
967 //// connect_one_smtpsrv_xamine_result
968 SendMsg->CurrMX = SendMsg->AllMX = IO->VParsedDNSReply;
969 //// TODO: should we remove the current ares context???
970 connect_one_smtpsrv(SendMsg);
976 int resolve_mx_records(void *Ctx)
978 SmtpOutMsg * SendMsg = Ctx;
980 SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
981 SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
982 SendMsg->IO.IOBuf = NewStrBuf();
983 SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
985 InitEventIO(&SendMsg->IO, SendMsg,
986 SMTP_C_DispatchReadDone,
987 SMTP_C_DispatchWriteDone,
991 SMTP_C_ReadServerStatus,
995 if (!QueueQuery(ns_t_mx,
998 smtp_resolve_mx_done))
1000 SendMsg->MyQEntry->Status = 5;
1001 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
1002 "No MX hosts found for <%s>", SendMsg->node);
1003 return 0; ///////TODO: abort!
1008 void smtp_try(OneQueItem *MyQItem,
1009 MailQEntry *MyQEntry,
1011 int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
1013 SmtpOutMsg * SendMsg;
1015 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
1016 memset(SendMsg, 0, sizeof(SmtpOutMsg));
1017 SendMsg->IO.sock = (-1);
1018 SendMsg->n = MsgCount++;
1019 SendMsg->MyQEntry = MyQEntry;
1020 SendMsg->MyQItem = MyQItem;
1021 SendMsg->IO.Data = SendMsg;
1023 SendMsg->msgtext = MsgText;
1025 SendMsg->msgtext = NewStrBufDup(MsgText);
1027 smtp_resolve_recipients(SendMsg);
1029 QueueEventContext(SendMsg,
1031 resolve_mx_records);
1038 void NewMailQEntry(OneQueItem *Item)
1040 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
1041 memset(Item->Current, 0, sizeof(MailQEntry));
1043 if (Item->MailQEntries == NULL)
1044 Item->MailQEntries = NewHash(1, Flathash);
1045 Item->Current->n = GetCount(Item->MailQEntries);
1046 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
1049 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
1051 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
1054 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
1056 if (Item->EnvelopeFrom == NULL)
1057 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
1058 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
1061 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
1063 if (Item->BounceTo == NULL)
1064 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
1065 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
1068 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
1070 if (Item->Current == NULL)
1071 NewMailQEntry(Item);
1072 if (Item->Current->Recipient == NULL)
1073 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
1074 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
1075 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
1076 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
1077 Item->Current = NULL; // TODO: is this always right?
1081 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
1083 if (Item->Current == NULL)
1084 NewMailQEntry(Item);
1085 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
1086 Item->Current->nAttempts++;
1087 if (Item->Current->nAttempts > MaxAttempts) {
1091 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1094 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1096 if (Item->Current == NULL)
1097 NewMailQEntry(Item);
1098 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1099 Item->Current->nAttempts++;
1100 if (Item->Current->nAttempts > MaxAttempts) {
1105 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1106 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1108 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1109 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1110 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1111 Item->LastAttempt.retry = SMTP_RETRY_MAX;
1121 * Called by smtp_do_queue() to handle an individual message.
1123 void smtp_do_procmsg(long msgnum, void *userdata) {
1124 struct CtdlMessage *msg = NULL;
1127 OneQueItem *MyQItem;
1134 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
1135 ///strcpy(envelope_from, "");
1137 msg = CtdlFetchMessage(msgnum, 1);
1139 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
1143 pch = instr = msg->cm_fields['M'];
1145 /* Strip out the headers (no not amd any other non-instruction) line */
1146 while (pch != NULL) {
1147 pch = strchr(pch, '\n');
1148 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1153 PlainQItem = NewStrBufPlain(instr, -1);
1154 CtdlFreeMessage(msg);
1155 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1156 FreeStrBuf(&PlainQItem);
1158 if (MyQItem == NULL) {
1159 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
1160 return; /* s.b. else is already processing... */
1164 * Postpone delivery if we've already tried recently.
1166 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1167 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1169 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1170 citthread_mutex_lock(&ActiveQItemsLock);
1172 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1173 DeleteEntryFromHash(ActiveQItems, It);
1175 citthread_mutex_unlock(&ActiveQItemsLock);
1176 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1179 }// TODO: reenable me.*/
1182 * Bail out if there's no actual message associated with this
1184 if (MyQItem->MessageID < 0L) {
1185 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
1186 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1187 citthread_mutex_lock(&ActiveQItemsLock);
1189 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1190 DeleteEntryFromHash(ActiveQItems, It);
1192 citthread_mutex_unlock(&ActiveQItemsLock);
1194 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1198 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1199 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
1201 MailQEntry *ThisItem = vQE;
1202 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
1206 CountActiveQueueEntries(MyQItem);
1207 if (MyQItem->ActiveDeliveries > 0)
1210 StrBuf *Msg = smtp_load_msg(MyQItem);
1211 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1212 while ((i <= MyQItem->ActiveDeliveries) &&
1213 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1215 MailQEntry *ThisItem = vQE;
1216 if (ThisItem->Active == 1) {
1217 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1218 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1226 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1227 citthread_mutex_lock(&ActiveQItemsLock);
1229 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1230 DeleteEntryFromHash(ActiveQItems, It);
1232 citthread_mutex_unlock(&ActiveQItemsLock);
1234 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1236 // TODO: bounce & delete?
1242 /*****************************************************************************/
1243 /* SMTP UTILITY COMMANDS */
1244 /*****************************************************************************/
1246 void cmd_smtp(char *argbuf) {
1253 if (CtdlAccessCheck(ac_aide)) return;
1255 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1257 if (!strcasecmp(cmd, "mx")) {
1258 extract_token(node, argbuf, 1, '|', sizeof node);
1259 num_mxhosts = getmx(buf, node);
1260 cprintf("%d %d MX hosts listed for %s\n",
1261 LISTING_FOLLOWS, num_mxhosts, node);
1262 for (i=0; i<num_mxhosts; ++i) {
1263 extract_token(node, buf, i, '|', sizeof node);
1264 cprintf("%s\n", node);
1270 else if (!strcasecmp(cmd, "runqueue")) {
1272 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1277 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1284 * smtp_queue_thread()
1286 * Run through the queue sending out messages.
1288 void *smtp_queue_thread(void *arg) {
1289 int num_processed = 0;
1290 struct CitContext smtp_queue_CC;
1292 CtdlThreadSleep(10);
1294 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1295 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1296 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1298 while (!CtdlThreadCheckStop()) {
1300 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1302 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1303 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1306 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1308 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1309 CtdlThreadSleep(60);
1312 CtdlClearSystemContext();
1318 * Initialize the SMTP outbound queue
1320 void smtp_init_spoolout(void) {
1321 struct ctdlroom qrbuf;
1324 * Create the room. This will silently fail if the room already
1325 * exists, and that's perfectly ok, because we want it to exist.
1327 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1330 * Make sure it's set to be a "system room" so it doesn't show up
1331 * in the <K>nown rooms list for Aides.
1333 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1334 qrbuf.QRflags2 |= QR2_SYSTEM;
1335 CtdlPutRoomLock(&qrbuf);
1340 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1341 SMTPC_read_greeting,
1342 SMTPC_read_EHLO_reply,
1343 SMTPC_read_HELO_reply,
1344 SMTPC_read_auth_reply,
1345 SMTPC_read_FROM_reply,
1346 SMTPC_read_RCPT_reply,
1347 SMTPC_read_DATAcmd_reply,
1349 SMTPC_read_data_body_reply,
1350 SMTPC_read_QUIT_reply
1353 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1354 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1361 SMTPC_send_data_body,
1362 SMTPC_send_terminate_data_body,
1366 eNextState SMTP_C_Terminate(void *Data)
1368 SmtpOutMsg *pMsg = Data;
1369 FinalizeMessageSend(pMsg);
1373 eNextState SMTP_C_Timeout(void *Data)
1375 SmtpOutMsg *pMsg = Data;
1376 FinalizeMessageSend(pMsg);
1380 eNextState SMTP_C_ConnFail(void *Data)
1382 SmtpOutMsg *pMsg = Data;
1383 FinalizeMessageSend(pMsg);
1387 eNextState SMTP_C_DispatchReadDone(void *Data)
1389 SmtpOutMsg *pMsg = Data;
1390 eNextState rc = ReadHandlers[pMsg->State](pMsg);
1395 eNextState SMTP_C_DispatchWriteDone(void *Data)
1397 SmtpOutMsg *pMsg = Data;
1398 return SendHandlers[pMsg->State](pMsg);
1403 CTDL_MODULE_INIT(smtp_eventclient)
1405 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1408 ActiveQItems = NewHash(1, Flathash);
1409 citthread_mutex_init(&ActiveQItemsLock, NULL);
1411 QItemHandlers = NewHash(0, NULL);
1413 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1414 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1415 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1416 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1417 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1418 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1419 ///submitted /TODO: flush qitemhandlers on exit
1422 smtp_init_spoolout();
1423 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1425 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1429 /* return our Subversion id for the Log */
1430 return "smtpeventclient";