SMTP-Client: add the stati of all delivery attempts to the status messages.
[citadel.git] / citadel / modules / smtp / serv_smtpeventclient.c
index 9c55123e2dc4cc41fa8d19f2ecda59d421a6bf7c..c744bb6e4cb81fd3bf2d99a60834d6c21c04f435 100644 (file)
 #include "smtpqueue.h"
 #include "smtp_clienthandlers.h"
 
+ConstStr SMTPStates[] = {
+       {HKEY("looking up mx - record")},
+       {HKEY("evaluating what to do next")},
+       {HKEY("looking up a - record")},
+       {HKEY("looking up aaaa - record")},
+       {HKEY("connecting remote")},
+       {HKEY("smtp conversation ongoing")},
+       {HKEY("smtp sending maildata")},
+       {HKEY("smtp sending done")},
+       {HKEY("smtp successfully finished")},
+       {HKEY("failed one attempt")},
+       {HKEY("failed temporarily")},
+       {HKEY("failed permanently")}
+};
+
+void SetSMTPState(AsyncIO *IO, smtpstate State)
+{
+       CitContext* CCC = IO->CitContext;
+       memcpy(CCC->cs_clientname, SMTPStates[State].Key, SMTPStates[State].len + 1);
+}
+
 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__);
+       EV_syslog(LOG_DEBUG, "%s Exit\n", __FUNCTION__);
 
        /* these are kept in our own space and free'd below */
        Msg->IO.ConnectMe = NULL;
@@ -123,11 +143,6 @@ 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)         *
@@ -135,17 +150,23 @@ eNextState FinalizeMessageSend_DB4(AsyncIO *IO);
  * 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)
+eNextState FinalizeMessageSend_DB(AsyncIO *IO)
 {
        const char *Status;
        SmtpOutMsg *Msg = IO->Data;
        
-       if (Msg->MyQEntry->Status == 2) 
+       if (Msg->MyQEntry->Status == 2) {
+               SetSMTPState(IO, eSTMPfinished);
                Status = "Delivery successful.";
-       else if (Msg->MyQEntry->Status == 5) 
+       }
+       else if (Msg->MyQEntry->Status == 5) {
+               SetSMTPState(IO, eSMTPFailTotal);
                Status = "Delivery failed permanently; giving up.";
-       else
+       }
+       else {
+               SetSMTPState(IO, eSMTPFailTemporary);
                Status = "Delivery failed temporarily; will retry later.";
+       }
                        
        EVS_syslog(LOG_INFO,
                   "%s Time[%fs] Recipient <%s> @ <%s> (%s) Status message: %s\n",
@@ -159,9 +180,10 @@ inline void FinalizeMessageSend_1(AsyncIO *IO)
 
        Msg->IDestructQueItem = DecreaseQReference(Msg->MyQItem);
 
-       Msg->nRemain = CountActiveQueueEntries(Msg->MyQItem);
+       Msg->nRemain = CountActiveQueueEntries(Msg->MyQItem, 0);
 
        if (Msg->MyQEntry->Active && 
+           !Msg->MyQEntry->StillActive &&
            CheckQEntryIsBounce(Msg->MyQEntry))
        {
                /* are we casue for a bounce mail? */
@@ -172,15 +194,6 @@ inline void FinalizeMessageSend_1(AsyncIO *IO)
                Msg->QMsgData = SerializeQueueItem(Msg->MyQItem);
        else
                Msg->QMsgData = NULL;
-}
-eNextState FinalizeMessageSend(SmtpOutMsg *Msg)
-{
-       return QueueDBOperation(&Msg->IO, FinalizeMessageSend_DB1);
-}
-
-inline void FinalizeMessageSend_DB_1(AsyncIO *IO)
-{
-       SmtpOutMsg *Msg = IO->Data;
 
        /*
         * Uncompleted delivery instructions remain, so delete the old
@@ -188,33 +201,10 @@ inline void FinalizeMessageSend_DB_1(AsyncIO *IO)
         */
        EVS_syslog(LOG_DEBUG, "%ld", Msg->MyQItem->QueMsgID);
        CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, "");
-}
-eNextState FinalizeMessageSend_DB1(AsyncIO *IO)
-{
-       FinalizeMessageSend_1(IO);
-       FinalizeMessageSend_DB_1(IO);
-       return NextDBOperation(IO, FinalizeMessageSend_DB2);
-}
-
-
-inline void FinalizeMessageSend_DB_2(AsyncIO *IO)
-{
-       SmtpOutMsg *Msg = IO->Data;
+       Msg->MyQItem->QueMsgID = -1;
 
        if (Msg->IDestructQueItem)
-               smtpq_do_bounce(Msg->MyQItem, Msg->msgtext);
-}
-eNextState FinalizeMessageSend_DB2(AsyncIO *IO)
-{
-       FinalizeMessageSend_DB_2(IO);
-
-       return NextDBOperation(IO, FinalizeMessageSend_DB3);
-}
-
-
-inline void FinalizeMessageSend_DB_3(AsyncIO *IO)
-{
-       SmtpOutMsg *Msg = IO->Data;
+               smtpq_do_bounce(Msg->MyQItem, Msg->msgtext, Msg->pCurrRelay);
 
        if (Msg->nRemain > 0)
        {
@@ -238,44 +228,32 @@ inline void FinalizeMessageSend_DB_3(AsyncIO *IO)
                                   "");
                FreeStrBuf(&Msg->QMsgData);
        }
-       DecreaseShutdownDeliveries(Msg->MyQItem);
-}
-eNextState FinalizeMessageSend_DB3(AsyncIO *IO)
-{
-       SmtpOutMsg *Msg = IO->Data;
-       FinalizeMessageSend_DB_3(IO);
-       if (!Msg->IDestructQueItem)
-               return eAbort;
-       return NextDBOperation(IO, FinalizeMessageSend_DB4);
-}
 
-eNextState FinalizeMessageSend_DB4(AsyncIO *IO)
-{
-       int Done;
-       SmtpOutMsg *Msg = IO->Data;
-
-       Done = GetShutdownDeliveries(Msg->MyQItem);
-       if (!Done) 
-               return NextDBOperation(IO, FinalizeMessageSend_DB4);
-       else
-               return eAbort;
+       RemoveContext(Msg->IO.CitContext);
+       return eAbort;
 }
 
-eNextState FinalizeMessageSend_DB(AsyncIO *IO)
+eNextState Terminate(AsyncIO *IO)
 {
        SmtpOutMsg *Msg = IO->Data;
 
-       RemoveContext(Msg->IO.CitContext);
        if (Msg->IDestructQueItem)
                RemoveQItem(Msg->MyQItem);
+
        DeleteSmtpOutMsg(Msg);
        return eAbort;
 }
+eNextState FinalizeMessageSend(SmtpOutMsg *Msg)
+{
+       /* hand over to DB Queue */
+       return QueueDBOperation(&Msg->IO, FinalizeMessageSend_DB);
+}
 
 eNextState FailOneAttempt(AsyncIO *IO)
 {
        SmtpOutMsg *Msg = IO->Data;
 
+       SetSMTPState(IO, eSTMPfailOne);
        if (Msg->MyQEntry->Status == 2)
                return eAbort;
 
@@ -284,10 +262,25 @@ eNextState FailOneAttempt(AsyncIO *IO)
         * - connection timeout
         * - dns lookup failed
         */
-       StopClientWatchers(IO);
+       StopClientWatchers(IO, 1);
+
+       Msg->MyQEntry->nAttempt ++;
+       if (Msg->MyQEntry->AllStatusMessages == NULL)
+               Msg->MyQEntry->AllStatusMessages = NewStrBuf();
+
+       StrBufAppendPrintf(Msg->MyQEntry->AllStatusMessages, "%ld) ", Msg->MyQEntry->nAttempt);
+       StrBufAppendBuf(Msg->MyQEntry->AllStatusMessages, Msg->MyQEntry->StatusMessage, 0);
+       StrBufAppendBufPlain(Msg->MyQEntry->AllStatusMessages, HKEY("; "), 0);
 
        if (Msg->pCurrRelay != NULL)
                Msg->pCurrRelay = Msg->pCurrRelay->Next;
+       if ((Msg->pCurrRelay != NULL) &&
+           !Msg->pCurrRelay->IsRelay &&
+           Msg->MyQItem->HaveRelay)
+       {
+               EVS_syslog(LOG_DEBUG, "%s Aborting; last relay failed.\n", __FUNCTION__);
+               return eAbort;
+       }
 
        if (Msg->pCurrRelay == NULL) {
                EVS_syslog(LOG_DEBUG, "%s Aborting\n", __FUNCTION__);
@@ -353,6 +346,7 @@ void SetConnectStatus(AsyncIO *IO)
 eNextState mx_connect_ip(AsyncIO *IO)
 {
        SmtpOutMsg *Msg = IO->Data;
+       SetSMTPState(IO, eSTMPconnecting);
 
        EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
 
@@ -372,6 +366,8 @@ eNextState get_one_mx_host_ip_done(AsyncIO *IO)
        SmtpOutMsg *Msg = IO->Data;
        struct hostent *hostent;
 
+       IO->ConnectMe = Msg->pCurrRelay;
+
        QueryCbDone(IO);
        EVS_syslog(LOG_DEBUG, "%s Time[%fs]\n",
                   __FUNCTION__,
@@ -389,7 +385,7 @@ eNextState get_one_mx_host_ip_done(AsyncIO *IO)
                        Msg->pCurrRelay->Addr.sin6_family =
                                hostent->h_addrtype;
                        Msg->pCurrRelay->Addr.sin6_port =
-                               htons(DefaultMXPort);
+                               htons(Msg->IO.ConnectMe->Port);
                }
                else {
                        struct sockaddr_in *addr;
@@ -407,7 +403,7 @@ eNextState get_one_mx_host_ip_done(AsyncIO *IO)
                               sizeof(uint32_t));
 
                        addr->sin_family = hostent->h_addrtype;
-                       addr->sin_port   = htons(DefaultMXPort);
+                       addr->sin_port   = htons(Msg->IO.ConnectMe->Port);
                }
                Msg->mx_host = Msg->pCurrRelay->Host;
                if (Msg->HostLookup.VParsedDNSReply != NULL) {
@@ -417,6 +413,7 @@ eNextState get_one_mx_host_ip_done(AsyncIO *IO)
                return mx_connect_ip(IO);
        }
        else {
+               SetSMTPState(IO, eSTMPfailOne);
                if (Msg->HostLookup.VParsedDNSReply != NULL) {
                        Msg->HostLookup.DNSReplyFree(Msg->HostLookup.VParsedDNSReply);
                        Msg->HostLookup.VParsedDNSReply = NULL;
@@ -434,6 +431,7 @@ eNextState get_one_mx_host_ip(AsyncIO *IO)
         * - the direct hostname if there was no mx record
         * - one of the mx'es
         */
+       SetSMTPState(IO, (Msg->pCurrRelay->IPv6)?eSTMPalookup:eSTMPaaaalookup);
 
        EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
 
@@ -550,6 +548,8 @@ eNextState resolve_mx_records(AsyncIO *IO)
 {
        SmtpOutMsg * Msg = IO->Data;
 
+       SetSMTPState(IO, eSTMPmxlookup);
+
        EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
        /* start resolving MX records here. */
        if (!QueueQuery(ns_t_mx,
@@ -580,6 +580,8 @@ SmtpOutMsg *new_smtp_outmsg(OneQueItem *MyQItem,
        SmtpOutMsg * Msg;
 
        Msg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
+       if (Msg == NULL)
+               return NULL;
        memset(Msg, 0, sizeof(SmtpOutMsg));
 
        Msg->n                = MsgCount;
@@ -617,11 +619,19 @@ void smtp_try_one_queue_entry(OneQueItem *MyQItem,
        SMTPC_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
 
        Msg = new_smtp_outmsg(MyQItem, MyQEntry, MsgCount);
+       if (Msg == NULL) {
+               SMTPC_syslog(LOG_DEBUG, "%s Failed to alocate message context.\n", __FUNCTION__);
+               if (KeepMsgText) 
+                       FreeStrBuf (&MsgText);
+               return;
+       }
        if (KeepMsgText) Msg->msgtext = MsgText;
        else             Msg->msgtext = NewStrBufDup(MsgText);
 
-       if (smtp_resolve_recipients(Msg)) {
-
+       if (smtp_resolve_recipients(Msg) &&
+           (!MyQItem->HaveRelay ||
+            (MyQItem->URL != NULL)))
+       {
                safestrncpy(
                        ((CitContext *)Msg->IO.CitContext)->cs_host,
                        Msg->node,
@@ -632,15 +642,19 @@ void smtp_try_one_queue_entry(OneQueItem *MyQItem,
                             Msg->MyQItem->MessageID,
                             ChrPtr(Msg->MyQEntry->Recipient),
                             ((CitContext*)Msg->IO.CitContext)->cs_pid);
-               if (Msg->pCurrRelay == NULL)
+               if (Msg->pCurrRelay == NULL) {
+                       SetSMTPState(&Msg->IO, eSTMPmxlookup);
                        QueueEventContext(&Msg->IO,
                                          resolve_mx_records);
+               }
                else { /* oh... via relay host */
                        if (Msg->pCurrRelay->IsIP) {
+                               SetSMTPState(&Msg->IO, eSTMPconnecting);
                                QueueEventContext(&Msg->IO,
                                                  mx_connect_ip);
                        }
                        else {
+                               SetSMTPState(&Msg->IO, eSTMPalookup);
                                /* uneducated admin has chosen to
                                   add DNS to the equation... */
                                QueueEventContext(&Msg->IO,
@@ -649,18 +663,16 @@ void smtp_try_one_queue_entry(OneQueItem *MyQItem,
                }
        }
        else {
+               SetSMTPState(&Msg->IO, eSMTPFailTotal);
                /* No recipients? well fail then. */
-               if ((Msg==NULL) ||
-                   (Msg->MyQEntry == NULL)) {
+               if (Msg->MyQEntry != NULL) {
                        Msg->MyQEntry->Status = 5;
-                       StrBufPlain(Msg->MyQEntry->StatusMessage,
-                                   HKEY("Invalid Recipient!"));
+                       if (StrLength(Msg->MyQEntry->StatusMessage) == 0)
+                               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);
+               DeleteSmtpOutMsg(Msg);
        }
 }
 
@@ -754,7 +766,7 @@ eNextState SMTP_C_Terminate(AsyncIO *IO)
 eNextState SMTP_C_TerminateDB(AsyncIO *IO)
 {
        EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
-       return FinalizeMessageSend_DB(IO);
+       return Terminate(IO);
 }
 eNextState SMTP_C_Timeout(AsyncIO *IO)
 {
@@ -789,6 +801,28 @@ eNextState SMTP_C_Shutdown(AsyncIO *IO)
        EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
        SmtpOutMsg *Msg = IO->Data;
 
+       switch (IO->NextState) {
+       case eSendDNSQuery:
+       case eReadDNSReply:
+
+               /* todo: abort c-ares */
+       case eConnect:
+       case eSendReply:
+       case eSendMore:
+       case eSendFile:
+       case eReadMessage:
+       case eReadMore:
+       case eReadPayload:
+       case eReadFile:
+               StopClientWatchers(IO, 1);
+               break;
+       case eDBQuery:
+
+               break;
+       case eTerminateConnection:
+       case eAbort:
+               break;
+       }
        Msg->MyQEntry->Status = 3;
        StrBufPlain(Msg->MyQEntry->StatusMessage,
                    HKEY("server shutdown during message submit."));