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)
635 SmtpOutMsg *SendMsg = Ctx;
636 if ((status == ARES_SUCCESS) && (hostent != NULL) ) {
638 SendMsg->IO.HEnt = hostent;
639 InitEventIO(&SendMsg->IO, SendMsg,
640 SMTP_C_DispatchReadDone,
641 SMTP_C_DispatchWriteDone,
645 SMTP_C_ReadServerStatus,
651 const char *DefaultMXPort = "25";
652 void connect_one_smtpsrv(SmtpOutMsg *SendMsg)
657 SendMsg->mx_port = DefaultMXPort;
659 SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
660 SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
661 SendMsg->IO.IOBuf = NewStrBuf();
662 SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
665 *SendMsg->mx_user = '\0';
666 *SendMsg->mx_pass = '\0';
667 if (num_tokens(buf, '@') > 1) {
668 strcpy (SendMsg->mx_user, buf);
669 endpart = strrchr(SendMsg->mx_user, '@');
671 strcpy (SendMsg->mx_host, endpart + 1);
672 endpart = strrchr(SendMsg->mx_user, ':');
673 if (endpart != NULL) {
674 strcpy(SendMsg->mx_pass, endpart+1);
678 endpart = strrchr(SendMsg->mx_host, ':');
681 strcpy(SendMsg->mx_port, endpart + 1);
686 SendMsg->mx_host = SendMsg->CurrMX->host;
687 SendMsg->CurrMX = SendMsg->CurrMX->next;
689 CtdlLogPrintf(CTDL_DEBUG,
690 "SMTP client[%ld]: connecting to %s : %s ...\n",
695 ares_gethostbyname(SendMsg->IO.DNSChannel,
697 AF_INET6, /* it falls back to ipv4 in doubt... */
698 get_one_mx_host_name_done,
701 if (!QueueQuery(ns_t_a,
704 connect_one_smtpsrv_xamine_result))
712 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
714 /* Process the SMTP greeting from the server */
717 if (!SMTP_IS_STATE('2')) {
718 if (SMTP_IS_STATE('4'))
726 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
728 /* At this point we know we are talking to a real SMTP server */
730 /* Do a EHLO command. If it fails, try the HELO command. */
731 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
732 "EHLO %s\r\n", config.c_fqdn);
738 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
742 if (SMTP_IS_STATE('2')) {
744 if (IsEmptyStr(SendMsg->mx_user))
745 SendMsg->State ++; /* Skip auth... */
747 /* else we fall back to 'helo' */
751 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
753 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
754 "HELO %s\r\n", config.c_fqdn);
760 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
764 if (!SMTP_IS_STATE('2')) {
765 if (SMTP_IS_STATE('4'))
770 if (!IsEmptyStr(SendMsg->mx_user))
771 SendMsg->State ++; /* Skip auth... */
775 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
780 /* Do an AUTH command if necessary */
781 sprintf(buf, "%s%c%s%c%s",
782 SendMsg->mx_user, '\0',
783 SendMsg->mx_user, '\0',
785 CtdlEncodeBase64(encoded, buf,
786 strlen(SendMsg->mx_user) +
787 strlen(SendMsg->mx_user) +
788 strlen(SendMsg->mx_pass) + 2, 0);
789 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
790 "AUTH PLAIN %s\r\n", encoded);
796 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
798 /* Do an AUTH command if necessary */
802 if (!SMTP_IS_STATE('2')) {
803 if (SMTP_IS_STATE('4'))
811 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
813 /* previous command succeeded, now try the MAIL FROM: command */
814 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
815 "MAIL FROM:<%s>\r\n",
816 SendMsg->envelope_from);
822 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
826 if (!SMTP_IS_STATE('2')) {
827 if (SMTP_IS_STATE('4'))
836 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
838 /* MAIL succeeded, now try the RCPT To: command */
839 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
840 "RCPT TO:<%s@%s>\r\n",
848 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
852 if (!SMTP_IS_STATE('2')) {
853 if (SMTP_IS_STATE('4'))
861 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
863 /* RCPT succeeded, now try the DATA command */
864 StrBufPlain(SendMsg->IO.SendBuf.Buf,
871 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
875 if (!SMTP_IS_STATE('3')) {
876 if (SMTP_IS_STATE('4'))
884 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
887 /* If we reach this point, the server is expecting data.*/
889 Buf = SendMsg->IO.SendBuf.Buf;
890 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
891 SendMsg->msgtext = Buf;
892 //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
898 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
902 Buf = SendMsg->IO.SendBuf.Buf;
903 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
904 SendMsg->msgtext = Buf;
906 StrBufPlain(SendMsg->IO.SendBuf.Buf,
913 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
917 if (!SMTP_IS_STATE('2')) {
918 if (SMTP_IS_STATE('4'))
925 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
926 &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
927 StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
928 SendMsg->MyQEntry->Status = 2;
932 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
934 StrBufPlain(SendMsg->IO.SendBuf.Buf,
941 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
945 CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
946 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
947 return eTerminateConnection;
950 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
955 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
960 eNextState smtp_resolve_mx_done(void *data)
963 SmtpOutMsg * SendMsg = IO->Data;
965 //// connect_one_smtpsrv_xamine_result
966 SendMsg->CurrMX = SendMsg->AllMX = IO->VParsedDNSReply;
967 //// TODO: should we remove the current ares context???
968 connect_one_smtpsrv(SendMsg);
974 int resolve_mx_records(void *Ctx)
976 SmtpOutMsg * SendMsg = Ctx;
978 SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
979 SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
980 SendMsg->IO.IOBuf = NewStrBuf();
981 SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
983 InitEventIO(&SendMsg->IO, SendMsg,
984 SMTP_C_DispatchReadDone,
985 SMTP_C_DispatchWriteDone,
989 SMTP_C_ReadServerStatus,
993 if (!QueueQuery(ns_t_mx,
996 smtp_resolve_mx_done))
998 SendMsg->MyQEntry->Status = 5;
999 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
1000 "No MX hosts found for <%s>", SendMsg->node);
1001 return 0; ///////TODO: abort!
1006 void smtp_try(OneQueItem *MyQItem,
1007 MailQEntry *MyQEntry,
1009 int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
1011 SmtpOutMsg * SendMsg;
1013 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
1014 memset(SendMsg, 0, sizeof(SmtpOutMsg));
1015 SendMsg->IO.sock = (-1);
1016 SendMsg->n = MsgCount++;
1017 SendMsg->MyQEntry = MyQEntry;
1018 SendMsg->MyQItem = MyQItem;
1019 SendMsg->IO.Data = SendMsg;
1021 SendMsg->msgtext = MsgText;
1023 SendMsg->msgtext = NewStrBufDup(MsgText);
1025 smtp_resolve_recipients(SendMsg);
1027 QueueEventContext(SendMsg,
1029 resolve_mx_records);
1036 void NewMailQEntry(OneQueItem *Item)
1038 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
1039 memset(Item->Current, 0, sizeof(MailQEntry));
1041 if (Item->MailQEntries == NULL)
1042 Item->MailQEntries = NewHash(1, Flathash);
1043 Item->Current->n = GetCount(Item->MailQEntries);
1044 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
1047 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
1049 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
1052 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
1054 if (Item->EnvelopeFrom == NULL)
1055 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
1056 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
1059 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
1061 if (Item->BounceTo == NULL)
1062 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
1063 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
1066 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
1068 if (Item->Current == NULL)
1069 NewMailQEntry(Item);
1070 if (Item->Current->Recipient == NULL)
1071 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
1072 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
1073 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
1074 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
1075 Item->Current = NULL; // TODO: is this always right?
1079 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
1081 if (Item->Current == NULL)
1082 NewMailQEntry(Item);
1083 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
1084 Item->Current->nAttempts++;
1085 if (Item->Current->nAttempts > MaxAttempts) {
1089 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1092 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1094 if (Item->Current == NULL)
1095 NewMailQEntry(Item);
1096 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1097 Item->Current->nAttempts++;
1098 if (Item->Current->nAttempts > MaxAttempts) {
1103 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1104 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1106 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1107 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1108 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1109 Item->LastAttempt.retry = SMTP_RETRY_MAX;
1119 * Called by smtp_do_queue() to handle an individual message.
1121 void smtp_do_procmsg(long msgnum, void *userdata) {
1122 struct CtdlMessage *msg = NULL;
1125 OneQueItem *MyQItem;
1132 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
1133 ///strcpy(envelope_from, "");
1135 msg = CtdlFetchMessage(msgnum, 1);
1137 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
1141 pch = instr = msg->cm_fields['M'];
1143 /* Strip out the headers (no not amd any other non-instruction) line */
1144 while (pch != NULL) {
1145 pch = strchr(pch, '\n');
1146 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1151 PlainQItem = NewStrBufPlain(instr, -1);
1152 CtdlFreeMessage(msg);
1153 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1154 FreeStrBuf(&PlainQItem);
1156 if (MyQItem == NULL) {
1157 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
1158 return; /* s.b. else is already processing... */
1162 * Postpone delivery if we've already tried recently.
1164 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1165 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1167 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1168 citthread_mutex_lock(&ActiveQItemsLock);
1170 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1171 DeleteEntryFromHash(ActiveQItems, It);
1173 citthread_mutex_unlock(&ActiveQItemsLock);
1174 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1177 }// TODO: reenable me.*/
1180 * Bail out if there's no actual message associated with this
1182 if (MyQItem->MessageID < 0L) {
1183 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
1184 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1185 citthread_mutex_lock(&ActiveQItemsLock);
1187 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1188 DeleteEntryFromHash(ActiveQItems, It);
1190 citthread_mutex_unlock(&ActiveQItemsLock);
1192 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1196 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1197 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
1199 MailQEntry *ThisItem = vQE;
1200 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
1204 CountActiveQueueEntries(MyQItem);
1205 if (MyQItem->ActiveDeliveries > 0)
1208 StrBuf *Msg = smtp_load_msg(MyQItem);
1209 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1210 while ((i <= MyQItem->ActiveDeliveries) &&
1211 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1213 MailQEntry *ThisItem = vQE;
1214 if (ThisItem->Active == 1) {
1215 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1216 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1224 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1225 citthread_mutex_lock(&ActiveQItemsLock);
1227 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1228 DeleteEntryFromHash(ActiveQItems, It);
1230 citthread_mutex_unlock(&ActiveQItemsLock);
1232 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1234 // TODO: bounce & delete?
1240 /*****************************************************************************/
1241 /* SMTP UTILITY COMMANDS */
1242 /*****************************************************************************/
1244 void cmd_smtp(char *argbuf) {
1251 if (CtdlAccessCheck(ac_aide)) return;
1253 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1255 if (!strcasecmp(cmd, "mx")) {
1256 extract_token(node, argbuf, 1, '|', sizeof node);
1257 num_mxhosts = getmx(buf, node);
1258 cprintf("%d %d MX hosts listed for %s\n",
1259 LISTING_FOLLOWS, num_mxhosts, node);
1260 for (i=0; i<num_mxhosts; ++i) {
1261 extract_token(node, buf, i, '|', sizeof node);
1262 cprintf("%s\n", node);
1268 else if (!strcasecmp(cmd, "runqueue")) {
1270 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1275 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1282 * smtp_queue_thread()
1284 * Run through the queue sending out messages.
1286 void *smtp_queue_thread(void *arg) {
1287 int num_processed = 0;
1288 struct CitContext smtp_queue_CC;
1290 CtdlThreadSleep(10);
1292 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1293 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1294 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1296 while (!CtdlThreadCheckStop()) {
1298 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1300 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1301 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1304 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1306 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1307 CtdlThreadSleep(60);
1310 CtdlClearSystemContext();
1316 * Initialize the SMTP outbound queue
1318 void smtp_init_spoolout(void) {
1319 struct ctdlroom qrbuf;
1322 * Create the room. This will silently fail if the room already
1323 * exists, and that's perfectly ok, because we want it to exist.
1325 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1328 * Make sure it's set to be a "system room" so it doesn't show up
1329 * in the <K>nown rooms list for Aides.
1331 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1332 qrbuf.QRflags2 |= QR2_SYSTEM;
1333 CtdlPutRoomLock(&qrbuf);
1338 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1339 SMTPC_read_greeting,
1340 SMTPC_read_EHLO_reply,
1341 SMTPC_read_HELO_reply,
1342 SMTPC_read_auth_reply,
1343 SMTPC_read_FROM_reply,
1344 SMTPC_read_RCPT_reply,
1345 SMTPC_read_DATAcmd_reply,
1347 SMTPC_read_data_body_reply,
1348 SMTPC_read_QUIT_reply
1351 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1352 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1359 SMTPC_send_data_body,
1360 SMTPC_send_terminate_data_body,
1364 eNextState SMTP_C_Terminate(void *Data)
1366 SmtpOutMsg *pMsg = Data;
1367 FinalizeMessageSend(pMsg);
1371 eNextState SMTP_C_Timeout(void *Data)
1373 SmtpOutMsg *pMsg = Data;
1374 FinalizeMessageSend(pMsg);
1378 eNextState SMTP_C_ConnFail(void *Data)
1380 SmtpOutMsg *pMsg = Data;
1381 FinalizeMessageSend(pMsg);
1385 eNextState SMTP_C_DispatchReadDone(void *Data)
1387 SmtpOutMsg *pMsg = Data;
1388 eNextState rc = ReadHandlers[pMsg->State](pMsg);
1393 eNextState SMTP_C_DispatchWriteDone(void *Data)
1395 SmtpOutMsg *pMsg = Data;
1396 return SendHandlers[pMsg->State](pMsg);
1401 CTDL_MODULE_INIT(smtp_eventclient)
1403 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1406 ActiveQItems = NewHash(1, Flathash);
1407 citthread_mutex_init(&ActiveQItemsLock, NULL);
1409 QItemHandlers = NewHash(0, NULL);
1411 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1412 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1413 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1414 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1415 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1416 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1417 ///submitted /TODO: flush qitemhandlers on exit
1420 smtp_init_spoolout();
1421 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1423 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1427 /* return our Subversion id for the Log */
1428 return "smtpeventclient";