X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fmodules%2Fsmtp%2Fserv_smtpeventclient.c;h=2869b8d44cc7e06c2531e71026f88bed4674549b;hb=1f492ac650c71a515d36571d5c0a5effd05779dd;hp=bb539fa629a66d900cc4b2bcffe08c8dbcc603bb;hpb=fdb030b497950239b2b7db63b3953f6a6f425f1e;p=citadel.git diff --git a/citadel/modules/smtp/serv_smtpeventclient.c b/citadel/modules/smtp/serv_smtpeventclient.c index bb539fa62..2869b8d44 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,1156 +87,757 @@ #include "smtp_util.h" #include "event_client.h" +#include "smtpqueue.h" +#include "smtp_clienthandlers.h" -#ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT - -int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */ -int MsgCount = 0; -/*****************************************************************************/ -/* 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 { - long n; - AsyncIO IO; - - eSMTP_C_States State; - - int SMTPstatus; - - int i_mx; - int n_mx; - int num_mxhosts; - char mx_user[1024]; - char mx_pass[1024]; - char mx_host[1024]; - char mx_port[1024]; - char mxhosts[SIZ]; - - StrBuf *msgtext; - char *envelope_from; - char user[1024]; - char node[1024]; - char name[1024]; - char addr[SIZ]; - char dsn[1024]; - char envelope_from_buf[1024]; - char mailfrom[1024]; -} SmtpOutMsg; - -eNextState SMTP_C_DispatchReadDone(void *Data); -eNextState SMTP_C_DispatchWriteDone(void *Data); - - -typedef eNextState (*SMTPReadHandler)(SmtpOutMsg *Msg); -typedef eNextState (*SMTPSendHandler)(SmtpOutMsg *Msg); - - -eReadState SMTP_C_ReadServerStatus(AsyncIO *IO) +int SMTPClientDebugEnabled = 0; +const unsigned short DefaultMXPort = 25; +void DeleteSmtpOutMsg(void *v) { - 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; -} - - - - -/** - * this one has to have the context for loading the message via the redirect buffer... - */ -SmtpOutMsg *smtp_load_msg(long msgnum, const char *addr, char *envelope_from) + SmtpOutMsg *Msg = v; + AsyncIO *IO = &Msg->IO; + EV_syslog(LOG_DEBUG, "SMTP: %s Aborting\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_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) { - CitContext *CCC=CC; - SmtpOutMsg *SendMsg; - - SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg)); - - memset(SendMsg, 0, sizeof(SmtpOutMsg)); - SendMsg->IO.sock = (-1); + const char *Status; + SmtpOutMsg *Msg = IO->Data; - SendMsg->n = MsgCount++; - /* Load the message out of the database */ - CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ); - CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) ); - SendMsg->msgtext = CCC->redirect_buffer; - CCC->redirect_buffer = NULL; - if ((StrLength(SendMsg->msgtext) > 0) && - ChrPtr(SendMsg->msgtext)[StrLength(SendMsg->msgtext) - 1] != '\n') { - CtdlLogPrintf(CTDL_WARNING, - "SMTP client[%ld]: Possible problem: message did not " - "correctly terminate. (expecting 0x10, got 0x%02x)\n", - SendMsg->n, - ChrPtr(SendMsg->msgtext)[StrLength(SendMsg->msgtext) - 1] ); - StrBufAppendBufPlain(SendMsg->msgtext, HKEY("\r\n"), 0); - } + if (Msg->MyQEntry->Status == 2) + Status = "Delivery Successfull."; + else if (Msg->MyQEntry->Status == 5) + Status = "Delivery failed permanently; giving up."; + else + Status = "Delivery failed temporarily; will retry later."; + + EVS_syslog(LOG_INFO, + "SMTP: %s Time[%fs] Recipient <%s> @ <%s> (%s) Statusmessage: %s\n", + Status, + Msg->IO.Now - Msg->IO.StartIO, + Msg->user, + Msg->node, + Msg->name, + ChrPtr(Msg->MyQEntry->StatusMessage)); - safestrncpy(SendMsg->addr, addr, SIZ); - safestrncpy(SendMsg->envelope_from_buf, envelope_from, 1024); - return SendMsg; -} + Msg->IDestructQueItem = DecreaseQReference(Msg->MyQItem); + Msg->nRemain = CountActiveQueueEntries(Msg->MyQItem); -int smtp_resolve_recipients(SmtpOutMsg *SendMsg) -{ - 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(SendMsg->addr, 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; + if (Msg->MyQEntry->Active && + CheckQEntryIsBounce(Msg->MyQEntry)) + { + /* are we casue for a bounce mail? */ + Msg->MyQItem->SendBounceMail |= (1<MyQEntry->Status); } - return 0; + if ((Msg->nRemain > 0) || Msg->IDestructQueItem) + Msg->QMsgData = SerializeQueueItem(Msg->MyQItem); + else + Msg->QMsgData = NULL; } - -void resolve_mx_hosts(SmtpOutMsg *SendMsg) +eNextState FinalizeMessageSend(SmtpOutMsg *Msg) { - /// well this is blocking and sux, but libevent jsut supports async dns since v2 - /* Figure out what mail exchanger host we have to connect to */ - SendMsg->num_mxhosts = getmx(SendMsg->mxhosts, SendMsg->node); - CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Number of MX hosts for <%s> is %d [%s]\n", - SendMsg->n, SendMsg->node, SendMsg->num_mxhosts, SendMsg->mxhosts); - if (SendMsg->num_mxhosts < 1) { - SendMsg->SMTPstatus = 5; - snprintf(SendMsg->dsn, SIZ, "No MX hosts found for <%s>", SendMsg->node); - return; ///////TODO: abort! - } - + return QueueDBOperation(&Msg->IO, FinalizeMessageSend_DB1); } -/* TODO: abort... */ -#define SMTP_ERROR(WHICH_ERR, ERRSTR) {SendMsg->SMTPstatus = WHICH_ERR; memcpy(SendMsg->dsn, HKEY(ERRSTR) + 1); return eAbort; } -#define SMTP_VERROR(WHICH_ERR) { SendMsg->SMTPstatus = WHICH_ERR; safestrncpy(SendMsg->dsn, &ChrPtr(SendMsg->IO.IOBuf)[4], sizeof(SendMsg->dsn)); return eAbort; } -#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 connect_one_smtpsrv(SmtpOutMsg *SendMsg) +inline void FinalizeMessageSend_DB_1(AsyncIO *IO) { - char *endpart; - char buf[SIZ]; - - extract_token(buf, SendMsg->mxhosts, SendMsg->n_mx, '|', sizeof(buf)); - strcpy(SendMsg->mx_user, ""); - strcpy(SendMsg->mx_pass, ""); - 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'; - } - } - else - strcpy (SendMsg->mx_host, buf); - endpart = strrchr(SendMsg->mx_host, ':'); - if (endpart != 0){ - *endpart = '\0'; - strcpy(SendMsg->mx_port, endpart + 1); - } - else { - strcpy(SendMsg->mx_port, "25"); - } - CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connecting to %s : %s ...\n", - SendMsg->n, SendMsg->mx_host, SendMsg->mx_port); + SmtpOutMsg *Msg = IO->Data; + /* + * Uncompleted delivery instructions remain, so delete the old + * instructions and replace with the updated ones. + */ + EVS_syslog(LOG_DEBUG, "SMTPQD: %ld", Msg->MyQItem->QueMsgID); + CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, ""); } - - -int connect_one_smtpsrv_xamine_result(void *Ctx) +eNextState FinalizeMessageSend_DB1(AsyncIO *IO) { - SmtpOutMsg *SendMsg = Ctx; - SendMsg->IO.SendBuf.fd = - SendMsg->IO.RecvBuf.fd = - SendMsg->IO.sock = sock_connect(SendMsg->mx_host, SendMsg->mx_port); - - snprintf(SendMsg->dsn, SIZ, "Could not connect: %s", strerror(errno)); - if (SendMsg->IO.sock >= 0) - { - CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connected!\n", SendMsg->n); - int fdflags; - fdflags = fcntl(SendMsg->IO.sock, F_GETFL); - if (fdflags < 0) - CtdlLogPrintf(CTDL_DEBUG, - "SMTP client[%ld]: unable to get socket flags! %s \n", - SendMsg->n, strerror(errno)); - fdflags = fdflags | O_NONBLOCK; - if (fcntl(SendMsg->IO.sock, F_SETFL, fdflags) < 0) - CtdlLogPrintf(CTDL_DEBUG, - "SMTP client[%ld]: unable to set socket nonblocking flags! %s \n", - SendMsg->n, strerror(errno)); - } - if (SendMsg->IO.sock < 0) { - if (errno > 0) { - snprintf(SendMsg->dsn, SIZ, "%s", strerror(errno)); - } - else { - snprintf(SendMsg->dsn, SIZ, "Unable to connect to %s : %s\n", - SendMsg->mx_host, SendMsg->mx_port); - } - } - /// hier: naechsten mx ausprobieren. - if (SendMsg->IO.sock < 0) { - SendMsg->SMTPstatus = 4; /* dsn is already filled in */ - //// hier: abbrechen & bounce. - return -1; - } - SendMsg->IO.SendBuf.Buf = NewStrBuf(); - SendMsg->IO.RecvBuf.Buf = NewStrBuf(); - SendMsg->IO.IOBuf = NewStrBuf(); - InitEventIO(&SendMsg->IO, SendMsg, - SMTP_C_DispatchReadDone, - SMTP_C_DispatchWriteDone, - SMTP_C_ReadServerStatus, - 1); - return 0; + FinalizeMessageSend_1(IO); + FinalizeMessageSend_DB_1(IO); + return NextDBOperation(IO, FinalizeMessageSend_DB2); } -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) - } - return eSendReply; -} -eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg) +inline void FinalizeMessageSend_DB_2(AsyncIO *IO) { - /* 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); + SmtpOutMsg *Msg = IO->Data; - SMTP_DBG_SEND(); - return eReadMessage; + if (Msg->IDestructQueItem) + smtpq_do_bounce(Msg->MyQItem, Msg->msgtext); } - -eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg) +eNextState FinalizeMessageSend_DB2(AsyncIO *IO) { - SMTP_DBG_READ(); + FinalizeMessageSend_DB_2(IO); - if (SMTP_IS_STATE('2')) { - SendMsg->State ++; - if (IsEmptyStr(SendMsg->mx_user)) - SendMsg->State ++; /* Skip auth... */ - } - /* else we fall back to 'helo' */ - return eSendReply; + return NextDBOperation(IO, FinalizeMessageSend_DB3); } -eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg) -{ - StrBufPrintf(SendMsg->IO.SendBuf.Buf, - "HELO %s\r\n", config.c_fqdn); - - SMTP_DBG_SEND(); - return eReadMessage; -} -eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg) +inline void FinalizeMessageSend_DB_3(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 (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, "SMTPQ: %ld", Msg->MyQItem->QueMsgID); + CtdlFreeMessage(msg); } - if (!IsEmptyStr(SendMsg->mx_user)) - SendMsg->State ++; /* Skip auth... */ - return eSendReply; -} - -eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg) -{ - 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; -} - -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) + else { + CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, + &Msg->MyQItem->MessageID, + 1, + ""); + FreeStrBuf(&Msg->QMsgData); } - return eSendReply; + DecreaseShutdownDeliveries(Msg->MyQItem); } - -eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg) +eNextState FinalizeMessageSend_DB3(AsyncIO *IO) { - /* previous command succeeded, now try the MAIL FROM: command */ - StrBufPrintf(SendMsg->IO.SendBuf.Buf, - "MAIL FROM:<%s>\r\n", - SendMsg->envelope_from); - - SMTP_DBG_SEND(); - return eReadMessage; + SmtpOutMsg *Msg = IO->Data; + FinalizeMessageSend_DB_3(IO); + if (!Msg->IDestructQueItem) + return eAbort; + return NextDBOperation(IO, FinalizeMessageSend_DB4); } -eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg) +eNextState FinalizeMessageSend_DB4(AsyncIO *IO) { - SMTP_DBG_READ(); + int Done; + SmtpOutMsg *Msg = IO->Data; - if (!SMTP_IS_STATE('2')) { - if (SMTP_IS_STATE('4')) - SMTP_VERROR(4) - else - SMTP_VERROR(5) - } - return eSendReply; -} - - -eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg) -{ - /* 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; + Done = GetShutdownDeliveries(Msg->MyQItem); + if (!Done) + return NextDBOperation(IO, FinalizeMessageSend_DB4); + else + return eAbort; } -eNextState SMTPC_read_RCPT_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) - } - return eSendReply; + RemoveContext(Msg->IO.CitContext); + if (Msg->IDestructQueItem) + RemoveQItem(Msg->MyQItem); + DeleteSmtpOutMsg(Msg); + return eAbort; } -eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg) +eNextState FailOneAttempt(AsyncIO *IO) { - /* RCPT succeeded, now try the DATA command */ - StrBufPlain(SendMsg->IO.SendBuf.Buf, - HKEY("DATA\r\n")); + SmtpOutMsg *Msg = IO->Data; - SMTP_DBG_SEND(); - return eReadMessage; -} + if (Msg->MyQEntry->Status == 2) + return eAbort; -eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg) -{ - SMTP_DBG_READ(); + /* + * possible ways here: + * - connection timeout + * - dns lookup failed + */ + StopClientWatchers(IO); - if (!SMTP_IS_STATE('3')) { - if (SMTP_IS_STATE('4')) - SMTP_VERROR(3) - else - SMTP_VERROR(5) + if (Msg->pCurrRelay != NULL) + Msg->pCurrRelay = Msg->pCurrRelay->Next; + + if (Msg->pCurrRelay == NULL) { + EVS_syslog(LOG_DEBUG, "SMTP: %s Aborting\n", __FUNCTION__); + return eAbort; + } + if (Msg->pCurrRelay->IsIP) { + EVS_syslog(LOG_DEBUG, "SMTP: %s connecting IP\n", __FUNCTION__); + return mx_connect_ip(IO); + } + else { + EVS_syslog(LOG_DEBUG, + "SMTP: %s resolving next MX Record\n", + __FUNCTION__); + return get_one_mx_host_ip(IO); } - return eSendReply; } -eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg) -{ - StrBuf *Buf; - /* If we reach this point, the server is expecting data.*/ - 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 ++; - - return eSendMore; -} - -eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg) +void SetConnectStatus(AsyncIO *IO) { - StrBuf *Buf; + SmtpOutMsg *Msg = IO->Data; + char buf[256]; + void *src; - Buf = SendMsg->IO.SendBuf.Buf; - SendMsg->IO.SendBuf.Buf = SendMsg->msgtext; - SendMsg->msgtext = Buf; + buf[0] = '\0'; - StrBufPlain(SendMsg->IO.SendBuf.Buf, - HKEY(".\r\n")); + if (IO->ConnectMe->IPv6) { + src = &IO->ConnectMe->Addr.sin6_addr; + } + else { + struct sockaddr_in *addr; - return eReadMessage; + addr = (struct sockaddr_in *)&IO->ConnectMe->Addr; + src = &addr->sin_addr.s_addr; + } -} + inet_ntop((IO->ConnectMe->IPv6)?AF_INET6:AF_INET, + src, + buf, + sizeof(buf)); -eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg) -{ - SMTP_DBG_READ(); + if (Msg->mx_host == NULL) + Msg->mx_host = ""; - if (!SMTP_IS_STATE('2')) { - if (SMTP_IS_STATE('4')) - SMTP_VERROR(4) - else - SMTP_VERROR(5) - } + EVS_syslog(LOG_DEBUG, + "SMTP client[%ld]: connecting to %s [%s]:%d ...\n", + Msg->n, + Msg->mx_host, + buf, + Msg->IO.ConnectMe->Port); - /* We did it! */ - safestrncpy(SendMsg->dsn, &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4], 1023); - SendMsg->SMTPstatus = 2; - return eSendReply; + 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_QUIT(SmtpOutMsg *SendMsg) +/***************************************************************************** + * So we connect our Relay IP here. * + *****************************************************************************/ +eNextState mx_connect_ip(AsyncIO *IO) { - StrBufPlain(SendMsg->IO.SendBuf.Buf, - HKEY("QUIT\r\n")); + SmtpOutMsg *Msg = IO->Data; - SMTP_DBG_SEND(); - return eReadMessage; -} + EVS_syslog(LOG_DEBUG, "SMTP: %s\n", __FUNCTION__); -eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg) -{ - SMTP_DBG_READ(); + IO->ConnectMe = Msg->pCurrRelay; + Msg->State = eConnectMX; - CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n", - SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name); - return eSendReply; -} + SetConnectStatus(IO); -eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg) -{ - return eSendReply; + return EvConnectSock(IO, + SMTP_C_ConnTimeout, + SMTP_C_ReadTimeouts[0], + 1); } -eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg) +eNextState get_one_mx_host_ip_done(AsyncIO *IO) { - return eReadMessage; -} - -/* - * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery - * instructions for "5" codes (permanent fatal errors) and produce/deliver - * a "bounce" message (delivery status notification). - */ -void smtp_do_bounce(char *instr) { - int i; - int lines; - int status; - char buf[1024]; - char key[1024]; - char addr[1024]; - char dsn[1024]; - char bounceto[1024]; - StrBuf *boundary; - int num_bounces = 0; - int bounce_this = 0; - long bounce_msgid = (-1); - time_t submitted = 0L; - struct CtdlMessage *bmsg = NULL; - int give_up = 0; - struct recptypes *valid; - int successful_bounce = 0; - static int seq = 0; - StrBuf *BounceMB; - long omsgid = (-1); - - CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n"); - strcpy(bounceto, ""); - boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_")); - StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq); - lines = num_tokens(instr, '\n'); - - /* See if it's time to give up on delivery of this message */ - for (i=0; iData; + struct hostent *hostent; + + QueryCbDone(IO); + EVS_syslog(LOG_DEBUG, "SMTP: %s Time[%fs]\n", + __FUNCTION__, + IO->Now - IO->DNS.Start); + + 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)); + + Msg->pCurrRelay->Addr.sin6_family = + hostent->h_addrtype; + Msg->pCurrRelay->Addr.sin6_port = + htons(DefaultMXPort); } - } - - if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) { - give_up = 1; - } + 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]); + */ - /* Start building our bounce message */ - - bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage)); - if (bmsg == NULL) return; - memset(bmsg, 0, sizeof(struct CtdlMessage)); - BounceMB = NewStrBufPlain(NULL, 1024); - - bmsg->cm_magic = CTDLMESSAGE_MAGIC; - bmsg->cm_anon_type = MES_NORMAL; - bmsg->cm_format_type = FMT_RFC822; - bmsg->cm_fields['A'] = strdup("Citadel"); - bmsg->cm_fields['O'] = strdup(MAILROOM); - bmsg->cm_fields['N'] = strdup(config.c_nodename); - bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)"); - StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0); - StrBufAppendBuf(BounceMB, boundary, 0); - StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0); - StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0); - StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0); - StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0); - StrBufAppendBufPlain(BounceMB, HKEY("--"), 0); - StrBufAppendBuf(BounceMB, boundary, 0); - StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0); - StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0); - - if (give_up) StrBufAppendBufPlain(BounceMB, HKEY( -"A message you sent could not be delivered to some or all of its recipients\n" -"due to prolonged unavailability of its destination(s).\n" -"Giving up on the following addresses:\n\n" - ), 0); - - else StrBufAppendBufPlain(BounceMB, HKEY( -"A message you sent could not be delivered to some or all of its recipients.\n" -"The following addresses were undeliverable:\n\n" - ), 0); + addr = (struct sockaddr_in*) &Msg->pCurrRelay->Addr; - /* - * Now go through the instructions checking for stuff. - */ - for (i=0; i addr=<%s> status=%d dsn=<%s>\n", - key, addr, status, dsn); - - if (!strcasecmp(key, "bounceto")) { - strcpy(bounceto, addr); - } - - if (!strcasecmp(key, "msgid")) { - omsgid = atol(addr); - } + memcpy(&addr->sin_addr.s_addr, + hostent->h_addr_list[0], + sizeof(uint32_t)); - if (!strcasecmp(key, "remote")) { - if (status == 5) bounce_this = 1; - if (give_up) bounce_this = 1; + addr->sin_family = hostent->h_addrtype; + addr->sin_port = htons(DefaultMXPort); } - - if (bounce_this) { - ++num_bounces; - - StrBufAppendBufPlain(BounceMB, addr, addrlen, 0); - StrBufAppendBufPlain(BounceMB, HKEY(": "), 0); - StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0); - StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0); - - remove_token(instr, i, '\n'); - --i; - --lines; + 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); } - - /* Attach the original message */ - if (omsgid >= 0) { - StrBufAppendBufPlain(BounceMB, HKEY("--"), 0); - StrBufAppendBuf(BounceMB, boundary, 0); - StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0); - StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0); - StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0); - StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0); - StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0); - - CC->redirect_buffer = NewStrBufPlain(NULL, SIZ); - CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0); - StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0); - FreeStrBuf(&CC->redirect_buffer); + else { + if (Msg->HostLookup.VParsedDNSReply != NULL) { + Msg->HostLookup.DNSReplyFree(Msg->HostLookup.VParsedDNSReply); + Msg->HostLookup.VParsedDNSReply = NULL; + } + return FailOneAttempt(IO); } +} - /* Close the multipart MIME scope */ - StrBufAppendBufPlain(BounceMB, HKEY("--"), 0); - StrBufAppendBuf(BounceMB, boundary, 0); - StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0); - if (bmsg->cm_fields['A'] != NULL) - free(bmsg->cm_fields['A']); - bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB); - /* Deliver the bounce if there's anything worth mentioning */ - CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces); - if (num_bounces > 0) { - - /* First try the user who sent the message */ - CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto); - if (IsEmptyStr(bounceto)) { - CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n"); - bounce_msgid = (-1L); - } +eNextState get_one_mx_host_ip(AsyncIO *IO) +{ + 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 + */ - /* Can we deliver the bounce to the original sender? */ - valid = validate_recipients(bounceto, smtp_get_Recipients (), 0); - if (valid != NULL) { - if (valid->num_error == 0) { - CtdlSubmitMsg(bmsg, valid, "", QP_EADDR); - successful_bounce = 1; - } - } + EVS_syslog(LOG_DEBUG, "SMTP: %s\n", __FUNCTION__); - /* If not, post it in the Aide> room */ - if (successful_bounce == 0) { - CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR); - } + EVS_syslog(LOG_DEBUG, + "SMTP client[%ld]: looking up %s-Record %s : %d ...\n", + Msg->n, + (Msg->pCurrRelay->IPv6)? "aaaa": "a", + Msg->pCurrRelay->Host, + Msg->pCurrRelay->Port); - /* Free up the memory we used */ - if (valid != NULL) { - free_recipients(valid); - } + 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; } - FreeStrBuf(&boundary); - CtdlFreeMessage(bmsg); - CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n"); + IO->NextState = eReadDNSReply; + return IO->NextState; } -/* - * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a - * set of delivery instructions for completed deliveries and remove them. - * - * It returns the number of incomplete deliveries remaining. - */ -int smtp_purge_completed_deliveries(char *instr) { - int i; - int lines; - int status; - char buf[1024]; - char key[1024]; - char addr[1024]; - char dsn[1024]; - int completed; - int incomplete = 0; - - lines = num_tokens(instr, '\n'); - for (i=0; iData; + ParsedURL **pp; + + QueryCbDone(IO); + + EVS_syslog(LOG_DEBUG, "SMTP: %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; } - - if (completed) { - remove_token(instr, i, '\n'); - --i; - --lines; + 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; } - - return(incomplete); + 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); } -void smtp_try(const char *key, const char *addr, int *status, - char *dsn, size_t n, long msgnum, char *envelope_from) +eNextState resolve_mx_records(AsyncIO *IO) { - SmtpOutMsg * SmtpC = smtp_load_msg(msgnum, addr, envelope_from); - smtp_resolve_recipients(SmtpC); - resolve_mx_hosts(SmtpC); - connect_one_smtpsrv(SmtpC); - QueueEventContext(SmtpC, connect_one_smtpsrv_xamine_result); -} - -/* - * 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; - char *results = NULL; - int i; - int lines; - int status; - char buf[1024]; - char key[1024]; - char addr[1024]; - char dsn[1024]; - char envelope_from[1024]; - long text_msgid = (-1); - int incomplete_deliveries_remaining; - time_t attempted = 0L; - time_t last_attempted = 0L; - time_t retry = SMTP_RETRY_INTERVAL; - - CtdlLogPrintf(CTDL_DEBUG, "SMTP client: smtp_do_procmsg(%ld)\n", msgnum); - strcpy(envelope_from, ""); - - msg = CtdlFetchMessage(msgnum, 1); - if (msg == NULL) { - CtdlLogPrintf(CTDL_ERR, "SMTP client: tried %ld but no such message!\n", msgnum); - return; - } - - instr = strdup(msg->cm_fields['M']); - CtdlFreeMessage(msg); - - /* Strip out the headers amd any other non-instruction line */ - lines = num_tokens(instr, '\n'); - for (i=0; iData; + + EVS_syslog(LOG_DEBUG, "SMTP: %s\n", __FUNCTION__); + /* start resolving MX records here. */ + if (!QueueQuery(ns_t_mx, + Msg->node, + &Msg->IO, + &Msg->MxLookup, + smtp_resolve_mx_record_done)) + { + Msg->MyQEntry->Status = 5; + StrBufPrintf(Msg->MyQEntry->StatusMessage, + "No MX hosts found for <%s>", Msg->node); + return IO->NextState; } + Msg->IO.NextState = eReadDNSReply; + return IO->NextState; +} - /* Learn the message ID and find out about recent delivery attempts */ - lines = num_tokens(instr, '\n'); - for (i=0; i SMTP_RETRY_MAX) { - retry = SMTP_RETRY_MAX; - } - remove_token(instr, i, '\n'); - } - if (!strcasecmp(key, "attempted")) { - attempted = extract_long(buf, 1); - if (attempted > last_attempted) - last_attempted = attempted; - } - } - /* - * Postpone delivery if we've already tried recently. - * / - if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) { - CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n"); - free(instr); - return; - } -TMP TODO */ - /* - * Bail out if there's no actual message associated with this - */ - if (text_msgid < 0L) { - CtdlLogPrintf(CTDL_ERR, "SMTP client: no 'msgid' directive found!\n"); - free(instr); - return; - } +/****************************************************************************** + * so, we're going to start a SMTP delivery. lets get it on. * + ******************************************************************************/ - /* Plow through the instructions looking for 'remote' directives and - * a status of 0 (no delivery yet attempted) or 3/4 (transient errors - * were experienced and it's time to try again) - */ - lines = num_tokens(instr, '\n'); - for (i=0; in = MsgCount; + Msg->MyQEntry = MyQEntry; + Msg->MyQItem = MyQItem; + Msg->pCurrRelay = MyQItem->URL; + + 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); + + Msg->IO.ErrMsg = Msg->MyQEntry->StatusMessage; + + return Msg; +} + +void smtp_try_one_queue_entry(OneQueItem *MyQItem, + MailQEntry *MyQEntry, + StrBuf *MsgText, + /*KeepMsgText allows us to use MsgText as ours.*/ + int KeepMsgText, + int MsgCount) +{ + SmtpOutMsg *Msg; + + syslog(LOG_DEBUG, "SMTP: %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)); + + syslog(LOG_DEBUG, "SMTP 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); } - - --i; - --lines; - CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Trying <%s>\n", addr); - smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid, envelope_from); - if (status != 2) { - if (results == NULL) { - results = malloc(1024); - memset(results, 0, 1024); - } - else { - results = realloc(results, strlen(results) + 1024); - } - snprintf(&results[strlen(results)], 1024, - "%s|%s|%d|%s\n", - key, addr, status, dsn); + else { + /* uneducated admin has chosen to + add DNS to the equation... */ + QueueEventContext(&Msg->IO, + get_one_mx_host_ip); } } } - - if (results != NULL) { - instr = realloc(instr, strlen(instr) + strlen(results) + 2); - strcat(instr, results); - free(results); + else { + /* No recipients? well fail then. */ + if ((Msg==NULL) || + (Msg->MyQEntry == NULL)) { + Msg->MyQEntry->Status = 5; + StrBufPlain(Msg->MyQEntry->StatusMessage, + HKEY("Invalid Recipient!")); + } + FinalizeMessageSend_1(&Msg->IO); + FinalizeMessageSend_DB_1(&Msg->IO); + FinalizeMessageSend_DB_2(&Msg->IO); + FinalizeMessageSend_DB_3(&Msg->IO); + FinalizeMessageSend_DB(&Msg->IO); } +} - /* Generate 'bounce' messages */ - smtp_do_bounce(instr); - /* Go through the delivery list, deleting completed deliveries */ - incomplete_deliveries_remaining = - smtp_purge_completed_deliveries(instr); - /* - * No delivery instructions remain, so delete both the instructions - * message and the message message. - */ - if (incomplete_deliveries_remaining <= 0) { - long delmsgs[2]; - delmsgs[0] = msgnum; - delmsgs[1] = text_msgid; - CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, ""); - } - - /* - * Uncompleted delivery instructions remain, so delete the old - * instructions and replace with the updated ones. - */ - if (incomplete_deliveries_remaining > 0) { - CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, ""); - 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'] = malloc(strlen(instr)+SIZ); - snprintf(msg->cm_fields['M'], - strlen(instr)+SIZ, - "Content-type: %s\n\n%s\n" - "attempted|%ld\n" - "retry|%ld\n", - SPOOLMIME, instr, (long)time(NULL), (long)retry ); - CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR); - CtdlFreeMessage(msg); - } - - free(instr); -} - /*****************************************************************************/ -/* SMTP UTILITY COMMANDS */ +/* SMTP CLIENT DISPATCHER */ /*****************************************************************************/ -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; + + EVS_syslog(LOG_DEBUG, "SMTP: %s\n", __FUNCTION__); + + 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; } - cprintf("000\n"); - return; - } - - else if (!strcasecmp(cmd, "runqueue")) { - run_queue_now = 1; - cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK); + 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; + } + 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, "SMTP: %s\n", __FUNCTION__); + SmtpOutMsg *Msg = IO->Data; + eNextState rc; - else { - cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE); + 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, "SMTP: %s\n", __FUNCTION__); + SmtpOutMsg *Msg = IO->Data; + eNextState rc; + rc = SendHandlers[Msg->State](Msg); + SMTPSetTimeout(rc, Msg); + return rc; } -/* - * smtp_queue_thread() - * - * Run through the queue sending out messages. - */ -void *smtp_queue_thread(void *arg) { - int num_processed = 0; - struct CitContext smtp_queue_CC; +/*****************************************************************************/ +/* SMTP CLIENT ERROR CATCHERS */ +/*****************************************************************************/ +eNextState SMTP_C_Terminate(AsyncIO *IO) +{ + SmtpOutMsg *Msg = IO->Data; - CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send"); - citthread_setspecific(MyConKey, (void *)&smtp_queue_CC); - CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n"); + EVS_syslog(LOG_DEBUG, "SMTP: %s\n", __FUNCTION__); + return FinalizeMessageSend(Msg); +} +eNextState SMTP_C_TerminateDB(AsyncIO *IO) +{ + EVS_syslog(LOG_DEBUG, "SMTP: %s\n", __FUNCTION__); + return FinalizeMessageSend_DB(IO); +} +eNextState SMTP_C_Timeout(AsyncIO *IO) +{ + SmtpOutMsg *Msg = IO->Data; - while (!CtdlThreadCheckStop()) { - - CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n"); + Msg->MyQEntry->Status = 4; + EVS_syslog(LOG_DEBUG, "SMTP: %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; - if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) { - CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM); - } - else { - num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL); - } - CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed); - CtdlThreadSleep(60); - } + Msg->MyQEntry->Status = 4; + EVS_syslog(LOG_DEBUG, "SMTP: %s\n", __FUNCTION__); + StrBufPlain(IO->ErrMsg, CKEY(ReadErrors[Msg->State])); + return FailOneAttempt(IO); +} +eNextState SMTP_C_DNSFail(AsyncIO *IO) +{ + SmtpOutMsg *Msg = IO->Data; + Msg->MyQEntry->Status = 4; + EVS_syslog(LOG_DEBUG, "SMTP: %s\n", __FUNCTION__); + return FailOneAttempt(IO); +} +eNextState SMTP_C_Shutdown(AsyncIO *IO) +{ + EVS_syslog(LOG_DEBUG, "SMTP: %s\n", __FUNCTION__); + SmtpOutMsg *Msg = IO->Data; - CtdlClearSystemContext(); - return(NULL); + Msg->MyQEntry->Status = 3; + StrBufPlain(Msg->MyQEntry->StatusMessage, + HKEY("server shutdown during message submit.")); + return FinalizeMessageSend(Msg); } -/* - * Initialize the SMTP outbound queue +/** + * @brief lineread Handler; + * understands when to read more SMTP lines, and when this is a one-lined reply. */ -void smtp_init_spoolout(void) { - struct ctdlroom qrbuf; +eReadState SMTP_C_ReadServerStatus(AsyncIO *IO) +{ + eReadState Finished = eBufferNotEmpty; - /* - * 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); + while (Finished == eBufferNotEmpty) { + Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf); - /* - * 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); + 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; } - -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_DispatchReadDone(void *Data) -{ - SmtpOutMsg *pMsg = Data; - eNextState rc = ReadHandlers[pMsg->State](pMsg); - pMsg->State++; - return rc; -} - -eNextState SMTP_C_DispatchWriteDone(void *Data) +void LogDebugEnableSMTPClient(void) { - SmtpOutMsg *pMsg = Data; - return SendHandlers[pMsg->State](pMsg); - + SMTPClientDebugEnabled = 1; } - -#endif CTDL_MODULE_INIT(smtp_eventclient) { -#ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT if (!threading) - { - smtp_init_spoolout(); - 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); return "smtpeventclient"; }