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_Timeout(void *Data);
224 eNextState SMTP_C_ConnFail(void *Data);
225 eNextState SMTP_C_DispatchReadDone(void *Data);
226 eNextState SMTP_C_DispatchWriteDone(void *Data);
227 eNextState SMTP_C_Terminate(void *Data);
228 eNextState SMTP_C_MXLookup(void *Data);
230 typedef eNextState (*SMTPReadHandler)(SmtpOutMsg *Msg);
231 typedef eNextState (*SMTPSendHandler)(SmtpOutMsg *Msg);
236 void FreeQueItem(OneQueItem **Item)
238 DeleteHash(&(*Item)->MailQEntries);
239 FreeStrBuf(&(*Item)->EnvelopeFrom);
240 FreeStrBuf(&(*Item)->BounceTo);
244 void HFreeQueItem(void *Item)
246 FreeQueItem((OneQueItem**)&Item);
250 /* inspect recipients with a status of:
251 * - 0 (no delivery yet attempted)
252 * - 3/4 (transient errors
253 * were experienced and it's time to try again)
255 int CountActiveQueueEntries(OneQueItem *MyQItem)
262 MyQItem->ActiveDeliveries = 0;
263 It = GetNewHashPos(MyQItem->MailQEntries, 0);
264 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
266 MailQEntry *ThisItem = vQE;
267 if ((ThisItem->Status == 0) ||
268 (ThisItem->Status == 3) ||
269 (ThisItem->Status == 4))
271 MyQItem->ActiveDeliveries++;
272 ThisItem->Active = 1;
275 ThisItem->Active = 0;
278 return MyQItem->ActiveDeliveries;
281 OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
284 const char *pLine = NULL;
289 Item = (OneQueItem*)malloc(sizeof(OneQueItem));
290 memset(Item, 0, sizeof(OneQueItem));
291 Item->LastAttempt.retry = SMTP_RETRY_INTERVAL;
292 Item->MessageID = -1;
293 Item->QueMsgID = QueMsgID;
295 citthread_mutex_lock(&ActiveQItemsLock);
296 if (GetHash(ActiveQItems,
297 IKEY(Item->QueMsgID),
300 /* WHOOPS. somebody else is already working on this. */
301 citthread_mutex_unlock(&ActiveQItemsLock);
306 /* mark our claim on this. */
308 IKEY(Item->QueMsgID),
311 citthread_mutex_unlock(&ActiveQItemsLock);
315 Line = NewStrBufPlain(NULL, 128);
316 while (pLine != StrBufNOTNULL) {
317 const char *pItemPart = NULL;
320 StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
321 if (StrLength(Line) == 0) continue;
322 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
323 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
326 H = (QItemHandler) vHandler;
327 H(Item, Line, &pItemPart);
335 StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
343 QMessage = NewStrBufPlain(NULL, SIZ);
344 StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
346 // "attempted|%ld\n" "retry|%ld\n",, (long)time(NULL), (long)retry );
347 StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
348 StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
350 if (StrLength(MyQItem->BounceTo) > 0) {
351 StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
352 StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
355 if (StrLength(MyQItem->EnvelopeFrom) > 0) {
356 StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
357 StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
360 It = GetNewHashPos(MyQItem->MailQEntries, 0);
361 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
363 MailQEntry *ThisItem = vQE;
366 if (!ThisItem->Active)
367 continue; /* skip already sent ones from the spoolfile. */
369 for (i=0; i < ThisItem->nAttempts; i++) {
370 StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
371 StrBufAppendPrintf(QMessage, "%ld",
372 ThisItem->Attempts[i].retry);
374 StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
375 StrBufAppendPrintf(QMessage, "%ld",
376 ThisItem->Attempts[i].when);
378 StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
379 StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
380 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
381 StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
382 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
383 StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
386 StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);
390 void FinalizeMessageSend(SmtpOutMsg *Msg)
392 int IDestructQueItem;
395 citthread_mutex_lock(&ActiveQItemsLock);
396 Msg->MyQItem->ActiveDeliveries--;
397 IDestructQueItem = Msg->MyQItem->ActiveDeliveries == 0;
398 citthread_mutex_unlock(&ActiveQItemsLock);
400 if (IDestructQueItem) {
404 nRemain = CountActiveQueueEntries(Msg->MyQItem);
407 MsgData = SerializeQueueItem(Msg->MyQItem);
409 * Uncompleted delivery instructions remain, so delete the old
410 * instructions and replace with the updated ones.
412 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, "");
414 /* Generate 'bounce' messages * /
415 smtp_do_bounce(instr); */
417 struct CtdlMessage *msg;
418 msg = malloc(sizeof(struct CtdlMessage));
419 memset(msg, 0, sizeof(struct CtdlMessage));
420 msg->cm_magic = CTDLMESSAGE_MAGIC;
421 msg->cm_anon_type = MES_NORMAL;
422 msg->cm_format_type = FMT_RFC822;
423 msg->cm_fields['M'] = SmashStrBuf(&MsgData);
425 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
426 CtdlFreeMessage(msg);
428 It = GetNewHashPos(Msg->MyQItem->MailQEntries, 0);
429 citthread_mutex_lock(&ActiveQItemsLock);
431 GetHashPosFromKey(ActiveQItems, IKEY(Msg->MyQItem->MessageID), It);
432 DeleteEntryFromHash(ActiveQItems, It);
434 citthread_mutex_unlock(&ActiveQItemsLock);
438 /// TODO : else free message...
440 DeleteSmtpOutMsg(Msg);
443 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
445 eReadState Finished = eBufferNotEmpty;
447 while (Finished == eBufferNotEmpty) {
448 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
451 case eMustReadMore: /// read new from socket...
454 case eBufferNotEmpty: /* shouldn't happen... */
455 case eReadSuccess: /// done for now...
456 if (StrLength(IO->IOBuf) < 4)
458 if (ChrPtr(IO->IOBuf)[3] == '-')
459 Finished = eBufferNotEmpty;
463 case eReadFail: /// WHUT?
472 * this one has to have the context for loading the message via the redirect buffer...
474 StrBuf *smtp_load_msg(OneQueItem *MyQItem)
479 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
480 CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
481 SendMsg = CCC->redirect_buffer;
482 CCC->redirect_buffer = NULL;
483 if ((StrLength(SendMsg) > 0) &&
484 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
485 CtdlLogPrintf(CTDL_WARNING,
486 "SMTP client[%ld]: Possible problem: message did not "
487 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
488 MsgCount, //yes uncool, but best choice here...
489 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
490 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
496 int smtp_resolve_recipients(SmtpOutMsg *SendMsg)
504 /* Parse out the host portion of the recipient address */
505 process_rfc822_addr(ChrPtr(SendMsg->MyQEntry->Recipient),
510 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
511 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
512 /* If no envelope_from is supplied, extract one from the message */
513 if ( (SendMsg->envelope_from == NULL) ||
514 (IsEmptyStr(SendMsg->envelope_from)) ) {
515 SendMsg->mailfrom[0] = '\0';
517 ptr = ChrPtr(SendMsg->msgtext);
519 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
522 if (!strncasecmp(buf, "From:", 5)) {
523 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
524 striplt(SendMsg->mailfrom);
525 for (i=0; SendMsg->mailfrom[i]; ++i) {
526 if (!isprint(SendMsg->mailfrom[i])) {
527 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
532 /* Strip out parenthesized names */
535 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
536 if (SendMsg->mailfrom[i] == '(') lp = i;
537 if (SendMsg->mailfrom[i] == ')') rp = i;
539 if ((lp>0)&&(rp>lp)) {
540 strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
543 /* Prefer brokketized names */
546 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
547 if (SendMsg->mailfrom[i] == '<') lp = i;
548 if (SendMsg->mailfrom[i] == '>') rp = i;
550 if ( (lp>=0) && (rp>lp) ) {
551 SendMsg->mailfrom[rp] = 0;
552 memmove(SendMsg->mailfrom,
553 &SendMsg->mailfrom[lp + 1],
559 } while (scan_done == 0);
560 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
561 stripallbut(SendMsg->mailfrom, '<', '>');
562 SendMsg->envelope_from = SendMsg->mailfrom;
568 void resolve_mx_hosts(SmtpOutMsg *SendMsg)
570 /// well this is blocking and sux, but libevent jsut supports async dns since v2
571 /* Figure out what mail exchanger host we have to connect to */
572 SendMsg->num_mxhosts = getmx(SendMsg->mxhosts, SendMsg->node);
573 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Number of MX hosts for <%s> is %d [%s]\n",
574 SendMsg->n, SendMsg->node, SendMsg->num_mxhosts, SendMsg->mxhosts);
575 if (SendMsg->num_mxhosts < 1) {
576 SendMsg->MyQEntry->Status = 5;
577 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
578 "No MX hosts found for <%s>", SendMsg->node);
579 return; ///////TODO: abort!
584 #define SMTP_ERROR(WHICH_ERR, ERRSTR) {SendMsg->MyQEntry->Status = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, HKEY(ERRSTR), 0); return eAbort; }
585 #define SMTP_VERROR(WHICH_ERR) { SendMsg->MyQEntry->Status = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, &ChrPtr(SendMsg->IO.IOBuf)[4], -1, 0); return eAbort; }
586 #define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(SendMsg->IO.IOBuf)[0] == WHICH_STATE)
588 #define SMTP_DBG_SEND() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: > %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
589 #define SMTP_DBG_READ() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: < %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
591 void connect_one_smtpsrv(SmtpOutMsg *SendMsg)
596 extract_token(buf, SendMsg->mxhosts, SendMsg->n_mx, '|', sizeof(buf));
597 strcpy(SendMsg->mx_user, "");
598 strcpy(SendMsg->mx_pass, "");
599 if (num_tokens(buf, '@') > 1) {
600 strcpy (SendMsg->mx_user, buf);
601 endpart = strrchr(SendMsg->mx_user, '@');
603 strcpy (SendMsg->mx_host, endpart + 1);
604 endpart = strrchr(SendMsg->mx_user, ':');
605 if (endpart != NULL) {
606 strcpy(SendMsg->mx_pass, endpart+1);
611 strcpy (SendMsg->mx_host, buf);
612 endpart = strrchr(SendMsg->mx_host, ':');
615 strcpy(SendMsg->mx_port, endpart + 1);
618 strcpy(SendMsg->mx_port, "25");
620 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connecting to %s : %s ...\n",
621 SendMsg->n, SendMsg->mx_host, SendMsg->mx_port);
626 int connect_one_smtpsrv_xamine_result(void *Ctx)
628 SmtpOutMsg *SendMsg = Ctx;
630 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connecting [%s:%s]!\n",
631 SendMsg->n, SendMsg->mx_host, SendMsg->mx_port);
633 SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
634 SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
635 SendMsg->IO.IOBuf = NewStrBuf();
636 SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
639 SendMsg->IO.SendBuf.fd =
640 SendMsg->IO.RecvBuf.fd =
641 SendMsg->IO.sock = sock_connect(SendMsg->mx_host, SendMsg->mx_port);
643 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
644 "Could not connect: %s", 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 InitEventIO(&SendMsg->IO, SendMsg,
667 SMTP_C_DispatchReadDone,
668 SMTP_C_DispatchWriteDone,
673 SMTP_C_ReadServerStatus,
679 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
681 /* Process the SMTP greeting from the server */
684 if (!SMTP_IS_STATE('2')) {
685 if (SMTP_IS_STATE('4'))
693 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
695 /* At this point we know we are talking to a real SMTP server */
697 /* Do a EHLO command. If it fails, try the HELO command. */
698 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
699 "EHLO %s\r\n", config.c_fqdn);
705 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
709 if (SMTP_IS_STATE('2')) {
711 if (IsEmptyStr(SendMsg->mx_user))
712 SendMsg->State ++; /* Skip auth... */
714 /* else we fall back to 'helo' */
718 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
720 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
721 "HELO %s\r\n", config.c_fqdn);
727 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
731 if (!SMTP_IS_STATE('2')) {
732 if (SMTP_IS_STATE('4'))
737 if (!IsEmptyStr(SendMsg->mx_user))
738 SendMsg->State ++; /* Skip auth... */
742 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
747 /* Do an AUTH command if necessary */
748 sprintf(buf, "%s%c%s%c%s",
749 SendMsg->mx_user, '\0',
750 SendMsg->mx_user, '\0',
752 CtdlEncodeBase64(encoded, buf,
753 strlen(SendMsg->mx_user) +
754 strlen(SendMsg->mx_user) +
755 strlen(SendMsg->mx_pass) + 2, 0);
756 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
757 "AUTH PLAIN %s\r\n", encoded);
763 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
765 /* Do an AUTH command if necessary */
769 if (!SMTP_IS_STATE('2')) {
770 if (SMTP_IS_STATE('4'))
778 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
780 /* previous command succeeded, now try the MAIL FROM: command */
781 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
782 "MAIL FROM:<%s>\r\n",
783 SendMsg->envelope_from);
789 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
793 if (!SMTP_IS_STATE('2')) {
794 if (SMTP_IS_STATE('4'))
803 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
805 /* MAIL succeeded, now try the RCPT To: command */
806 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
807 "RCPT TO:<%s@%s>\r\n",
815 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
819 if (!SMTP_IS_STATE('2')) {
820 if (SMTP_IS_STATE('4'))
828 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
830 /* RCPT succeeded, now try the DATA command */
831 StrBufPlain(SendMsg->IO.SendBuf.Buf,
838 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
842 if (!SMTP_IS_STATE('3')) {
843 if (SMTP_IS_STATE('4'))
851 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
854 /* If we reach this point, the server is expecting data.*/
856 Buf = SendMsg->IO.SendBuf.Buf;
857 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
858 SendMsg->msgtext = Buf;
859 //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
865 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
869 Buf = SendMsg->IO.SendBuf.Buf;
870 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
871 SendMsg->msgtext = Buf;
873 StrBufPlain(SendMsg->IO.SendBuf.Buf,
880 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
884 if (!SMTP_IS_STATE('2')) {
885 if (SMTP_IS_STATE('4'))
892 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
893 &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
894 StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
895 SendMsg->MyQEntry->Status = 2;
899 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
901 StrBufPlain(SendMsg->IO.SendBuf.Buf,
908 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
912 CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
913 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
914 return eTerminateConnection;
917 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
922 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
927 eNextState smtp_resolve_one_smtpsrv_start(void *data)
930 SmtpOutMsg * SendMsg = IO->Data;
931 /// resolve_mx_hosts(SendMsg);
932 //// connect_one_smtpsrv_xamine_result
934 connect_one_smtpsrv(SendMsg);
937 int resolve_mx_records(void *Ctx)
939 SmtpOutMsg * SendMsg = Ctx;
940 if (!QueueQuery(ns_t_mx,
943 smtp_resolve_one_smtpsrv_start))
949 void smtp_try(OneQueItem *MyQItem,
950 MailQEntry *MyQEntry,
952 int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
954 SmtpOutMsg * SendMsg;
956 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
957 memset(SendMsg, 0, sizeof(SmtpOutMsg));
958 SendMsg->IO.sock = (-1);
959 SendMsg->n = MsgCount++;
960 SendMsg->MyQEntry = MyQEntry;
961 SendMsg->MyQItem = MyQItem;
963 SendMsg->msgtext = MsgText;
965 SendMsg->msgtext = NewStrBufDup(MsgText);
967 smtp_resolve_recipients(SendMsg);
969 QueueEventContext(SendMsg,
978 void NewMailQEntry(OneQueItem *Item)
980 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
981 memset(Item->Current, 0, sizeof(MailQEntry));
983 if (Item->MailQEntries == NULL)
984 Item->MailQEntries = NewHash(1, Flathash);
985 Item->Current->n = GetCount(Item->MailQEntries);
986 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
989 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
991 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
994 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
996 if (Item->EnvelopeFrom == NULL)
997 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
998 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
1001 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
1003 if (Item->BounceTo == NULL)
1004 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
1005 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
1008 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
1010 if (Item->Current == NULL)
1011 NewMailQEntry(Item);
1012 if (Item->Current->Recipient == NULL)
1013 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
1014 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
1015 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
1016 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
1017 Item->Current = NULL; // TODO: is this always right?
1021 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
1023 if (Item->Current == NULL)
1024 NewMailQEntry(Item);
1025 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
1026 Item->Current->nAttempts++;
1027 if (Item->Current->nAttempts > MaxAttempts) {
1031 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1034 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1036 if (Item->Current == NULL)
1037 NewMailQEntry(Item);
1038 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1039 Item->Current->nAttempts++;
1040 if (Item->Current->nAttempts > MaxAttempts) {
1045 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1046 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1048 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1049 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1050 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1051 Item->LastAttempt.retry = SMTP_RETRY_MAX;
1061 * Called by smtp_do_queue() to handle an individual message.
1063 void smtp_do_procmsg(long msgnum, void *userdata) {
1064 struct CtdlMessage *msg = NULL;
1067 OneQueItem *MyQItem;
1074 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
1075 ///strcpy(envelope_from, "");
1077 msg = CtdlFetchMessage(msgnum, 1);
1079 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
1083 pch = instr = msg->cm_fields['M'];
1085 /* Strip out the headers (no not amd any other non-instruction) line */
1086 while (pch != NULL) {
1087 pch = strchr(pch, '\n');
1088 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1093 PlainQItem = NewStrBufPlain(instr, -1);
1094 CtdlFreeMessage(msg);
1095 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1096 FreeStrBuf(&PlainQItem);
1098 if (MyQItem == NULL) {
1099 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
1100 return; /* s.b. else is already processing... */
1104 * Postpone delivery if we've already tried recently.
1106 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1107 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1109 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1110 citthread_mutex_lock(&ActiveQItemsLock);
1112 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1113 DeleteEntryFromHash(ActiveQItems, It);
1115 citthread_mutex_unlock(&ActiveQItemsLock);
1116 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1119 }// TODO: reenable me.*/
1122 * Bail out if there's no actual message associated with this
1124 if (MyQItem->MessageID < 0L) {
1125 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
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?
1138 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1139 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
1141 MailQEntry *ThisItem = vQE;
1142 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
1146 CountActiveQueueEntries(MyQItem);
1147 if (MyQItem->ActiveDeliveries > 0)
1150 StrBuf *Msg = smtp_load_msg(MyQItem);
1151 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1152 while ((i <= MyQItem->ActiveDeliveries) &&
1153 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1155 MailQEntry *ThisItem = vQE;
1156 if (ThisItem->Active == 1) {
1157 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1158 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1166 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1167 citthread_mutex_lock(&ActiveQItemsLock);
1169 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1170 DeleteEntryFromHash(ActiveQItems, It);
1172 citthread_mutex_unlock(&ActiveQItemsLock);
1174 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1176 // TODO: bounce & delete?
1182 /*****************************************************************************/
1183 /* SMTP UTILITY COMMANDS */
1184 /*****************************************************************************/
1186 void cmd_smtp(char *argbuf) {
1193 if (CtdlAccessCheck(ac_aide)) return;
1195 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1197 if (!strcasecmp(cmd, "mx")) {
1198 extract_token(node, argbuf, 1, '|', sizeof node);
1199 num_mxhosts = getmx(buf, node);
1200 cprintf("%d %d MX hosts listed for %s\n",
1201 LISTING_FOLLOWS, num_mxhosts, node);
1202 for (i=0; i<num_mxhosts; ++i) {
1203 extract_token(node, buf, i, '|', sizeof node);
1204 cprintf("%s\n", node);
1210 else if (!strcasecmp(cmd, "runqueue")) {
1212 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1217 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1224 * smtp_queue_thread()
1226 * Run through the queue sending out messages.
1228 void *smtp_queue_thread(void *arg) {
1229 int num_processed = 0;
1230 struct CitContext smtp_queue_CC;
1232 CtdlThreadSleep(10);
1234 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1235 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1236 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1238 while (!CtdlThreadCheckStop()) {
1240 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1242 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1243 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1246 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1248 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1249 CtdlThreadSleep(60);
1252 CtdlClearSystemContext();
1258 * Initialize the SMTP outbound queue
1260 void smtp_init_spoolout(void) {
1261 struct ctdlroom qrbuf;
1264 * Create the room. This will silently fail if the room already
1265 * exists, and that's perfectly ok, because we want it to exist.
1267 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1270 * Make sure it's set to be a "system room" so it doesn't show up
1271 * in the <K>nown rooms list for Aides.
1273 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1274 qrbuf.QRflags2 |= QR2_SYSTEM;
1275 CtdlPutRoomLock(&qrbuf);
1280 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1281 SMTPC_read_greeting,
1282 SMTPC_read_EHLO_reply,
1283 SMTPC_read_HELO_reply,
1284 SMTPC_read_auth_reply,
1285 SMTPC_read_FROM_reply,
1286 SMTPC_read_RCPT_reply,
1287 SMTPC_read_DATAcmd_reply,
1289 SMTPC_read_data_body_reply,
1290 SMTPC_read_QUIT_reply
1293 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1294 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1301 SMTPC_send_data_body,
1302 SMTPC_send_terminate_data_body,
1306 eNextState SMTP_C_Terminate(void *Data)
1308 SmtpOutMsg *pMsg = Data;
1309 FinalizeMessageSend(pMsg);
1313 eNextState SMTP_C_Timeout(void *Data)
1315 SmtpOutMsg *pMsg = Data;
1316 FinalizeMessageSend(pMsg);
1320 eNextState SMTP_C_ConnFail(void *Data)
1322 SmtpOutMsg *pMsg = Data;
1323 FinalizeMessageSend(pMsg);
1327 eNextState SMTP_C_DispatchReadDone(void *Data)
1329 SmtpOutMsg *pMsg = Data;
1330 eNextState rc = ReadHandlers[pMsg->State](pMsg);
1335 eNextState SMTP_C_DispatchWriteDone(void *Data)
1337 SmtpOutMsg *pMsg = Data;
1338 return SendHandlers[pMsg->State](pMsg);
1342 eNextState SMTP_C_MXLookup(void *Data)
1349 CTDL_MODULE_INIT(smtp_eventclient)
1351 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1354 ActiveQItems = NewHash(1, Flathash);
1355 citthread_mutex_init(&ActiveQItemsLock, NULL);
1357 QItemHandlers = NewHash(0, NULL);
1359 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1360 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1361 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1362 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1363 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1364 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1365 ///submitted /TODO: flush qitemhandlers on exit
1368 smtp_init_spoolout();
1369 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1371 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1375 /* return our Subversion id for the Log */
1376 return "smtpeventclient";