X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fmodules%2Fsmtp%2Fserv_smtpeventclient.c;h=dbe2c297db94d940f03d74078ac3ef5cd0b97100;hb=eee9a1429dd032114946aad9e70fd8d84afbe918;hp=7e2ffb8685e4f7f0741fbbe0a615ca6aba4c0528;hpb=e3534186f2a80bb2b5145c725ca4c9004927ce18;p=citadel.git diff --git a/citadel/modules/smtp/serv_smtpeventclient.c b/citadel/modules/smtp/serv_smtpeventclient.c index 7e2ffb868..dbe2c297d 100644 --- a/citadel/modules/smtp/serv_smtpeventclient.c +++ b/citadel/modules/smtp/serv_smtpeventclient.c @@ -16,25 +16,25 @@ * RFC 2821 - Simple Mail Transfer Protocol * RFC 2822 - Internet Message Format * RFC 2920 - SMTP Service Extension for Command Pipelining - * + * * The VRFY and EXPN commands have been removed from this implementation * because nobody uses these commands anymore, except for spammers. * - * Copyright (c) 1998-2009 by the citadel.org team + * Copyright (c) 1998-2012 by the citadel.org team * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. + * This program is open source software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * + * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * */ #include "sysdep.h" @@ -87,1285 +87,755 @@ #include "smtp_util.h" #include "event_client.h" +#include "smtpqueue.h" +#include "smtp_clienthandlers.h" -#ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT -HashList *QItemHandlers = NULL; - -citthread_mutex_t ActiveQItemsLock; -HashList *ActiveQItems = NULL; - -int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */ -int MsgCount = 0; -/*****************************************************************************/ -/* SMTP CLIENT (Queue Management) STUFF */ -/*****************************************************************************/ - -#define MaxAttempts 15 -typedef struct _delivery_attempt { - time_t when; - time_t retry; -}DeliveryAttempt; - -typedef struct _mailq_entry { - DeliveryAttempt Attempts[MaxAttempts]; - int nAttempts; - StrBuf *Recipient; - StrBuf *StatusMessage; - int Status; - int n; - int Active; -}MailQEntry; -void FreeMailQEntry(void *qv) -{ - MailQEntry *Q = qv; - FreeStrBuf(&Q->Recipient); - FreeStrBuf(&Q->StatusMessage); - free(Q); -} - -typedef struct queueitem { - long MessageID; - long QueMsgID; - int FailNow; - HashList *MailQEntries; - MailQEntry *Current; /* copy of the currently parsed item in the MailQEntries list; if null add a new one. */ - DeliveryAttempt LastAttempt; - long ActiveDeliveries; - StrBuf *EnvelopeFrom; - StrBuf *BounceTo; -} OneQueItem; -typedef void (*QItemHandler)(OneQueItem *Item, StrBuf *Line, const char **Pos); - -/*****************************************************************************/ -/* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */ -/*****************************************************************************/ - -typedef enum _eSMTP_C_States { - eConnect, - eEHLO, - eHELO, - eSMTPAuth, - eFROM, - eRCPT, - eDATA, - eDATABody, - eDATATerminateBody, - eQUIT, - eMaxSMTPC -} eSMTP_C_States; - -const long SMTP_C_ReadTimeouts[eMaxSMTPC] = { - 90, /* Greeting... */ - 30, /* EHLO */ - 30, /* HELO */ - 30, /* Auth */ - 30, /* From */ - 30, /* RCPT */ - 30, /* DATA */ - 90, /* DATABody */ - 900, /* end of body... */ - 30 /* QUIT */ -}; -/* -const long SMTP_C_SendTimeouts[eMaxSMTPC] = { - -}; */ -const char *ReadErrors[eMaxSMTPC] = { - "Connection broken during SMTP conversation", - "Connection broken during SMTP EHLO", - "Connection broken during SMTP HELO", - "Connection broken during SMTP AUTH", - "Connection broken during SMTP MAIL FROM", - "Connection broken during SMTP RCPT", - "Connection broken during SMTP DATA", - "Connection broken during SMTP message transmit", - ""/* quit reply, don't care. */ -}; - - -typedef struct _stmp_out_msg { - MailQEntry *MyQEntry; - OneQueItem *MyQItem; - long n; - AsyncIO IO; - - eSMTP_C_States State; - - struct ares_mx_reply *AllMX; - struct ares_mx_reply *CurrMX; - const char *mx_port; - const char *mx_host; - - struct hostent *OneMX; - - char mx_user[1024]; - char mx_pass[1024]; - StrBuf *msgtext; - char *envelope_from; - char user[1024]; - char node[1024]; - char name[1024]; - char mailfrom[1024]; -} SmtpOutMsg; +int SMTPClientDebugEnabled = 0; +const unsigned short DefaultMXPort = 25; void DeleteSmtpOutMsg(void *v) { SmtpOutMsg *Msg = v; + AsyncIO *IO = &Msg->IO; + EVS_syslog(LOG_DEBUG, "%s Exit\n", __FUNCTION__); + + /* these are kept in our own space and free'd below */ + Msg->IO.ConnectMe = NULL; ares_free_data(Msg->AllMX); + if (Msg->HostLookup.VParsedDNSReply != NULL) + Msg->HostLookup.DNSReplyFree(Msg->HostLookup.VParsedDNSReply); + FreeURL(&Msg->Relay); FreeStrBuf(&Msg->msgtext); FreeAsyncIOContents(&Msg->IO); + memset (Msg, 0, sizeof(SmtpOutMsg)); /* just to be shure... */ free(Msg); } -eNextState SMTP_C_Timeout(void *Data); -eNextState SMTP_C_ConnFail(void *Data); -eNextState SMTP_C_DispatchReadDone(void *Data); -eNextState SMTP_C_DispatchWriteDone(void *Data); -eNextState SMTP_C_Terminate(void *Data); -eNextState SMTP_C_MXLookup(void *Data); - -typedef eNextState (*SMTPReadHandler)(SmtpOutMsg *Msg); -typedef eNextState (*SMTPSendHandler)(SmtpOutMsg *Msg); +eNextState SMTP_C_Shutdown(AsyncIO *IO); +eNextState SMTP_C_Timeout(AsyncIO *IO); +eNextState SMTP_C_ConnFail(AsyncIO *IO); +eNextState SMTP_C_DispatchReadDone(AsyncIO *IO); +eNextState SMTP_C_DispatchWriteDone(AsyncIO *IO); +eNextState SMTP_C_DNSFail(AsyncIO *IO); +eNextState SMTP_C_Terminate(AsyncIO *IO); +eNextState SMTP_C_TerminateDB(AsyncIO *IO); +eReadState SMTP_C_ReadServerStatus(AsyncIO *IO); + +eNextState mx_connect_ip(AsyncIO *IO); +eNextState get_one_mx_host_ip(AsyncIO *IO); +eNextState FinalizeMessageSendDB(AsyncIO *IO); +eNextState FinalizeMessageSend_DB1(AsyncIO *IO); +eNextState FinalizeMessageSend_DB2(AsyncIO *IO); +eNextState FinalizeMessageSend_DB3(AsyncIO *IO); +eNextState FinalizeMessageSend_DB4(AsyncIO *IO); + +/****************************************************************************** + * So, we're finished with sending (regardless of success or failure) * + * This Message might be referenced by several Queue-Items, if we're the last,* + * we need to free the memory and send bounce messages (on terminal failure) * + * else we just free our SMTP-Message struct. * + ******************************************************************************/ +inline void FinalizeMessageSend_1(AsyncIO *IO) +{ + const char *Status; + SmtpOutMsg *Msg = IO->Data; + + if (Msg->MyQEntry->Status == 2) + Status = "Delivery successful."; + else if (Msg->MyQEntry->Status == 5) + Status = "Delivery failed permanently; giving up."; + else + Status = "Delivery failed temporarily; will retry later."; + + EVS_syslog(LOG_INFO, + "%s Time[%fs] Recipient <%s> @ <%s> (%s) Status message: %s\n", + Status, + Msg->IO.Now - Msg->IO.StartIO, + Msg->user, + Msg->node, + Msg->name, + ChrPtr(Msg->MyQEntry->StatusMessage)); + Msg->IDestructQueItem = DecreaseQReference(Msg->MyQItem); + Msg->nRemain = CountActiveQueueEntries(Msg->MyQItem); -void FreeQueItem(OneQueItem **Item) -{ - DeleteHash(&(*Item)->MailQEntries); - FreeStrBuf(&(*Item)->EnvelopeFrom); - FreeStrBuf(&(*Item)->BounceTo); - free(*Item); - Item = NULL; -} -void HFreeQueItem(void *Item) -{ - FreeQueItem((OneQueItem**)&Item); -} - - -/* inspect recipients with a status of: - * - 0 (no delivery yet attempted) - * - 3/4 (transient errors - * were experienced and it's time to try again) - */ -int CountActiveQueueEntries(OneQueItem *MyQItem) -{ - HashPos *It; - long len; - const char *Key; - void *vQE; - - MyQItem->ActiveDeliveries = 0; - It = GetNewHashPos(MyQItem->MailQEntries, 0); - while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)) - { - MailQEntry *ThisItem = vQE; - if ((ThisItem->Status == 0) || - (ThisItem->Status == 3) || - (ThisItem->Status == 4)) - { - MyQItem->ActiveDeliveries++; - ThisItem->Active = 1; - } - else - ThisItem->Active = 0; - } - DeleteHashPos(&It); - return MyQItem->ActiveDeliveries; -} - -OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID) -{ - OneQueItem *Item; - const char *pLine = NULL; - StrBuf *Line; - StrBuf *Token; - void *v; - - Item = (OneQueItem*)malloc(sizeof(OneQueItem)); - memset(Item, 0, sizeof(OneQueItem)); - Item->LastAttempt.retry = SMTP_RETRY_INTERVAL; - Item->MessageID = -1; - Item->QueMsgID = QueMsgID; - - citthread_mutex_lock(&ActiveQItemsLock); - if (GetHash(ActiveQItems, - IKEY(Item->QueMsgID), - &v)) + if (Msg->MyQEntry->Active && + CheckQEntryIsBounce(Msg->MyQEntry)) { - /* WHOOPS. somebody else is already working on this. */ - citthread_mutex_unlock(&ActiveQItemsLock); - FreeQueItem(&Item); - return NULL; - } - else { - /* mark our claim on this. */ - Put(ActiveQItems, - IKEY(Item->QueMsgID), - Item, - HFreeQueItem); - citthread_mutex_unlock(&ActiveQItemsLock); + /* are we casue for a bounce mail? */ + Msg->MyQItem->SendBounceMail |= (1<MyQEntry->Status); } - Token = NewStrBuf(); - Line = NewStrBufPlain(NULL, 128); - while (pLine != StrBufNOTNULL) { - const char *pItemPart = NULL; - void *vHandler; - - StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n'); - if (StrLength(Line) == 0) continue; - StrBufExtract_NextToken(Token, Line, &pItemPart, '|'); - if (GetHash(QItemHandlers, SKEY(Token), &vHandler)) - { - QItemHandler H; - H = (QItemHandler) vHandler; - H(Item, Line, &pItemPart); - } - } - FreeStrBuf(&Line); - FreeStrBuf(&Token); - return Item; + if ((Msg->nRemain > 0) || Msg->IDestructQueItem) + Msg->QMsgData = SerializeQueueItem(Msg->MyQItem); + else + Msg->QMsgData = NULL; } - -StrBuf *SerializeQueueItem(OneQueItem *MyQItem) +eNextState FinalizeMessageSend(SmtpOutMsg *Msg) { - StrBuf *QMessage; - HashPos *It; - const char *Key; - long len; - void *vQE; - - QMessage = NewStrBufPlain(NULL, SIZ); - StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME); - -// "attempted|%ld\n" "retry|%ld\n",, (long)time(NULL), (long)retry ); - StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0); - StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID); - - if (StrLength(MyQItem->BounceTo) > 0) { - StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0); - StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0); - } - - if (StrLength(MyQItem->EnvelopeFrom) > 0) { - StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0); - StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0); - } - - It = GetNewHashPos(MyQItem->MailQEntries, 0); - while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)) - { - MailQEntry *ThisItem = vQE; - int i; - - if (!ThisItem->Active) - continue; /* skip already sent ones from the spoolfile. */ - - for (i=0; i < ThisItem->nAttempts; i++) { - StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0); - StrBufAppendPrintf(QMessage, "%ld", - ThisItem->Attempts[i].retry); - - StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0); - StrBufAppendPrintf(QMessage, "%ld", - ThisItem->Attempts[i].when); - } - StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0); - StrBufAppendBuf(QMessage, ThisItem->Recipient, 0); - StrBufAppendBufPlain(QMessage, HKEY("|"), 0); - StrBufAppendPrintf(QMessage, "%d", ThisItem->Status); - StrBufAppendBufPlain(QMessage, HKEY("|"), 0); - StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0); - } - DeleteHashPos(&It); - StrBufAppendBufPlain(QMessage, HKEY("\n"), 0); - return QMessage; + return QueueDBOperation(&Msg->IO, FinalizeMessageSend_DB1); } -void FinalizeMessageSend(SmtpOutMsg *Msg) +inline void FinalizeMessageSend_DB_1(AsyncIO *IO) { - int IDestructQueItem; - HashPos *It; - - citthread_mutex_lock(&ActiveQItemsLock); - Msg->MyQItem->ActiveDeliveries--; - IDestructQueItem = Msg->MyQItem->ActiveDeliveries == 0; - citthread_mutex_unlock(&ActiveQItemsLock); - - if (IDestructQueItem) { - int nRemain; - StrBuf *MsgData; - - nRemain = CountActiveQueueEntries(Msg->MyQItem); - - if (nRemain > 0) - MsgData = SerializeQueueItem(Msg->MyQItem); - /* - * Uncompleted delivery instructions remain, so delete the old - * instructions and replace with the updated ones. - */ - CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, ""); - - /* Generate 'bounce' messages * / - smtp_do_bounce(instr); */ - if (nRemain > 0) { - struct CtdlMessage *msg; - msg = malloc(sizeof(struct CtdlMessage)); - memset(msg, 0, sizeof(struct CtdlMessage)); - msg->cm_magic = CTDLMESSAGE_MAGIC; - msg->cm_anon_type = MES_NORMAL; - msg->cm_format_type = FMT_RFC822; - msg->cm_fields['M'] = SmashStrBuf(&MsgData); + SmtpOutMsg *Msg = IO->Data; - CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR); - CtdlFreeMessage(msg); - } - It = GetNewHashPos(Msg->MyQItem->MailQEntries, 0); - citthread_mutex_lock(&ActiveQItemsLock); - { - GetHashPosFromKey(ActiveQItems, IKEY(Msg->MyQItem->MessageID), It); - DeleteEntryFromHash(ActiveQItems, It); - } - citthread_mutex_unlock(&ActiveQItemsLock); - DeleteHashPos(&It); - } - -/// TODO : else free message... - close(Msg->IO.sock); - DeleteSmtpOutMsg(Msg); -} - -eReadState SMTP_C_ReadServerStatus(AsyncIO *IO) -{ - eReadState Finished = eBufferNotEmpty; - - while (Finished == eBufferNotEmpty) { - Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf); - - switch (Finished) { - case eMustReadMore: /// read new from socket... - return Finished; - break; - case eBufferNotEmpty: /* shouldn't happen... */ - case eReadSuccess: /// done for now... - if (StrLength(IO->IOBuf) < 4) - continue; - if (ChrPtr(IO->IOBuf)[3] == '-') - Finished = eBufferNotEmpty; - else - return Finished; - break; - case eReadFail: /// WHUT? - ///todo: shut down! - break; - } - } - return Finished; + /* + * Uncompleted delivery instructions remain, so delete the old + * instructions and replace with the updated ones. + */ + EVS_syslog(LOG_DEBUG, "%ld", Msg->MyQItem->QueMsgID); + CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, ""); } - -/** - * this one has to have the context for loading the message via the redirect buffer... - */ -StrBuf *smtp_load_msg(OneQueItem *MyQItem) +eNextState FinalizeMessageSend_DB1(AsyncIO *IO) { - CitContext *CCC=CC; - StrBuf *SendMsg; - - CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ); - CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) ); - SendMsg = CCC->redirect_buffer; - CCC->redirect_buffer = NULL; - if ((StrLength(SendMsg) > 0) && - ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') { - CtdlLogPrintf(CTDL_WARNING, - "SMTP client[%ld]: Possible problem: message did not " - "correctly terminate. (expecting 0x10, got 0x%02x)\n", - MsgCount, //yes uncool, but best choice here... - ChrPtr(SendMsg)[StrLength(SendMsg) - 1] ); - StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0); - } - return SendMsg; + FinalizeMessageSend_1(IO); + FinalizeMessageSend_DB_1(IO); + return NextDBOperation(IO, FinalizeMessageSend_DB2); } -int smtp_resolve_recipients(SmtpOutMsg *SendMsg) +inline void FinalizeMessageSend_DB_2(AsyncIO *IO) { - const char *ptr; - char buf[1024]; - int scan_done; - int lp, rp; - int i; - - /* Parse out the host portion of the recipient address */ - process_rfc822_addr(ChrPtr(SendMsg->MyQEntry->Recipient), - SendMsg->user, - SendMsg->node, - SendMsg->name); - - CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n", - SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name); - /* If no envelope_from is supplied, extract one from the message */ - if ( (SendMsg->envelope_from == NULL) || - (IsEmptyStr(SendMsg->envelope_from)) ) { - SendMsg->mailfrom[0] = '\0'; - scan_done = 0; - ptr = ChrPtr(SendMsg->msgtext); - do { - if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) { - scan_done = 1; - } - if (!strncasecmp(buf, "From:", 5)) { - safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom); - striplt(SendMsg->mailfrom); - for (i=0; SendMsg->mailfrom[i]; ++i) { - if (!isprint(SendMsg->mailfrom[i])) { - strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]); - i=0; - } - } - - /* Strip out parenthesized names */ - lp = (-1); - rp = (-1); - for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) { - if (SendMsg->mailfrom[i] == '(') lp = i; - if (SendMsg->mailfrom[i] == ')') rp = i; - } - if ((lp>0)&&(rp>lp)) { - strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]); - } - - /* Prefer brokketized names */ - lp = (-1); - rp = (-1); - for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) { - if (SendMsg->mailfrom[i] == '<') lp = i; - if (SendMsg->mailfrom[i] == '>') rp = i; - } - if ( (lp>=0) && (rp>lp) ) { - SendMsg->mailfrom[rp] = 0; - memmove(SendMsg->mailfrom, - &SendMsg->mailfrom[lp + 1], - rp - lp); - } - - scan_done = 1; - } - } while (scan_done == 0); - if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org"); - stripallbut(SendMsg->mailfrom, '<', '>'); - SendMsg->envelope_from = SendMsg->mailfrom; - } + SmtpOutMsg *Msg = IO->Data; - return 0; + if (Msg->IDestructQueItem) + smtpq_do_bounce(Msg->MyQItem, Msg->msgtext); } - - -#define SMTP_ERROR(WHICH_ERR, ERRSTR) do {\ - SendMsg->MyQEntry->Status = WHICH_ERR; \ - StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, HKEY(ERRSTR), 0); \ - return eAbort; } \ - while (0) - -#define SMTP_VERROR(WHICH_ERR) do {\ - SendMsg->MyQEntry->Status = WHICH_ERR; \ - StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, &ChrPtr(SendMsg->IO.IOBuf)[4], -1, 0); \ - return eAbort; } \ - while (0) - -#define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(SendMsg->IO.IOBuf)[0] == WHICH_STATE) - -#define SMTP_DBG_SEND() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: > %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf)) -#define SMTP_DBG_READ() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: < %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf)) - -void get_one_mx_host_name_done(void *Ctx, - int status, - int timeouts, - struct hostent *hostent) +eNextState FinalizeMessageSend_DB2(AsyncIO *IO) { - AsyncIO *IO = Ctx; - SmtpOutMsg *SendMsg = IO->Data; - if ((status == ARES_SUCCESS) && (hostent != NULL) ) { - CtdlLogPrintf(CTDL_DEBUG, - "SMTP client[%ld]: connecting to %s : %d ...\n", - SendMsg->n, - SendMsg->mx_host, - SendMsg->IO.dport); - - - SendMsg->IO.HEnt = hostent; - InitEventIO(IO, SendMsg, - SMTP_C_DispatchReadDone, - SMTP_C_DispatchWriteDone, - SMTP_C_Terminate, - SMTP_C_Timeout, - SMTP_C_ConnFail, - SMTP_C_ReadServerStatus, - 1); + FinalizeMessageSend_DB_2(IO); - } + return NextDBOperation(IO, FinalizeMessageSend_DB3); } -const unsigned short DefaultMXPort = 25; -void connect_one_smtpsrv(SmtpOutMsg *SendMsg) + +inline void FinalizeMessageSend_DB_3(AsyncIO *IO) { - //char *endpart; - //char buf[SIZ]; - - SendMsg->IO.dport = DefaultMXPort; - - -/* TODO: Relay! - *SendMsg->mx_user = '\0'; - *SendMsg->mx_pass = '\0'; - if (num_tokens(buf, '@') > 1) { - strcpy (SendMsg->mx_user, buf); - endpart = strrchr(SendMsg->mx_user, '@'); - *endpart = '\0'; - strcpy (SendMsg->mx_host, endpart + 1); - endpart = strrchr(SendMsg->mx_user, ':'); - if (endpart != NULL) { - strcpy(SendMsg->mx_pass, endpart+1); - *endpart = '\0'; - } + SmtpOutMsg *Msg = IO->Data; - endpart = strrchr(SendMsg->mx_host, ':'); - if (endpart != 0){ - *endpart = '\0'; - strcpy(SendMsg->mx_port, endpart + 1); - } + if (Msg->nRemain > 0) + { + struct CtdlMessage *msg; + msg = malloc(sizeof(struct CtdlMessage)); + memset(msg, 0, sizeof(struct CtdlMessage)); + msg->cm_magic = CTDLMESSAGE_MAGIC; + msg->cm_anon_type = MES_NORMAL; + msg->cm_format_type = FMT_RFC822; + msg->cm_fields['M'] = SmashStrBuf(&Msg->QMsgData); + msg->cm_fields['U'] = strdup("QMSG"); + Msg->MyQItem->QueMsgID = + CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR); + EVS_syslog(LOG_DEBUG, "%ld", Msg->MyQItem->QueMsgID); + CtdlFreeMessage(msg); } - else -*/ - SendMsg->mx_host = SendMsg->CurrMX->host; - SendMsg->CurrMX = SendMsg->CurrMX->next; - - CtdlLogPrintf(CTDL_DEBUG, - "SMTP client[%ld]: looking up %s : %d ...\n", - SendMsg->n, - SendMsg->mx_host); - - ares_gethostbyname(SendMsg->IO.DNSChannel, - SendMsg->mx_host, - AF_INET6, /* it falls back to ipv4 in doubt... */ - get_one_mx_host_name_done, - &SendMsg->IO); -} - - -eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg) -{ - /* Process the SMTP greeting from the server */ - SMTP_DBG_READ(); - - if (!SMTP_IS_STATE('2')) { - if (SMTP_IS_STATE('4')) - SMTP_VERROR(4); - else - SMTP_VERROR(5); + else { + CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, + &Msg->MyQItem->MessageID, + 1, + ""); + FreeStrBuf(&Msg->QMsgData); } - return eSendReply; -} - -eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg) -{ - /* At this point we know we are talking to a real SMTP server */ - - /* Do a EHLO command. If it fails, try the HELO command. */ - StrBufPrintf(SendMsg->IO.SendBuf.Buf, - "EHLO %s\r\n", config.c_fqdn); - - SMTP_DBG_SEND(); - return eReadMessage; + DecreaseShutdownDeliveries(Msg->MyQItem); } - -eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg) +eNextState FinalizeMessageSend_DB3(AsyncIO *IO) { - SMTP_DBG_READ(); - - if (SMTP_IS_STATE('2')) { - SendMsg->State ++; - if (IsEmptyStr(SendMsg->mx_user)) - SendMsg->State ++; /* Skip auth... */ - } - /* else we fall back to 'helo' */ - return eSendReply; + SmtpOutMsg *Msg = IO->Data; + FinalizeMessageSend_DB_3(IO); + if (!Msg->IDestructQueItem) + return eAbort; + return NextDBOperation(IO, FinalizeMessageSend_DB4); } -eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg) +eNextState FinalizeMessageSend_DB4(AsyncIO *IO) { - StrBufPrintf(SendMsg->IO.SendBuf.Buf, - "HELO %s\r\n", config.c_fqdn); + int Done; + SmtpOutMsg *Msg = IO->Data; - SMTP_DBG_SEND(); - return eReadMessage; + Done = GetShutdownDeliveries(Msg->MyQItem); + if (!Done) + return NextDBOperation(IO, FinalizeMessageSend_DB4); + else + return eAbort; } -eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg) +eNextState FinalizeMessageSend_DB(AsyncIO *IO) { - SMTP_DBG_READ(); + SmtpOutMsg *Msg = IO->Data; - if (!SMTP_IS_STATE('2')) { - if (SMTP_IS_STATE('4')) - SMTP_VERROR(4); - else - SMTP_VERROR(5); - } - if (!IsEmptyStr(SendMsg->mx_user)) - SendMsg->State ++; /* Skip auth... */ - return eSendReply; + RemoveContext(Msg->IO.CitContext); + if (Msg->IDestructQueItem) + RemoveQItem(Msg->MyQItem); + DeleteSmtpOutMsg(Msg); + return eAbort; } -eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg) +eNextState FailOneAttempt(AsyncIO *IO) { - char buf[SIZ]; - char encoded[1024]; - - /* Do an AUTH command if necessary */ - sprintf(buf, "%s%c%s%c%s", - SendMsg->mx_user, '\0', - SendMsg->mx_user, '\0', - SendMsg->mx_pass); - CtdlEncodeBase64(encoded, buf, - strlen(SendMsg->mx_user) + - strlen(SendMsg->mx_user) + - strlen(SendMsg->mx_pass) + 2, 0); - StrBufPrintf(SendMsg->IO.SendBuf.Buf, - "AUTH PLAIN %s\r\n", encoded); - - SMTP_DBG_SEND(); - return eReadMessage; -} + SmtpOutMsg *Msg = IO->Data; -eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg) -{ - /* Do an AUTH command if necessary */ - - SMTP_DBG_READ(); - - if (!SMTP_IS_STATE('2')) { - if (SMTP_IS_STATE('4')) - SMTP_VERROR(4); - else - SMTP_VERROR(5); - } - return eSendReply; -} + if (Msg->MyQEntry->Status == 2) + return eAbort; -eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg) -{ - /* previous command succeeded, now try the MAIL FROM: command */ - StrBufPrintf(SendMsg->IO.SendBuf.Buf, - "MAIL FROM:<%s>\r\n", - SendMsg->envelope_from); + /* + * possible ways here: + * - connection timeout + * - dns lookup failed + */ + StopClientWatchers(IO); - SMTP_DBG_SEND(); - return eReadMessage; -} + if (Msg->pCurrRelay != NULL) + Msg->pCurrRelay = Msg->pCurrRelay->Next; -eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg) -{ - SMTP_DBG_READ(); - - if (!SMTP_IS_STATE('2')) { - if (SMTP_IS_STATE('4')) - SMTP_VERROR(4); - else - SMTP_VERROR(5); + if (Msg->pCurrRelay == NULL) { + EVS_syslog(LOG_DEBUG, "%s Aborting\n", __FUNCTION__); + return eAbort; + } + if (Msg->pCurrRelay->IsIP) { + EVS_syslog(LOG_DEBUG, "%s connecting IP\n", __FUNCTION__); + return mx_connect_ip(IO); + } + else { + EVS_syslog(LOG_DEBUG, + "%s resolving next MX Record\n", + __FUNCTION__); + return get_one_mx_host_ip(IO); } - return eSendReply; } -eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg) +void SetConnectStatus(AsyncIO *IO) { - /* MAIL succeeded, now try the RCPT To: command */ - StrBufPrintf(SendMsg->IO.SendBuf.Buf, - "RCPT TO:<%s@%s>\r\n", - SendMsg->user, - SendMsg->node); - - SMTP_DBG_SEND(); - return eReadMessage; -} + SmtpOutMsg *Msg = IO->Data; + char buf[256]; + void *src; -eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg) -{ - SMTP_DBG_READ(); + buf[0] = '\0'; - if (!SMTP_IS_STATE('2')) { - if (SMTP_IS_STATE('4')) - SMTP_VERROR(4); - else - SMTP_VERROR(5); + if (IO->ConnectMe->IPv6) { + src = &IO->ConnectMe->Addr.sin6_addr; } - return eSendReply; -} - -eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg) -{ - /* RCPT succeeded, now try the DATA command */ - StrBufPlain(SendMsg->IO.SendBuf.Buf, - HKEY("DATA\r\n")); - - SMTP_DBG_SEND(); - return eReadMessage; -} - -eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg) -{ - SMTP_DBG_READ(); + else { + struct sockaddr_in *addr; - if (!SMTP_IS_STATE('3')) { - if (SMTP_IS_STATE('4')) - SMTP_VERROR(3); - else - SMTP_VERROR(5); + addr = (struct sockaddr_in *)&IO->ConnectMe->Addr; + src = &addr->sin_addr.s_addr; } - return eSendReply; -} -eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg) -{ - StrBuf *Buf; - /* If we reach this point, the server is expecting data.*/ + inet_ntop((IO->ConnectMe->IPv6)?AF_INET6:AF_INET, + src, + buf, + sizeof(buf)); + + if (Msg->mx_host == NULL) + Msg->mx_host = ""; - Buf = SendMsg->IO.SendBuf.Buf; - SendMsg->IO.SendBuf.Buf = SendMsg->msgtext; - SendMsg->msgtext = Buf; - //// TODO timeout like that: (SendMsg->msg_size / 128) + 50); - SendMsg->State ++; + EVS_syslog(LOG_INFO, + "connecting to %s [%s]:%d ...\n", + Msg->mx_host, + buf, + Msg->IO.ConnectMe->Port); - return eSendMore; + Msg->MyQEntry->Status = 5; + StrBufPrintf(Msg->MyQEntry->StatusMessage, + "Timeout while connecting %s [%s]:%d ", + Msg->mx_host, + buf, + Msg->IO.ConnectMe->Port); + Msg->IO.NextState = eConnect; } -eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg) +/***************************************************************************** + * So we connect our Relay IP here. * + *****************************************************************************/ +eNextState mx_connect_ip(AsyncIO *IO) { - StrBuf *Buf; + SmtpOutMsg *Msg = IO->Data; - Buf = SendMsg->IO.SendBuf.Buf; - SendMsg->IO.SendBuf.Buf = SendMsg->msgtext; - SendMsg->msgtext = Buf; + EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); - StrBufPlain(SendMsg->IO.SendBuf.Buf, - HKEY(".\r\n")); + IO->ConnectMe = Msg->pCurrRelay; + Msg->State = eConnectMX; - return eReadMessage; + SetConnectStatus(IO); + return EvConnectSock(IO, + SMTP_C_ConnTimeout, + SMTP_C_ReadTimeouts[0], + 1); } -eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg) +eNextState get_one_mx_host_ip_done(AsyncIO *IO) { - SMTP_DBG_READ(); + SmtpOutMsg *Msg = IO->Data; + struct hostent *hostent; - if (!SMTP_IS_STATE('2')) { - if (SMTP_IS_STATE('4')) - SMTP_VERROR(4); - else - SMTP_VERROR(5); - } - - /* We did it! */ - StrBufPlain(SendMsg->MyQEntry->StatusMessage, - &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4], - StrLength(SendMsg->IO.RecvBuf.Buf) - 4); - SendMsg->MyQEntry->Status = 2; - return eSendReply; -} + QueryCbDone(IO); + EVS_syslog(LOG_DEBUG, "%s Time[%fs]\n", + __FUNCTION__, + IO->Now - IO->DNS.Start); -eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg) -{ - StrBufPlain(SendMsg->IO.SendBuf.Buf, - HKEY("QUIT\r\n")); + hostent = Msg->HostLookup.VParsedDNSReply; + if ((Msg->HostLookup.DNSStatus == ARES_SUCCESS) && + (hostent != NULL) ) { + memset(&Msg->pCurrRelay->Addr, 0, sizeof(struct in6_addr)); + if (Msg->pCurrRelay->IPv6) { + memcpy(&Msg->pCurrRelay->Addr.sin6_addr.s6_addr, + &hostent->h_addr_list[0], + sizeof(struct in6_addr)); - SMTP_DBG_SEND(); - return eReadMessage; + Msg->pCurrRelay->Addr.sin6_family = + hostent->h_addrtype; + Msg->pCurrRelay->Addr.sin6_port = + htons(DefaultMXPort); + } + else { + struct sockaddr_in *addr; + /* + * Bypass the ns lookup result like this: + * IO->Addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + * addr->sin_addr.s_addr = + * htonl((uint32_t)&hostent->h_addr_list[0]); + */ + + addr = (struct sockaddr_in*) &Msg->pCurrRelay->Addr; + + memcpy(&addr->sin_addr.s_addr, + hostent->h_addr_list[0], + sizeof(uint32_t)); + + addr->sin_family = hostent->h_addrtype; + addr->sin_port = htons(DefaultMXPort); + } + Msg->mx_host = Msg->pCurrRelay->Host; + if (Msg->HostLookup.VParsedDNSReply != NULL) { + Msg->HostLookup.DNSReplyFree(Msg->HostLookup.VParsedDNSReply); + Msg->HostLookup.VParsedDNSReply = NULL; + } + return mx_connect_ip(IO); + } + else { + if (Msg->HostLookup.VParsedDNSReply != NULL) { + Msg->HostLookup.DNSReplyFree(Msg->HostLookup.VParsedDNSReply); + Msg->HostLookup.VParsedDNSReply = NULL; + } + return FailOneAttempt(IO); + } } -eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg) +eNextState get_one_mx_host_ip(AsyncIO *IO) { - SMTP_DBG_READ(); - - CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n", - SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name); - return eTerminateConnection; -} + SmtpOutMsg * Msg = IO->Data; + /* + * here we start with the lookup of one host. it might be... + * - the relay host *sigh* + * - the direct hostname if there was no mx record + * - one of the mx'es + */ -eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg) -{ - return eSendReply; -} + EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); -eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg) -{ - return eReadMessage; -} + EVS_syslog(LOG_DEBUG, + "looking up %s-Record %s : %d ...\n", + (Msg->pCurrRelay->IPv6)? "aaaa": "a", + Msg->pCurrRelay->Host, + Msg->pCurrRelay->Port); -eNextState smtp_resolve_mx_done(void *data) -{/// VParsedDNSReply - AsyncIO *IO = data; - SmtpOutMsg * SendMsg = IO->Data; - - SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024); - SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024); - SendMsg->IO.IOBuf = NewStrBuf(); - SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage; - - //// connect_one_smtpsrv_xamine_result - SendMsg->CurrMX = SendMsg->AllMX = IO->VParsedDNSReply; - //// TODO: should we remove the current ares context??? - connect_one_smtpsrv(SendMsg); - return 0; + if (!QueueQuery((Msg->pCurrRelay->IPv6)? ns_t_aaaa : ns_t_a, + Msg->pCurrRelay->Host, + &Msg->IO, + &Msg->HostLookup, + get_one_mx_host_ip_done)) + { + Msg->MyQEntry->Status = 5; + StrBufPrintf(Msg->MyQEntry->StatusMessage, + "No MX hosts found for <%s>", Msg->node); + Msg->IO.NextState = eTerminateConnection; + return IO->NextState; + } + IO->NextState = eReadDNSReply; + return IO->NextState; +} + + +/***************************************************************************** + * here we try to find out about the MX records for our recipients. * + *****************************************************************************/ +eNextState smtp_resolve_mx_record_done(AsyncIO *IO) +{ + SmtpOutMsg * Msg = IO->Data; + ParsedURL **pp; + + QueryCbDone(IO); + + EVS_syslog(LOG_DEBUG, "%s Time[%fs]\n", + __FUNCTION__, + IO->Now - IO->DNS.Start); + + pp = &Msg->Relay; + while ((pp != NULL) && (*pp != NULL) && ((*pp)->Next != NULL)) + pp = &(*pp)->Next; + + if ((IO->DNS.Query->DNSStatus == ARES_SUCCESS) && + (IO->DNS.Query->VParsedDNSReply != NULL)) + { /* ok, we found mx records. */ + + Msg->CurrMX + = Msg->AllMX + = IO->DNS.Query->VParsedDNSReply; + while (Msg->CurrMX) { + int i; + for (i = 0; i < 2; i++) { + ParsedURL *p; + + p = (ParsedURL*) malloc(sizeof(ParsedURL)); + memset(p, 0, sizeof(ParsedURL)); + p->Priority = Msg->CurrMX->priority; + p->IsIP = 0; + p->Port = DefaultMXPort; + p->IPv6 = i == 1; + p->Host = Msg->CurrMX->host; + if (*pp == NULL) + *pp = p; + else { + ParsedURL *ppp = *pp; + + while ((ppp->Next != NULL) && + (ppp->Next->Priority <= p->Priority)) + ppp = ppp->Next; + if ((ppp == *pp) && + (ppp->Priority > p->Priority)) { + p->Next = *pp; + *pp = p; + } + else { + p->Next = ppp->Next; + ppp->Next = p; + } + } + } + Msg->CurrMX = Msg->CurrMX->next; + } + Msg->CXFlags = Msg->CXFlags & F_HAVE_MX; + } + else { /* else fall back to the plain hostname */ + int i; + for (i = 0; i < 2; i++) { + ParsedURL *p; + + p = (ParsedURL*) malloc(sizeof(ParsedURL)); + memset(p, 0, sizeof(ParsedURL)); + p->IsIP = 0; + p->Port = DefaultMXPort; + p->IPv6 = i == 1; + p->Host = Msg->node; + + *pp = p; + pp = &p->Next; + } + Msg->CXFlags = Msg->CXFlags & F_DIRECT; + } + if (Msg->MyQItem->FallBackHost != NULL) + { + Msg->MyQItem->FallBackHost->Next = *pp; + *pp = Msg->MyQItem->FallBackHost; + } + Msg->pCurrRelay = Msg->Relay; + return get_one_mx_host_ip(IO); } - - -int resolve_mx_records(void *Ctx) +eNextState resolve_mx_records(AsyncIO *IO) { - SmtpOutMsg * SendMsg = Ctx; + SmtpOutMsg * Msg = IO->Data; - if (!QueueQuery(ns_t_mx, - SendMsg->node, - &SendMsg->IO, - smtp_resolve_mx_done)) + EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); + /* start resolving MX records here. */ + if (!QueueQuery(ns_t_mx, + Msg->node, + &Msg->IO, + &Msg->MxLookup, + smtp_resolve_mx_record_done)) { - SendMsg->MyQEntry->Status = 5; - StrBufPrintf(SendMsg->MyQEntry->StatusMessage, - "No MX hosts found for <%s>", SendMsg->node); - return 0; ///////TODO: abort! + Msg->MyQEntry->Status = 5; + StrBufPrintf(Msg->MyQEntry->StatusMessage, + "No MX hosts found for <%s>", Msg->node); + return IO->NextState; } - return 0; + Msg->IO.NextState = eReadDNSReply; + return IO->NextState; } -void smtp_try(OneQueItem *MyQItem, - MailQEntry *MyQEntry, - StrBuf *MsgText, - int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */ -{ - SmtpOutMsg * SendMsg; - - SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg)); - memset(SendMsg, 0, sizeof(SmtpOutMsg)); - SendMsg->IO.sock = (-1); - SendMsg->n = MsgCount++; - SendMsg->MyQEntry = MyQEntry; - SendMsg->MyQItem = MyQItem; - SendMsg->IO.Data = SendMsg; - if (KeepMsgText) - SendMsg->msgtext = MsgText; - else - SendMsg->msgtext = NewStrBufDup(MsgText); - - smtp_resolve_recipients(SendMsg); - - QueueEventContext(SendMsg, - &SendMsg->IO, - resolve_mx_records); -} +/****************************************************************************** + * so, we're going to start a SMTP delivery. lets get it on. * + ******************************************************************************/ -void NewMailQEntry(OneQueItem *Item) +SmtpOutMsg *new_smtp_outmsg(OneQueItem *MyQItem, + MailQEntry *MyQEntry, + int MsgCount) { - Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry)); - memset(Item->Current, 0, sizeof(MailQEntry)); + SmtpOutMsg * Msg; - if (Item->MailQEntries == NULL) - Item->MailQEntries = NewHash(1, Flathash); - Item->Current->n = GetCount(Item->MailQEntries); - Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry); -} + Msg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg)); + memset(Msg, 0, sizeof(SmtpOutMsg)); -void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos) -{ - Item->MessageID = StrBufExtractNext_int(Line, Pos, '|'); -} + Msg->n = MsgCount; + Msg->MyQEntry = MyQEntry; + Msg->MyQItem = MyQItem; + Msg->pCurrRelay = MyQItem->URL; -void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos) -{ - if (Item->EnvelopeFrom == NULL) - Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line)); - StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|'); -} + InitIOStruct(&Msg->IO, + Msg, + eReadMessage, + SMTP_C_ReadServerStatus, + SMTP_C_DNSFail, + SMTP_C_DispatchWriteDone, + SMTP_C_DispatchReadDone, + SMTP_C_Terminate, + SMTP_C_TerminateDB, + SMTP_C_ConnFail, + SMTP_C_Timeout, + SMTP_C_Shutdown); -void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos) -{ - if (Item->BounceTo == NULL) - Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line)); - StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|'); -} + Msg->IO.ErrMsg = Msg->MyQEntry->StatusMessage; -void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos) -{ - if (Item->Current == NULL) - NewMailQEntry(Item); - if (Item->Current->Recipient == NULL) - Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line)); - StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|'); - Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|'); - StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|'); - Item->Current = NULL; // TODO: is this always right? + return Msg; } - -void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos) +void smtp_try_one_queue_entry(OneQueItem *MyQItem, + MailQEntry *MyQEntry, + StrBuf *MsgText, + /*KeepMsgText allows us to use MsgText as ours.*/ + int KeepMsgText, + int MsgCount) { - if (Item->Current == NULL) - NewMailQEntry(Item); - if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0) - Item->Current->nAttempts++; - if (Item->Current->nAttempts > MaxAttempts) { - Item->FailNow = 1; - return; - } - Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|'); -} + SmtpOutMsg *Msg; -void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos) -{ - if (Item->Current == NULL) - NewMailQEntry(Item); - if (Item->Current->Attempts[Item->Current->nAttempts].when != 0) - Item->Current->nAttempts++; - if (Item->Current->nAttempts > MaxAttempts) { - Item->FailNow = 1; - return; - } - - Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|'); - if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when) - { - Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when; - Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2; - if (Item->LastAttempt.retry > SMTP_RETRY_MAX) - Item->LastAttempt.retry = SMTP_RETRY_MAX; - } -} + SMTPC_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); + Msg = new_smtp_outmsg(MyQItem, MyQEntry, MsgCount); + if (KeepMsgText) Msg->msgtext = MsgText; + else Msg->msgtext = NewStrBufDup(MsgText); + if (smtp_resolve_recipients(Msg)) { + safestrncpy( + ((CitContext *)Msg->IO.CitContext)->cs_host, + Msg->node, + sizeof(((CitContext *) + Msg->IO.CitContext)->cs_host)); -/* - * smtp_do_procmsg() - * - * Called by smtp_do_queue() to handle an individual message. - */ -void smtp_do_procmsg(long msgnum, void *userdata) { - struct CtdlMessage *msg = NULL; - char *instr = NULL; - StrBuf *PlainQItem; - OneQueItem *MyQItem; - char *pch; - HashPos *It; - void *vQE; - long len; - const char *Key; - - CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum); - ///strcpy(envelope_from, ""); - - msg = CtdlFetchMessage(msgnum, 1); - if (msg == NULL) { - CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum); - return; - } - - pch = instr = msg->cm_fields['M']; - - /* Strip out the headers (no not amd any other non-instruction) line */ - while (pch != NULL) { - pch = strchr(pch, '\n'); - if ((pch != NULL) && (*(pch + 1) == '\n')) { - instr = pch + 2; - pch = NULL; - } - } - PlainQItem = NewStrBufPlain(instr, -1); - CtdlFreeMessage(msg); - MyQItem = DeserializeQueueItem(PlainQItem, msgnum); - FreeStrBuf(&PlainQItem); - - if (MyQItem == NULL) { - CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum); - return; /* s.b. else is already processing... */ - } - - /* - * Postpone delivery if we've already tried recently. - * / - if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) { - CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n"); - - It = GetNewHashPos(MyQItem->MailQEntries, 0); - citthread_mutex_lock(&ActiveQItemsLock); - { - GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It); - DeleteEntryFromHash(ActiveQItems, It); - } - citthread_mutex_unlock(&ActiveQItemsLock); - ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this? - DeleteHashPos(&It); - return; - }// TODO: reenable me.*/ - - /* - * Bail out if there's no actual message associated with this - */ - if (MyQItem->MessageID < 0L) { - CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n"); - It = GetNewHashPos(MyQItem->MailQEntries, 0); - citthread_mutex_lock(&ActiveQItemsLock); - { - GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It); - DeleteEntryFromHash(ActiveQItems, It); - } - citthread_mutex_unlock(&ActiveQItemsLock); - DeleteHashPos(&It); - ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this? - return; - } - - It = GetNewHashPos(MyQItem->MailQEntries, 0); - while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)) - { - MailQEntry *ThisItem = vQE; - CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active); - } - DeleteHashPos(&It); - - CountActiveQueueEntries(MyQItem); - if (MyQItem->ActiveDeliveries > 0) - { - int i = 1; - StrBuf *Msg = smtp_load_msg(MyQItem); - It = GetNewHashPos(MyQItem->MailQEntries, 0); - while ((i <= MyQItem->ActiveDeliveries) && - (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))) - { - MailQEntry *ThisItem = vQE; - if (ThisItem->Active == 1) { - CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient)); - smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries)); - i++; + SMTPC_syslog(LOG_DEBUG, "Starting: [%ld] <%s> CC <%d> \n", + Msg->MyQItem->MessageID, + ChrPtr(Msg->MyQEntry->Recipient), + ((CitContext*)Msg->IO.CitContext)->cs_pid); + if (Msg->pCurrRelay == NULL) + QueueEventContext(&Msg->IO, + resolve_mx_records); + else { /* oh... via relay host */ + if (Msg->pCurrRelay->IsIP) { + QueueEventContext(&Msg->IO, + mx_connect_ip); + } + else { + /* uneducated admin has chosen to + add DNS to the equation... */ + QueueEventContext(&Msg->IO, + get_one_mx_host_ip); } } - DeleteHashPos(&It); } - else - { - It = GetNewHashPos(MyQItem->MailQEntries, 0); - citthread_mutex_lock(&ActiveQItemsLock); - { - GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It); - DeleteEntryFromHash(ActiveQItems, It); + else { + /* No recipients? well fail then. */ + if ((Msg==NULL) || + (Msg->MyQEntry == NULL)) { + Msg->MyQEntry->Status = 5; + StrBufPlain(Msg->MyQEntry->StatusMessage, + HKEY("Invalid Recipient!")); } - citthread_mutex_unlock(&ActiveQItemsLock); - DeleteHashPos(&It); - ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this? - -// TODO: bounce & delete? - + FinalizeMessageSend_1(&Msg->IO); + FinalizeMessageSend_DB_1(&Msg->IO); + FinalizeMessageSend_DB_2(&Msg->IO); + FinalizeMessageSend_DB_3(&Msg->IO); + FinalizeMessageSend_DB(&Msg->IO); } } -/*****************************************************************************/ -/* SMTP UTILITY COMMANDS */ -/*****************************************************************************/ -void cmd_smtp(char *argbuf) { - char cmd[64]; - char node[256]; - char buf[1024]; - int i; - int num_mxhosts; - - if (CtdlAccessCheck(ac_aide)) return; - - extract_token(cmd, argbuf, 0, '|', sizeof cmd); - - if (!strcasecmp(cmd, "mx")) { - extract_token(node, argbuf, 1, '|', sizeof node); - num_mxhosts = getmx(buf, node); - cprintf("%d %d MX hosts listed for %s\n", - LISTING_FOLLOWS, num_mxhosts, node); - for (i=0; iIO; - while (!CtdlThreadCheckStop()) { - - CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n"); + EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); - if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) { - CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM); + switch (NextTCPState) { + case eSendFile: + case eSendReply: + case eSendMore: + Timeout = SMTP_C_SendTimeouts[Msg->State]; + if (Msg->State == eDATABody) { + /* if we're sending a huge message, + * we need more time. + */ + Timeout += StrLength(Msg->msgtext) / 512; } - else { - num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL); + break; + case eReadMessage: + Timeout = SMTP_C_ReadTimeouts[Msg->State]; + if (Msg->State == eDATATerminateBody) { + /* + * some mailservers take a nap before accepting + * the message content inspection and such. + */ + Timeout += StrLength(Msg->msgtext) / 512; } - CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed); - CtdlThreadSleep(60); + break; + case eSendDNSQuery: + case eReadDNSReply: + case eDBQuery: + case eReadFile: + case eReadMore: + case eReadPayload: + case eConnect: + case eTerminateConnection: + case eAbort: + return; } + SetNextTimeout(&Msg->IO, Timeout); +} +eNextState SMTP_C_DispatchReadDone(AsyncIO *IO) +{ + EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); + SmtpOutMsg *Msg = IO->Data; + eNextState rc; - CtdlClearSystemContext(); - return(NULL); + rc = ReadHandlers[Msg->State](Msg); + if (rc != eAbort) + { + Msg->State++; + SMTPSetTimeout(rc, Msg); + } + return rc; } +eNextState SMTP_C_DispatchWriteDone(AsyncIO *IO) +{ + EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); + SmtpOutMsg *Msg = IO->Data; + eNextState rc; + rc = SendHandlers[Msg->State](Msg); + SMTPSetTimeout(rc, Msg); + return rc; +} -/* - * Initialize the SMTP outbound queue - */ -void smtp_init_spoolout(void) { - struct ctdlroom qrbuf; - /* - * Create the room. This will silently fail if the room already - * exists, and that's perfectly ok, because we want it to exist. - */ - CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX); +/*****************************************************************************/ +/* SMTP CLIENT ERROR CATCHERS */ +/*****************************************************************************/ +eNextState SMTP_C_Terminate(AsyncIO *IO) +{ + SmtpOutMsg *Msg = IO->Data; - /* - * Make sure it's set to be a "system room" so it doesn't show up - * in the nown rooms list for Aides. - */ - if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) { - qrbuf.QRflags2 |= QR2_SYSTEM; - CtdlPutRoomLock(&qrbuf); - } + EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); + return FinalizeMessageSend(Msg); } - - -SMTPReadHandler ReadHandlers[eMaxSMTPC] = { - SMTPC_read_greeting, - SMTPC_read_EHLO_reply, - SMTPC_read_HELO_reply, - SMTPC_read_auth_reply, - SMTPC_read_FROM_reply, - SMTPC_read_RCPT_reply, - SMTPC_read_DATAcmd_reply, - SMTPC_read_dummy, - SMTPC_read_data_body_reply, - SMTPC_read_QUIT_reply -}; - -SMTPSendHandler SendHandlers[eMaxSMTPC] = { - SMTPC_send_dummy, /* we don't send a greeting, the server does... */ - SMTPC_send_EHLO, - STMPC_send_HELO, - SMTPC_send_auth, - SMTPC_send_FROM, - SMTPC_send_RCPT, - SMTPC_send_DATAcmd, - SMTPC_send_data_body, - SMTPC_send_terminate_data_body, - SMTPC_send_QUIT -}; - -eNextState SMTP_C_Terminate(void *Data) +eNextState SMTP_C_TerminateDB(AsyncIO *IO) { - SmtpOutMsg *pMsg = Data; - FinalizeMessageSend(pMsg); - return 0; + EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); + return FinalizeMessageSend_DB(IO); } - -eNextState SMTP_C_Timeout(void *Data) +eNextState SMTP_C_Timeout(AsyncIO *IO) { - SmtpOutMsg *pMsg = Data; - FinalizeMessageSend(pMsg); - return 0; + SmtpOutMsg *Msg = IO->Data; + + Msg->MyQEntry->Status = 4; + EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); + StrBufPlain(IO->ErrMsg, CKEY(ReadErrors[Msg->State])); + if (Msg->State > eRCPT) + return eAbort; + else + return FailOneAttempt(IO); } +eNextState SMTP_C_ConnFail(AsyncIO *IO) +{ + SmtpOutMsg *Msg = IO->Data; -eNextState SMTP_C_ConnFail(void *Data) + Msg->MyQEntry->Status = 4; + EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); + StrBufPlain(IO->ErrMsg, CKEY(ReadErrors[Msg->State])); + return FailOneAttempt(IO); +} +eNextState SMTP_C_DNSFail(AsyncIO *IO) { - SmtpOutMsg *pMsg = Data; - FinalizeMessageSend(pMsg); - return 0; + SmtpOutMsg *Msg = IO->Data; + Msg->MyQEntry->Status = 4; + EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); + return FailOneAttempt(IO); } - -eNextState SMTP_C_DispatchReadDone(void *Data) +eNextState SMTP_C_Shutdown(AsyncIO *IO) { - SmtpOutMsg *pMsg = Data; - eNextState rc = ReadHandlers[pMsg->State](pMsg); - pMsg->State++; - return rc; + EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); + SmtpOutMsg *Msg = IO->Data; + + Msg->MyQEntry->Status = 3; + StrBufPlain(Msg->MyQEntry->StatusMessage, + HKEY("server shutdown during message submit.")); + return FinalizeMessageSend(Msg); } -eNextState SMTP_C_DispatchWriteDone(void *Data) + +/** + * @brief lineread Handler; + * understands when to read more SMTP lines, and when this is a one-lined reply. + */ +eReadState SMTP_C_ReadServerStatus(AsyncIO *IO) { - SmtpOutMsg *pMsg = Data; - return SendHandlers[pMsg->State](pMsg); - + eReadState Finished = eBufferNotEmpty; + + while (Finished == eBufferNotEmpty) { + Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf); + + switch (Finished) { + case eMustReadMore: /// read new from socket... + return Finished; + break; + case eBufferNotEmpty: /* shouldn't happen... */ + case eReadSuccess: /// done for now... + if (StrLength(IO->IOBuf) < 4) + continue; + if (ChrPtr(IO->IOBuf)[3] == '-') + Finished = eBufferNotEmpty; + else + return Finished; + break; + case eReadFail: /// WHUT? + ///todo: shut down! + break; + } + } + return Finished; } -void smtp_evc_cleanup(void) +void LogDebugEnableSMTPClient(const int n) { - DeleteHash(&QItemHandlers); - DeleteHash(&ActiveQItems); + SMTPClientDebugEnabled = n; } -#endif CTDL_MODULE_INIT(smtp_eventclient) { -#ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT if (!threading) - { - ActiveQItems = NewHash(1, Flathash); - citthread_mutex_init(&ActiveQItemsLock, NULL); - - QItemHandlers = NewHash(0, NULL); - - Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler); - Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler); - Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler); - Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler); - Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler); - Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler); -///submitted /TODO: flush qitemhandlers on exit - - - smtp_init_spoolout(); - - CtdlRegisterCleanupHook(smtp_evc_cleanup); - CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL); - - CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands"); - } -#endif - - /* return our Subversion id for the Log */ + CtdlRegisterDebugFlagHook(HKEY("smtpeventclient"), LogDebugEnableSMTPClient, &SMTPClientDebugEnabled); return "smtpeventclient"; }