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;
660 *SendMsg->mx_user = '\0';
661 *SendMsg->mx_pass = '\0';
662 if (num_tokens(buf, '@') > 1) {
663 strcpy (SendMsg->mx_user, buf);
664 endpart = strrchr(SendMsg->mx_user, '@');
666 strcpy (SendMsg->mx_host, endpart + 1);
667 endpart = strrchr(SendMsg->mx_user, ':');
668 if (endpart != NULL) {
669 strcpy(SendMsg->mx_pass, endpart+1);
673 endpart = strrchr(SendMsg->mx_host, ':');
676 strcpy(SendMsg->mx_port, endpart + 1);
681 SendMsg->mx_host = SendMsg->CurrMX->host;
682 SendMsg->CurrMX = SendMsg->CurrMX->next;
684 CtdlLogPrintf(CTDL_DEBUG,
685 "SMTP client[%ld]: connecting to %s : %s ...\n",
690 ares_gethostbyname(SendMsg->IO.DNSChannel,
692 AF_INET6, /* it falls back to ipv4 in doubt... */
693 get_one_mx_host_name_done,
696 if (!QueueQuery(ns_t_a,
699 connect_one_smtpsrv_xamine_result))
707 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
709 /* Process the SMTP greeting from the server */
712 if (!SMTP_IS_STATE('2')) {
713 if (SMTP_IS_STATE('4'))
721 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
723 /* At this point we know we are talking to a real SMTP server */
725 /* Do a EHLO command. If it fails, try the HELO command. */
726 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
727 "EHLO %s\r\n", config.c_fqdn);
733 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
737 if (SMTP_IS_STATE('2')) {
739 if (IsEmptyStr(SendMsg->mx_user))
740 SendMsg->State ++; /* Skip auth... */
742 /* else we fall back to 'helo' */
746 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
748 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
749 "HELO %s\r\n", config.c_fqdn);
755 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
759 if (!SMTP_IS_STATE('2')) {
760 if (SMTP_IS_STATE('4'))
765 if (!IsEmptyStr(SendMsg->mx_user))
766 SendMsg->State ++; /* Skip auth... */
770 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
775 /* Do an AUTH command if necessary */
776 sprintf(buf, "%s%c%s%c%s",
777 SendMsg->mx_user, '\0',
778 SendMsg->mx_user, '\0',
780 CtdlEncodeBase64(encoded, buf,
781 strlen(SendMsg->mx_user) +
782 strlen(SendMsg->mx_user) +
783 strlen(SendMsg->mx_pass) + 2, 0);
784 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
785 "AUTH PLAIN %s\r\n", encoded);
791 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
793 /* Do an AUTH command if necessary */
797 if (!SMTP_IS_STATE('2')) {
798 if (SMTP_IS_STATE('4'))
806 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
808 /* previous command succeeded, now try the MAIL FROM: command */
809 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
810 "MAIL FROM:<%s>\r\n",
811 SendMsg->envelope_from);
817 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
821 if (!SMTP_IS_STATE('2')) {
822 if (SMTP_IS_STATE('4'))
831 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
833 /* MAIL succeeded, now try the RCPT To: command */
834 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
835 "RCPT TO:<%s@%s>\r\n",
843 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
847 if (!SMTP_IS_STATE('2')) {
848 if (SMTP_IS_STATE('4'))
856 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
858 /* RCPT succeeded, now try the DATA command */
859 StrBufPlain(SendMsg->IO.SendBuf.Buf,
866 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
870 if (!SMTP_IS_STATE('3')) {
871 if (SMTP_IS_STATE('4'))
879 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
882 /* If we reach this point, the server is expecting data.*/
884 Buf = SendMsg->IO.SendBuf.Buf;
885 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
886 SendMsg->msgtext = Buf;
887 //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
893 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
897 Buf = SendMsg->IO.SendBuf.Buf;
898 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
899 SendMsg->msgtext = Buf;
901 StrBufPlain(SendMsg->IO.SendBuf.Buf,
908 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
912 if (!SMTP_IS_STATE('2')) {
913 if (SMTP_IS_STATE('4'))
920 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
921 &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
922 StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
923 SendMsg->MyQEntry->Status = 2;
927 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
929 StrBufPlain(SendMsg->IO.SendBuf.Buf,
936 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
940 CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
941 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
942 return eTerminateConnection;
945 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
950 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
955 eNextState smtp_resolve_mx_done(void *data)
958 SmtpOutMsg * SendMsg = IO->Data;
960 //// connect_one_smtpsrv_xamine_result
961 SendMsg->CurrMX = SendMsg->AllMX = IO->VParsedDNSReply;
962 //// TODO: should we remove the current ares context???
963 connect_one_smtpsrv(SendMsg);
969 int resolve_mx_records(void *Ctx)
971 SmtpOutMsg * SendMsg = Ctx;
973 InitEventIO(&SendMsg->IO, SendMsg,
974 SMTP_C_DispatchReadDone,
975 SMTP_C_DispatchWriteDone,
979 SMTP_C_ReadServerStatus,
982 if (!QueueQuery(ns_t_mx,
985 smtp_resolve_mx_done))
987 SendMsg->MyQEntry->Status = 5;
988 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
989 "No MX hosts found for <%s>", SendMsg->node);
990 return 0; ///////TODO: abort!
995 void smtp_try(OneQueItem *MyQItem,
996 MailQEntry *MyQEntry,
998 int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
1000 SmtpOutMsg * SendMsg;
1002 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
1003 memset(SendMsg, 0, sizeof(SmtpOutMsg));
1004 SendMsg->IO.sock = (-1);
1005 SendMsg->n = MsgCount++;
1006 SendMsg->MyQEntry = MyQEntry;
1007 SendMsg->MyQItem = MyQItem;
1008 SendMsg->IO.Data = SendMsg;
1010 SendMsg->msgtext = MsgText;
1012 SendMsg->msgtext = NewStrBufDup(MsgText);
1014 smtp_resolve_recipients(SendMsg);
1016 QueueEventContext(SendMsg,
1018 resolve_mx_records);
1025 void NewMailQEntry(OneQueItem *Item)
1027 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
1028 memset(Item->Current, 0, sizeof(MailQEntry));
1030 if (Item->MailQEntries == NULL)
1031 Item->MailQEntries = NewHash(1, Flathash);
1032 Item->Current->n = GetCount(Item->MailQEntries);
1033 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
1036 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
1038 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
1041 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
1043 if (Item->EnvelopeFrom == NULL)
1044 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
1045 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
1048 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
1050 if (Item->BounceTo == NULL)
1051 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
1052 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
1055 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
1057 if (Item->Current == NULL)
1058 NewMailQEntry(Item);
1059 if (Item->Current->Recipient == NULL)
1060 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
1061 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
1062 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
1063 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
1064 Item->Current = NULL; // TODO: is this always right?
1068 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
1070 if (Item->Current == NULL)
1071 NewMailQEntry(Item);
1072 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
1073 Item->Current->nAttempts++;
1074 if (Item->Current->nAttempts > MaxAttempts) {
1078 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1081 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1083 if (Item->Current == NULL)
1084 NewMailQEntry(Item);
1085 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1086 Item->Current->nAttempts++;
1087 if (Item->Current->nAttempts > MaxAttempts) {
1092 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1093 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1095 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1096 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1097 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1098 Item->LastAttempt.retry = SMTP_RETRY_MAX;
1108 * Called by smtp_do_queue() to handle an individual message.
1110 void smtp_do_procmsg(long msgnum, void *userdata) {
1111 struct CtdlMessage *msg = NULL;
1114 OneQueItem *MyQItem;
1121 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
1122 ///strcpy(envelope_from, "");
1124 msg = CtdlFetchMessage(msgnum, 1);
1126 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
1130 pch = instr = msg->cm_fields['M'];
1132 /* Strip out the headers (no not amd any other non-instruction) line */
1133 while (pch != NULL) {
1134 pch = strchr(pch, '\n');
1135 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1140 PlainQItem = NewStrBufPlain(instr, -1);
1141 CtdlFreeMessage(msg);
1142 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1143 FreeStrBuf(&PlainQItem);
1145 if (MyQItem == NULL) {
1146 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
1147 return; /* s.b. else is already processing... */
1151 * Postpone delivery if we've already tried recently.
1153 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1154 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1156 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1157 citthread_mutex_lock(&ActiveQItemsLock);
1159 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1160 DeleteEntryFromHash(ActiveQItems, It);
1162 citthread_mutex_unlock(&ActiveQItemsLock);
1163 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1166 }// TODO: reenable me.*/
1169 * Bail out if there's no actual message associated with this
1171 if (MyQItem->MessageID < 0L) {
1172 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
1173 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1174 citthread_mutex_lock(&ActiveQItemsLock);
1176 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1177 DeleteEntryFromHash(ActiveQItems, It);
1179 citthread_mutex_unlock(&ActiveQItemsLock);
1181 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1185 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1186 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
1188 MailQEntry *ThisItem = vQE;
1189 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
1193 CountActiveQueueEntries(MyQItem);
1194 if (MyQItem->ActiveDeliveries > 0)
1197 StrBuf *Msg = smtp_load_msg(MyQItem);
1198 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1199 while ((i <= MyQItem->ActiveDeliveries) &&
1200 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1202 MailQEntry *ThisItem = vQE;
1203 if (ThisItem->Active == 1) {
1204 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1205 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1213 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1214 citthread_mutex_lock(&ActiveQItemsLock);
1216 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1217 DeleteEntryFromHash(ActiveQItems, It);
1219 citthread_mutex_unlock(&ActiveQItemsLock);
1221 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1223 // TODO: bounce & delete?
1229 /*****************************************************************************/
1230 /* SMTP UTILITY COMMANDS */
1231 /*****************************************************************************/
1233 void cmd_smtp(char *argbuf) {
1240 if (CtdlAccessCheck(ac_aide)) return;
1242 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1244 if (!strcasecmp(cmd, "mx")) {
1245 extract_token(node, argbuf, 1, '|', sizeof node);
1246 num_mxhosts = getmx(buf, node);
1247 cprintf("%d %d MX hosts listed for %s\n",
1248 LISTING_FOLLOWS, num_mxhosts, node);
1249 for (i=0; i<num_mxhosts; ++i) {
1250 extract_token(node, buf, i, '|', sizeof node);
1251 cprintf("%s\n", node);
1257 else if (!strcasecmp(cmd, "runqueue")) {
1259 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1264 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1271 * smtp_queue_thread()
1273 * Run through the queue sending out messages.
1275 void *smtp_queue_thread(void *arg) {
1276 int num_processed = 0;
1277 struct CitContext smtp_queue_CC;
1279 CtdlThreadSleep(10);
1281 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1282 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1283 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1285 while (!CtdlThreadCheckStop()) {
1287 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1289 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1290 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1293 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1295 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1296 CtdlThreadSleep(60);
1299 CtdlClearSystemContext();
1305 * Initialize the SMTP outbound queue
1307 void smtp_init_spoolout(void) {
1308 struct ctdlroom qrbuf;
1311 * Create the room. This will silently fail if the room already
1312 * exists, and that's perfectly ok, because we want it to exist.
1314 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1317 * Make sure it's set to be a "system room" so it doesn't show up
1318 * in the <K>nown rooms list for Aides.
1320 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1321 qrbuf.QRflags2 |= QR2_SYSTEM;
1322 CtdlPutRoomLock(&qrbuf);
1327 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1328 SMTPC_read_greeting,
1329 SMTPC_read_EHLO_reply,
1330 SMTPC_read_HELO_reply,
1331 SMTPC_read_auth_reply,
1332 SMTPC_read_FROM_reply,
1333 SMTPC_read_RCPT_reply,
1334 SMTPC_read_DATAcmd_reply,
1336 SMTPC_read_data_body_reply,
1337 SMTPC_read_QUIT_reply
1340 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1341 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1348 SMTPC_send_data_body,
1349 SMTPC_send_terminate_data_body,
1353 eNextState SMTP_C_Terminate(void *Data)
1355 SmtpOutMsg *pMsg = Data;
1356 FinalizeMessageSend(pMsg);
1360 eNextState SMTP_C_Timeout(void *Data)
1362 SmtpOutMsg *pMsg = Data;
1363 FinalizeMessageSend(pMsg);
1367 eNextState SMTP_C_ConnFail(void *Data)
1369 SmtpOutMsg *pMsg = Data;
1370 FinalizeMessageSend(pMsg);
1374 eNextState SMTP_C_DispatchReadDone(void *Data)
1376 SmtpOutMsg *pMsg = Data;
1377 eNextState rc = ReadHandlers[pMsg->State](pMsg);
1382 eNextState SMTP_C_DispatchWriteDone(void *Data)
1384 SmtpOutMsg *pMsg = Data;
1385 return SendHandlers[pMsg->State](pMsg);
1390 CTDL_MODULE_INIT(smtp_eventclient)
1392 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1395 ActiveQItems = NewHash(1, Flathash);
1396 citthread_mutex_init(&ActiveQItemsLock, NULL);
1398 QItemHandlers = NewHash(0, NULL);
1400 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1401 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1402 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1403 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1404 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1405 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1406 ///submitted /TODO: flush qitemhandlers on exit
1409 smtp_init_spoolout();
1410 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1412 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1416 /* return our Subversion id for the Log */
1417 return "smtpeventclient";