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 void smtp_try(OneQueItem *MyQItem,
928 MailQEntry *MyQEntry,
930 int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
932 SmtpOutMsg * SendMsg;
934 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
935 memset(SendMsg, 0, sizeof(SmtpOutMsg));
936 SendMsg->IO.sock = (-1);
937 SendMsg->n = MsgCount++;
938 SendMsg->MyQEntry = MyQEntry;
939 SendMsg->MyQItem = MyQItem;
941 SendMsg->msgtext = MsgText;
943 SendMsg->msgtext = NewStrBufDup(MsgText);
945 smtp_resolve_recipients(SendMsg);
946 resolve_mx_hosts(SendMsg);
947 connect_one_smtpsrv(SendMsg);
948 QueueEventContext(SendMsg,
950 connect_one_smtpsrv_xamine_result);
955 void NewMailQEntry(OneQueItem *Item)
957 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
958 memset(Item->Current, 0, sizeof(MailQEntry));
960 if (Item->MailQEntries == NULL)
961 Item->MailQEntries = NewHash(1, Flathash);
962 Item->Current->n = GetCount(Item->MailQEntries);
963 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
966 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
968 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
971 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
973 if (Item->EnvelopeFrom == NULL)
974 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
975 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
978 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
980 if (Item->BounceTo == NULL)
981 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
982 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
985 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
987 if (Item->Current == NULL)
989 if (Item->Current->Recipient == NULL)
990 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
991 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
992 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
993 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
994 Item->Current = NULL; // TODO: is this always right?
998 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
1000 if (Item->Current == NULL)
1001 NewMailQEntry(Item);
1002 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
1003 Item->Current->nAttempts++;
1004 if (Item->Current->nAttempts > MaxAttempts) {
1008 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1011 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1013 if (Item->Current == NULL)
1014 NewMailQEntry(Item);
1015 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1016 Item->Current->nAttempts++;
1017 if (Item->Current->nAttempts > MaxAttempts) {
1022 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1023 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1025 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1026 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1027 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1028 Item->LastAttempt.retry = SMTP_RETRY_MAX;
1038 * Called by smtp_do_queue() to handle an individual message.
1040 void smtp_do_procmsg(long msgnum, void *userdata) {
1041 struct CtdlMessage *msg = NULL;
1044 OneQueItem *MyQItem;
1051 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
1052 ///strcpy(envelope_from, "");
1054 msg = CtdlFetchMessage(msgnum, 1);
1056 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
1060 pch = instr = msg->cm_fields['M'];
1062 /* Strip out the headers (no not amd any other non-instruction) line */
1063 while (pch != NULL) {
1064 pch = strchr(pch, '\n');
1065 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1070 PlainQItem = NewStrBufPlain(instr, -1);
1071 CtdlFreeMessage(msg);
1072 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1073 FreeStrBuf(&PlainQItem);
1075 if (MyQItem == NULL) {
1076 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
1077 return; /* s.b. else is already processing... */
1081 * Postpone delivery if we've already tried recently.
1083 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1084 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1086 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1087 citthread_mutex_lock(&ActiveQItemsLock);
1089 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1090 DeleteEntryFromHash(ActiveQItems, It);
1092 citthread_mutex_unlock(&ActiveQItemsLock);
1093 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1096 }// TODO: reenable me.*/
1099 * Bail out if there's no actual message associated with this
1101 if (MyQItem->MessageID < 0L) {
1102 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
1103 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1104 citthread_mutex_lock(&ActiveQItemsLock);
1106 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1107 DeleteEntryFromHash(ActiveQItems, It);
1109 citthread_mutex_unlock(&ActiveQItemsLock);
1111 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1115 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1116 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
1118 MailQEntry *ThisItem = vQE;
1119 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
1123 CountActiveQueueEntries(MyQItem);
1124 if (MyQItem->ActiveDeliveries > 0)
1127 StrBuf *Msg = smtp_load_msg(MyQItem);
1128 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1129 while ((i <= MyQItem->ActiveDeliveries) &&
1130 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1132 MailQEntry *ThisItem = vQE;
1133 if (ThisItem->Active == 1) {
1134 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1135 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1143 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1144 citthread_mutex_lock(&ActiveQItemsLock);
1146 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1147 DeleteEntryFromHash(ActiveQItems, It);
1149 citthread_mutex_unlock(&ActiveQItemsLock);
1151 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1153 // TODO: bounce & delete?
1159 /*****************************************************************************/
1160 /* SMTP UTILITY COMMANDS */
1161 /*****************************************************************************/
1163 void cmd_smtp(char *argbuf) {
1170 if (CtdlAccessCheck(ac_aide)) return;
1172 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1174 if (!strcasecmp(cmd, "mx")) {
1175 extract_token(node, argbuf, 1, '|', sizeof node);
1176 num_mxhosts = getmx(buf, node);
1177 cprintf("%d %d MX hosts listed for %s\n",
1178 LISTING_FOLLOWS, num_mxhosts, node);
1179 for (i=0; i<num_mxhosts; ++i) {
1180 extract_token(node, buf, i, '|', sizeof node);
1181 cprintf("%s\n", node);
1187 else if (!strcasecmp(cmd, "runqueue")) {
1189 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1194 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1201 * smtp_queue_thread()
1203 * Run through the queue sending out messages.
1205 void *smtp_queue_thread(void *arg) {
1206 int num_processed = 0;
1207 struct CitContext smtp_queue_CC;
1209 CtdlThreadSleep(10);
1211 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1212 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1213 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1215 while (!CtdlThreadCheckStop()) {
1217 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1219 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1220 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1223 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1225 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1226 CtdlThreadSleep(60);
1229 CtdlClearSystemContext();
1235 * Initialize the SMTP outbound queue
1237 void smtp_init_spoolout(void) {
1238 struct ctdlroom qrbuf;
1241 * Create the room. This will silently fail if the room already
1242 * exists, and that's perfectly ok, because we want it to exist.
1244 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1247 * Make sure it's set to be a "system room" so it doesn't show up
1248 * in the <K>nown rooms list for Aides.
1250 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1251 qrbuf.QRflags2 |= QR2_SYSTEM;
1252 CtdlPutRoomLock(&qrbuf);
1257 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1258 SMTPC_read_greeting,
1259 SMTPC_read_EHLO_reply,
1260 SMTPC_read_HELO_reply,
1261 SMTPC_read_auth_reply,
1262 SMTPC_read_FROM_reply,
1263 SMTPC_read_RCPT_reply,
1264 SMTPC_read_DATAcmd_reply,
1266 SMTPC_read_data_body_reply,
1267 SMTPC_read_QUIT_reply
1270 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1271 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1278 SMTPC_send_data_body,
1279 SMTPC_send_terminate_data_body,
1283 eNextState SMTP_C_Terminate(void *Data)
1285 SmtpOutMsg *pMsg = Data;
1286 FinalizeMessageSend(pMsg);
1290 eNextState SMTP_C_Timeout(void *Data)
1292 SmtpOutMsg *pMsg = Data;
1293 FinalizeMessageSend(pMsg);
1297 eNextState SMTP_C_ConnFail(void *Data)
1299 SmtpOutMsg *pMsg = Data;
1300 FinalizeMessageSend(pMsg);
1304 eNextState SMTP_C_DispatchReadDone(void *Data)
1306 SmtpOutMsg *pMsg = Data;
1307 eNextState rc = ReadHandlers[pMsg->State](pMsg);
1312 eNextState SMTP_C_DispatchWriteDone(void *Data)
1314 SmtpOutMsg *pMsg = Data;
1315 return SendHandlers[pMsg->State](pMsg);
1319 eNextState SMTP_C_MXLookup(void *Data)
1326 CTDL_MODULE_INIT(smtp_eventclient)
1328 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1331 ActiveQItems = NewHash(1, Flathash);
1332 citthread_mutex_init(&ActiveQItemsLock, NULL);
1334 QItemHandlers = NewHash(0, NULL);
1336 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1337 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1338 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1339 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1340 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1341 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1342 ///submitted /TODO: flush qitemhandlers on exit
1345 smtp_init_spoolout();
1346 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1348 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1352 /* return our Subversion id for the Log */
1353 return "smtpeventclient";