2 * This module is an SMTP and ESMTP implementation for the Citadel system.
3 * It is compliant with all of the following:
5 * RFC 821 - Simple Mail Transfer Protocol
6 * RFC 876 - Survey of SMTP Implementations
7 * RFC 1047 - Duplicate messages and SMTP
8 * RFC 1652 - 8 bit MIME
9 * RFC 1869 - Extended Simple Mail Transfer Protocol
10 * RFC 1870 - SMTP Service Extension for Message Size Declaration
11 * RFC 2033 - Local Mail Transfer Protocol
12 * RFC 2197 - SMTP Service Extension for Command Pipelining
13 * RFC 2476 - Message Submission
14 * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
15 * RFC 2554 - SMTP Service Extension for Authentication
16 * RFC 2821 - Simple Mail Transfer Protocol
17 * RFC 2822 - Internet Message Format
18 * RFC 2920 - SMTP Service Extension for Command Pipelining
20 * The VRFY and EXPN commands have been removed from this implementation
21 * because nobody uses these commands anymore, except for spammers.
23 * Copyright (c) 1998-2009 by the citadel.org team
25 * This program is free software; you can redistribute it and/or modify
26 * it under the terms of the GNU General Public License as published by
27 * the Free Software Foundation; either version 3 of the License, or
28 * (at your option) any later version.
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 * GNU General Public License for more details.
35 * You should have received a copy of the GNU General Public License
36 * along with this program; if not, write to the Free Software
37 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
49 #include <sys/types.h>
52 #if TIME_WITH_SYS_TIME
53 # include <sys/time.h>
57 # include <sys/time.h>
66 #include <sys/socket.h>
67 #include <netinet/in.h>
68 #include <arpa/inet.h>
69 #include <libcitadel.h>
72 #include "citserver.h"
79 #include "internet_addressing.h"
82 #include "clientsocket.h"
83 #include "locate_host.h"
84 #include "citadel_dirs.h"
86 #include "ctdl_module.h"
88 #include "smtp_util.h"
89 #include "event_client.h"
91 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
92 HashList *QItemHandlers = NULL;
94 citthread_mutex_t ActiveQItemsLock;
95 HashList *ActiveQItems = NULL;
97 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
99 /*****************************************************************************/
100 /* SMTP CLIENT (Queue Management) STUFF */
101 /*****************************************************************************/
103 #define MaxAttempts 15
104 typedef struct _delivery_attempt {
109 typedef struct _mailq_entry {
110 DeliveryAttempt Attempts[MaxAttempts];
113 StrBuf *StatusMessage;
118 void FreeMailQEntry(void *qv)
121 FreeStrBuf(&Q->Recipient);
122 FreeStrBuf(&Q->StatusMessage);
126 typedef struct queueitem {
130 HashList *MailQEntries;
131 MailQEntry *Current; /* copy of the currently parsed item in the MailQEntries list; if null add a new one. */
132 DeliveryAttempt LastAttempt;
133 long ActiveDeliveries;
134 StrBuf *EnvelopeFrom;
137 typedef void (*QItemHandler)(OneQueItem *Item, StrBuf *Line, const char **Pos);
139 /*****************************************************************************/
140 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
141 /*****************************************************************************/
143 typedef enum _eSMTP_C_States {
157 const long SMTP_C_ReadTimeouts[eMaxSMTPC] = {
158 90, /* Greeting... */
166 900, /* end of body... */
170 const long SMTP_C_SendTimeouts[eMaxSMTPC] = {
173 const char *ReadErrors[eMaxSMTPC] = {
174 "Connection broken during SMTP conversation",
175 "Connection broken during SMTP EHLO",
176 "Connection broken during SMTP HELO",
177 "Connection broken during SMTP AUTH",
178 "Connection broken during SMTP MAIL FROM",
179 "Connection broken during SMTP RCPT",
180 "Connection broken during SMTP DATA",
181 "Connection broken during SMTP message transmit",
182 ""/* quit reply, don't care. */
186 typedef struct _stmp_out_msg {
187 MailQEntry *MyQEntry;
192 eSMTP_C_States State;
194 struct ares_mx_reply *AllMX;
195 struct ares_mx_reply *CurrMX;
199 struct hostent *OneMX;
211 void DeleteSmtpOutMsg(void *v)
214 FreeStrBuf(&Msg->msgtext);
215 FreeAsyncIOContents(&Msg->IO);
219 eNextState SMTP_C_Timeout(void *Data);
220 eNextState SMTP_C_ConnFail(void *Data);
221 eNextState SMTP_C_DispatchReadDone(void *Data);
222 eNextState SMTP_C_DispatchWriteDone(void *Data);
223 eNextState SMTP_C_Terminate(void *Data);
224 eNextState SMTP_C_MXLookup(void *Data);
226 typedef eNextState (*SMTPReadHandler)(SmtpOutMsg *Msg);
227 typedef eNextState (*SMTPSendHandler)(SmtpOutMsg *Msg);
232 void FreeQueItem(OneQueItem **Item)
234 DeleteHash(&(*Item)->MailQEntries);
235 FreeStrBuf(&(*Item)->EnvelopeFrom);
236 FreeStrBuf(&(*Item)->BounceTo);
240 void HFreeQueItem(void *Item)
242 FreeQueItem((OneQueItem**)&Item);
246 /* inspect recipients with a status of:
247 * - 0 (no delivery yet attempted)
248 * - 3/4 (transient errors
249 * were experienced and it's time to try again)
251 int CountActiveQueueEntries(OneQueItem *MyQItem)
258 MyQItem->ActiveDeliveries = 0;
259 It = GetNewHashPos(MyQItem->MailQEntries, 0);
260 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
262 MailQEntry *ThisItem = vQE;
263 if ((ThisItem->Status == 0) ||
264 (ThisItem->Status == 3) ||
265 (ThisItem->Status == 4))
267 MyQItem->ActiveDeliveries++;
268 ThisItem->Active = 1;
271 ThisItem->Active = 0;
274 return MyQItem->ActiveDeliveries;
277 OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
280 const char *pLine = NULL;
285 Item = (OneQueItem*)malloc(sizeof(OneQueItem));
286 memset(Item, 0, sizeof(OneQueItem));
287 Item->LastAttempt.retry = SMTP_RETRY_INTERVAL;
288 Item->MessageID = -1;
289 Item->QueMsgID = QueMsgID;
291 citthread_mutex_lock(&ActiveQItemsLock);
292 if (GetHash(ActiveQItems,
293 IKEY(Item->QueMsgID),
296 /* WHOOPS. somebody else is already working on this. */
297 citthread_mutex_unlock(&ActiveQItemsLock);
302 /* mark our claim on this. */
304 IKEY(Item->QueMsgID),
307 citthread_mutex_unlock(&ActiveQItemsLock);
311 Line = NewStrBufPlain(NULL, 128);
312 while (pLine != StrBufNOTNULL) {
313 const char *pItemPart = NULL;
316 StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
317 if (StrLength(Line) == 0) continue;
318 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
319 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
322 H = (QItemHandler) vHandler;
323 H(Item, Line, &pItemPart);
331 StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
339 QMessage = NewStrBufPlain(NULL, SIZ);
340 StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
342 // "attempted|%ld\n" "retry|%ld\n",, (long)time(NULL), (long)retry );
343 StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
344 StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
346 if (StrLength(MyQItem->BounceTo) > 0) {
347 StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
348 StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
351 if (StrLength(MyQItem->EnvelopeFrom) > 0) {
352 StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
353 StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
356 It = GetNewHashPos(MyQItem->MailQEntries, 0);
357 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
359 MailQEntry *ThisItem = vQE;
362 if (!ThisItem->Active)
363 continue; /* skip already sent ones from the spoolfile. */
365 for (i=0; i < ThisItem->nAttempts; i++) {
366 StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
367 StrBufAppendPrintf(QMessage, "%ld",
368 ThisItem->Attempts[i].retry);
370 StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
371 StrBufAppendPrintf(QMessage, "%ld",
372 ThisItem->Attempts[i].when);
374 StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
375 StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
376 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
377 StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
378 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
379 StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
382 StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);
386 void FinalizeMessageSend(SmtpOutMsg *Msg)
388 int IDestructQueItem;
391 citthread_mutex_lock(&ActiveQItemsLock);
392 Msg->MyQItem->ActiveDeliveries--;
393 IDestructQueItem = Msg->MyQItem->ActiveDeliveries == 0;
394 citthread_mutex_unlock(&ActiveQItemsLock);
396 if (IDestructQueItem) {
400 nRemain = CountActiveQueueEntries(Msg->MyQItem);
403 MsgData = SerializeQueueItem(Msg->MyQItem);
405 * Uncompleted delivery instructions remain, so delete the old
406 * instructions and replace with the updated ones.
408 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, "");
410 /* Generate 'bounce' messages * /
411 smtp_do_bounce(instr); */
413 struct CtdlMessage *msg;
414 msg = malloc(sizeof(struct CtdlMessage));
415 memset(msg, 0, sizeof(struct CtdlMessage));
416 msg->cm_magic = CTDLMESSAGE_MAGIC;
417 msg->cm_anon_type = MES_NORMAL;
418 msg->cm_format_type = FMT_RFC822;
419 msg->cm_fields['M'] = SmashStrBuf(&MsgData);
421 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
422 CtdlFreeMessage(msg);
424 It = GetNewHashPos(Msg->MyQItem->MailQEntries, 0);
425 citthread_mutex_lock(&ActiveQItemsLock);
427 GetHashPosFromKey(ActiveQItems, IKEY(Msg->MyQItem->MessageID), It);
428 DeleteEntryFromHash(ActiveQItems, It);
430 citthread_mutex_unlock(&ActiveQItemsLock);
434 /// TODO : else free message...
436 DeleteSmtpOutMsg(Msg);
439 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
441 eReadState Finished = eBufferNotEmpty;
443 while (Finished == eBufferNotEmpty) {
444 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
447 case eMustReadMore: /// read new from socket...
450 case eBufferNotEmpty: /* shouldn't happen... */
451 case eReadSuccess: /// done for now...
452 if (StrLength(IO->IOBuf) < 4)
454 if (ChrPtr(IO->IOBuf)[3] == '-')
455 Finished = eBufferNotEmpty;
459 case eReadFail: /// WHUT?
468 * this one has to have the context for loading the message via the redirect buffer...
470 StrBuf *smtp_load_msg(OneQueItem *MyQItem)
475 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
476 CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
477 SendMsg = CCC->redirect_buffer;
478 CCC->redirect_buffer = NULL;
479 if ((StrLength(SendMsg) > 0) &&
480 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
481 CtdlLogPrintf(CTDL_WARNING,
482 "SMTP client[%ld]: Possible problem: message did not "
483 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
484 MsgCount, //yes uncool, but best choice here...
485 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
486 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
492 int smtp_resolve_recipients(SmtpOutMsg *SendMsg)
500 /* Parse out the host portion of the recipient address */
501 process_rfc822_addr(ChrPtr(SendMsg->MyQEntry->Recipient),
506 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
507 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
508 /* If no envelope_from is supplied, extract one from the message */
509 if ( (SendMsg->envelope_from == NULL) ||
510 (IsEmptyStr(SendMsg->envelope_from)) ) {
511 SendMsg->mailfrom[0] = '\0';
513 ptr = ChrPtr(SendMsg->msgtext);
515 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
518 if (!strncasecmp(buf, "From:", 5)) {
519 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
520 striplt(SendMsg->mailfrom);
521 for (i=0; SendMsg->mailfrom[i]; ++i) {
522 if (!isprint(SendMsg->mailfrom[i])) {
523 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
528 /* Strip out parenthesized names */
531 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
532 if (SendMsg->mailfrom[i] == '(') lp = i;
533 if (SendMsg->mailfrom[i] == ')') rp = i;
535 if ((lp>0)&&(rp>lp)) {
536 strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
539 /* Prefer brokketized names */
542 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
543 if (SendMsg->mailfrom[i] == '<') lp = i;
544 if (SendMsg->mailfrom[i] == '>') rp = i;
546 if ( (lp>=0) && (rp>lp) ) {
547 SendMsg->mailfrom[rp] = 0;
548 memmove(SendMsg->mailfrom,
549 &SendMsg->mailfrom[lp + 1],
555 } while (scan_done == 0);
556 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
557 stripallbut(SendMsg->mailfrom, '<', '>');
558 SendMsg->envelope_from = SendMsg->mailfrom;
565 #define SMTP_ERROR(WHICH_ERR, ERRSTR) {SendMsg->MyQEntry->Status = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, HKEY(ERRSTR), 0); return eAbort; }
566 #define SMTP_VERROR(WHICH_ERR) { SendMsg->MyQEntry->Status = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, &ChrPtr(SendMsg->IO.IOBuf)[4], -1, 0); return eAbort; }
567 #define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(SendMsg->IO.IOBuf)[0] == WHICH_STATE)
569 #define SMTP_DBG_SEND() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: > %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
570 #define SMTP_DBG_READ() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: < %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
573 void connect_one_smtpsrv_xamine_result(void *Ctx,
576 struct hostent *hostent)
578 SmtpOutMsg *SendMsg = Ctx;
580 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connecting [%s:%s]!\n",
581 SendMsg->n, SendMsg->mx_host, SendMsg->mx_port);
583 SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
584 SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
585 SendMsg->IO.IOBuf = NewStrBuf();
586 SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
589 SendMsg->IO.SendBuf.fd =
590 SendMsg->IO.RecvBuf.fd =
591 SendMsg->IO.sock = sock_connect(SendMsg->mx_host, SendMsg->mx_port);
593 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
594 "Could not connect: %s", strerror(errno));
597 if (SendMsg->IO.sock < 0) {
599 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
600 strerror(errno), -1);
603 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
604 "Unable to connect to %s : %s\n",
605 SendMsg->mx_host, SendMsg->mx_port);
608 /// hier: naechsten mx ausprobieren.
609 if (SendMsg->IO.sock < 0) {
610 SendMsg->MyQEntry->Status = 4; /* dsn is already filled in */
611 //// hier: abbrechen & bounce.
616 InitEventIO(&SendMsg->IO, SendMsg,
617 SMTP_C_DispatchReadDone,
618 SMTP_C_DispatchWriteDone,
623 SMTP_C_ReadServerStatus,
629 void get_one_mx_host_name_done(void *Ctx,
632 struct hostent *hostent)
634 SmtpOutMsg *SendMsg = Ctx;
635 if ((status == ARES_SUCCESS) && (hostent != NULL) ) {
637 SendMsg->IO.HEnt = hostent;
638 InitEventIO(&SendMsg->IO, SendMsg,
639 SMTP_C_DispatchReadDone,
640 SMTP_C_DispatchWriteDone,
644 SMTP_C_ReadServerStatus,
650 const char *DefaultMXPort = "25";
651 void connect_one_smtpsrv(SmtpOutMsg *SendMsg)
656 SendMsg->mx_port = DefaultMXPort;
659 *SendMsg->mx_user = '\0';
660 *SendMsg->mx_pass = '\0';
661 if (num_tokens(buf, '@') > 1) {
662 strcpy (SendMsg->mx_user, buf);
663 endpart = strrchr(SendMsg->mx_user, '@');
665 strcpy (SendMsg->mx_host, endpart + 1);
666 endpart = strrchr(SendMsg->mx_user, ':');
667 if (endpart != NULL) {
668 strcpy(SendMsg->mx_pass, endpart+1);
672 endpart = strrchr(SendMsg->mx_host, ':');
675 strcpy(SendMsg->mx_port, endpart + 1);
680 SendMsg->mx_host = SendMsg->CurrMX->host;
681 SendMsg->CurrMX = SendMsg->CurrMX->next;
683 CtdlLogPrintf(CTDL_DEBUG,
684 "SMTP client[%ld]: connecting to %s : %s ...\n",
689 ares_gethostbyname(SendMsg->IO.DNSChannel,
691 AF_INET6, /* it falls back to ipv4 in doubt... */
692 get_one_mx_host_name_done,
695 if (!QueueQuery(ns_t_a,
698 connect_one_smtpsrv_xamine_result))
706 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
708 /* Process the SMTP greeting from the server */
711 if (!SMTP_IS_STATE('2')) {
712 if (SMTP_IS_STATE('4'))
720 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
722 /* At this point we know we are talking to a real SMTP server */
724 /* Do a EHLO command. If it fails, try the HELO command. */
725 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
726 "EHLO %s\r\n", config.c_fqdn);
732 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
736 if (SMTP_IS_STATE('2')) {
738 if (IsEmptyStr(SendMsg->mx_user))
739 SendMsg->State ++; /* Skip auth... */
741 /* else we fall back to 'helo' */
745 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
747 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
748 "HELO %s\r\n", config.c_fqdn);
754 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
758 if (!SMTP_IS_STATE('2')) {
759 if (SMTP_IS_STATE('4'))
764 if (!IsEmptyStr(SendMsg->mx_user))
765 SendMsg->State ++; /* Skip auth... */
769 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
774 /* Do an AUTH command if necessary */
775 sprintf(buf, "%s%c%s%c%s",
776 SendMsg->mx_user, '\0',
777 SendMsg->mx_user, '\0',
779 CtdlEncodeBase64(encoded, buf,
780 strlen(SendMsg->mx_user) +
781 strlen(SendMsg->mx_user) +
782 strlen(SendMsg->mx_pass) + 2, 0);
783 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
784 "AUTH PLAIN %s\r\n", encoded);
790 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
792 /* Do an AUTH command if necessary */
796 if (!SMTP_IS_STATE('2')) {
797 if (SMTP_IS_STATE('4'))
805 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
807 /* previous command succeeded, now try the MAIL FROM: command */
808 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
809 "MAIL FROM:<%s>\r\n",
810 SendMsg->envelope_from);
816 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
820 if (!SMTP_IS_STATE('2')) {
821 if (SMTP_IS_STATE('4'))
830 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
832 /* MAIL succeeded, now try the RCPT To: command */
833 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
834 "RCPT TO:<%s@%s>\r\n",
842 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
846 if (!SMTP_IS_STATE('2')) {
847 if (SMTP_IS_STATE('4'))
855 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
857 /* RCPT succeeded, now try the DATA command */
858 StrBufPlain(SendMsg->IO.SendBuf.Buf,
865 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
869 if (!SMTP_IS_STATE('3')) {
870 if (SMTP_IS_STATE('4'))
878 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
881 /* If we reach this point, the server is expecting data.*/
883 Buf = SendMsg->IO.SendBuf.Buf;
884 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
885 SendMsg->msgtext = Buf;
886 //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
892 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
896 Buf = SendMsg->IO.SendBuf.Buf;
897 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
898 SendMsg->msgtext = Buf;
900 StrBufPlain(SendMsg->IO.SendBuf.Buf,
907 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
911 if (!SMTP_IS_STATE('2')) {
912 if (SMTP_IS_STATE('4'))
919 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
920 &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
921 StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
922 SendMsg->MyQEntry->Status = 2;
926 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
928 StrBufPlain(SendMsg->IO.SendBuf.Buf,
935 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
939 CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
940 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
941 return eTerminateConnection;
944 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
949 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
954 eNextState smtp_resolve_mx_done(void *data)
957 SmtpOutMsg * SendMsg = IO->Data;
959 //// connect_one_smtpsrv_xamine_result
960 SendMsg->CurrMX = SendMsg->AllMX = IO->VParsedDNSReply;
961 //// TODO: should we remove the current ares context???
962 connect_one_smtpsrv(SendMsg);
967 int resolve_mx_records(void *Ctx)
969 SmtpOutMsg * SendMsg = Ctx;
970 if (!QueueQuery(ns_t_mx,
973 smtp_resolve_mx_done))
975 SendMsg->MyQEntry->Status = 5;
976 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
977 "No MX hosts found for <%s>", SendMsg->node);
978 return; ///////TODO: abort!
982 void smtp_try(OneQueItem *MyQItem,
983 MailQEntry *MyQEntry,
985 int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
987 SmtpOutMsg * SendMsg;
989 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
990 memset(SendMsg, 0, sizeof(SmtpOutMsg));
991 SendMsg->IO.sock = (-1);
992 SendMsg->n = MsgCount++;
993 SendMsg->MyQEntry = MyQEntry;
994 SendMsg->MyQItem = MyQItem;
995 SendMsg->IO.Data = SendMsg;
997 SendMsg->msgtext = MsgText;
999 SendMsg->msgtext = NewStrBufDup(MsgText);
1001 smtp_resolve_recipients(SendMsg);
1003 QueueEventContext(SendMsg,
1005 resolve_mx_records);
1012 void NewMailQEntry(OneQueItem *Item)
1014 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
1015 memset(Item->Current, 0, sizeof(MailQEntry));
1017 if (Item->MailQEntries == NULL)
1018 Item->MailQEntries = NewHash(1, Flathash);
1019 Item->Current->n = GetCount(Item->MailQEntries);
1020 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
1023 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
1025 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
1028 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
1030 if (Item->EnvelopeFrom == NULL)
1031 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
1032 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
1035 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
1037 if (Item->BounceTo == NULL)
1038 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
1039 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
1042 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
1044 if (Item->Current == NULL)
1045 NewMailQEntry(Item);
1046 if (Item->Current->Recipient == NULL)
1047 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
1048 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
1049 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
1050 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
1051 Item->Current = NULL; // TODO: is this always right?
1055 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
1057 if (Item->Current == NULL)
1058 NewMailQEntry(Item);
1059 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
1060 Item->Current->nAttempts++;
1061 if (Item->Current->nAttempts > MaxAttempts) {
1065 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1068 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1070 if (Item->Current == NULL)
1071 NewMailQEntry(Item);
1072 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1073 Item->Current->nAttempts++;
1074 if (Item->Current->nAttempts > MaxAttempts) {
1079 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1080 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1082 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1083 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1084 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1085 Item->LastAttempt.retry = SMTP_RETRY_MAX;
1095 * Called by smtp_do_queue() to handle an individual message.
1097 void smtp_do_procmsg(long msgnum, void *userdata) {
1098 struct CtdlMessage *msg = NULL;
1101 OneQueItem *MyQItem;
1108 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
1109 ///strcpy(envelope_from, "");
1111 msg = CtdlFetchMessage(msgnum, 1);
1113 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
1117 pch = instr = msg->cm_fields['M'];
1119 /* Strip out the headers (no not amd any other non-instruction) line */
1120 while (pch != NULL) {
1121 pch = strchr(pch, '\n');
1122 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1127 PlainQItem = NewStrBufPlain(instr, -1);
1128 CtdlFreeMessage(msg);
1129 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1130 FreeStrBuf(&PlainQItem);
1132 if (MyQItem == NULL) {
1133 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
1134 return; /* s.b. else is already processing... */
1138 * Postpone delivery if we've already tried recently.
1140 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1141 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
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);
1150 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1153 }// TODO: reenable me.*/
1156 * Bail out if there's no actual message associated with this
1158 if (MyQItem->MessageID < 0L) {
1159 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
1160 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1161 citthread_mutex_lock(&ActiveQItemsLock);
1163 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1164 DeleteEntryFromHash(ActiveQItems, It);
1166 citthread_mutex_unlock(&ActiveQItemsLock);
1168 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1172 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1173 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
1175 MailQEntry *ThisItem = vQE;
1176 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
1180 CountActiveQueueEntries(MyQItem);
1181 if (MyQItem->ActiveDeliveries > 0)
1184 StrBuf *Msg = smtp_load_msg(MyQItem);
1185 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1186 while ((i <= MyQItem->ActiveDeliveries) &&
1187 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1189 MailQEntry *ThisItem = vQE;
1190 if (ThisItem->Active == 1) {
1191 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1192 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1200 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1201 citthread_mutex_lock(&ActiveQItemsLock);
1203 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1204 DeleteEntryFromHash(ActiveQItems, It);
1206 citthread_mutex_unlock(&ActiveQItemsLock);
1208 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1210 // TODO: bounce & delete?
1216 /*****************************************************************************/
1217 /* SMTP UTILITY COMMANDS */
1218 /*****************************************************************************/
1220 void cmd_smtp(char *argbuf) {
1227 if (CtdlAccessCheck(ac_aide)) return;
1229 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1231 if (!strcasecmp(cmd, "mx")) {
1232 extract_token(node, argbuf, 1, '|', sizeof node);
1233 num_mxhosts = getmx(buf, node);
1234 cprintf("%d %d MX hosts listed for %s\n",
1235 LISTING_FOLLOWS, num_mxhosts, node);
1236 for (i=0; i<num_mxhosts; ++i) {
1237 extract_token(node, buf, i, '|', sizeof node);
1238 cprintf("%s\n", node);
1244 else if (!strcasecmp(cmd, "runqueue")) {
1246 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1251 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1258 * smtp_queue_thread()
1260 * Run through the queue sending out messages.
1262 void *smtp_queue_thread(void *arg) {
1263 int num_processed = 0;
1264 struct CitContext smtp_queue_CC;
1266 CtdlThreadSleep(10);
1268 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1269 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1270 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1272 while (!CtdlThreadCheckStop()) {
1274 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1276 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1277 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1280 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1282 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1283 CtdlThreadSleep(60);
1286 CtdlClearSystemContext();
1292 * Initialize the SMTP outbound queue
1294 void smtp_init_spoolout(void) {
1295 struct ctdlroom qrbuf;
1298 * Create the room. This will silently fail if the room already
1299 * exists, and that's perfectly ok, because we want it to exist.
1301 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1304 * Make sure it's set to be a "system room" so it doesn't show up
1305 * in the <K>nown rooms list for Aides.
1307 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1308 qrbuf.QRflags2 |= QR2_SYSTEM;
1309 CtdlPutRoomLock(&qrbuf);
1314 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1315 SMTPC_read_greeting,
1316 SMTPC_read_EHLO_reply,
1317 SMTPC_read_HELO_reply,
1318 SMTPC_read_auth_reply,
1319 SMTPC_read_FROM_reply,
1320 SMTPC_read_RCPT_reply,
1321 SMTPC_read_DATAcmd_reply,
1323 SMTPC_read_data_body_reply,
1324 SMTPC_read_QUIT_reply
1327 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1328 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1335 SMTPC_send_data_body,
1336 SMTPC_send_terminate_data_body,
1340 eNextState SMTP_C_Terminate(void *Data)
1342 SmtpOutMsg *pMsg = Data;
1343 FinalizeMessageSend(pMsg);
1347 eNextState SMTP_C_Timeout(void *Data)
1349 SmtpOutMsg *pMsg = Data;
1350 FinalizeMessageSend(pMsg);
1354 eNextState SMTP_C_ConnFail(void *Data)
1356 SmtpOutMsg *pMsg = Data;
1357 FinalizeMessageSend(pMsg);
1361 eNextState SMTP_C_DispatchReadDone(void *Data)
1363 SmtpOutMsg *pMsg = Data;
1364 eNextState rc = ReadHandlers[pMsg->State](pMsg);
1369 eNextState SMTP_C_DispatchWriteDone(void *Data)
1371 SmtpOutMsg *pMsg = Data;
1372 return SendHandlers[pMsg->State](pMsg);
1376 eNextState SMTP_C_MXLookup(void *Data)
1383 CTDL_MODULE_INIT(smtp_eventclient)
1385 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1388 ActiveQItems = NewHash(1, Flathash);
1389 citthread_mutex_init(&ActiveQItemsLock, NULL);
1391 QItemHandlers = NewHash(0, NULL);
1393 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1394 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1395 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1396 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1397 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1398 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1399 ///submitted /TODO: flush qitemhandlers on exit
1402 smtp_init_spoolout();
1403 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1405 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1409 /* return our Subversion id for the Log */
1410 return "smtpeventclient";