2 * This module is an SMTP and ESMTP implementation for the Citadel system.
3 * It is compliant with all of the following:
5 * RFC 821 - Simple Mail Transfer Protocol
6 * RFC 876 - Survey of SMTP Implementations
7 * RFC 1047 - Duplicate messages and SMTP
8 * RFC 1652 - 8 bit MIME
9 * RFC 1869 - Extended Simple Mail Transfer Protocol
10 * RFC 1870 - SMTP Service Extension for Message Size Declaration
11 * RFC 2033 - Local Mail Transfer Protocol
12 * RFC 2197 - SMTP Service Extension for Command Pipelining
13 * RFC 2476 - Message Submission
14 * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
15 * RFC 2554 - SMTP Service Extension for Authentication
16 * RFC 2821 - Simple Mail Transfer Protocol
17 * RFC 2822 - Internet Message Format
18 * RFC 2920 - SMTP Service Extension for Command Pipelining
20 * The VRFY and EXPN commands have been removed from this implementation
21 * because nobody uses these commands anymore, except for spammers.
23 * Copyright (c) 1998-2009 by the citadel.org team
25 * This program is free software; you can redistribute it and/or modify
26 * it under the terms of the GNU General Public License as published by
27 * the Free Software Foundation; either version 3 of the License, or
28 * (at your option) any later version.
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 * GNU General Public License for more details.
35 * You should have received a copy of the GNU General Public License
36 * along with this program; if not, write to the Free Software
37 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
49 #include <sys/types.h>
52 #if TIME_WITH_SYS_TIME
53 # include <sys/time.h>
57 # include <sys/time.h>
66 #include <sys/socket.h>
67 #include <netinet/in.h>
68 #include <arpa/inet.h>
69 #include <libcitadel.h>
72 #include "citserver.h"
79 #include "internet_addressing.h"
82 #include "clientsocket.h"
83 #include "locate_host.h"
84 #include "citadel_dirs.h"
86 #include "ctdl_module.h"
88 #include "smtp_util.h"
89 #include "event_client.h"
91 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
92 HashList *QItemHandlers = NULL;
94 citthread_mutex_t ActiveQItemsLock;
95 HashList *ActiveQItems = NULL;
97 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
99 /*****************************************************************************/
100 /* SMTP CLIENT (Queue Management) STUFF */
101 /*****************************************************************************/
103 #define MaxAttempts 15
104 typedef struct _delivery_attempt {
109 typedef struct _mailq_entry {
110 DeliveryAttempt Attempts[MaxAttempts];
113 StrBuf *StatusMessage;
118 void FreeMailQEntry(void *qv)
121 FreeStrBuf(&Q->Recipient);
122 FreeStrBuf(&Q->StatusMessage);
126 typedef struct queueitem {
130 HashList *MailQEntries;
131 MailQEntry *Current; /* copy of the currently parsed item in the MailQEntries list; if null add a new one. */
132 DeliveryAttempt LastAttempt;
133 long ActiveDeliveries;
134 StrBuf *EnvelopeFrom;
137 typedef void (*QItemHandler)(OneQueItem *Item, StrBuf *Line, const char **Pos);
139 /*****************************************************************************/
140 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
141 /*****************************************************************************/
143 typedef enum _eSMTP_C_States {
157 const long SMTP_C_ReadTimeouts[eMaxSMTPC] = {
158 90, /* Greeting... */
166 900, /* end of body... */
170 const long SMTP_C_SendTimeouts[eMaxSMTPC] = {
173 const char *ReadErrors[eMaxSMTPC] = {
174 "Connection broken during SMTP conversation",
175 "Connection broken during SMTP EHLO",
176 "Connection broken during SMTP HELO",
177 "Connection broken during SMTP AUTH",
178 "Connection broken during SMTP MAIL FROM",
179 "Connection broken during SMTP RCPT",
180 "Connection broken during SMTP DATA",
181 "Connection broken during SMTP message transmit",
182 ""/* quit reply, don't care. */
186 typedef struct _stmp_out_msg {
187 MailQEntry *MyQEntry;
192 eSMTP_C_States State;
194 /// int SMTPstatus; ->MyQEntry->Status
210 /// char addr[SIZ]; -> MyQEntry->Recipient
211 /// char dsn[1024]; -> MyQEntry->StatusMessage
212 /// char envelope_from_buf[1024]; MyQItem->EnvelopeFrom
215 void DeleteSmtpOutMsg(void *v)
218 FreeStrBuf(&Msg->msgtext);
219 FreeAsyncIOContents(&Msg->IO);
223 eNextState SMTP_C_DispatchReadDone(void *Data);
224 eNextState SMTP_C_DispatchWriteDone(void *Data);
225 eNextState SMTP_C_Terminate(void *Data);
227 typedef eNextState (*SMTPReadHandler)(SmtpOutMsg *Msg);
228 typedef eNextState (*SMTPSendHandler)(SmtpOutMsg *Msg);
233 void FreeQueItem(OneQueItem **Item)
235 DeleteHash(&(*Item)->MailQEntries);
236 FreeStrBuf(&(*Item)->EnvelopeFrom);
237 FreeStrBuf(&(*Item)->BounceTo);
241 void HFreeQueItem(void *Item)
243 FreeQueItem((OneQueItem**)&Item);
247 /* inspect recipients with a status of:
248 * - 0 (no delivery yet attempted)
249 * - 3/4 (transient errors
250 * were experienced and it's time to try again)
252 int CountActiveQueueEntries(OneQueItem *MyQItem)
259 MyQItem->ActiveDeliveries = 0;
260 It = GetNewHashPos(MyQItem->MailQEntries, 0);
261 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
263 MailQEntry *ThisItem = vQE;
264 if ((ThisItem->Status == 0) ||
265 (ThisItem->Status == 3) ||
266 (ThisItem->Status == 4))
268 MyQItem->ActiveDeliveries++;
269 ThisItem->Active = 1;
272 ThisItem->Active = 0;
275 return MyQItem->ActiveDeliveries;
278 OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
281 const char *pLine = NULL;
286 Item = (OneQueItem*)malloc(sizeof(OneQueItem));
287 memset(Item, 0, sizeof(OneQueItem));
288 Item->LastAttempt.retry = SMTP_RETRY_INTERVAL;
289 Item->MessageID = -1;
290 Item->QueMsgID = QueMsgID;
292 citthread_mutex_lock(&ActiveQItemsLock);
293 if (GetHash(ActiveQItems,
294 IKEY(Item->QueMsgID),
297 /* WHOOPS. somebody else is already working on this. */
298 citthread_mutex_unlock(&ActiveQItemsLock);
303 /* mark our claim on this. */
305 IKEY(Item->QueMsgID),
308 citthread_mutex_unlock(&ActiveQItemsLock);
312 Line = NewStrBufPlain(NULL, 128);
313 while (pLine != StrBufNOTNULL) {
314 const char *pItemPart = NULL;
317 StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
318 if (StrLength(Line) == 0) continue;
319 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
320 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
323 H = (QItemHandler) vHandler;
324 H(Item, Line, &pItemPart);
332 StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
340 QMessage = NewStrBufPlain(NULL, SIZ);
341 StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
343 // "attempted|%ld\n" "retry|%ld\n",, (long)time(NULL), (long)retry );
344 StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
345 StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
347 if (StrLength(MyQItem->BounceTo) > 0) {
348 StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
349 StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
352 if (StrLength(MyQItem->EnvelopeFrom) > 0) {
353 StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
354 StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
357 It = GetNewHashPos(MyQItem->MailQEntries, 0);
358 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
360 MailQEntry *ThisItem = vQE;
363 if (!ThisItem->Active)
364 continue; /* skip already sent ones from the spoolfile. */
366 for (i=0; i < ThisItem->nAttempts; i++) {
367 StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
368 StrBufAppendPrintf(QMessage, "%ld",
369 ThisItem->Attempts[i].retry);
371 StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
372 StrBufAppendPrintf(QMessage, "%ld",
373 ThisItem->Attempts[i].when);
375 StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
376 StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
377 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
378 StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
379 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
380 StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
383 StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);
387 void FinalizeMessageSend(SmtpOutMsg *Msg)
389 int IDestructQueItem;
392 citthread_mutex_lock(&ActiveQItemsLock);
393 Msg->MyQItem->ActiveDeliveries--;
394 IDestructQueItem = Msg->MyQItem->ActiveDeliveries == 0;
395 citthread_mutex_unlock(&ActiveQItemsLock);
397 if (IDestructQueItem) {
401 nRemain = CountActiveQueueEntries(Msg->MyQItem);
404 MsgData = SerializeQueueItem(Msg->MyQItem);
406 * Uncompleted delivery instructions remain, so delete the old
407 * instructions and replace with the updated ones.
409 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, "");
411 /* Generate 'bounce' messages * /
412 smtp_do_bounce(instr); */
414 struct CtdlMessage *msg;
415 msg = malloc(sizeof(struct CtdlMessage));
416 memset(msg, 0, sizeof(struct CtdlMessage));
417 msg->cm_magic = CTDLMESSAGE_MAGIC;
418 msg->cm_anon_type = MES_NORMAL;
419 msg->cm_format_type = FMT_RFC822;
420 msg->cm_fields['M'] = SmashStrBuf(&MsgData);
422 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
423 CtdlFreeMessage(msg);
425 It = GetNewHashPos(Msg->MyQItem->MailQEntries, 0);
426 citthread_mutex_lock(&ActiveQItemsLock);
428 GetHashPosFromKey(ActiveQItems, IKEY(Msg->MyQItem->MessageID), It);
429 DeleteEntryFromHash(ActiveQItems, It);
431 citthread_mutex_unlock(&ActiveQItemsLock);
435 /// TODO : else free message...
437 DeleteSmtpOutMsg(Msg);
440 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
442 eReadState Finished = eBufferNotEmpty;
444 while (Finished == eBufferNotEmpty) {
445 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
448 case eMustReadMore: /// read new from socket...
451 case eBufferNotEmpty: /* shouldn't happen... */
452 case eReadSuccess: /// done for now...
453 if (StrLength(IO->IOBuf) < 4)
455 if (ChrPtr(IO->IOBuf)[3] == '-')
456 Finished = eBufferNotEmpty;
460 case eReadFail: /// WHUT?
469 * this one has to have the context for loading the message via the redirect buffer...
471 StrBuf *smtp_load_msg(OneQueItem *MyQItem)
476 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
477 CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
478 SendMsg = CCC->redirect_buffer;
479 CCC->redirect_buffer = NULL;
480 if ((StrLength(SendMsg) > 0) &&
481 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
482 CtdlLogPrintf(CTDL_WARNING,
483 "SMTP client[%ld]: Possible problem: message did not "
484 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
485 MsgCount, //yes uncool, but best choice here...
486 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
487 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
493 int smtp_resolve_recipients(SmtpOutMsg *SendMsg)
501 /* Parse out the host portion of the recipient address */
502 process_rfc822_addr(ChrPtr(SendMsg->MyQEntry->Recipient),
507 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
508 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
509 /* If no envelope_from is supplied, extract one from the message */
510 if ( (SendMsg->envelope_from == NULL) ||
511 (IsEmptyStr(SendMsg->envelope_from)) ) {
512 SendMsg->mailfrom[0] = '\0';
514 ptr = ChrPtr(SendMsg->msgtext);
516 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
519 if (!strncasecmp(buf, "From:", 5)) {
520 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
521 striplt(SendMsg->mailfrom);
522 for (i=0; SendMsg->mailfrom[i]; ++i) {
523 if (!isprint(SendMsg->mailfrom[i])) {
524 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
529 /* Strip out parenthesized names */
532 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
533 if (SendMsg->mailfrom[i] == '(') lp = i;
534 if (SendMsg->mailfrom[i] == ')') rp = i;
536 if ((lp>0)&&(rp>lp)) {
537 strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
540 /* Prefer brokketized names */
543 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
544 if (SendMsg->mailfrom[i] == '<') lp = i;
545 if (SendMsg->mailfrom[i] == '>') rp = i;
547 if ( (lp>=0) && (rp>lp) ) {
548 SendMsg->mailfrom[rp] = 0;
549 memmove(SendMsg->mailfrom,
550 &SendMsg->mailfrom[lp + 1],
556 } while (scan_done == 0);
557 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
558 stripallbut(SendMsg->mailfrom, '<', '>');
559 SendMsg->envelope_from = SendMsg->mailfrom;
565 void resolve_mx_hosts(SmtpOutMsg *SendMsg)
567 /// well this is blocking and sux, but libevent jsut supports async dns since v2
568 /* Figure out what mail exchanger host we have to connect to */
569 SendMsg->num_mxhosts = getmx(SendMsg->mxhosts, SendMsg->node);
570 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Number of MX hosts for <%s> is %d [%s]\n",
571 SendMsg->n, SendMsg->node, SendMsg->num_mxhosts, SendMsg->mxhosts);
572 if (SendMsg->num_mxhosts < 1) {
573 SendMsg->MyQEntry->Status = 5;
574 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
575 "No MX hosts found for <%s>", SendMsg->node);
576 return; ///////TODO: abort!
581 #define SMTP_ERROR(WHICH_ERR, ERRSTR) {SendMsg->MyQEntry->Status = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, HKEY(ERRSTR), 0); return eAbort; }
582 #define SMTP_VERROR(WHICH_ERR) { SendMsg->MyQEntry->Status = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, &ChrPtr(SendMsg->IO.IOBuf)[4], -1, 0); return eAbort; }
583 #define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(SendMsg->IO.IOBuf)[0] == WHICH_STATE)
585 #define SMTP_DBG_SEND() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: > %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
586 #define SMTP_DBG_READ() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: < %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
588 void connect_one_smtpsrv(SmtpOutMsg *SendMsg)
593 extract_token(buf, SendMsg->mxhosts, SendMsg->n_mx, '|', sizeof(buf));
594 strcpy(SendMsg->mx_user, "");
595 strcpy(SendMsg->mx_pass, "");
596 if (num_tokens(buf, '@') > 1) {
597 strcpy (SendMsg->mx_user, buf);
598 endpart = strrchr(SendMsg->mx_user, '@');
600 strcpy (SendMsg->mx_host, endpart + 1);
601 endpart = strrchr(SendMsg->mx_user, ':');
602 if (endpart != NULL) {
603 strcpy(SendMsg->mx_pass, endpart+1);
608 strcpy (SendMsg->mx_host, buf);
609 endpart = strrchr(SendMsg->mx_host, ':');
612 strcpy(SendMsg->mx_port, endpart + 1);
615 strcpy(SendMsg->mx_port, "25");
617 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connecting to %s : %s ...\n",
618 SendMsg->n, SendMsg->mx_host, SendMsg->mx_port);
623 int connect_one_smtpsrv_xamine_result(void *Ctx)
625 SmtpOutMsg *SendMsg = Ctx;
627 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connecting [%s:%s]!\n",
628 SendMsg->n, SendMsg->mx_host, SendMsg->mx_port);
630 SendMsg->IO.SendBuf.fd =
631 SendMsg->IO.RecvBuf.fd =
632 SendMsg->IO.sock = sock_connect(SendMsg->mx_host, SendMsg->mx_port);
634 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
635 "Could not connect: %s", strerror(errno));
636 if (SendMsg->IO.sock >= 0)
638 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld:%ld]: connected!\n", SendMsg->n, SendMsg->IO.sock);
640 fdflags = fcntl(SendMsg->IO.sock, F_GETFL);
642 CtdlLogPrintf(CTDL_DEBUG,
643 "SMTP client[%ld]: unable to get socket flags! %s \n",
644 SendMsg->n, strerror(errno));
645 fdflags = fdflags | O_NONBLOCK;
646 if (fcntl(SendMsg->IO.sock, F_SETFL, fdflags) < 0)
647 CtdlLogPrintf(CTDL_DEBUG,
648 "SMTP client[%ld]: unable to set socket nonblocking flags! %s \n",
649 SendMsg->n, strerror(errno));
651 if (SendMsg->IO.sock < 0) {
653 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
654 strerror(errno), -1);
657 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
658 "Unable to connect to %s : %s\n",
659 SendMsg->mx_host, SendMsg->mx_port);
662 /// hier: naechsten mx ausprobieren.
663 if (SendMsg->IO.sock < 0) {
664 SendMsg->MyQEntry->Status = 4; /* dsn is already filled in */
665 //// hier: abbrechen & bounce.
670 SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
671 SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
672 SendMsg->IO.IOBuf = NewStrBuf();
673 InitEventIO(&SendMsg->IO, SendMsg,
674 SMTP_C_DispatchReadDone,
675 SMTP_C_DispatchWriteDone,
677 SMTP_C_ReadServerStatus,
682 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
684 /* Process the SMTP greeting from the server */
687 if (!SMTP_IS_STATE('2')) {
688 if (SMTP_IS_STATE('4'))
696 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
698 /* At this point we know we are talking to a real SMTP server */
700 /* Do a EHLO command. If it fails, try the HELO command. */
701 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
702 "EHLO %s\r\n", config.c_fqdn);
708 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
712 if (SMTP_IS_STATE('2')) {
714 if (IsEmptyStr(SendMsg->mx_user))
715 SendMsg->State ++; /* Skip auth... */
717 /* else we fall back to 'helo' */
721 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
723 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
724 "HELO %s\r\n", config.c_fqdn);
730 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
734 if (!SMTP_IS_STATE('2')) {
735 if (SMTP_IS_STATE('4'))
740 if (!IsEmptyStr(SendMsg->mx_user))
741 SendMsg->State ++; /* Skip auth... */
745 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
750 /* Do an AUTH command if necessary */
751 sprintf(buf, "%s%c%s%c%s",
752 SendMsg->mx_user, '\0',
753 SendMsg->mx_user, '\0',
755 CtdlEncodeBase64(encoded, buf,
756 strlen(SendMsg->mx_user) +
757 strlen(SendMsg->mx_user) +
758 strlen(SendMsg->mx_pass) + 2, 0);
759 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
760 "AUTH PLAIN %s\r\n", encoded);
766 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
768 /* Do an AUTH command if necessary */
772 if (!SMTP_IS_STATE('2')) {
773 if (SMTP_IS_STATE('4'))
781 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
783 /* previous command succeeded, now try the MAIL FROM: command */
784 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
785 "MAIL FROM:<%s>\r\n",
786 SendMsg->envelope_from);
792 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
796 if (!SMTP_IS_STATE('2')) {
797 if (SMTP_IS_STATE('4'))
806 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
808 /* MAIL succeeded, now try the RCPT To: command */
809 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
810 "RCPT TO:<%s@%s>\r\n",
818 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
822 if (!SMTP_IS_STATE('2')) {
823 if (SMTP_IS_STATE('4'))
831 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
833 /* RCPT succeeded, now try the DATA command */
834 StrBufPlain(SendMsg->IO.SendBuf.Buf,
841 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
845 if (!SMTP_IS_STATE('3')) {
846 if (SMTP_IS_STATE('4'))
854 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
857 /* If we reach this point, the server is expecting data.*/
859 Buf = SendMsg->IO.SendBuf.Buf;
860 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
861 SendMsg->msgtext = Buf;
862 //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
868 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
872 Buf = SendMsg->IO.SendBuf.Buf;
873 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
874 SendMsg->msgtext = Buf;
876 StrBufPlain(SendMsg->IO.SendBuf.Buf,
883 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
887 if (!SMTP_IS_STATE('2')) {
888 if (SMTP_IS_STATE('4'))
895 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
896 &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
897 StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
898 SendMsg->MyQEntry->Status = 2;
902 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
904 StrBufPlain(SendMsg->IO.SendBuf.Buf,
911 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
915 CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
916 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
917 return eTerminateConnection;
920 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
925 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
930 void smtp_try(OneQueItem *MyQItem,
931 MailQEntry *MyQEntry,
933 int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
935 SmtpOutMsg * SendMsg;
937 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
938 memset(SendMsg, 0, sizeof(SmtpOutMsg));
939 SendMsg->IO.sock = (-1);
940 SendMsg->n = MsgCount++;
941 SendMsg->MyQEntry = MyQEntry;
942 SendMsg->MyQItem = MyQItem;
944 SendMsg->msgtext = MsgText;
946 SendMsg->msgtext = NewStrBufDup(MsgText);
948 smtp_resolve_recipients(SendMsg);
949 resolve_mx_hosts(SendMsg);
950 connect_one_smtpsrv(SendMsg);
951 QueueEventContext(SendMsg,
953 connect_one_smtpsrv_xamine_result);
958 void NewMailQEntry(OneQueItem *Item)
960 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
961 memset(Item->Current, 0, sizeof(MailQEntry));
963 if (Item->MailQEntries == NULL)
964 Item->MailQEntries = NewHash(1, Flathash);
965 Item->Current->n = GetCount(Item->MailQEntries);
966 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
969 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
971 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
974 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
976 if (Item->EnvelopeFrom == NULL)
977 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
978 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
981 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
983 if (Item->BounceTo == NULL)
984 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
985 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
988 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
990 if (Item->Current == NULL)
992 if (Item->Current->Recipient == NULL)
993 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
994 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
995 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
996 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
997 Item->Current = NULL; // TODO: is this always right?
1001 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
1003 if (Item->Current == NULL)
1004 NewMailQEntry(Item);
1005 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
1006 Item->Current->nAttempts++;
1007 if (Item->Current->nAttempts > MaxAttempts) {
1011 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1014 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1016 if (Item->Current == NULL)
1017 NewMailQEntry(Item);
1018 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1019 Item->Current->nAttempts++;
1020 if (Item->Current->nAttempts > MaxAttempts) {
1025 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1026 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1028 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1029 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1030 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1031 Item->LastAttempt.retry = SMTP_RETRY_MAX;
1041 * Called by smtp_do_queue() to handle an individual message.
1043 void smtp_do_procmsg(long msgnum, void *userdata) {
1044 struct CtdlMessage *msg = NULL;
1047 OneQueItem *MyQItem;
1054 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
1055 ///strcpy(envelope_from, "");
1057 msg = CtdlFetchMessage(msgnum, 1);
1059 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
1063 pch = instr = msg->cm_fields['M'];
1065 /* Strip out the headers (no not amd any other non-instruction) line */
1066 while (pch != NULL) {
1067 pch = strchr(pch, '\n');
1068 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1073 PlainQItem = NewStrBufPlain(instr, -1);
1074 CtdlFreeMessage(msg);
1075 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1076 FreeStrBuf(&PlainQItem);
1078 if (MyQItem == NULL) {
1079 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
1080 return; /* s.b. else is already processing... */
1084 * Postpone delivery if we've already tried recently.
1086 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1087 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1089 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1090 citthread_mutex_lock(&ActiveQItemsLock);
1092 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1093 DeleteEntryFromHash(ActiveQItems, It);
1095 citthread_mutex_unlock(&ActiveQItemsLock);
1096 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1099 }// TODO: reenable me.*/
1102 * Bail out if there's no actual message associated with this
1104 if (MyQItem->MessageID < 0L) {
1105 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
1106 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1107 citthread_mutex_lock(&ActiveQItemsLock);
1109 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1110 DeleteEntryFromHash(ActiveQItems, It);
1112 citthread_mutex_unlock(&ActiveQItemsLock);
1114 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1118 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1119 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
1121 MailQEntry *ThisItem = vQE;
1122 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
1126 CountActiveQueueEntries(MyQItem);
1127 if (MyQItem->ActiveDeliveries > 0)
1130 StrBuf *Msg = smtp_load_msg(MyQItem);
1131 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1132 while ((i <= MyQItem->ActiveDeliveries) &&
1133 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1135 MailQEntry *ThisItem = vQE;
1136 if (ThisItem->Active == 1) {
1137 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1138 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1146 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1147 citthread_mutex_lock(&ActiveQItemsLock);
1149 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1150 DeleteEntryFromHash(ActiveQItems, It);
1152 citthread_mutex_unlock(&ActiveQItemsLock);
1154 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1156 // TODO: bounce & delete?
1162 /*****************************************************************************/
1163 /* SMTP UTILITY COMMANDS */
1164 /*****************************************************************************/
1166 void cmd_smtp(char *argbuf) {
1173 if (CtdlAccessCheck(ac_aide)) return;
1175 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1177 if (!strcasecmp(cmd, "mx")) {
1178 extract_token(node, argbuf, 1, '|', sizeof node);
1179 num_mxhosts = getmx(buf, node);
1180 cprintf("%d %d MX hosts listed for %s\n",
1181 LISTING_FOLLOWS, num_mxhosts, node);
1182 for (i=0; i<num_mxhosts; ++i) {
1183 extract_token(node, buf, i, '|', sizeof node);
1184 cprintf("%s\n", node);
1190 else if (!strcasecmp(cmd, "runqueue")) {
1192 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1197 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1204 * smtp_queue_thread()
1206 * Run through the queue sending out messages.
1208 void *smtp_queue_thread(void *arg) {
1209 int num_processed = 0;
1210 struct CitContext smtp_queue_CC;
1212 CtdlThreadSleep(10);
1214 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1215 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1216 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1218 while (!CtdlThreadCheckStop()) {
1220 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1222 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1223 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1226 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1228 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1229 CtdlThreadSleep(60);
1232 CtdlClearSystemContext();
1238 * Initialize the SMTP outbound queue
1240 void smtp_init_spoolout(void) {
1241 struct ctdlroom qrbuf;
1244 * Create the room. This will silently fail if the room already
1245 * exists, and that's perfectly ok, because we want it to exist.
1247 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1250 * Make sure it's set to be a "system room" so it doesn't show up
1251 * in the <K>nown rooms list for Aides.
1253 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1254 qrbuf.QRflags2 |= QR2_SYSTEM;
1255 CtdlPutRoomLock(&qrbuf);
1260 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1261 SMTPC_read_greeting,
1262 SMTPC_read_EHLO_reply,
1263 SMTPC_read_HELO_reply,
1264 SMTPC_read_auth_reply,
1265 SMTPC_read_FROM_reply,
1266 SMTPC_read_RCPT_reply,
1267 SMTPC_read_DATAcmd_reply,
1269 SMTPC_read_data_body_reply,
1270 SMTPC_read_QUIT_reply
1273 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1274 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1281 SMTPC_send_data_body,
1282 SMTPC_send_terminate_data_body,
1286 eNextState SMTP_C_Terminate(void *Data)
1288 SmtpOutMsg *pMsg = Data;
1289 FinalizeMessageSend(pMsg);
1292 eNextState SMTP_C_DispatchReadDone(void *Data)
1294 SmtpOutMsg *pMsg = Data;
1295 eNextState rc = ReadHandlers[pMsg->State](pMsg);
1300 eNextState SMTP_C_DispatchWriteDone(void *Data)
1302 SmtpOutMsg *pMsg = Data;
1303 return SendHandlers[pMsg->State](pMsg);
1309 CTDL_MODULE_INIT(smtp_eventclient)
1311 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1314 ActiveQItems = NewHash(1, Flathash);
1315 citthread_mutex_init(&ActiveQItemsLock, NULL);
1317 QItemHandlers = NewHash(0, NULL);
1319 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1320 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1321 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1322 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1323 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1324 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1325 ///submitted /TODO: flush qitemhandlers on exit
1328 smtp_init_spoolout();
1329 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1331 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1335 /* return our Subversion id for the Log */
1336 return "smtpeventclient";