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 /// int SMTPstatus; ->MyQEntry->Status
210 /// char addr[SIZ]; -> MyQEntry->Recipient
211 /// char dsn[1024]; -> MyQEntry->StatusMessage
212 /// char envelope_from_buf[1024]; MyQItem->EnvelopeFrom
215 void DeleteSmtpOutMsg(void *v)
218 FreeStrBuf(&Msg->msgtext);
219 FreeAsyncIOContents(&Msg->IO);
223 eNextState SMTP_C_DispatchReadDone(void *Data);
224 eNextState SMTP_C_DispatchWriteDone(void *Data);
225 eNextState SMTP_C_Terminate(void *Data);
227 typedef eNextState (*SMTPReadHandler)(SmtpOutMsg *Msg);
228 typedef eNextState (*SMTPSendHandler)(SmtpOutMsg *Msg);
233 void FreeQueItem(OneQueItem **Item)
235 DeleteHash(&(*Item)->MailQEntries);
236 FreeStrBuf(&(*Item)->EnvelopeFrom);
237 FreeStrBuf(&(*Item)->BounceTo);
241 void HFreeQueItem(void *Item)
243 FreeQueItem((OneQueItem**)&Item);
247 /* inspect recipients with a status of:
248 * - 0 (no delivery yet attempted)
249 * - 3/4 (transient errors
250 * were experienced and it's time to try again)
252 int CountActiveQueueEntries(OneQueItem *MyQItem)
259 MyQItem->ActiveDeliveries = 0;
260 It = GetNewHashPos(MyQItem->MailQEntries, 0);
261 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
263 MailQEntry *ThisItem = vQE;
264 if ((ThisItem->Status == 0) ||
265 (ThisItem->Status == 3) ||
266 (ThisItem->Status == 4))
268 MyQItem->ActiveDeliveries++;
269 ThisItem->Active = 1;
272 ThisItem->Active = 0;
275 return MyQItem->ActiveDeliveries;
278 OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
281 const char *pLine = NULL;
286 Item = (OneQueItem*)malloc(sizeof(OneQueItem));
287 memset(Item, 0, sizeof(OneQueItem));
288 Item->LastAttempt.retry = SMTP_RETRY_INTERVAL;
289 Item->MessageID = -1;
290 Item->QueMsgID = QueMsgID;
292 citthread_mutex_lock(&ActiveQItemsLock);
293 if (GetHash(ActiveQItems,
294 IKEY(Item->QueMsgID),
297 /* WHOOPS. somebody else is already working on this. */
298 citthread_mutex_unlock(&ActiveQItemsLock);
303 /* mark our claim on this. */
305 IKEY(Item->QueMsgID),
308 citthread_mutex_unlock(&ActiveQItemsLock);
312 Line = NewStrBufPlain(NULL, 128);
313 while (pLine != StrBufNOTNULL) {
314 const char *pItemPart = NULL;
317 StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
318 if (StrLength(Line) == 0) continue;
319 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
320 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
323 H = (QItemHandler) vHandler;
324 H(Item, Line, &pItemPart);
332 StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
340 QMessage = NewStrBufPlain(NULL, SIZ);
341 StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
343 // "attempted|%ld\n" "retry|%ld\n",, (long)time(NULL), (long)retry );
344 StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
345 StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
347 if (StrLength(MyQItem->BounceTo) > 0) {
348 StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
349 StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
352 if (StrLength(MyQItem->EnvelopeFrom) > 0) {
353 StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
354 StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
357 It = GetNewHashPos(MyQItem->MailQEntries, 0);
358 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
360 MailQEntry *ThisItem = vQE;
363 if (!ThisItem->Active)
364 continue; /* skip already sent ones from the spoolfile. */
366 for (i=0; i < ThisItem->nAttempts; i++) {
367 StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
368 StrBufAppendPrintf(QMessage, "%ld",
369 ThisItem->Attempts[i].retry);
371 StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
372 StrBufAppendPrintf(QMessage, "%ld",
373 ThisItem->Attempts[i].when);
375 StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
376 StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
377 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
378 StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
379 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
380 StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
383 StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);
387 void FinalizeMessageSend(SmtpOutMsg *Msg)
389 int IDestructQueItem;
392 citthread_mutex_lock(&ActiveQItemsLock);
393 Msg->MyQItem->ActiveDeliveries--;
394 IDestructQueItem = Msg->MyQItem->ActiveDeliveries == 0;
395 citthread_mutex_unlock(&ActiveQItemsLock);
397 if (IDestructQueItem) {
401 nRemain = CountActiveQueueEntries(Msg->MyQItem);
404 MsgData = SerializeQueueItem(Msg->MyQItem);
406 * Uncompleted delivery instructions remain, so delete the old
407 * instructions and replace with the updated ones.
409 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, "");
411 /* Generate 'bounce' messages * /
412 smtp_do_bounce(instr); */
414 struct CtdlMessage *msg;
415 msg = malloc(sizeof(struct CtdlMessage));
416 memset(msg, 0, sizeof(struct CtdlMessage));
417 msg->cm_magic = CTDLMESSAGE_MAGIC;
418 msg->cm_anon_type = MES_NORMAL;
419 msg->cm_format_type = FMT_RFC822;
420 msg->cm_fields['M'] = SmashStrBuf(&MsgData);
422 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
423 CtdlFreeMessage(msg);
425 It = GetNewHashPos(Msg->MyQItem->MailQEntries, 0);
426 citthread_mutex_lock(&ActiveQItemsLock);
428 GetHashPosFromKey(ActiveQItems, IKEY(Msg->MyQItem->MessageID), It);
429 DeleteEntryFromHash(ActiveQItems, It);
431 citthread_mutex_unlock(&ActiveQItemsLock);
435 /// TODO : else free message...
437 DeleteSmtpOutMsg(Msg);
440 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
442 eReadState Finished = eBufferNotEmpty;
444 while (Finished == eBufferNotEmpty) {
445 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
448 case eMustReadMore: /// read new from socket...
451 case eBufferNotEmpty: /* shouldn't happen... */
452 case eReadSuccess: /// done for now...
453 if (StrLength(IO->IOBuf) < 4)
455 if (ChrPtr(IO->IOBuf)[3] == '-')
456 Finished = eBufferNotEmpty;
460 case eReadFail: /// WHUT?
469 * this one has to have the context for loading the message via the redirect buffer...
471 StrBuf *smtp_load_msg(OneQueItem *MyQItem)
476 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
477 CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
478 SendMsg = CCC->redirect_buffer;
479 CCC->redirect_buffer = NULL;
480 if ((StrLength(SendMsg) > 0) &&
481 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
482 CtdlLogPrintf(CTDL_WARNING,
483 "SMTP client[%ld]: Possible problem: message did not "
484 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
485 MsgCount, //yes uncool, but best choice here...
486 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
487 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
493 int smtp_resolve_recipients(SmtpOutMsg *SendMsg)
501 /* Parse out the host portion of the recipient address */
502 process_rfc822_addr(ChrPtr(SendMsg->MyQEntry->Recipient),
507 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
508 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
509 /* If no envelope_from is supplied, extract one from the message */
510 if ( (SendMsg->envelope_from == NULL) ||
511 (IsEmptyStr(SendMsg->envelope_from)) ) {
512 SendMsg->mailfrom[0] = '\0';
514 ptr = ChrPtr(SendMsg->msgtext);
516 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
519 if (!strncasecmp(buf, "From:", 5)) {
520 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
521 striplt(SendMsg->mailfrom);
522 for (i=0; SendMsg->mailfrom[i]; ++i) {
523 if (!isprint(SendMsg->mailfrom[i])) {
524 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
529 /* Strip out parenthesized names */
532 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
533 if (SendMsg->mailfrom[i] == '(') lp = i;
534 if (SendMsg->mailfrom[i] == ')') rp = i;
536 if ((lp>0)&&(rp>lp)) {
537 strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
540 /* Prefer brokketized names */
543 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
544 if (SendMsg->mailfrom[i] == '<') lp = i;
545 if (SendMsg->mailfrom[i] == '>') rp = i;
547 if ( (lp>=0) && (rp>lp) ) {
548 SendMsg->mailfrom[rp] = 0;
549 memmove(SendMsg->mailfrom,
550 &SendMsg->mailfrom[lp + 1],
556 } while (scan_done == 0);
557 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
558 stripallbut(SendMsg->mailfrom, '<', '>');
559 SendMsg->envelope_from = SendMsg->mailfrom;
565 void resolve_mx_hosts(SmtpOutMsg *SendMsg)
567 /// well this is blocking and sux, but libevent jsut supports async dns since v2
568 /* Figure out what mail exchanger host we have to connect to */
569 SendMsg->num_mxhosts = getmx(SendMsg->mxhosts, SendMsg->node);
570 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Number of MX hosts for <%s> is %d [%s]\n",
571 SendMsg->n, SendMsg->node, SendMsg->num_mxhosts, SendMsg->mxhosts);
572 if (SendMsg->num_mxhosts < 1) {
573 SendMsg->MyQEntry->Status = 5;
574 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
575 "No MX hosts found for <%s>", SendMsg->node);
576 return; ///////TODO: abort!
581 #define SMTP_ERROR(WHICH_ERR, ERRSTR) {SendMsg->MyQEntry->Status = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, HKEY(ERRSTR), 0); return eAbort; }
582 #define SMTP_VERROR(WHICH_ERR) { SendMsg->MyQEntry->Status = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, &ChrPtr(SendMsg->IO.IOBuf)[4], -1, 0); return eAbort; }
583 #define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(SendMsg->IO.IOBuf)[0] == WHICH_STATE)
585 #define SMTP_DBG_SEND() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: > %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
586 #define SMTP_DBG_READ() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: < %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
588 void connect_one_smtpsrv(SmtpOutMsg *SendMsg)
593 extract_token(buf, SendMsg->mxhosts, SendMsg->n_mx, '|', sizeof(buf));
594 strcpy(SendMsg->mx_user, "");
595 strcpy(SendMsg->mx_pass, "");
596 if (num_tokens(buf, '@') > 1) {
597 strcpy (SendMsg->mx_user, buf);
598 endpart = strrchr(SendMsg->mx_user, '@');
600 strcpy (SendMsg->mx_host, endpart + 1);
601 endpart = strrchr(SendMsg->mx_user, ':');
602 if (endpart != NULL) {
603 strcpy(SendMsg->mx_pass, endpart+1);
608 strcpy (SendMsg->mx_host, buf);
609 endpart = strrchr(SendMsg->mx_host, ':');
612 strcpy(SendMsg->mx_port, endpart + 1);
615 strcpy(SendMsg->mx_port, "25");
617 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connecting to %s : %s ...\n",
618 SendMsg->n, SendMsg->mx_host, SendMsg->mx_port);
623 int connect_one_smtpsrv_xamine_result(void *Ctx)
625 SmtpOutMsg *SendMsg = Ctx;
626 SendMsg->IO.SendBuf.fd =
627 SendMsg->IO.RecvBuf.fd =
628 SendMsg->IO.sock = sock_connect(SendMsg->mx_host, SendMsg->mx_port);
630 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
631 "Could not connect: %s", strerror(errno));
632 if (SendMsg->IO.sock >= 0)
634 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connected!\n", SendMsg->n);
636 fdflags = fcntl(SendMsg->IO.sock, F_GETFL);
638 CtdlLogPrintf(CTDL_DEBUG,
639 "SMTP client[%ld]: unable to get socket flags! %s \n",
640 SendMsg->n, strerror(errno));
641 fdflags = fdflags | O_NONBLOCK;
642 if (fcntl(SendMsg->IO.sock, F_SETFL, fdflags) < 0)
643 CtdlLogPrintf(CTDL_DEBUG,
644 "SMTP client[%ld]: unable to set socket nonblocking flags! %s \n",
645 SendMsg->n, strerror(errno));
647 if (SendMsg->IO.sock < 0) {
649 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
650 strerror(errno), -1);
653 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
654 "Unable to connect to %s : %s\n",
655 SendMsg->mx_host, SendMsg->mx_port);
658 /// hier: naechsten mx ausprobieren.
659 if (SendMsg->IO.sock < 0) {
660 SendMsg->MyQEntry->Status = 4; /* dsn is already filled in */
661 //// hier: abbrechen & bounce.
666 SendMsg->IO.SendBuf.Buf = NewStrBuf();
667 SendMsg->IO.RecvBuf.Buf = NewStrBuf();
668 SendMsg->IO.IOBuf = NewStrBuf();
669 InitEventIO(&SendMsg->IO, SendMsg,
670 SMTP_C_DispatchReadDone,
671 SMTP_C_DispatchWriteDone,
673 SMTP_C_ReadServerStatus,
678 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
680 /* Process the SMTP greeting from the server */
683 if (!SMTP_IS_STATE('2')) {
684 if (SMTP_IS_STATE('4'))
692 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
694 /* At this point we know we are talking to a real SMTP server */
696 /* Do a EHLO command. If it fails, try the HELO command. */
697 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
698 "EHLO %s\r\n", config.c_fqdn);
704 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
708 if (SMTP_IS_STATE('2')) {
710 if (IsEmptyStr(SendMsg->mx_user))
711 SendMsg->State ++; /* Skip auth... */
713 /* else we fall back to 'helo' */
717 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
719 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
720 "HELO %s\r\n", config.c_fqdn);
726 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
730 if (!SMTP_IS_STATE('2')) {
731 if (SMTP_IS_STATE('4'))
736 if (!IsEmptyStr(SendMsg->mx_user))
737 SendMsg->State ++; /* Skip auth... */
741 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
746 /* Do an AUTH command if necessary */
747 sprintf(buf, "%s%c%s%c%s",
748 SendMsg->mx_user, '\0',
749 SendMsg->mx_user, '\0',
751 CtdlEncodeBase64(encoded, buf,
752 strlen(SendMsg->mx_user) +
753 strlen(SendMsg->mx_user) +
754 strlen(SendMsg->mx_pass) + 2, 0);
755 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
756 "AUTH PLAIN %s\r\n", encoded);
762 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
764 /* Do an AUTH command if necessary */
768 if (!SMTP_IS_STATE('2')) {
769 if (SMTP_IS_STATE('4'))
777 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
779 /* previous command succeeded, now try the MAIL FROM: command */
780 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
781 "MAIL FROM:<%s>\r\n",
782 SendMsg->envelope_from);
788 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
792 if (!SMTP_IS_STATE('2')) {
793 if (SMTP_IS_STATE('4'))
802 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
804 /* MAIL succeeded, now try the RCPT To: command */
805 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
806 "RCPT TO:<%s@%s>\r\n",
814 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
818 if (!SMTP_IS_STATE('2')) {
819 if (SMTP_IS_STATE('4'))
827 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
829 /* RCPT succeeded, now try the DATA command */
830 StrBufPlain(SendMsg->IO.SendBuf.Buf,
837 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
841 if (!SMTP_IS_STATE('3')) {
842 if (SMTP_IS_STATE('4'))
850 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
853 /* If we reach this point, the server is expecting data.*/
855 Buf = SendMsg->IO.SendBuf.Buf;
856 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
857 SendMsg->msgtext = Buf;
858 //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
864 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
868 Buf = SendMsg->IO.SendBuf.Buf;
869 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
870 SendMsg->msgtext = Buf;
872 StrBufPlain(SendMsg->IO.SendBuf.Buf,
879 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
883 if (!SMTP_IS_STATE('2')) {
884 if (SMTP_IS_STATE('4'))
891 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
892 &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
893 StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
894 SendMsg->MyQEntry->Status = 2;
898 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
900 StrBufPlain(SendMsg->IO.SendBuf.Buf,
907 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
911 CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
912 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
913 return eTerminateConnection;
916 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
921 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
926 void smtp_try(OneQueItem *MyQItem,
927 MailQEntry *MyQEntry,
929 int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
931 SmtpOutMsg * SendMsg;
933 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
934 memset(SendMsg, 0, sizeof(SmtpOutMsg));
935 SendMsg->IO.sock = (-1);
936 SendMsg->n = MsgCount++;
937 SendMsg->MyQEntry = MyQEntry;
938 SendMsg->MyQItem = MyQItem;
940 SendMsg->msgtext = MsgText;
942 SendMsg->msgtext = NewStrBufDup(MsgText);
944 smtp_resolve_recipients(SendMsg);
945 resolve_mx_hosts(SendMsg);
946 connect_one_smtpsrv(SendMsg);
947 QueueEventContext(SendMsg,
949 connect_one_smtpsrv_xamine_result);
954 void NewMailQEntry(OneQueItem *Item)
956 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
957 memset(Item->Current, 0, sizeof(MailQEntry));
959 if (Item->MailQEntries == NULL)
960 Item->MailQEntries = NewHash(1, Flathash);
961 Item->Current->n = GetCount(Item->MailQEntries);
962 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
965 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
967 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
970 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
972 if (Item->EnvelopeFrom == NULL)
973 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
974 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
977 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
979 if (Item->BounceTo == NULL)
980 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
981 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
984 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
986 if (Item->Current == NULL)
988 if (Item->Current->Recipient == NULL)
989 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
990 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
991 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
992 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
993 Item->Current = NULL; // TODO: is this always right?
997 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
999 if (Item->Current == NULL)
1000 NewMailQEntry(Item);
1001 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
1002 Item->Current->nAttempts++;
1003 if (Item->Current->nAttempts > MaxAttempts) {
1007 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1010 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1012 if (Item->Current == NULL)
1013 NewMailQEntry(Item);
1014 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1015 Item->Current->nAttempts++;
1016 if (Item->Current->nAttempts > MaxAttempts) {
1021 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1022 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1024 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1025 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1026 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1027 Item->LastAttempt.retry = SMTP_RETRY_MAX;
1037 * Called by smtp_do_queue() to handle an individual message.
1039 void smtp_do_procmsg(long msgnum, void *userdata) {
1040 struct CtdlMessage *msg = NULL;
1043 OneQueItem *MyQItem;
1050 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: smtp_do_procmsg(%ld)\n", msgnum);
1051 ///strcpy(envelope_from, "");
1053 msg = CtdlFetchMessage(msgnum, 1);
1055 CtdlLogPrintf(CTDL_ERR, "SMTP client: tried %ld but no such message!\n", msgnum);
1059 pch = instr = msg->cm_fields['M'];
1061 /* Strip out the headers (no not amd any other non-instruction) line */
1062 while (pch != NULL) {
1063 pch = strchr(pch, '\n');
1064 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1069 PlainQItem = NewStrBufPlain(instr, -1);
1070 CtdlFreeMessage(msg);
1071 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1072 FreeStrBuf(&PlainQItem);
1074 if (MyQItem == NULL) {
1075 CtdlLogPrintf(CTDL_ERR, "SMTP client: Msg No %ld: already in progress!\n", msgnum);
1076 return; /* s.b. else is already processing... */
1080 * Postpone delivery if we've already tried recently.
1082 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1083 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1085 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1086 citthread_mutex_lock(&ActiveQItemsLock);
1088 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1089 DeleteEntryFromHash(ActiveQItems, It);
1091 citthread_mutex_unlock(&ActiveQItemsLock);
1092 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1095 }// TODO: reenable me.*/
1098 * Bail out if there's no actual message associated with this
1100 if (MyQItem->MessageID < 0L) {
1101 CtdlLogPrintf(CTDL_ERR, "SMTP client: no 'msgid' directive found!\n");
1102 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1103 citthread_mutex_lock(&ActiveQItemsLock);
1105 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1106 DeleteEntryFromHash(ActiveQItems, It);
1108 citthread_mutex_unlock(&ActiveQItemsLock);
1110 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1114 CountActiveQueueEntries(MyQItem);
1115 if (MyQItem->ActiveDeliveries > 0)
1118 StrBuf *Msg = smtp_load_msg(MyQItem);
1119 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1120 while ((i <= MyQItem->ActiveDeliveries) &&
1121 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1123 MailQEntry *ThisItem = vQE;
1124 if (ThisItem->Active == 1) {
1125 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1126 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1134 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1135 citthread_mutex_lock(&ActiveQItemsLock);
1137 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1138 DeleteEntryFromHash(ActiveQItems, It);
1140 citthread_mutex_unlock(&ActiveQItemsLock);
1142 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1144 // TODO: bounce & delete?
1150 /*****************************************************************************/
1151 /* SMTP UTILITY COMMANDS */
1152 /*****************************************************************************/
1154 void cmd_smtp(char *argbuf) {
1161 if (CtdlAccessCheck(ac_aide)) return;
1163 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1165 if (!strcasecmp(cmd, "mx")) {
1166 extract_token(node, argbuf, 1, '|', sizeof node);
1167 num_mxhosts = getmx(buf, node);
1168 cprintf("%d %d MX hosts listed for %s\n",
1169 LISTING_FOLLOWS, num_mxhosts, node);
1170 for (i=0; i<num_mxhosts; ++i) {
1171 extract_token(node, buf, i, '|', sizeof node);
1172 cprintf("%s\n", node);
1178 else if (!strcasecmp(cmd, "runqueue")) {
1180 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1185 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1192 * smtp_queue_thread()
1194 * Run through the queue sending out messages.
1196 void *smtp_queue_thread(void *arg) {
1197 int num_processed = 0;
1198 struct CitContext smtp_queue_CC;
1200 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1201 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1202 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1204 while (!CtdlThreadCheckStop()) {
1206 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1208 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1209 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1212 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1214 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1215 CtdlThreadSleep(60);
1218 CtdlClearSystemContext();
1224 * Initialize the SMTP outbound queue
1226 void smtp_init_spoolout(void) {
1227 struct ctdlroom qrbuf;
1230 * Create the room. This will silently fail if the room already
1231 * exists, and that's perfectly ok, because we want it to exist.
1233 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1236 * Make sure it's set to be a "system room" so it doesn't show up
1237 * in the <K>nown rooms list for Aides.
1239 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1240 qrbuf.QRflags2 |= QR2_SYSTEM;
1241 CtdlPutRoomLock(&qrbuf);
1246 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1247 SMTPC_read_greeting,
1248 SMTPC_read_EHLO_reply,
1249 SMTPC_read_HELO_reply,
1250 SMTPC_read_auth_reply,
1251 SMTPC_read_FROM_reply,
1252 SMTPC_read_RCPT_reply,
1253 SMTPC_read_DATAcmd_reply,
1255 SMTPC_read_data_body_reply,
1256 SMTPC_read_QUIT_reply
1259 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1260 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1267 SMTPC_send_data_body,
1268 SMTPC_send_terminate_data_body,
1272 eNextState SMTP_C_Terminate(void *Data)
1274 SmtpOutMsg *pMsg = Data;
1275 FinalizeMessageSend(pMsg);
1278 eNextState SMTP_C_DispatchReadDone(void *Data)
1280 SmtpOutMsg *pMsg = Data;
1281 eNextState rc = ReadHandlers[pMsg->State](pMsg);
1286 eNextState SMTP_C_DispatchWriteDone(void *Data)
1288 SmtpOutMsg *pMsg = Data;
1289 return SendHandlers[pMsg->State](pMsg);
1295 CTDL_MODULE_INIT(smtp_eventclient)
1297 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1300 ActiveQItems = NewHash(1, Flathash);
1301 citthread_mutex_init(&ActiveQItemsLock, NULL);
1303 QItemHandlers = NewHash(0, NULL);
1305 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1306 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1307 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1308 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1309 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1310 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1311 ///submitted /TODO: flush qitemhandlers on exit
1314 smtp_init_spoolout();
1315 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1317 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1321 /* return our Subversion id for the Log */
1322 return "smtpeventclient";