Read multiline SMTP-Replies; we need to analyze all of them to detect the proper...
[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                 msg->cm_fields['M'] = SmashStrBuf(&Msg->QMsgData);
227                 msg->cm_fields['U'] = strdup("QMSG");
228                 Msg->MyQItem->QueMsgID =
229                         CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
230                 EVS_syslog(LOG_DEBUG, "%ld", Msg->MyQItem->QueMsgID);
231                 CtdlFreeMessage(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);
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\n", __FUNCTION__);
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                         if (Msg->pCurrRelay->IsIP) {
661                                 SetSMTPState(&Msg->IO, eSTMPconnecting);
662                                 QueueEventContext(&Msg->IO,
663                                                   mx_connect_ip);
664                         }
665                         else {
666                                 SetSMTPState(&Msg->IO, eSTMPalookup);
667                                 /* uneducated admin has chosen to
668                                    add DNS to the equation... */
669                                 QueueEventContext(&Msg->IO,
670                                                   get_one_mx_host_ip);
671                         }
672                 }
673         }
674         else {
675                 SetSMTPState(&Msg->IO, eSMTPFailTotal);
676                 /* No recipients? well fail then. */
677                 if (Msg->MyQEntry != NULL) {
678                         Msg->MyQEntry->Status = 5;
679                         if (StrLength(Msg->MyQEntry->StatusMessage) == 0)
680                                 StrBufPlain(Msg->MyQEntry->StatusMessage,
681                                             HKEY("Invalid Recipient!"));
682                 }
683                 FinalizeMessageSend_DB(&Msg->IO);
684                 DeleteSmtpOutMsg(Msg);
685         }
686 }
687
688
689
690
691
692
693 /*****************************************************************************/
694 /*                     SMTP CLIENT DISPATCHER                                */
695 /*****************************************************************************/
696
697 void SMTPSetTimeout(eNextState NextTCPState, SmtpOutMsg *Msg)
698 {
699         double Timeout = 0.0;
700         AsyncIO *IO = &Msg->IO;
701
702         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
703
704         switch (NextTCPState) {
705         case eSendFile:
706         case eSendReply:
707         case eSendMore:
708                 Timeout = SMTP_C_SendTimeouts[Msg->State];
709                 if (Msg->State == eDATABody) {
710                         /* if we're sending a huge message,
711                          * we need more time.
712                          */
713                         Timeout += StrLength(Msg->msgtext) / 512;
714                 }
715                 break;
716         case eReadMessage:
717                 Timeout = SMTP_C_ReadTimeouts[Msg->State];
718                 if (Msg->State == eDATATerminateBody) {
719                         /*
720                          * some mailservers take a nap before accepting
721                          * the message content inspection and such.
722                          */
723                         Timeout += StrLength(Msg->msgtext) / 512;
724                 }
725                 break;
726         case eSendDNSQuery:
727         case eReadDNSReply:
728         case eDBQuery:
729         case eReadFile:
730         case eReadMore:
731         case eReadPayload:
732         case eConnect:
733         case eTerminateConnection:
734         case eAbort:
735                 return;
736         }
737         SetNextTimeout(&Msg->IO, Timeout);
738 }
739 eNextState SMTP_C_DispatchReadDone(AsyncIO *IO)
740 {
741         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
742         SmtpOutMsg *Msg = IO->Data;
743         eNextState rc;
744
745         rc = ReadHandlers[Msg->State](Msg);
746         if (rc != eAbort)
747         {
748                 Msg->State++;
749                 SMTPSetTimeout(rc, Msg);
750         }
751         return rc;
752 }
753 eNextState SMTP_C_DispatchWriteDone(AsyncIO *IO)
754 {
755         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
756         SmtpOutMsg *Msg = IO->Data;
757         eNextState rc;
758
759         rc = SendHandlers[Msg->State](Msg);
760         SMTPSetTimeout(rc, Msg);
761         return rc;
762 }
763
764
765 /*****************************************************************************/
766 /*                     SMTP CLIENT ERROR CATCHERS                            */
767 /*****************************************************************************/
768 eNextState SMTP_C_Terminate(AsyncIO *IO)
769 {
770         SmtpOutMsg *Msg = IO->Data;
771
772         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
773         return FinalizeMessageSend(Msg);
774 }
775 eNextState SMTP_C_TerminateDB(AsyncIO *IO)
776 {
777         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
778         return Terminate(IO);
779 }
780 eNextState SMTP_C_Timeout(AsyncIO *IO)
781 {
782         SmtpOutMsg *Msg = IO->Data;
783
784         Msg->MyQEntry->Status = 4;
785         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
786         StrBufPrintf(IO->ErrMsg, "Timeout: %s while talking to %s",
787                      ReadErrors[Msg->State].Key,
788                      Msg->mx_host);
789         if (Msg->State > eRCPT)
790                 return eAbort;
791         else
792                 return FailOneAttempt(IO);
793 }
794 eNextState SMTP_C_ConnFail(AsyncIO *IO)
795 {
796         SmtpOutMsg *Msg = IO->Data;
797
798         Msg->MyQEntry->Status = 4;
799         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
800         StrBufPrintf(IO->ErrMsg, "Connection failure: %s while talking to %s",
801                      ReadErrors[Msg->State].Key,
802                      Msg->mx_host);
803
804         return FailOneAttempt(IO);
805 }
806 eNextState SMTP_C_DNSFail(AsyncIO *IO)
807 {
808         SmtpOutMsg *Msg = IO->Data;
809         Msg->MyQEntry->Status = 4;
810         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
811         return FailOneAttempt(IO);
812 }
813 eNextState SMTP_C_Shutdown(AsyncIO *IO)
814 {
815         EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
816         SmtpOutMsg *Msg = IO->Data;
817
818         switch (IO->NextState) {
819         case eSendDNSQuery:
820         case eReadDNSReply:
821
822                 /* todo: abort c-ares */
823         case eConnect:
824         case eSendReply:
825         case eSendMore:
826         case eSendFile:
827         case eReadMessage:
828         case eReadMore:
829         case eReadPayload:
830         case eReadFile:
831                 StopClientWatchers(IO, 1);
832                 break;
833         case eDBQuery:
834
835                 break;
836         case eTerminateConnection:
837         case eAbort:
838                 break;
839         }
840         Msg->MyQEntry->Status = 3;
841         StrBufPlain(Msg->MyQEntry->StatusMessage,
842                     HKEY("server shutdown during message submit."));
843         return FinalizeMessageSend(Msg);
844 }
845
846
847 /**
848  * @brief lineread Handler;
849  * understands when to read more SMTP lines, and when this is a one-lined reply.
850  */
851 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
852 {
853         eReadState Finished = eBufferNotEmpty;
854
855         while (Finished == eBufferNotEmpty) {
856                 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
857
858                 switch (Finished) {
859                 case eMustReadMore: /// read new from socket...
860                         return Finished;
861                         break;
862                 case eBufferNotEmpty: /* shouldn't happen... */
863                 case eReadSuccess: /// done for now...
864                         if (StrLength(IO->IOBuf) < 4)
865                                 continue;
866                         if (ChrPtr(IO->IOBuf)[3] == '-')
867                         {
868                                 SmtpOutMsg *Msg;
869                                 Msg = (SmtpOutMsg *)IO->Data;
870                                 if (Msg->MultiLineBuf == NULL)
871                                         Msg->MultiLineBuf = NewStrBuf ();
872                                 else
873                                         StrBufAppendBufPlain(Msg->MultiLineBuf, HKEY("\n"), 0);
874                                 StrBufAppendBuf(Msg->MultiLineBuf, IO->IOBuf, 0);
875                                 Finished = eBufferNotEmpty;
876                         }
877                         else
878                                 return Finished;
879                         break;
880                 case eReadFail: /// WHUT?
881                         ///todo: shut down!
882                         break;
883                 }
884         }
885         return Finished;
886 }
887
888 void LogDebugEnableSMTPClient(const int n)
889 {
890         SMTPClientDebugEnabled = n;
891 }
892
893 CTDL_MODULE_INIT(smtp_eventclient)
894 {
895         if (!threading)
896                 CtdlRegisterDebugFlagHook(HKEY("smtpeventclient"), LogDebugEnableSMTPClient, &SMTPClientDebugEnabled);
897         return "smtpeventclient";
898 }