34363eb6f64545a95ae8e88da333a3820a868180
[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         memcpy(CCC->cs_clientname, SMTPStates[State].Key, SMTPStates[State].len + 1);
112 }
113
114 int SMTPClientDebugEnabled = 0;
115 void DeleteSmtpOutMsg(void *v)
116 {
117         SmtpOutMsg *Msg = v;
118         AsyncIO *IO = &Msg->IO;
119         EV_syslog(LOG_DEBUG, "%s Exit\n", __FUNCTION__);
120
121         /* these are kept in our own space and free'd below */
122         Msg->IO.ConnectMe = NULL;
123
124         ares_free_data(Msg->AllMX);
125         if (Msg->HostLookup.VParsedDNSReply != NULL)
126                 Msg->HostLookup.DNSReplyFree(Msg->HostLookup.VParsedDNSReply);
127         FreeURL(&Msg->Relay);
128         FreeStrBuf(&Msg->msgtext);
129         FreeAsyncIOContents(&Msg->IO);
130         memset (Msg, 0, sizeof(SmtpOutMsg)); /* just to be shure... */
131         free(Msg);
132 }
133
134 eNextState SMTP_C_Shutdown(AsyncIO *IO);
135 eNextState SMTP_C_Timeout(AsyncIO *IO);
136 eNextState SMTP_C_ConnFail(AsyncIO *IO);
137 eNextState SMTP_C_DispatchReadDone(AsyncIO *IO);
138 eNextState SMTP_C_DispatchWriteDone(AsyncIO *IO);
139 eNextState SMTP_C_DNSFail(AsyncIO *IO);
140 eNextState SMTP_C_Terminate(AsyncIO *IO);
141 eNextState SMTP_C_TerminateDB(AsyncIO *IO);
142 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO);
143
144 eNextState mx_connect_ip(AsyncIO *IO);
145 eNextState get_one_mx_host_ip(AsyncIO *IO);
146
147 /******************************************************************************
148  * So, we're finished with sending (regardless of success or failure)         *
149  * This Message might be referenced by several Queue-Items, if we're the last,*
150  * we need to free the memory and send bounce messages (on terminal failure)  *
151  * else we just free our SMTP-Message struct.                                 *
152  ******************************************************************************/
153 eNextState FinalizeMessageSend_DB(AsyncIO *IO)
154 {
155         const char *Status;
156         SmtpOutMsg *Msg = IO->Data;
157         
158         if (Msg->MyQEntry->Status == 2) {
159                 SetSMTPState(IO, eSTMPfinished);
160                 Status = "Delivery successful.";
161         }
162         else if (Msg->MyQEntry->Status == 5) {
163                 SetSMTPState(IO, eSMTPFailTotal);
164                 Status = "Delivery failed permanently; giving up.";
165         }
166         else {
167                 SetSMTPState(IO, eSMTPFailTemporary);
168                 Status = "Delivery failed temporarily; will retry later.";
169         }
170                         
171         EVS_syslog(LOG_INFO,
172                    "%s Time[%fs] Recipient <%s> @ <%s> (%s) Status message: %s\n",
173                    Status,
174                    Msg->IO.Now - Msg->IO.StartIO,
175                    Msg->user,
176                    Msg->node,
177                    Msg->name,
178                    ChrPtr(Msg->MyQEntry->StatusMessage));
179
180
181         Msg->IDestructQueItem = DecreaseQReference(Msg->MyQItem);
182
183         Msg->nRemain = CountActiveQueueEntries(Msg->MyQItem, 0);
184
185         if (Msg->MyQEntry->Active && 
186             !Msg->MyQEntry->StillActive &&
187             CheckQEntryIsBounce(Msg->MyQEntry))
188         {
189                 /* are we casue for a bounce mail? */
190                 Msg->MyQItem->SendBounceMail |= (1<<Msg->MyQEntry->Status);
191         }
192
193         if ((Msg->nRemain > 0) || Msg->IDestructQueItem)
194                 Msg->QMsgData = SerializeQueueItem(Msg->MyQItem);
195         else
196                 Msg->QMsgData = NULL;
197
198         /*
199          * Uncompleted delivery instructions remain, so delete the old
200          * instructions and replace with the updated ones.
201          */
202         EVS_syslog(LOG_DEBUG, "%ld", Msg->MyQItem->QueMsgID);
203         CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, "");
204         Msg->MyQItem->QueMsgID = -1;
205
206         if (Msg->IDestructQueItem)
207                 smtpq_do_bounce(Msg->MyQItem, Msg->msgtext, Msg->pCurrRelay);
208
209         if (Msg->nRemain > 0)
210         {
211                 struct CtdlMessage *msg;
212                 msg = malloc(sizeof(struct CtdlMessage));
213                 memset(msg, 0, sizeof(struct CtdlMessage));
214                 msg->cm_magic = CTDLMESSAGE_MAGIC;
215                 msg->cm_anon_type = MES_NORMAL;
216                 msg->cm_format_type = FMT_RFC822;
217                 msg->cm_fields['M'] = SmashStrBuf(&Msg->QMsgData);
218                 msg->cm_fields['U'] = strdup("QMSG");
219                 Msg->MyQItem->QueMsgID =
220                         CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
221                 EVS_syslog(LOG_DEBUG, "%ld", Msg->MyQItem->QueMsgID);
222                 CtdlFreeMessage(msg);
223         }
224         else {
225                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM,
226                                    &Msg->MyQItem->MessageID,
227                                    1,
228                                    "");
229                 FreeStrBuf(&Msg->QMsgData);
230         }
231
232         RemoveContext(Msg->IO.CitContext);
233         return eAbort;
234 }
235
236 eNextState Terminate(AsyncIO *IO)
237 {
238         SmtpOutMsg *Msg = IO->Data;
239
240         if (Msg->IDestructQueItem)
241                 RemoveQItem(Msg->MyQItem);
242
243         DeleteSmtpOutMsg(Msg);
244         return eAbort;
245 }
246 eNextState FinalizeMessageSend(SmtpOutMsg *Msg)
247 {
248         /* hand over to DB Queue */
249         return QueueDBOperation(&Msg->IO, FinalizeMessageSend_DB);
250 }
251
252 eNextState FailOneAttempt(AsyncIO *IO)
253 {
254         SmtpOutMsg *Msg = IO->Data;
255
256         SetSMTPState(IO, eSTMPfailOne);
257         if (Msg->MyQEntry->Status == 2)
258                 return eAbort;
259
260         /*
261          * possible ways here:
262          * - connection timeout
263          * - dns lookup failed
264          */
265         StopClientWatchers(IO, 1);
266
267         if (Msg->pCurrRelay != NULL)
268                 Msg->pCurrRelay = Msg->pCurrRelay->Next;
269         if ((Msg->pCurrRelay != NULL) &&
270             !Msg->pCurrRelay->IsRelay &&
271             Msg->MyQItem->HaveRelay)
272         {
273                 EVS_syslog(LOG_DEBUG, "%s Aborting; last relay failed.\n", __FUNCTION__);
274                 return eAbort;
275         }
276
277         if (Msg->pCurrRelay == NULL) {
278                 EVS_syslog(LOG_DEBUG, "%s Aborting\n", __FUNCTION__);
279                 return eAbort;
280         }
281         if (Msg->pCurrRelay->IsIP) {
282                 EVS_syslog(LOG_DEBUG, "%s connecting IP\n", __FUNCTION__);
283                 return mx_connect_ip(IO);
284         }
285         else {
286                 EVS_syslog(LOG_DEBUG,
287                            "%s resolving next MX Record\n",
288                            __FUNCTION__);
289                 return get_one_mx_host_ip(IO);
290         }
291 }
292
293
294 void SetConnectStatus(AsyncIO *IO)
295 {
296         SmtpOutMsg *Msg = IO->Data;
297         char buf[256];
298         void *src;
299
300         buf[0] = '\0';
301
302         if (IO->ConnectMe->IPv6) {
303                 src = &IO->ConnectMe->Addr.sin6_addr;
304         }
305         else {
306                 struct sockaddr_in *addr;
307
308                 addr = (struct sockaddr_in *)&IO->ConnectMe->Addr;
309                 src = &addr->sin_addr.s_addr;
310         }
311
312         inet_ntop((IO->ConnectMe->IPv6)?AF_INET6:AF_INET,
313                   src,
314                   buf,
315                   sizeof(buf));
316
317         if (Msg->mx_host == NULL)
318                 Msg->mx_host = "<no MX-Record>";
319
320         EVS_syslog(LOG_INFO,
321                   "connecting to %s [%s]:%d ...\n",
322                   Msg->mx_host,
323                   buf,
324                   Msg->IO.ConnectMe->Port);
325
326         Msg->MyQEntry->Status = 4;
327         StrBufPrintf(Msg->MyQEntry->StatusMessage,
328                      "Timeout while connecting %s [%s]:%d ",
329                      Msg->mx_host,
330                      buf,
331                      Msg->IO.ConnectMe->Port);
332         Msg->IO.NextState = eConnect;
333 }
334
335 /*****************************************************************************
336  * So we connect our Relay IP here.                                          *
337  *****************************************************************************/
338 eNextState mx_connect_ip(AsyncIO *IO)
339 {
340         SmtpOutMsg *Msg = IO->Data;
341         SetSMTPState(IO, eSTMPconnecting);
342
343         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
344
345         IO->ConnectMe = Msg->pCurrRelay;
346         Msg->State = eConnectMX;
347
348         SetConnectStatus(IO);
349
350         return EvConnectSock(IO,
351                              SMTP_C_ConnTimeout,
352                              SMTP_C_ReadTimeouts[0],
353                              1);
354 }
355
356 eNextState get_one_mx_host_ip_done(AsyncIO *IO)
357 {
358         SmtpOutMsg *Msg = IO->Data;
359         struct hostent *hostent;
360
361         IO->ConnectMe = Msg->pCurrRelay;
362
363         QueryCbDone(IO);
364         EVS_syslog(LOG_DEBUG, "%s Time[%fs]\n",
365                    __FUNCTION__,
366                    IO->Now - IO->DNS.Start);
367
368         hostent = Msg->HostLookup.VParsedDNSReply;
369         if ((Msg->HostLookup.DNSStatus == ARES_SUCCESS) &&
370             (hostent != NULL) ) {
371                 memset(&Msg->pCurrRelay->Addr, 0, sizeof(struct in6_addr));
372                 if (Msg->pCurrRelay->IPv6) {
373                         memcpy(&Msg->pCurrRelay->Addr.sin6_addr.s6_addr,
374                                &hostent->h_addr_list[0],
375                                sizeof(struct in6_addr));
376
377                         Msg->pCurrRelay->Addr.sin6_family =
378                                 hostent->h_addrtype;
379                         Msg->pCurrRelay->Addr.sin6_port =
380                                 htons(Msg->IO.ConnectMe->Port);
381                 }
382                 else {
383                         struct sockaddr_in *addr;
384                         /*
385                          * Bypass the ns lookup result like this:
386                          * IO->Addr.sin_addr.s_addr = inet_addr("127.0.0.1");
387                          * addr->sin_addr.s_addr =
388                          *   htonl((uint32_t)&hostent->h_addr_list[0]);
389                          */
390
391                         addr = (struct sockaddr_in*) &Msg->pCurrRelay->Addr;
392
393                         memcpy(&addr->sin_addr.s_addr,
394                                hostent->h_addr_list[0],
395                                sizeof(uint32_t));
396
397                         addr->sin_family = hostent->h_addrtype;
398                         addr->sin_port   = htons(Msg->IO.ConnectMe->Port);
399                 }
400                 Msg->mx_host = Msg->pCurrRelay->Host;
401                 if (Msg->HostLookup.VParsedDNSReply != NULL) {
402                         Msg->HostLookup.DNSReplyFree(Msg->HostLookup.VParsedDNSReply);
403                         Msg->HostLookup.VParsedDNSReply = NULL;
404                 }
405                 return mx_connect_ip(IO);
406         }
407         else {
408                 SetSMTPState(IO, eSTMPfailOne);
409                 if (Msg->HostLookup.VParsedDNSReply != NULL) {
410                         Msg->HostLookup.DNSReplyFree(Msg->HostLookup.VParsedDNSReply);
411                         Msg->HostLookup.VParsedDNSReply = NULL;
412                 }
413                 return FailOneAttempt(IO);
414         }
415 }
416
417 eNextState get_one_mx_host_ip(AsyncIO *IO)
418 {
419         SmtpOutMsg * Msg = IO->Data;
420         /*
421          * here we start with the lookup of one host. it might be...
422          * - the relay host *sigh*
423          * - the direct hostname if there was no mx record
424          * - one of the mx'es
425          */
426         SetSMTPState(IO, (Msg->pCurrRelay->IPv6)?eSTMPalookup:eSTMPaaaalookup);
427
428         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
429
430         EVS_syslog(LOG_DEBUG,
431                   "looking up %s-Record %s : %d ...\n",
432                   (Msg->pCurrRelay->IPv6)? "aaaa": "a",
433                   Msg->pCurrRelay->Host,
434                   Msg->pCurrRelay->Port);
435
436         if (!QueueQuery((Msg->pCurrRelay->IPv6)? ns_t_aaaa : ns_t_a,
437                         Msg->pCurrRelay->Host,
438                         &Msg->IO,
439                         &Msg->HostLookup,
440                         get_one_mx_host_ip_done))
441         {
442                 Msg->MyQEntry->Status = 5;
443                 StrBufPrintf(Msg->MyQEntry->StatusMessage,
444                              "No MX hosts found for <%s>", Msg->node);
445                 Msg->IO.NextState = eTerminateConnection;
446                 return IO->NextState;
447         }
448         IO->NextState = eReadDNSReply;
449         return IO->NextState;
450 }
451
452
453 /*****************************************************************************
454  * here we try to find out about the MX records for our recipients.          *
455  *****************************************************************************/
456 eNextState smtp_resolve_mx_record_done(AsyncIO *IO)
457 {
458         SmtpOutMsg * Msg = IO->Data;
459         ParsedURL **pp;
460
461         QueryCbDone(IO);
462
463         EVS_syslog(LOG_DEBUG, "%s Time[%fs]\n",
464                    __FUNCTION__,
465                    IO->Now - IO->DNS.Start);
466
467         pp = &Msg->Relay;
468         while ((pp != NULL) && (*pp != NULL) && ((*pp)->Next != NULL))
469                 pp = &(*pp)->Next;
470
471         if ((IO->DNS.Query->DNSStatus == ARES_SUCCESS) &&
472             (IO->DNS.Query->VParsedDNSReply != NULL))
473         { /* ok, we found mx records. */
474
475                 Msg->CurrMX
476                         = Msg->AllMX
477                         = IO->DNS.Query->VParsedDNSReply;
478                 while (Msg->CurrMX) {
479                         int i;
480                         for (i = 0; i < 2; i++) {
481                                 ParsedURL *p;
482
483                                 p = (ParsedURL*) malloc(sizeof(ParsedURL));
484                                 memset(p, 0, sizeof(ParsedURL));
485                                 p->Priority = Msg->CurrMX->priority;
486                                 p->IsIP = 0;
487                                 p->Port = DefaultMXPort;
488                                 p->IPv6 = i == 1;
489                                 p->Host = Msg->CurrMX->host;
490                                 if (*pp == NULL)
491                                         *pp = p;
492                                 else {
493                                         ParsedURL *ppp = *pp;
494
495                                         while ((ppp->Next != NULL) &&
496                                                (ppp->Next->Priority <= p->Priority))
497                                                ppp = ppp->Next;
498                                         if ((ppp == *pp) &&
499                                             (ppp->Priority > p->Priority)) {
500                                                 p->Next = *pp;
501                                                 *pp = p;
502                                         }
503                                         else {
504                                                 p->Next = ppp->Next;
505                                                 ppp->Next = p;
506                                         }
507                                 }
508                         }
509                         Msg->CurrMX    = Msg->CurrMX->next;
510                 }
511                 Msg->CXFlags   = Msg->CXFlags & F_HAVE_MX;
512         }
513         else { /* else fall back to the plain hostname */
514                 int i;
515                 for (i = 0; i < 2; i++) {
516                         ParsedURL *p;
517
518                         p = (ParsedURL*) malloc(sizeof(ParsedURL));
519                         memset(p, 0, sizeof(ParsedURL));
520                         p->IsIP = 0;
521                         p->Port = DefaultMXPort;
522                         p->IPv6 = i == 1;
523                         p->Host = Msg->node;
524
525                         *pp = p;
526                         pp = &p->Next;
527                 }
528                 Msg->CXFlags   = Msg->CXFlags & F_DIRECT;
529         }
530         if (Msg->MyQItem->FallBackHost != NULL)
531         {
532                 Msg->MyQItem->FallBackHost->Next = *pp;
533                 *pp = Msg->MyQItem->FallBackHost;
534         }
535         Msg->pCurrRelay = Msg->Relay;
536         return get_one_mx_host_ip(IO);
537 }
538
539 eNextState resolve_mx_records(AsyncIO *IO)
540 {
541         SmtpOutMsg * Msg = IO->Data;
542
543         SetSMTPState(IO, eSTMPmxlookup);
544
545         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
546         /* start resolving MX records here. */
547         if (!QueueQuery(ns_t_mx,
548                         Msg->node,
549                         &Msg->IO,
550                         &Msg->MxLookup,
551                         smtp_resolve_mx_record_done))
552         {
553                 Msg->MyQEntry->Status = 5;
554                 StrBufPrintf(Msg->MyQEntry->StatusMessage,
555                              "No MX hosts found for <%s>", Msg->node);
556                 return IO->NextState;
557         }
558         Msg->IO.NextState = eReadDNSReply;
559         return IO->NextState;
560 }
561
562
563
564 /******************************************************************************
565  *  so, we're going to start a SMTP delivery.  lets get it on.                *
566  ******************************************************************************/
567
568 SmtpOutMsg *new_smtp_outmsg(OneQueItem *MyQItem,
569                             MailQEntry *MyQEntry,
570                             int MsgCount)
571 {
572         SmtpOutMsg * Msg;
573
574         Msg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
575         if (Msg == NULL)
576                 return NULL;
577         memset(Msg, 0, sizeof(SmtpOutMsg));
578
579         Msg->n                = MsgCount;
580         Msg->MyQEntry         = MyQEntry;
581         Msg->MyQItem          = MyQItem;
582         Msg->pCurrRelay       = MyQItem->URL;
583
584         InitIOStruct(&Msg->IO,
585                      Msg,
586                      eReadMessage,
587                      SMTP_C_ReadServerStatus,
588                      SMTP_C_DNSFail,
589                      SMTP_C_DispatchWriteDone,
590                      SMTP_C_DispatchReadDone,
591                      SMTP_C_Terminate,
592                      SMTP_C_TerminateDB,
593                      SMTP_C_ConnFail,
594                      SMTP_C_Timeout,
595                      SMTP_C_Shutdown);
596
597         Msg->IO.ErrMsg = Msg->MyQEntry->StatusMessage;
598
599         return Msg;
600 }
601
602 void smtp_try_one_queue_entry(OneQueItem *MyQItem,
603                               MailQEntry *MyQEntry,
604                               StrBuf *MsgText,
605                         /*KeepMsgText allows us to use MsgText as ours.*/
606                               int KeepMsgText,
607                               int MsgCount)
608 {
609         SmtpOutMsg *Msg;
610
611         SMTPC_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
612
613         Msg = new_smtp_outmsg(MyQItem, MyQEntry, MsgCount);
614         if (Msg == NULL) {
615                 SMTPC_syslog(LOG_DEBUG, "%s Failed to alocate message context.\n", __FUNCTION__);
616                 if (KeepMsgText) 
617                         FreeStrBuf (&MsgText);
618                 return;
619         }
620         if (KeepMsgText) Msg->msgtext = MsgText;
621         else             Msg->msgtext = NewStrBufDup(MsgText);
622
623         if (smtp_resolve_recipients(Msg) &&
624             (!MyQItem->HaveRelay ||
625              (MyQItem->URL != NULL)))
626         {
627                 safestrncpy(
628                         ((CitContext *)Msg->IO.CitContext)->cs_host,
629                         Msg->node,
630                         sizeof(((CitContext *)
631                                 Msg->IO.CitContext)->cs_host));
632
633                 SMTPC_syslog(LOG_DEBUG, "Starting: [%ld] <%s> CC <%d> \n",
634                              Msg->MyQItem->MessageID,
635                              ChrPtr(Msg->MyQEntry->Recipient),
636                              ((CitContext*)Msg->IO.CitContext)->cs_pid);
637                 if (Msg->pCurrRelay == NULL) {
638                         SetSMTPState(&Msg->IO, eSTMPmxlookup);
639                         QueueEventContext(&Msg->IO,
640                                           resolve_mx_records);
641                 }
642                 else { /* oh... via relay host */
643                         if (Msg->pCurrRelay->IsIP) {
644                                 SetSMTPState(&Msg->IO, eSTMPconnecting);
645                                 QueueEventContext(&Msg->IO,
646                                                   mx_connect_ip);
647                         }
648                         else {
649                                 SetSMTPState(&Msg->IO, eSTMPalookup);
650                                 /* uneducated admin has chosen to
651                                    add DNS to the equation... */
652                                 QueueEventContext(&Msg->IO,
653                                                   get_one_mx_host_ip);
654                         }
655                 }
656         }
657         else {
658                 SetSMTPState(&Msg->IO, eSMTPFailTotal);
659                 /* No recipients? well fail then. */
660                 if (Msg->MyQEntry != NULL) {
661                         Msg->MyQEntry->Status = 5;
662                         if (StrLength(Msg->MyQEntry->StatusMessage) == 0)
663                                 StrBufPlain(Msg->MyQEntry->StatusMessage,
664                                             HKEY("Invalid Recipient!"));
665                 }
666                 FinalizeMessageSend_DB(&Msg->IO);
667                 DeleteSmtpOutMsg(Msg);
668         }
669 }
670
671
672
673
674
675
676 /*****************************************************************************/
677 /*                     SMTP CLIENT DISPATCHER                                */
678 /*****************************************************************************/
679
680 void SMTPSetTimeout(eNextState NextTCPState, SmtpOutMsg *Msg)
681 {
682         double Timeout = 0.0;
683         AsyncIO *IO = &Msg->IO;
684
685         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
686
687         switch (NextTCPState) {
688         case eSendFile:
689         case eSendReply:
690         case eSendMore:
691                 Timeout = SMTP_C_SendTimeouts[Msg->State];
692                 if (Msg->State == eDATABody) {
693                         /* if we're sending a huge message,
694                          * we need more time.
695                          */
696                         Timeout += StrLength(Msg->msgtext) / 512;
697                 }
698                 break;
699         case eReadMessage:
700                 Timeout = SMTP_C_ReadTimeouts[Msg->State];
701                 if (Msg->State == eDATATerminateBody) {
702                         /*
703                          * some mailservers take a nap before accepting
704                          * the message content inspection and such.
705                          */
706                         Timeout += StrLength(Msg->msgtext) / 512;
707                 }
708                 break;
709         case eSendDNSQuery:
710         case eReadDNSReply:
711         case eDBQuery:
712         case eReadFile:
713         case eReadMore:
714         case eReadPayload:
715         case eConnect:
716         case eTerminateConnection:
717         case eAbort:
718                 return;
719         }
720         SetNextTimeout(&Msg->IO, Timeout);
721 }
722 eNextState SMTP_C_DispatchReadDone(AsyncIO *IO)
723 {
724         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
725         SmtpOutMsg *Msg = IO->Data;
726         eNextState rc;
727
728         rc = ReadHandlers[Msg->State](Msg);
729         if (rc != eAbort)
730         {
731                 Msg->State++;
732                 SMTPSetTimeout(rc, Msg);
733         }
734         return rc;
735 }
736 eNextState SMTP_C_DispatchWriteDone(AsyncIO *IO)
737 {
738         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
739         SmtpOutMsg *Msg = IO->Data;
740         eNextState rc;
741
742         rc = SendHandlers[Msg->State](Msg);
743         SMTPSetTimeout(rc, Msg);
744         return rc;
745 }
746
747
748 /*****************************************************************************/
749 /*                     SMTP CLIENT ERROR CATCHERS                            */
750 /*****************************************************************************/
751 eNextState SMTP_C_Terminate(AsyncIO *IO)
752 {
753         SmtpOutMsg *Msg = IO->Data;
754
755         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
756         return FinalizeMessageSend(Msg);
757 }
758 eNextState SMTP_C_TerminateDB(AsyncIO *IO)
759 {
760         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
761         return Terminate(IO);
762 }
763 eNextState SMTP_C_Timeout(AsyncIO *IO)
764 {
765         SmtpOutMsg *Msg = IO->Data;
766
767         Msg->MyQEntry->Status = 4;
768         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
769         StrBufPlain(IO->ErrMsg, CKEY(ReadErrors[Msg->State]));
770         if (Msg->State > eRCPT)
771                 return eAbort;
772         else
773                 return FailOneAttempt(IO);
774 }
775 eNextState SMTP_C_ConnFail(AsyncIO *IO)
776 {
777         SmtpOutMsg *Msg = IO->Data;
778
779         Msg->MyQEntry->Status = 4;
780         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
781         StrBufPlain(IO->ErrMsg, CKEY(ReadErrors[Msg->State]));
782         return FailOneAttempt(IO);
783 }
784 eNextState SMTP_C_DNSFail(AsyncIO *IO)
785 {
786         SmtpOutMsg *Msg = IO->Data;
787         Msg->MyQEntry->Status = 4;
788         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
789         return FailOneAttempt(IO);
790 }
791 eNextState SMTP_C_Shutdown(AsyncIO *IO)
792 {
793         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
794         SmtpOutMsg *Msg = IO->Data;
795
796         switch (IO->NextState) {
797         case eSendDNSQuery:
798         case eReadDNSReply:
799
800                 /* todo: abort c-ares */
801         case eConnect:
802         case eSendReply:
803         case eSendMore:
804         case eSendFile:
805         case eReadMessage:
806         case eReadMore:
807         case eReadPayload:
808         case eReadFile:
809                 StopClientWatchers(IO, 1);
810                 break;
811         case eDBQuery:
812
813                 break;
814         case eTerminateConnection:
815         case eAbort:
816                 break;
817         }
818         Msg->MyQEntry->Status = 3;
819         StrBufPlain(Msg->MyQEntry->StatusMessage,
820                     HKEY("server shutdown during message submit."));
821         return FinalizeMessageSend(Msg);
822 }
823
824
825 /**
826  * @brief lineread Handler;
827  * understands when to read more SMTP lines, and when this is a one-lined reply.
828  */
829 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
830 {
831         eReadState Finished = eBufferNotEmpty;
832
833         while (Finished == eBufferNotEmpty) {
834                 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
835
836                 switch (Finished) {
837                 case eMustReadMore: /// read new from socket...
838                         return Finished;
839                         break;
840                 case eBufferNotEmpty: /* shouldn't happen... */
841                 case eReadSuccess: /// done for now...
842                         if (StrLength(IO->IOBuf) < 4)
843                                 continue;
844                         if (ChrPtr(IO->IOBuf)[3] == '-')
845                                 Finished = eBufferNotEmpty;
846                         else
847                                 return Finished;
848                         break;
849                 case eReadFail: /// WHUT?
850                         ///todo: shut down!
851                         break;
852                 }
853         }
854         return Finished;
855 }
856
857 void LogDebugEnableSMTPClient(const int n)
858 {
859         SMTPClientDebugEnabled = n;
860 }
861
862 CTDL_MODULE_INIT(smtp_eventclient)
863 {
864         if (!threading)
865                 CtdlRegisterDebugFlagHook(HKEY("smtpeventclient"), LogDebugEnableSMTPClient, &SMTPClientDebugEnabled);
866         return "smtpeventclient";
867 }