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;
661 *SendMsg->mx_user = '\0';
662 *SendMsg->mx_pass = '\0';
663 if (num_tokens(buf, '@') > 1) {
664 strcpy (SendMsg->mx_user, buf);
665 endpart = strrchr(SendMsg->mx_user, '@');
667 strcpy (SendMsg->mx_host, endpart + 1);
668 endpart = strrchr(SendMsg->mx_user, ':');
669 if (endpart != NULL) {
670 strcpy(SendMsg->mx_pass, endpart+1);
674 endpart = strrchr(SendMsg->mx_host, ':');
677 strcpy(SendMsg->mx_port, endpart + 1);
682 SendMsg->mx_host = SendMsg->CurrMX->host;
683 SendMsg->CurrMX = SendMsg->CurrMX->next;
685 CtdlLogPrintf(CTDL_DEBUG,
686 "SMTP client[%ld]: connecting to %s : %s ...\n",
691 ares_gethostbyname(SendMsg->IO.DNSChannel,
693 AF_INET6, /* it falls back to ipv4 in doubt... */
694 get_one_mx_host_name_done,
697 if (!QueueQuery(ns_t_a,
700 connect_one_smtpsrv_xamine_result))
708 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
710 /* Process the SMTP greeting from the server */
713 if (!SMTP_IS_STATE('2')) {
714 if (SMTP_IS_STATE('4'))
722 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
724 /* At this point we know we are talking to a real SMTP server */
726 /* Do a EHLO command. If it fails, try the HELO command. */
727 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
728 "EHLO %s\r\n", config.c_fqdn);
734 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
738 if (SMTP_IS_STATE('2')) {
740 if (IsEmptyStr(SendMsg->mx_user))
741 SendMsg->State ++; /* Skip auth... */
743 /* else we fall back to 'helo' */
747 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
749 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
750 "HELO %s\r\n", config.c_fqdn);
756 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
760 if (!SMTP_IS_STATE('2')) {
761 if (SMTP_IS_STATE('4'))
766 if (!IsEmptyStr(SendMsg->mx_user))
767 SendMsg->State ++; /* Skip auth... */
771 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
776 /* Do an AUTH command if necessary */
777 sprintf(buf, "%s%c%s%c%s",
778 SendMsg->mx_user, '\0',
779 SendMsg->mx_user, '\0',
781 CtdlEncodeBase64(encoded, buf,
782 strlen(SendMsg->mx_user) +
783 strlen(SendMsg->mx_user) +
784 strlen(SendMsg->mx_pass) + 2, 0);
785 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
786 "AUTH PLAIN %s\r\n", encoded);
792 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
794 /* Do an AUTH command if necessary */
798 if (!SMTP_IS_STATE('2')) {
799 if (SMTP_IS_STATE('4'))
807 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
809 /* previous command succeeded, now try the MAIL FROM: command */
810 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
811 "MAIL FROM:<%s>\r\n",
812 SendMsg->envelope_from);
818 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
822 if (!SMTP_IS_STATE('2')) {
823 if (SMTP_IS_STATE('4'))
832 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
834 /* MAIL succeeded, now try the RCPT To: command */
835 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
836 "RCPT TO:<%s@%s>\r\n",
844 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
848 if (!SMTP_IS_STATE('2')) {
849 if (SMTP_IS_STATE('4'))
857 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
859 /* RCPT succeeded, now try the DATA command */
860 StrBufPlain(SendMsg->IO.SendBuf.Buf,
867 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
871 if (!SMTP_IS_STATE('3')) {
872 if (SMTP_IS_STATE('4'))
880 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
883 /* If we reach this point, the server is expecting data.*/
885 Buf = SendMsg->IO.SendBuf.Buf;
886 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
887 SendMsg->msgtext = Buf;
888 //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
894 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
898 Buf = SendMsg->IO.SendBuf.Buf;
899 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
900 SendMsg->msgtext = Buf;
902 StrBufPlain(SendMsg->IO.SendBuf.Buf,
909 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
913 if (!SMTP_IS_STATE('2')) {
914 if (SMTP_IS_STATE('4'))
921 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
922 &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
923 StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
924 SendMsg->MyQEntry->Status = 2;
928 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
930 StrBufPlain(SendMsg->IO.SendBuf.Buf,
937 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
941 CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
942 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
943 return eTerminateConnection;
946 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
951 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
956 eNextState smtp_resolve_mx_done(void *data)
959 SmtpOutMsg * SendMsg = IO->Data;
961 SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
962 SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
963 SendMsg->IO.IOBuf = NewStrBuf();
964 SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
966 //// connect_one_smtpsrv_xamine_result
967 SendMsg->CurrMX = SendMsg->AllMX = IO->VParsedDNSReply;
968 //// TODO: should we remove the current ares context???
969 connect_one_smtpsrv(SendMsg);
975 int resolve_mx_records(void *Ctx)
977 SmtpOutMsg * SendMsg = Ctx;
979 SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
980 SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
981 SendMsg->IO.IOBuf = NewStrBuf();
982 SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
984 InitEventIO(&SendMsg->IO, SendMsg,
985 SMTP_C_DispatchReadDone,
986 SMTP_C_DispatchWriteDone,
990 SMTP_C_ReadServerStatus,
994 if (!QueueQuery(ns_t_mx,
997 smtp_resolve_mx_done))
999 SendMsg->MyQEntry->Status = 5;
1000 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
1001 "No MX hosts found for <%s>", SendMsg->node);
1002 return 0; ///////TODO: abort!
1007 void smtp_try(OneQueItem *MyQItem,
1008 MailQEntry *MyQEntry,
1010 int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
1012 SmtpOutMsg * SendMsg;
1014 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
1015 memset(SendMsg, 0, sizeof(SmtpOutMsg));
1016 SendMsg->IO.sock = (-1);
1017 SendMsg->n = MsgCount++;
1018 SendMsg->MyQEntry = MyQEntry;
1019 SendMsg->MyQItem = MyQItem;
1020 SendMsg->IO.Data = SendMsg;
1022 SendMsg->msgtext = MsgText;
1024 SendMsg->msgtext = NewStrBufDup(MsgText);
1026 smtp_resolve_recipients(SendMsg);
1028 QueueEventContext(SendMsg,
1030 resolve_mx_records);
1037 void NewMailQEntry(OneQueItem *Item)
1039 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
1040 memset(Item->Current, 0, sizeof(MailQEntry));
1042 if (Item->MailQEntries == NULL)
1043 Item->MailQEntries = NewHash(1, Flathash);
1044 Item->Current->n = GetCount(Item->MailQEntries);
1045 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
1048 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
1050 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
1053 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
1055 if (Item->EnvelopeFrom == NULL)
1056 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
1057 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
1060 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
1062 if (Item->BounceTo == NULL)
1063 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
1064 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
1067 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
1069 if (Item->Current == NULL)
1070 NewMailQEntry(Item);
1071 if (Item->Current->Recipient == NULL)
1072 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
1073 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
1074 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
1075 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
1076 Item->Current = NULL; // TODO: is this always right?
1080 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
1082 if (Item->Current == NULL)
1083 NewMailQEntry(Item);
1084 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
1085 Item->Current->nAttempts++;
1086 if (Item->Current->nAttempts > MaxAttempts) {
1090 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1093 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1095 if (Item->Current == NULL)
1096 NewMailQEntry(Item);
1097 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1098 Item->Current->nAttempts++;
1099 if (Item->Current->nAttempts > MaxAttempts) {
1104 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1105 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1107 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1108 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1109 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1110 Item->LastAttempt.retry = SMTP_RETRY_MAX;
1120 * Called by smtp_do_queue() to handle an individual message.
1122 void smtp_do_procmsg(long msgnum, void *userdata) {
1123 struct CtdlMessage *msg = NULL;
1126 OneQueItem *MyQItem;
1133 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
1134 ///strcpy(envelope_from, "");
1136 msg = CtdlFetchMessage(msgnum, 1);
1138 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
1142 pch = instr = msg->cm_fields['M'];
1144 /* Strip out the headers (no not amd any other non-instruction) line */
1145 while (pch != NULL) {
1146 pch = strchr(pch, '\n');
1147 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1152 PlainQItem = NewStrBufPlain(instr, -1);
1153 CtdlFreeMessage(msg);
1154 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1155 FreeStrBuf(&PlainQItem);
1157 if (MyQItem == NULL) {
1158 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
1159 return; /* s.b. else is already processing... */
1163 * Postpone delivery if we've already tried recently.
1165 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1166 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1168 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1169 citthread_mutex_lock(&ActiveQItemsLock);
1171 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1172 DeleteEntryFromHash(ActiveQItems, It);
1174 citthread_mutex_unlock(&ActiveQItemsLock);
1175 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1178 }// TODO: reenable me.*/
1181 * Bail out if there's no actual message associated with this
1183 if (MyQItem->MessageID < 0L) {
1184 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
1185 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1186 citthread_mutex_lock(&ActiveQItemsLock);
1188 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1189 DeleteEntryFromHash(ActiveQItems, It);
1191 citthread_mutex_unlock(&ActiveQItemsLock);
1193 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1197 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1198 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
1200 MailQEntry *ThisItem = vQE;
1201 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
1205 CountActiveQueueEntries(MyQItem);
1206 if (MyQItem->ActiveDeliveries > 0)
1209 StrBuf *Msg = smtp_load_msg(MyQItem);
1210 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1211 while ((i <= MyQItem->ActiveDeliveries) &&
1212 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1214 MailQEntry *ThisItem = vQE;
1215 if (ThisItem->Active == 1) {
1216 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1217 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1225 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1226 citthread_mutex_lock(&ActiveQItemsLock);
1228 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1229 DeleteEntryFromHash(ActiveQItems, It);
1231 citthread_mutex_unlock(&ActiveQItemsLock);
1233 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1235 // TODO: bounce & delete?
1241 /*****************************************************************************/
1242 /* SMTP UTILITY COMMANDS */
1243 /*****************************************************************************/
1245 void cmd_smtp(char *argbuf) {
1252 if (CtdlAccessCheck(ac_aide)) return;
1254 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1256 if (!strcasecmp(cmd, "mx")) {
1257 extract_token(node, argbuf, 1, '|', sizeof node);
1258 num_mxhosts = getmx(buf, node);
1259 cprintf("%d %d MX hosts listed for %s\n",
1260 LISTING_FOLLOWS, num_mxhosts, node);
1261 for (i=0; i<num_mxhosts; ++i) {
1262 extract_token(node, buf, i, '|', sizeof node);
1263 cprintf("%s\n", node);
1269 else if (!strcasecmp(cmd, "runqueue")) {
1271 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1276 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1283 * smtp_queue_thread()
1285 * Run through the queue sending out messages.
1287 void *smtp_queue_thread(void *arg) {
1288 int num_processed = 0;
1289 struct CitContext smtp_queue_CC;
1291 CtdlThreadSleep(10);
1293 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1294 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1295 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1297 while (!CtdlThreadCheckStop()) {
1299 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1301 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1302 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1305 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1307 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1308 CtdlThreadSleep(60);
1311 CtdlClearSystemContext();
1317 * Initialize the SMTP outbound queue
1319 void smtp_init_spoolout(void) {
1320 struct ctdlroom qrbuf;
1323 * Create the room. This will silently fail if the room already
1324 * exists, and that's perfectly ok, because we want it to exist.
1326 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1329 * Make sure it's set to be a "system room" so it doesn't show up
1330 * in the <K>nown rooms list for Aides.
1332 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1333 qrbuf.QRflags2 |= QR2_SYSTEM;
1334 CtdlPutRoomLock(&qrbuf);
1339 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1340 SMTPC_read_greeting,
1341 SMTPC_read_EHLO_reply,
1342 SMTPC_read_HELO_reply,
1343 SMTPC_read_auth_reply,
1344 SMTPC_read_FROM_reply,
1345 SMTPC_read_RCPT_reply,
1346 SMTPC_read_DATAcmd_reply,
1348 SMTPC_read_data_body_reply,
1349 SMTPC_read_QUIT_reply
1352 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1353 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1360 SMTPC_send_data_body,
1361 SMTPC_send_terminate_data_body,
1365 eNextState SMTP_C_Terminate(void *Data)
1367 SmtpOutMsg *pMsg = Data;
1368 FinalizeMessageSend(pMsg);
1372 eNextState SMTP_C_Timeout(void *Data)
1374 SmtpOutMsg *pMsg = Data;
1375 FinalizeMessageSend(pMsg);
1379 eNextState SMTP_C_ConnFail(void *Data)
1381 SmtpOutMsg *pMsg = Data;
1382 FinalizeMessageSend(pMsg);
1386 eNextState SMTP_C_DispatchReadDone(void *Data)
1388 SmtpOutMsg *pMsg = Data;
1389 eNextState rc = ReadHandlers[pMsg->State](pMsg);
1394 eNextState SMTP_C_DispatchWriteDone(void *Data)
1396 SmtpOutMsg *pMsg = Data;
1397 return SendHandlers[pMsg->State](pMsg);
1402 CTDL_MODULE_INIT(smtp_eventclient)
1404 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1407 ActiveQItems = NewHash(1, Flathash);
1408 citthread_mutex_init(&ActiveQItemsLock, NULL);
1410 QItemHandlers = NewHash(0, NULL);
1412 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1413 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1414 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1415 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1416 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1417 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1418 ///submitted /TODO: flush qitemhandlers on exit
1421 smtp_init_spoolout();
1422 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1424 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1428 /* return our Subversion id for the Log */
1429 return "smtpeventclient";