Use a single function to transition from IO to DB Queue
[citadel.git] / citadel / modules / smtp / serv_smtpeventclient.c
1 /*
2  * This module is an SMTP and ESMTP implementation for the Citadel system.
3  * It is compliant with all of the following:
4  *
5  * RFC  821 - Simple Mail Transfer Protocol
6  * RFC  876 - Survey of SMTP Implementations
7  * RFC 1047 - Duplicate messages and SMTP
8  * RFC 1652 - 8 bit MIME
9  * RFC 1869 - Extended Simple Mail Transfer Protocol
10  * RFC 1870 - SMTP Service Extension for Message Size Declaration
11  * RFC 2033 - Local Mail Transfer Protocol
12  * RFC 2197 - SMTP Service Extension for Command Pipelining
13  * RFC 2476 - Message Submission
14  * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
15  * RFC 2554 - SMTP Service Extension for Authentication
16  * RFC 2821 - Simple Mail Transfer Protocol
17  * RFC 2822 - Internet Message Format
18  * RFC 2920 - SMTP Service Extension for Command Pipelining
19  *
20  * The VRFY and EXPN commands have been removed from this implementation
21  * because nobody uses these commands anymore, except for spammers.
22  *
23  * Copyright (c) 1998-2012 by the citadel.org team
24  *
25  *  This program is open source software; you can redistribute it and/or modify
26  *  it under the terms of the GNU General Public License version 3.
27  *  
28  *  
29  *
30  *  This program is distributed in the hope that it will be useful,
31  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
32  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
33  *  GNU General Public License for more details.
34  *
35  *  
36  *  
37  *  
38  */
39
40 #include "sysdep.h"
41 #include <stdlib.h>
42 #include <unistd.h>
43 #include <stdio.h>
44 #include <termios.h>
45 #include <fcntl.h>
46 #include <signal.h>
47 #include <pwd.h>
48 #include <errno.h>
49 #include <sys/types.h>
50 #include <syslog.h>
51
52 #if TIME_WITH_SYS_TIME
53 # include <sys/time.h>
54 # include <time.h>
55 #else
56 # if HAVE_SYS_TIME_H
57 #  include <sys/time.h>
58 # else
59 #  include <time.h>
60 # endif
61 #endif
62 #include <sys/wait.h>
63 #include <ctype.h>
64 #include <string.h>
65 #include <limits.h>
66 #include <sys/socket.h>
67 #include <netinet/in.h>
68 #include <arpa/inet.h>
69 #include <libcitadel.h>
70 #include "citadel.h"
71 #include "server.h"
72 #include "citserver.h"
73 #include "support.h"
74 #include "config.h"
75 #include "control.h"
76 #include "user_ops.h"
77 #include "database.h"
78 #include "msgbase.h"
79 #include "internet_addressing.h"
80 #include "genstamp.h"
81 #include "domain.h"
82 #include "clientsocket.h"
83 #include "locate_host.h"
84 #include "citadel_dirs.h"
85
86 #include "ctdl_module.h"
87
88 #include "smtp_util.h"
89 #include "event_client.h"
90 #include "smtpqueue.h"
91 #include "smtp_clienthandlers.h"
92
93 ConstStr SMTPStates[] = {
94         {HKEY("looking up mx - record")},
95         {HKEY("evaluating what to do next")},
96         {HKEY("looking up a - record")},
97         {HKEY("looking up aaaa - record")},
98         {HKEY("connecting remote")},
99         {HKEY("smtp conversation ongoing")},
100         {HKEY("smtp sending maildata")},
101         {HKEY("smtp sending done")},
102         {HKEY("smtp successfully finished")},
103         {HKEY("failed one attempt")},
104         {HKEY("failed temporarily")},
105         {HKEY("failed permanently")}
106 };
107
108 void SetSMTPState(AsyncIO *IO, smtpstate State)
109 {
110         CitContext* CCC = IO->CitContext;
111         if (CCC != NULL)
112                 memcpy(CCC->cs_clientname, SMTPStates[State].Key, SMTPStates[State].len + 1);
113 }
114
115 int SMTPClientDebugEnabled = 0;
116 void DeleteSmtpOutMsg(void *v)
117 {
118         SmtpOutMsg *Msg = v;
119         AsyncIO *IO = &Msg->IO;
120         EV_syslog(LOG_DEBUG, "%s Exit\n", __FUNCTION__);
121
122         /* these are kept in our own space and free'd below */
123         Msg->IO.ConnectMe = NULL;
124
125         ares_free_data(Msg->AllMX);
126         if (Msg->HostLookup.VParsedDNSReply != NULL)
127                 Msg->HostLookup.DNSReplyFree(Msg->HostLookup.VParsedDNSReply);
128         FreeURL(&Msg->Relay);
129         FreeStrBuf(&Msg->msgtext);
130         FreeStrBuf(&Msg->MultiLineBuf);
131         FreeAsyncIOContents(&Msg->IO);
132         memset (Msg, 0, sizeof(SmtpOutMsg)); /* just to be shure... */
133         free(Msg);
134 }
135
136 eNextState SMTP_C_Shutdown(AsyncIO *IO);
137 eNextState SMTP_C_Timeout(AsyncIO *IO);
138 eNextState SMTP_C_ConnFail(AsyncIO *IO);
139 eNextState SMTP_C_DispatchReadDone(AsyncIO *IO);
140 eNextState SMTP_C_DispatchWriteDone(AsyncIO *IO);
141 eNextState SMTP_C_DNSFail(AsyncIO *IO);
142 eNextState SMTP_C_Terminate(AsyncIO *IO);
143 eNextState SMTP_C_TerminateDB(AsyncIO *IO);
144 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO);
145
146 eNextState mx_connect_ip(AsyncIO *IO);
147 eNextState get_one_mx_host_ip(AsyncIO *IO);
148
149 /******************************************************************************
150  * So, we're finished with sending (regardless of success or failure)         *
151  * This Message might be referenced by several Queue-Items, if we're the last,*
152  * we need to free the memory and send bounce messages (on terminal failure)  *
153  * else we just free our SMTP-Message struct.                                 *
154  ******************************************************************************/
155 eNextState FinalizeMessageSend_DB(AsyncIO *IO)
156 {
157         const char *Status;
158         SmtpOutMsg *Msg = IO->Data;
159         StrBuf *StatusMessage;
160
161         if (Msg->MyQEntry->AllStatusMessages != NULL)
162                 StatusMessage = Msg->MyQEntry->AllStatusMessages;
163         else
164                 StatusMessage = Msg->MyQEntry->StatusMessage;
165
166
167         if (Msg->MyQEntry->Status == 2) {
168                 SetSMTPState(IO, eSTMPfinished);
169                 Status = "Delivery successful.";
170         }
171         else if (Msg->MyQEntry->Status == 5) {
172                 SetSMTPState(IO, eSMTPFailTotal);
173                 Status = "Delivery failed permanently; giving up.";
174         }
175         else {
176                 SetSMTPState(IO, eSMTPFailTemporary);
177                 Status = "Delivery failed temporarily; will retry later.";
178         }
179                         
180         EVS_syslog(LOG_INFO,
181                    "%s Time[%fs] Recipient <%s> @ <%s> (%s) Status message: %s\n",
182                    Status,
183                    Msg->IO.Now - Msg->IO.StartIO,
184                    Msg->user,
185                    Msg->node,
186                    Msg->name,
187                    ChrPtr(StatusMessage));
188
189
190         Msg->IDestructQueItem = DecreaseQReference(Msg->MyQItem);
191
192         Msg->nRemain = CountActiveQueueEntries(Msg->MyQItem, 0);
193
194         if (Msg->MyQEntry->Active && 
195             !Msg->MyQEntry->StillActive &&
196             CheckQEntryIsBounce(Msg->MyQEntry))
197         {
198                 /* are we casue for a bounce mail? */
199                 Msg->MyQItem->SendBounceMail |= (1<<Msg->MyQEntry->Status);
200         }
201
202         if ((Msg->nRemain > 0) || Msg->IDestructQueItem)
203                 Msg->QMsgData = SerializeQueueItem(Msg->MyQItem);
204         else
205                 Msg->QMsgData = NULL;
206
207         /*
208          * Uncompleted delivery instructions remain, so delete the old
209          * instructions and replace with the updated ones.
210          */
211         EVS_syslog(LOG_DEBUG, "%ld", Msg->MyQItem->QueMsgID);
212         CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, "");
213         Msg->MyQItem->QueMsgID = -1;
214
215         if (Msg->IDestructQueItem)
216                 smtpq_do_bounce(Msg->MyQItem, Msg->msgtext, Msg->pCurrRelay);
217
218         if (Msg->nRemain > 0)
219         {
220                 struct CtdlMessage *msg;
221                 msg = malloc(sizeof(struct CtdlMessage));
222                 memset(msg, 0, sizeof(struct CtdlMessage));
223                 msg->cm_magic = CTDLMESSAGE_MAGIC;
224                 msg->cm_anon_type = MES_NORMAL;
225                 msg->cm_format_type = FMT_RFC822;
226                 CM_SetAsFieldSB(msg, eMesageText, &Msg->QMsgData);
227                 CM_SetField(msg, eMsgSubject, HKEY("QMSG"));
228                 Msg->MyQItem->QueMsgID =
229                         CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
230                 EVS_syslog(LOG_DEBUG, "%ld", Msg->MyQItem->QueMsgID);
231                 CM_Free(msg);
232         }
233         else {
234                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM,
235                                    &Msg->MyQItem->MessageID,
236                                    1,
237                                    "");
238                 FreeStrBuf(&Msg->QMsgData);
239         }
240
241         RemoveContext(Msg->IO.CitContext);
242         return eAbort;
243 }
244
245 eNextState Terminate(AsyncIO *IO)
246 {
247         SmtpOutMsg *Msg = IO->Data;
248
249         if (Msg->IDestructQueItem)
250                 RemoveQItem(Msg->MyQItem);
251
252         DeleteSmtpOutMsg(Msg);
253         return eAbort;
254 }
255 eNextState FinalizeMessageSend(SmtpOutMsg *Msg)
256 {
257         /* hand over to DB Queue */
258         return EventQueueDBOperation(&Msg->IO, FinalizeMessageSend_DB, 0);
259 }
260
261 eNextState FailOneAttempt(AsyncIO *IO)
262 {
263         SmtpOutMsg *Msg = IO->Data;
264
265         SetSMTPState(IO, eSTMPfailOne);
266         if (Msg->MyQEntry->Status == 2)
267                 return eAbort;
268
269         /*
270          * possible ways here:
271          * - connection timeout
272          * - dns lookup failed
273          */
274         StopClientWatchers(IO, 1);
275
276         Msg->MyQEntry->nAttempt ++;
277         if (Msg->MyQEntry->AllStatusMessages == NULL)
278                 Msg->MyQEntry->AllStatusMessages = NewStrBuf();
279
280         StrBufAppendPrintf(Msg->MyQEntry->AllStatusMessages, "%ld) ", Msg->MyQEntry->nAttempt);
281         StrBufAppendBuf(Msg->MyQEntry->AllStatusMessages, Msg->MyQEntry->StatusMessage, 0);
282         StrBufAppendBufPlain(Msg->MyQEntry->AllStatusMessages, HKEY("; "), 0);
283
284         if (Msg->pCurrRelay != NULL)
285                 Msg->pCurrRelay = Msg->pCurrRelay->Next;
286         if ((Msg->pCurrRelay != NULL) &&
287             !Msg->pCurrRelay->IsRelay &&
288             Msg->MyQItem->HaveRelay)
289         {
290                 EVS_syslog(LOG_DEBUG, "%s Aborting; last relay failed.\n", __FUNCTION__);
291                 return eAbort;
292         }
293
294         if (Msg->pCurrRelay == NULL) {
295                 EVS_syslog(LOG_DEBUG, "%s Aborting\n", __FUNCTION__);
296                 return eAbort;
297         }
298         if (Msg->pCurrRelay->IsIP) {
299                 EVS_syslog(LOG_DEBUG, "%s connecting IP\n", __FUNCTION__);
300                 return mx_connect_ip(IO);
301         }
302         else {
303                 EVS_syslog(LOG_DEBUG,
304                            "%s resolving next MX Record\n",
305                            __FUNCTION__);
306                 return get_one_mx_host_ip(IO);
307         }
308 }
309
310
311 void SetConnectStatus(AsyncIO *IO)
312 {
313         SmtpOutMsg *Msg = IO->Data;
314         char buf[256];
315         void *src;
316
317         buf[0] = '\0';
318
319         if (IO->ConnectMe->IPv6) {
320                 src = &IO->ConnectMe->Addr.sin6_addr;
321         }
322         else {
323                 struct sockaddr_in *addr;
324
325                 addr = (struct sockaddr_in *)&IO->ConnectMe->Addr;
326                 src = &addr->sin_addr.s_addr;
327         }
328
329         inet_ntop((IO->ConnectMe->IPv6)?AF_INET6:AF_INET,
330                   src,
331                   buf,
332                   sizeof(buf));
333
334         if (Msg->mx_host == NULL)
335                 Msg->mx_host = "<no MX-Record>";
336
337         EVS_syslog(LOG_INFO,
338                   "connecting to %s [%s]:%d ...\n",
339                   Msg->mx_host,
340                   buf,
341                   Msg->IO.ConnectMe->Port);
342
343         Msg->MyQEntry->Status = 4;
344         StrBufPrintf(Msg->MyQEntry->StatusMessage,
345                      "Timeout while connecting %s [%s]:%d ",
346                      Msg->mx_host,
347                      buf,
348                      Msg->IO.ConnectMe->Port);
349         Msg->IO.NextState = eConnect;
350 }
351
352 /*****************************************************************************
353  * So we connect our Relay IP here.                                          *
354  *****************************************************************************/
355 eNextState mx_connect_ip(AsyncIO *IO)
356 {
357         SmtpOutMsg *Msg = IO->Data;
358         SetSMTPState(IO, eSTMPconnecting);
359
360         EVS_syslog(LOG_DEBUG, "%s(%s)\n", __FUNCTION__, (Msg->IsRelay)? "Relay":"Remote");
361
362         IO->ConnectMe = Msg->pCurrRelay;
363         Msg->State = eConnectMX;
364
365         SetConnectStatus(IO);
366
367         return EvConnectSock(IO,
368                              SMTP_C_ConnTimeout,
369                              SMTP_C_ReadTimeouts[0],
370                              1);
371 }
372
373 eNextState get_one_mx_host_ip_done(AsyncIO *IO)
374 {
375         SmtpOutMsg *Msg = IO->Data;
376         struct hostent *hostent;
377
378         IO->ConnectMe = Msg->pCurrRelay;
379
380         QueryCbDone(IO);
381         EVS_syslog(LOG_DEBUG, "%s Time[%fs]\n",
382                    __FUNCTION__,
383                    IO->Now - IO->DNS.Start);
384
385         hostent = Msg->HostLookup.VParsedDNSReply;
386         if ((Msg->HostLookup.DNSStatus == ARES_SUCCESS) &&
387             (hostent != NULL) ) {
388                 memset(&Msg->pCurrRelay->Addr, 0, sizeof(struct in6_addr));
389                 if (Msg->pCurrRelay->IPv6) {
390                         memcpy(&Msg->pCurrRelay->Addr.sin6_addr.s6_addr,
391                                &hostent->h_addr_list[0],
392                                sizeof(struct in6_addr));
393
394                         Msg->pCurrRelay->Addr.sin6_family =
395                                 hostent->h_addrtype;
396                         Msg->pCurrRelay->Addr.sin6_port =
397                                 htons(Msg->IO.ConnectMe->Port);
398                 }
399                 else {
400                         struct sockaddr_in *addr;
401                         /*
402                          * Bypass the ns lookup result like this:
403                          * IO->Addr.sin_addr.s_addr = inet_addr("127.0.0.1");
404                          * addr->sin_addr.s_addr =
405                          *   htonl((uint32_t)&hostent->h_addr_list[0]);
406                          */
407
408                         addr = (struct sockaddr_in*) &Msg->pCurrRelay->Addr;
409
410                         memcpy(&addr->sin_addr.s_addr,
411                                hostent->h_addr_list[0],
412                                sizeof(uint32_t));
413
414                         addr->sin_family = hostent->h_addrtype;
415                         addr->sin_port   = htons(Msg->IO.ConnectMe->Port);
416                 }
417                 Msg->mx_host = Msg->pCurrRelay->Host;
418                 if (Msg->HostLookup.VParsedDNSReply != NULL) {
419                         Msg->HostLookup.DNSReplyFree(Msg->HostLookup.VParsedDNSReply);
420                         Msg->HostLookup.VParsedDNSReply = NULL;
421                 }
422                 return mx_connect_ip(IO);
423         }
424         else {
425                 SetSMTPState(IO, eSTMPfailOne);
426                 if (Msg->HostLookup.VParsedDNSReply != NULL) {
427                         Msg->HostLookup.DNSReplyFree(Msg->HostLookup.VParsedDNSReply);
428                         Msg->HostLookup.VParsedDNSReply = NULL;
429                 }
430                 return FailOneAttempt(IO);
431         }
432 }
433
434 eNextState get_one_mx_host_ip(AsyncIO *IO)
435 {
436         SmtpOutMsg * Msg = IO->Data;
437         /*
438          * here we start with the lookup of one host. it might be...
439          * - the relay host *sigh*
440          * - the direct hostname if there was no mx record
441          * - one of the mx'es
442          */
443         SetSMTPState(IO, (Msg->pCurrRelay->IPv6)?eSTMPalookup:eSTMPaaaalookup);
444
445         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
446
447         EVS_syslog(LOG_DEBUG,
448                   "looking up %s-Record %s : %d ...\n",
449                   (Msg->pCurrRelay->IPv6)? "aaaa": "a",
450                   Msg->pCurrRelay->Host,
451                   Msg->pCurrRelay->Port);
452
453         if (!QueueQuery((Msg->pCurrRelay->IPv6)? ns_t_aaaa : ns_t_a,
454                         Msg->pCurrRelay->Host,
455                         &Msg->IO,
456                         &Msg->HostLookup,
457                         get_one_mx_host_ip_done))
458         {
459                 Msg->MyQEntry->Status = 5;
460                 StrBufPrintf(Msg->MyQEntry->StatusMessage,
461                              "No MX hosts found for <%s>", Msg->node);
462                 Msg->IO.NextState = eTerminateConnection;
463                 return IO->NextState;
464         }
465         IO->NextState = eReadDNSReply;
466         return IO->NextState;
467 }
468
469
470 /*****************************************************************************
471  * here we try to find out about the MX records for our recipients.          *
472  *****************************************************************************/
473 eNextState smtp_resolve_mx_record_done(AsyncIO *IO)
474 {
475         SmtpOutMsg * Msg = IO->Data;
476         ParsedURL **pp;
477
478         QueryCbDone(IO);
479
480         EVS_syslog(LOG_DEBUG, "%s Time[%fs]\n",
481                    __FUNCTION__,
482                    IO->Now - IO->DNS.Start);
483
484         pp = &Msg->Relay;
485         while ((pp != NULL) && (*pp != NULL) && ((*pp)->Next != NULL))
486                 pp = &(*pp)->Next;
487
488         if ((IO->DNS.Query->DNSStatus == ARES_SUCCESS) &&
489             (IO->DNS.Query->VParsedDNSReply != NULL))
490         { /* ok, we found mx records. */
491
492                 Msg->CurrMX
493                         = Msg->AllMX
494                         = IO->DNS.Query->VParsedDNSReply;
495                 while (Msg->CurrMX) {
496                         int i;
497                         for (i = 0; i < 2; i++) {
498                                 ParsedURL *p;
499
500                                 p = (ParsedURL*) malloc(sizeof(ParsedURL));
501                                 memset(p, 0, sizeof(ParsedURL));
502                                 p->Priority = Msg->CurrMX->priority;
503                                 p->IsIP = 0;
504                                 p->Port = DefaultMXPort;
505                                 p->IPv6 = i == 1;
506                                 p->Host = Msg->CurrMX->host;
507                                 if (*pp == NULL)
508                                         *pp = p;
509                                 else {
510                                         ParsedURL *ppp = *pp;
511
512                                         while ((ppp->Next != NULL) &&
513                                                (ppp->Next->Priority <= p->Priority))
514                                                ppp = ppp->Next;
515                                         if ((ppp == *pp) &&
516                                             (ppp->Priority > p->Priority)) {
517                                                 p->Next = *pp;
518                                                 *pp = p;
519                                         }
520                                         else {
521                                                 p->Next = ppp->Next;
522                                                 ppp->Next = p;
523                                         }
524                                 }
525                         }
526                         Msg->CurrMX    = Msg->CurrMX->next;
527                 }
528                 Msg->CXFlags   = Msg->CXFlags & F_HAVE_MX;
529         }
530         else { /* else fall back to the plain hostname */
531                 int i;
532                 for (i = 0; i < 2; i++) {
533                         ParsedURL *p;
534
535                         p = (ParsedURL*) malloc(sizeof(ParsedURL));
536                         memset(p, 0, sizeof(ParsedURL));
537                         p->IsIP = 0;
538                         p->Port = DefaultMXPort;
539                         p->IPv6 = i == 1;
540                         p->Host = Msg->node;
541
542                         *pp = p;
543                         pp = &p->Next;
544                 }
545                 Msg->CXFlags   = Msg->CXFlags & F_DIRECT;
546         }
547         if (Msg->MyQItem->FallBackHost != NULL)
548         {
549                 Msg->MyQItem->FallBackHost->Next = *pp;
550                 *pp = Msg->MyQItem->FallBackHost;
551         }
552         Msg->pCurrRelay = Msg->Relay;
553         return get_one_mx_host_ip(IO);
554 }
555
556 eNextState resolve_mx_records(AsyncIO *IO)
557 {
558         SmtpOutMsg * Msg = IO->Data;
559
560         SetSMTPState(IO, eSTMPmxlookup);
561
562         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
563         /* start resolving MX records here. */
564         if (!QueueQuery(ns_t_mx,
565                         Msg->node,
566                         &Msg->IO,
567                         &Msg->MxLookup,
568                         smtp_resolve_mx_record_done))
569         {
570                 Msg->MyQEntry->Status = 5;
571                 StrBufPrintf(Msg->MyQEntry->StatusMessage,
572                              "No MX hosts found for <%s>", Msg->node);
573                 return IO->NextState;
574         }
575         Msg->IO.NextState = eReadDNSReply;
576         return IO->NextState;
577 }
578
579
580
581 /******************************************************************************
582  *  so, we're going to start a SMTP delivery.  lets get it on.                *
583  ******************************************************************************/
584
585 SmtpOutMsg *new_smtp_outmsg(OneQueItem *MyQItem,
586                             MailQEntry *MyQEntry,
587                             int MsgCount)
588 {
589         SmtpOutMsg * Msg;
590
591         Msg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
592         if (Msg == NULL)
593                 return NULL;
594         memset(Msg, 0, sizeof(SmtpOutMsg));
595
596         Msg->n                = MsgCount;
597         Msg->MyQEntry         = MyQEntry;
598         Msg->MyQItem          = MyQItem;
599         Msg->pCurrRelay       = MyQItem->URL;
600
601         InitIOStruct(&Msg->IO,
602                      Msg,
603                      eReadMessage,
604                      SMTP_C_ReadServerStatus,
605                      SMTP_C_DNSFail,
606                      SMTP_C_DispatchWriteDone,
607                      SMTP_C_DispatchReadDone,
608                      SMTP_C_Terminate,
609                      SMTP_C_TerminateDB,
610                      SMTP_C_ConnFail,
611                      SMTP_C_Timeout,
612                      SMTP_C_Shutdown);
613
614         Msg->IO.ErrMsg = Msg->MyQEntry->StatusMessage;
615
616         return Msg;
617 }
618
619 void smtp_try_one_queue_entry(OneQueItem *MyQItem,
620                               MailQEntry *MyQEntry,
621                               StrBuf *MsgText,
622                         /*KeepMsgText allows us to use MsgText as ours.*/
623                               int KeepMsgText,
624                               int MsgCount)
625 {
626         SmtpOutMsg *Msg;
627
628         SMTPC_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
629
630         Msg = new_smtp_outmsg(MyQItem, MyQEntry, MsgCount);
631         if (Msg == NULL) {
632                 SMTPC_syslog(LOG_DEBUG, "%s Failed to alocate message context.\n", __FUNCTION__);
633                 if (KeepMsgText) 
634                         FreeStrBuf (&MsgText);
635                 return;
636         }
637         if (KeepMsgText) Msg->msgtext = MsgText;
638         else             Msg->msgtext = NewStrBufDup(MsgText);
639
640         if (smtp_resolve_recipients(Msg) &&
641             (!MyQItem->HaveRelay ||
642              (MyQItem->URL != NULL)))
643         {
644                 safestrncpy(
645                         ((CitContext *)Msg->IO.CitContext)->cs_host,
646                         Msg->node,
647                         sizeof(((CitContext *)
648                                 Msg->IO.CitContext)->cs_host));
649
650                 SMTPC_syslog(LOG_DEBUG, "Starting: [%ld] <%s> CC <%d> \n",
651                              Msg->MyQItem->MessageID,
652                              ChrPtr(Msg->MyQEntry->Recipient),
653                              ((CitContext*)Msg->IO.CitContext)->cs_pid);
654                 if (Msg->pCurrRelay == NULL) {
655                         SetSMTPState(&Msg->IO, eSTMPmxlookup);
656                         QueueEventContext(&Msg->IO,
657                                           resolve_mx_records);
658                 }
659                 else { /* oh... via relay host */
660                         Msg->IsRelay = 1;
661                         if (Msg->pCurrRelay->IsIP) {
662                                 SetSMTPState(&Msg->IO, eSTMPconnecting);
663                                 QueueEventContext(&Msg->IO,
664                                                   mx_connect_ip);
665                         }
666                         else {
667                                 SetSMTPState(&Msg->IO, eSTMPalookup);
668                                 /* uneducated admin has chosen to
669                                    add DNS to the equation... */
670                                 QueueEventContext(&Msg->IO,
671                                                   get_one_mx_host_ip);
672                         }
673                 }
674         }
675         else {
676                 SetSMTPState(&Msg->IO, eSMTPFailTotal);
677                 /* No recipients? well fail then. */
678                 if (Msg->MyQEntry != NULL) {
679                         Msg->MyQEntry->Status = 5;
680                         if (StrLength(Msg->MyQEntry->StatusMessage) == 0)
681                                 StrBufPlain(Msg->MyQEntry->StatusMessage,
682                                             HKEY("Invalid Recipient!"));
683                 }
684                 FinalizeMessageSend_DB(&Msg->IO);
685                 DeleteSmtpOutMsg(Msg);
686         }
687 }
688
689
690
691
692
693
694 /*****************************************************************************/
695 /*                     SMTP CLIENT DISPATCHER                                */
696 /*****************************************************************************/
697
698 void SMTPSetTimeout(eNextState NextTCPState, SmtpOutMsg *Msg)
699 {
700         double Timeout = 0.0;
701         AsyncIO *IO = &Msg->IO;
702
703         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
704
705         switch (NextTCPState) {
706         case eSendFile:
707         case eSendReply:
708         case eSendMore:
709                 Timeout = SMTP_C_SendTimeouts[Msg->State];
710                 if (Msg->State == eDATABody) {
711                         /* if we're sending a huge message,
712                          * we need more time.
713                          */
714                         Timeout += StrLength(Msg->msgtext) / 512;
715                 }
716                 break;
717         case eReadMessage:
718                 Timeout = SMTP_C_ReadTimeouts[Msg->State];
719                 if (Msg->State == eDATATerminateBody) {
720                         /*
721                          * some mailservers take a nap before accepting
722                          * the message content inspection and such.
723                          */
724                         Timeout += StrLength(Msg->msgtext) / 512;
725                 }
726                 break;
727         case eSendDNSQuery:
728         case eReadDNSReply:
729         case eDBQuery:
730         case eReadFile:
731         case eReadMore:
732         case eReadPayload:
733         case eConnect:
734         case eTerminateConnection:
735         case eAbort:
736                 return;
737         }
738         SetNextTimeout(&Msg->IO, Timeout);
739 }
740 eNextState SMTP_C_DispatchReadDone(AsyncIO *IO)
741 {
742         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
743         SmtpOutMsg *Msg = IO->Data;
744         eNextState rc;
745
746         rc = ReadHandlers[Msg->State](Msg);
747         if (rc != eAbort)
748         {
749                 Msg->State++;
750                 SMTPSetTimeout(rc, Msg);
751         }
752         return rc;
753 }
754 eNextState SMTP_C_DispatchWriteDone(AsyncIO *IO)
755 {
756         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
757         SmtpOutMsg *Msg = IO->Data;
758         eNextState rc;
759
760         rc = SendHandlers[Msg->State](Msg);
761         SMTPSetTimeout(rc, Msg);
762         return rc;
763 }
764
765
766 /*****************************************************************************/
767 /*                     SMTP CLIENT ERROR CATCHERS                            */
768 /*****************************************************************************/
769 eNextState SMTP_C_Terminate(AsyncIO *IO)
770 {
771         SmtpOutMsg *Msg = IO->Data;
772
773         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
774         return FinalizeMessageSend(Msg);
775 }
776 eNextState SMTP_C_TerminateDB(AsyncIO *IO)
777 {
778         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
779         return Terminate(IO);
780 }
781 eNextState SMTP_C_Timeout(AsyncIO *IO)
782 {
783         SmtpOutMsg *Msg = IO->Data;
784
785         Msg->MyQEntry->Status = 4;
786         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
787         StrBufPrintf(IO->ErrMsg, "Timeout: %s while talking to %s",
788                      ReadErrors[Msg->State].Key,
789                      Msg->mx_host);
790         if (Msg->State > eRCPT)
791                 return eAbort;
792         else
793                 return FailOneAttempt(IO);
794 }
795 eNextState SMTP_C_ConnFail(AsyncIO *IO)
796 {
797         SmtpOutMsg *Msg = IO->Data;
798
799         Msg->MyQEntry->Status = 4;
800         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
801         StrBufPrintf(IO->ErrMsg, "Connection failure: %s while talking to %s",
802                      ReadErrors[Msg->State].Key,
803                      Msg->mx_host);
804
805         return FailOneAttempt(IO);
806 }
807 eNextState SMTP_C_DNSFail(AsyncIO *IO)
808 {
809         SmtpOutMsg *Msg = IO->Data;
810         Msg->MyQEntry->Status = 4;
811         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
812         return FailOneAttempt(IO);
813 }
814 eNextState SMTP_C_Shutdown(AsyncIO *IO)
815 {
816         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
817         SmtpOutMsg *Msg = IO->Data;
818
819         switch (IO->NextState) {
820         case eSendDNSQuery:
821         case eReadDNSReply:
822
823                 /* todo: abort c-ares */
824         case eConnect:
825         case eSendReply:
826         case eSendMore:
827         case eSendFile:
828         case eReadMessage:
829         case eReadMore:
830         case eReadPayload:
831         case eReadFile:
832                 StopClientWatchers(IO, 1);
833                 break;
834         case eDBQuery:
835
836                 break;
837         case eTerminateConnection:
838         case eAbort:
839                 break;
840         }
841         Msg->MyQEntry->Status = 3;
842         StrBufPlain(Msg->MyQEntry->StatusMessage,
843                     HKEY("server shutdown during message submit."));
844         return FinalizeMessageSend(Msg);
845 }
846
847
848 /**
849  * @brief lineread Handler;
850  * understands when to read more SMTP lines, and when this is a one-lined reply.
851  */
852 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
853 {
854         eReadState Finished = eBufferNotEmpty;
855
856         while (Finished == eBufferNotEmpty) {
857                 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
858
859                 switch (Finished) {
860                 case eMustReadMore: /// read new from socket...
861                         return Finished;
862                         break;
863                 case eBufferNotEmpty: /* shouldn't happen... */
864                 case eReadSuccess: /// done for now...
865                         if (StrLength(IO->IOBuf) < 4)
866                                 continue;
867                         if (ChrPtr(IO->IOBuf)[3] == '-')
868                         {
869                                 SmtpOutMsg *Msg;
870                                 Msg = (SmtpOutMsg *)IO->Data;
871                                 if (Msg->MultiLineBuf == NULL)
872                                         Msg->MultiLineBuf = NewStrBuf ();
873                                 else
874                                         StrBufAppendBufPlain(Msg->MultiLineBuf, HKEY("\n"), 0);
875                                 StrBufAppendBuf(Msg->MultiLineBuf, IO->IOBuf, 0);
876                                 Finished = eBufferNotEmpty;
877                         }
878                         else
879                                 return Finished;
880                         break;
881                 case eReadFail: /// WHUT?
882                         ///todo: shut down!
883                         break;
884                 }
885         }
886         return Finished;
887 }
888
889 void LogDebugEnableSMTPClient(const int n)
890 {
891         SMTPClientDebugEnabled = n;
892 }
893
894 CTDL_MODULE_INIT(smtp_eventclient)
895 {
896         if (!threading)
897                 CtdlRegisterDebugFlagHook(HKEY("smtpeventclient"), LogDebugEnableSMTPClient, &SMTPClientDebugEnabled);
898         return "smtpeventclient";
899 }