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;
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);
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 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...
435 DeleteSmtpOutMsg(Msg);
438 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
440 eReadState Finished = eBufferNotEmpty;
442 while (Finished == eBufferNotEmpty) {
443 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
446 case eMustReadMore: /// read new from socket...
449 case eBufferNotEmpty: /* shouldn't happen... */
450 case eReadSuccess: /// done for now...
451 if (StrLength(IO->IOBuf) < 4)
453 if (ChrPtr(IO->IOBuf)[3] == '-')
454 Finished = eBufferNotEmpty;
458 case eReadFail: /// WHUT?
467 * this one has to have the context for loading the message via the redirect buffer...
469 StrBuf *smtp_load_msg(OneQueItem *MyQItem)
474 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
475 CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
476 SendMsg = CCC->redirect_buffer;
477 CCC->redirect_buffer = NULL;
478 if ((StrLength(SendMsg) > 0) &&
479 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
480 CtdlLogPrintf(CTDL_WARNING,
481 "SMTP client[%ld]: Possible problem: message did not "
482 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
483 MsgCount, //yes uncool, but best choice here...
484 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
485 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
491 int smtp_resolve_recipients(SmtpOutMsg *SendMsg)
499 /* Parse out the host portion of the recipient address */
500 process_rfc822_addr(ChrPtr(SendMsg->MyQEntry->Recipient),
505 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
506 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
507 /* If no envelope_from is supplied, extract one from the message */
508 if ( (SendMsg->envelope_from == NULL) ||
509 (IsEmptyStr(SendMsg->envelope_from)) ) {
510 SendMsg->mailfrom[0] = '\0';
512 ptr = ChrPtr(SendMsg->msgtext);
514 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
517 if (!strncasecmp(buf, "From:", 5)) {
518 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
519 striplt(SendMsg->mailfrom);
520 for (i=0; SendMsg->mailfrom[i]; ++i) {
521 if (!isprint(SendMsg->mailfrom[i])) {
522 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
527 /* Strip out parenthesized names */
530 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
531 if (SendMsg->mailfrom[i] == '(') lp = i;
532 if (SendMsg->mailfrom[i] == ')') rp = i;
534 if ((lp>0)&&(rp>lp)) {
535 strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
538 /* Prefer brokketized names */
541 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
542 if (SendMsg->mailfrom[i] == '<') lp = i;
543 if (SendMsg->mailfrom[i] == '>') rp = i;
545 if ( (lp>=0) && (rp>lp) ) {
546 SendMsg->mailfrom[rp] = 0;
547 memmove(SendMsg->mailfrom,
548 &SendMsg->mailfrom[lp + 1],
554 } while (scan_done == 0);
555 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
556 stripallbut(SendMsg->mailfrom, '<', '>');
557 SendMsg->envelope_from = SendMsg->mailfrom;
563 void resolve_mx_hosts(SmtpOutMsg *SendMsg)
565 /// well this is blocking and sux, but libevent jsut supports async dns since v2
566 /* Figure out what mail exchanger host we have to connect to */
567 SendMsg->num_mxhosts = getmx(SendMsg->mxhosts, SendMsg->node);
568 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Number of MX hosts for <%s> is %d [%s]\n",
569 SendMsg->n, SendMsg->node, SendMsg->num_mxhosts, SendMsg->mxhosts);
570 if (SendMsg->num_mxhosts < 1) {
571 SendMsg->SMTPstatus = 5;
572 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
573 "No MX hosts found for <%s>", SendMsg->node);
574 return; ///////TODO: abort!
579 #define SMTP_ERROR(WHICH_ERR, ERRSTR) {SendMsg->SMTPstatus = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, HKEY(ERRSTR), 0); return eAbort; }
580 #define SMTP_VERROR(WHICH_ERR) { SendMsg->SMTPstatus = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, &ChrPtr(SendMsg->IO.IOBuf)[4], -1, 0); return eAbort; }
581 #define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(SendMsg->IO.IOBuf)[0] == WHICH_STATE)
583 #define SMTP_DBG_SEND() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: > %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
584 #define SMTP_DBG_READ() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: < %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
586 void connect_one_smtpsrv(SmtpOutMsg *SendMsg)
591 extract_token(buf, SendMsg->mxhosts, SendMsg->n_mx, '|', sizeof(buf));
592 strcpy(SendMsg->mx_user, "");
593 strcpy(SendMsg->mx_pass, "");
594 if (num_tokens(buf, '@') > 1) {
595 strcpy (SendMsg->mx_user, buf);
596 endpart = strrchr(SendMsg->mx_user, '@');
598 strcpy (SendMsg->mx_host, endpart + 1);
599 endpart = strrchr(SendMsg->mx_user, ':');
600 if (endpart != NULL) {
601 strcpy(SendMsg->mx_pass, endpart+1);
606 strcpy (SendMsg->mx_host, buf);
607 endpart = strrchr(SendMsg->mx_host, ':');
610 strcpy(SendMsg->mx_port, endpart + 1);
613 strcpy(SendMsg->mx_port, "25");
615 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connecting to %s : %s ...\n",
616 SendMsg->n, SendMsg->mx_host, SendMsg->mx_port);
621 int connect_one_smtpsrv_xamine_result(void *Ctx)
623 SmtpOutMsg *SendMsg = Ctx;
624 SendMsg->IO.SendBuf.fd =
625 SendMsg->IO.RecvBuf.fd =
626 SendMsg->IO.sock = sock_connect(SendMsg->mx_host, SendMsg->mx_port);
628 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
629 "Could not connect: %s", strerror(errno));
630 if (SendMsg->IO.sock >= 0)
632 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connected!\n", SendMsg->n);
634 fdflags = fcntl(SendMsg->IO.sock, F_GETFL);
636 CtdlLogPrintf(CTDL_DEBUG,
637 "SMTP client[%ld]: unable to get socket flags! %s \n",
638 SendMsg->n, strerror(errno));
639 fdflags = fdflags | O_NONBLOCK;
640 if (fcntl(SendMsg->IO.sock, F_SETFL, fdflags) < 0)
641 CtdlLogPrintf(CTDL_DEBUG,
642 "SMTP client[%ld]: unable to set socket nonblocking flags! %s \n",
643 SendMsg->n, strerror(errno));
645 if (SendMsg->IO.sock < 0) {
647 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
648 strerror(errno), -1);
651 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
652 "Unable to connect to %s : %s\n",
653 SendMsg->mx_host, SendMsg->mx_port);
656 /// hier: naechsten mx ausprobieren.
657 if (SendMsg->IO.sock < 0) {
658 SendMsg->SMTPstatus = 4; /* dsn is already filled in */
659 //// hier: abbrechen & bounce.
664 SendMsg->IO.SendBuf.Buf = NewStrBuf();
665 SendMsg->IO.RecvBuf.Buf = NewStrBuf();
666 SendMsg->IO.IOBuf = NewStrBuf();
667 InitEventIO(&SendMsg->IO, SendMsg,
668 SMTP_C_DispatchReadDone,
669 SMTP_C_DispatchWriteDone,
670 SMTP_C_ReadServerStatus,
675 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
677 /* Process the SMTP greeting from the server */
680 if (!SMTP_IS_STATE('2')) {
681 if (SMTP_IS_STATE('4'))
689 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
691 /* At this point we know we are talking to a real SMTP server */
693 /* Do a EHLO command. If it fails, try the HELO command. */
694 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
695 "EHLO %s\r\n", config.c_fqdn);
701 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
705 if (SMTP_IS_STATE('2')) {
707 if (IsEmptyStr(SendMsg->mx_user))
708 SendMsg->State ++; /* Skip auth... */
710 /* else we fall back to 'helo' */
714 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
716 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
717 "HELO %s\r\n", config.c_fqdn);
723 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
727 if (!SMTP_IS_STATE('2')) {
728 if (SMTP_IS_STATE('4'))
733 if (!IsEmptyStr(SendMsg->mx_user))
734 SendMsg->State ++; /* Skip auth... */
738 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
743 /* Do an AUTH command if necessary */
744 sprintf(buf, "%s%c%s%c%s",
745 SendMsg->mx_user, '\0',
746 SendMsg->mx_user, '\0',
748 CtdlEncodeBase64(encoded, buf,
749 strlen(SendMsg->mx_user) +
750 strlen(SendMsg->mx_user) +
751 strlen(SendMsg->mx_pass) + 2, 0);
752 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
753 "AUTH PLAIN %s\r\n", encoded);
759 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
761 /* Do an AUTH command if necessary */
765 if (!SMTP_IS_STATE('2')) {
766 if (SMTP_IS_STATE('4'))
774 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
776 /* previous command succeeded, now try the MAIL FROM: command */
777 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
778 "MAIL FROM:<%s>\r\n",
779 SendMsg->envelope_from);
785 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
789 if (!SMTP_IS_STATE('2')) {
790 if (SMTP_IS_STATE('4'))
799 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
801 /* MAIL succeeded, now try the RCPT To: command */
802 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
803 "RCPT TO:<%s@%s>\r\n",
811 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
815 if (!SMTP_IS_STATE('2')) {
816 if (SMTP_IS_STATE('4'))
824 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
826 /* RCPT succeeded, now try the DATA command */
827 StrBufPlain(SendMsg->IO.SendBuf.Buf,
834 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
838 if (!SMTP_IS_STATE('3')) {
839 if (SMTP_IS_STATE('4'))
847 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
850 /* If we reach this point, the server is expecting data.*/
852 Buf = SendMsg->IO.SendBuf.Buf;
853 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
854 SendMsg->msgtext = Buf;
855 //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
861 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
865 Buf = SendMsg->IO.SendBuf.Buf;
866 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
867 SendMsg->msgtext = Buf;
869 StrBufPlain(SendMsg->IO.SendBuf.Buf,
876 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
880 if (!SMTP_IS_STATE('2')) {
881 if (SMTP_IS_STATE('4'))
888 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
889 &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
890 StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
891 SendMsg->SMTPstatus = 2;
895 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
897 StrBufPlain(SendMsg->IO.SendBuf.Buf,
904 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
908 CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
909 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
913 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
918 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
923 void smtp_try(OneQueItem *MyQItem,
924 MailQEntry *MyQEntry,
926 int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
928 SmtpOutMsg * SendMsg;
930 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
931 memset(SendMsg, 0, sizeof(SmtpOutMsg));
932 SendMsg->IO.sock = (-1);
933 SendMsg->n = MsgCount++;
934 SendMsg->MyQEntry = MyQEntry;
935 SendMsg->MyQItem = MyQItem;
936 SendMsg->msgtext = MsgText;
938 smtp_resolve_recipients(SendMsg);
939 resolve_mx_hosts(SendMsg);
940 connect_one_smtpsrv(SendMsg);
941 QueueEventContext(SendMsg,
943 connect_one_smtpsrv_xamine_result);
948 void NewMailQEntry(OneQueItem *Item)
950 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
951 memset(Item->Current, 0, sizeof(MailQEntry));
953 if (Item->MailQEntries == NULL)
954 Item->MailQEntries = NewHash(1, Flathash);
955 Item->Current->n = GetCount(Item->MailQEntries);
956 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
959 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
961 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
964 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
966 if (Item->EnvelopeFrom == NULL)
967 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
968 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
971 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
973 if (Item->BounceTo == NULL)
974 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
975 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
978 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
980 if (Item->Current == NULL)
982 if (Item->Current->Recipient == NULL)
983 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
984 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
985 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
986 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
990 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
992 if (Item->Current == NULL)
994 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
995 Item->Current->nAttempts++;
996 if (Item->Current->nAttempts > MaxAttempts) {
1000 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1003 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1005 if (Item->Current == NULL)
1006 NewMailQEntry(Item);
1007 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1008 Item->Current->nAttempts++;
1009 if (Item->Current->nAttempts > MaxAttempts) {
1014 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1015 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1017 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1018 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1019 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1020 Item->LastAttempt.retry = SMTP_RETRY_MAX;
1030 * Called by smtp_do_queue() to handle an individual message.
1032 void smtp_do_procmsg(long msgnum, void *userdata) {
1033 struct CtdlMessage *msg = NULL;
1036 OneQueItem *MyQItem;
1043 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: smtp_do_procmsg(%ld)\n", msgnum);
1044 ///strcpy(envelope_from, "");
1046 msg = CtdlFetchMessage(msgnum, 1);
1048 CtdlLogPrintf(CTDL_ERR, "SMTP client: tried %ld but no such message!\n", msgnum);
1052 pch = instr = msg->cm_fields['M'];
1054 /* Strip out the headers (no not amd any other non-instruction) line */
1055 while (pch != NULL) {
1056 pch = strchr(pch, '\n');
1057 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1062 PlainQItem = NewStrBufPlain(instr, -1);
1063 CtdlFreeMessage(msg);
1064 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1065 FreeStrBuf(&PlainQItem);
1067 if (MyQItem == NULL)
1068 return; /* s.b. else is already processing... */
1072 * Postpone delivery if we've already tried recently.
1074 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1075 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1077 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1078 citthread_mutex_lock(&ActiveQItemsLock);
1080 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1081 DeleteEntryFromHash(ActiveQItems, It);
1083 citthread_mutex_unlock(&ActiveQItemsLock);
1084 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1087 }// TODO: reenable me.*/
1090 * Bail out if there's no actual message associated with this
1092 if (MyQItem->MessageID < 0L) {
1093 CtdlLogPrintf(CTDL_ERR, "SMTP client: no 'msgid' directive found!\n");
1094 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1095 citthread_mutex_lock(&ActiveQItemsLock);
1097 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1098 DeleteEntryFromHash(ActiveQItems, It);
1100 citthread_mutex_unlock(&ActiveQItemsLock);
1102 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1106 CountActiveQueueEntries(MyQItem);
1107 if (MyQItem->ActiveDeliveries > 0)
1110 StrBuf *Msg = smtp_load_msg(MyQItem);
1111 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1112 while ((i <= MyQItem->ActiveDeliveries) &&
1113 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1115 MailQEntry *ThisItem = vQE;
1116 if (ThisItem->Active == 1) {
1117 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1118 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1126 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1127 citthread_mutex_lock(&ActiveQItemsLock);
1129 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1130 DeleteEntryFromHash(ActiveQItems, It);
1132 citthread_mutex_unlock(&ActiveQItemsLock);
1134 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1136 // TODO: bounce & delete?
1142 /*****************************************************************************/
1143 /* SMTP UTILITY COMMANDS */
1144 /*****************************************************************************/
1146 void cmd_smtp(char *argbuf) {
1153 if (CtdlAccessCheck(ac_aide)) return;
1155 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1157 if (!strcasecmp(cmd, "mx")) {
1158 extract_token(node, argbuf, 1, '|', sizeof node);
1159 num_mxhosts = getmx(buf, node);
1160 cprintf("%d %d MX hosts listed for %s\n",
1161 LISTING_FOLLOWS, num_mxhosts, node);
1162 for (i=0; i<num_mxhosts; ++i) {
1163 extract_token(node, buf, i, '|', sizeof node);
1164 cprintf("%s\n", node);
1170 else if (!strcasecmp(cmd, "runqueue")) {
1172 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1177 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1184 * smtp_queue_thread()
1186 * Run through the queue sending out messages.
1188 void *smtp_queue_thread(void *arg) {
1189 int num_processed = 0;
1190 struct CitContext smtp_queue_CC;
1192 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1193 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1194 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1196 while (!CtdlThreadCheckStop()) {
1198 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1200 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1201 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1204 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1206 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1207 CtdlThreadSleep(60);
1210 CtdlClearSystemContext();
1216 * Initialize the SMTP outbound queue
1218 void smtp_init_spoolout(void) {
1219 struct ctdlroom qrbuf;
1222 * Create the room. This will silently fail if the room already
1223 * exists, and that's perfectly ok, because we want it to exist.
1225 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1228 * Make sure it's set to be a "system room" so it doesn't show up
1229 * in the <K>nown rooms list for Aides.
1231 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1232 qrbuf.QRflags2 |= QR2_SYSTEM;
1233 CtdlPutRoomLock(&qrbuf);
1238 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1239 SMTPC_read_greeting,
1240 SMTPC_read_EHLO_reply,
1241 SMTPC_read_HELO_reply,
1242 SMTPC_read_auth_reply,
1243 SMTPC_read_FROM_reply,
1244 SMTPC_read_RCPT_reply,
1245 SMTPC_read_DATAcmd_reply,
1247 SMTPC_read_data_body_reply,
1248 SMTPC_read_QUIT_reply
1251 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1252 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1259 SMTPC_send_data_body,
1260 SMTPC_send_terminate_data_body,
1264 eNextState SMTP_C_DispatchReadDone(void *Data)
1266 SmtpOutMsg *pMsg = Data;
1267 eNextState rc = ReadHandlers[pMsg->State](pMsg);
1272 eNextState SMTP_C_DispatchWriteDone(void *Data)
1274 SmtpOutMsg *pMsg = Data;
1275 return SendHandlers[pMsg->State](pMsg);
1281 CTDL_MODULE_INIT(smtp_eventclient)
1283 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1286 ActiveQItems = NewHash(1, Flathash);
1287 citthread_mutex_init(&ActiveQItemsLock, NULL);
1289 QItemHandlers = NewHash(0, NULL);
1291 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1292 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1293 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1294 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1295 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1296 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1297 ///submitted /TODO: flush qitemhandlers on exit
1300 smtp_init_spoolout();
1301 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1303 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1307 /* return our Subversion id for the Log */
1308 return "smtpeventclient";