Libev migration Cleanup, print resolved IP to log
[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-2009 by the citadel.org team
24  *
25  *  This program is free software; you can redistribute it and/or modify
26  *  it under the terms of the GNU General Public License as published by
27  *  the Free Software Foundation; either version 3 of the License, or
28  *  (at your option) any later version.
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  *  You should have received a copy of the GNU General Public License
36  *  along with this program; if not, write to the Free Software
37  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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
92 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
93 /*****************************************************************************/
94 /*               SMTP CLIENT (OUTBOUND PROCESSING) STUFF                     */
95 /*****************************************************************************/
96
97 typedef enum _eSMTP_C_States {
98         eConnect, 
99         eEHLO,
100         eHELO,
101         eSMTPAuth,
102         eFROM,
103         eRCPT,
104         eDATA,
105         eDATABody,
106         eDATATerminateBody,
107         eQUIT,
108         eMaxSMTPC
109 } eSMTP_C_States;
110
111 const long SMTP_C_ConnTimeout = 60; /* wail 1 minute for connections... */
112 const long SMTP_C_ReadTimeouts[eMaxSMTPC] = {
113         90, /* Greeting... */
114         30, /* EHLO */
115         30, /* HELO */
116         30, /* Auth */
117         30, /* From */
118         30, /* RCPT */
119         30, /* DATA */
120         90, /* DATABody */
121         900, /* end of body... */
122         30  /* QUIT */
123 };
124 /*
125 const long SMTP_C_SendTimeouts[eMaxSMTPC] = {
126
127 }; */
128 const char *ReadErrors[eMaxSMTPC] = {
129         "Connection broken during SMTP conversation",
130         "Connection broken during SMTP EHLO",
131         "Connection broken during SMTP HELO",
132         "Connection broken during SMTP AUTH",
133         "Connection broken during SMTP MAIL FROM",
134         "Connection broken during SMTP RCPT",
135         "Connection broken during SMTP DATA",
136         "Connection broken during SMTP message transmit",
137         ""/* quit reply, don't care. */
138 };
139
140
141 typedef struct _stmp_out_msg {
142         MailQEntry *MyQEntry;
143         OneQueItem *MyQItem;
144         long n;
145         AsyncIO IO;
146
147         eSMTP_C_States State;
148
149         struct ares_mx_reply *AllMX;
150         struct ares_mx_reply *CurrMX;
151         const char *mx_port;
152         const char *mx_host;
153
154         struct hostent *OneMX;
155
156         char mx_user[1024];
157         char mx_pass[1024];
158         StrBuf *msgtext;
159         char *envelope_from;
160         char user[1024];
161         char node[1024];
162         char name[1024];
163         char mailfrom[1024];
164 } SmtpOutMsg;
165
166 void DeleteSmtpOutMsg(void *v)
167 {
168         SmtpOutMsg *Msg = v;
169
170         ares_free_data(Msg->AllMX);
171         
172         FreeStrBuf(&Msg->msgtext);
173         FreeAsyncIOContents(&Msg->IO);
174         free(Msg);
175 }
176
177 eNextState SMTP_C_Timeout(void *Data);
178 eNextState SMTP_C_ConnFail(void *Data);
179 eNextState SMTP_C_DispatchReadDone(void *Data);
180 eNextState SMTP_C_DispatchWriteDone(void *Data);
181 eNextState SMTP_C_Terminate(void *Data);
182 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO);
183
184 typedef eNextState (*SMTPReadHandler)(SmtpOutMsg *Msg);
185 typedef eNextState (*SMTPSendHandler)(SmtpOutMsg *Msg);
186
187
188 #define SMTP_ERROR(WHICH_ERR, ERRSTR) do {\
189                 SendMsg->MyQEntry->Status = WHICH_ERR; \
190                 StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, HKEY(ERRSTR), 0); \
191                 return eAbort; } \
192         while (0)
193
194 #define SMTP_VERROR(WHICH_ERR) do {\
195                 SendMsg->MyQEntry->Status = WHICH_ERR; \
196                 StrBufPlain(SendMsg->MyQEntry->StatusMessage, \
197                             ChrPtr(SendMsg->IO.IOBuf) + 4, \
198                             StrLength(SendMsg->IO.IOBuf) - 4); \
199                 return eAbort; } \
200         while (0)
201
202 #define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(SendMsg->IO.IOBuf)[0] == WHICH_STATE)
203
204 #define SMTP_DBG_SEND() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: > %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
205 #define SMTP_DBG_READ() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: < %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
206
207
208 void FinalizeMessageSend(SmtpOutMsg *Msg)
209 {
210         if (DecreaseQReference(Msg->MyQItem)) 
211         {
212                 int nRemain;
213                 StrBuf *MsgData;
214
215                 nRemain = CountActiveQueueEntries(Msg->MyQItem);
216
217                 MsgData = SerializeQueueItem(Msg->MyQItem);
218                 /*
219                  * Uncompleted delivery instructions remain, so delete the old
220                  * instructions and replace with the updated ones.
221                  */
222                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, "");
223                 smtpq_do_bounce(Msg->MyQItem,
224                                Msg->msgtext); 
225                 if (nRemain > 0) {
226                         struct CtdlMessage *msg;
227                         msg = malloc(sizeof(struct CtdlMessage));
228                         memset(msg, 0, sizeof(struct CtdlMessage));
229                         msg->cm_magic = CTDLMESSAGE_MAGIC;
230                         msg->cm_anon_type = MES_NORMAL;
231                         msg->cm_format_type = FMT_RFC822;
232                         msg->cm_fields['M'] = SmashStrBuf(&MsgData);
233                         /* Generate 'bounce' messages */
234                         smtp_do_bounce(msg->cm_fields['M'],
235                                        Msg->msgtext); 
236
237                         CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
238                         CtdlFreeMessage(msg);
239                 }
240                 else 
241                         CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->MessageID, 1, "");
242
243                 RemoveQItem(Msg->MyQItem);
244         }
245         DeleteSmtpOutMsg(Msg);
246 }
247
248
249
250
251 void get_one_mx_host_ip_done(void *Ctx, 
252                              int status,
253                              int timeouts,
254                              struct hostent *hostent)
255 {
256         AsyncIO *IO = Ctx;
257         SmtpOutMsg *SendMsg = IO->Data;
258         if ((status == ARES_SUCCESS) && (hostent != NULL) ) {
259                 unsigned long psaddr;
260                 // TODO: IPV6
261                 memcpy(&psaddr, hostent->h_addr_list[0], sizeof(psaddr));
262                 psaddr = ntohl(psaddr); 
263
264                 CtdlLogPrintf(CTDL_DEBUG, 
265                               "SMTP client[%ld]: connecting to %s [%d.%d.%d.%d:%d] ...\n", 
266                               SendMsg->n, 
267                               SendMsg->mx_host, 
268                               (psaddr >> 24) & 0xFF,
269                               (psaddr >> 16) & 0xFF,
270                               (psaddr >>  8) & 0xFF,
271                               (psaddr >>  0) & 0xFF,
272                               SendMsg->IO.dport);
273
274                 SendMsg->MyQEntry->Status = 5; 
275                 StrBufPrintf(SendMsg->MyQEntry->StatusMessage, 
276                              "Timeout while connecting %s", 
277                              SendMsg->mx_host);
278
279                 SendMsg->IO.HEnt = hostent;
280                 InitEventIO(IO, SendMsg, 
281                             SMTP_C_DispatchReadDone, 
282                             SMTP_C_DispatchWriteDone, 
283                             SMTP_C_Terminate,
284                             SMTP_C_Timeout,
285                             SMTP_C_ConnFail,
286                             SMTP_C_ReadServerStatus,
287                             SMTP_C_ConnTimeout, 
288                             SMTP_C_ReadTimeouts[0],
289                             1);
290
291         }
292 }
293
294 const unsigned short DefaultMXPort = 25;
295 void get_one_mx_host_ip(SmtpOutMsg *SendMsg)
296 {
297         //char *endpart;
298         //char buf[SIZ];
299
300         SendMsg->IO.dport = DefaultMXPort;
301
302
303 /* TODO: Relay!
304         *SendMsg->mx_user =  '\0';
305         *SendMsg->mx_pass = '\0';
306         if (num_tokens(buf, '@') > 1) {
307                 strcpy (SendMsg->mx_user, buf);
308                 endpart = strrchr(SendMsg->mx_user, '@');
309                 *endpart = '\0';
310                 strcpy (SendMsg->mx_host, endpart + 1);
311                 endpart = strrchr(SendMsg->mx_user, ':');
312                 if (endpart != NULL) {
313                         strcpy(SendMsg->mx_pass, endpart+1);
314                         *endpart = '\0';
315                 }
316
317         endpart = strrchr(SendMsg->mx_host, ':');
318         if (endpart != 0){
319                 *endpart = '\0';
320                 strcpy(SendMsg->mx_port, endpart + 1);
321         }               
322         }
323         else
324 */
325         SendMsg->mx_host = SendMsg->CurrMX->host;
326         SendMsg->CurrMX = SendMsg->CurrMX->next;
327
328         CtdlLogPrintf(CTDL_DEBUG, 
329                       "SMTP client[%ld]: looking up %s : %d ...\n", 
330                       SendMsg->n, 
331                       SendMsg->mx_host);
332
333         ares_gethostbyname(SendMsg->IO.DNSChannel,
334                            SendMsg->mx_host,   
335                            AF_INET6, /* it falls back to ipv4 in doubt... */
336                            get_one_mx_host_ip_done,
337                            &SendMsg->IO);
338 }
339
340
341 eNextState smtp_resolve_mx_done(void *data)
342 {
343         AsyncIO *IO = data;
344         SmtpOutMsg * SendMsg = IO->Data;
345
346         SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
347         SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
348         SendMsg->IO.IOBuf = NewStrBuf();
349         SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
350
351         SendMsg->CurrMX = SendMsg->AllMX = IO->VParsedDNSReply;
352         //// TODO: should we remove the current ares context???
353         get_one_mx_host_ip(SendMsg);
354         return 0;
355 }
356
357
358 int resolve_mx_records(void *Ctx)
359 {
360         SmtpOutMsg * SendMsg = Ctx;
361
362         if (!QueueQuery(ns_t_mx, 
363                         SendMsg->node, 
364                         &SendMsg->IO, 
365                         smtp_resolve_mx_done))
366         {
367                 SendMsg->MyQEntry->Status = 5;
368                 StrBufPrintf(SendMsg->MyQEntry->StatusMessage, 
369                              "No MX hosts found for <%s>", SendMsg->node);
370                 return 0; ///////TODO: abort!
371         }
372         return 0;
373 }
374
375
376 int smtp_resolve_recipients(SmtpOutMsg *SendMsg)
377 {
378         const char *ptr;
379         char buf[1024];
380         int scan_done;
381         int lp, rp;
382         int i;
383
384         if ((SendMsg==NULL) || 
385             (SendMsg->MyQEntry == NULL) || 
386             (StrLength(SendMsg->MyQEntry->Recipient) == 0)) {
387                 return 0;
388         }
389
390         /* Parse out the host portion of the recipient address */
391         process_rfc822_addr(ChrPtr(SendMsg->MyQEntry->Recipient), 
392                             SendMsg->user, 
393                             SendMsg->node, 
394                             SendMsg->name);
395
396         CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
397                       SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
398         /* If no envelope_from is supplied, extract one from the message */
399         if ( (SendMsg->envelope_from == NULL) || 
400              (IsEmptyStr(SendMsg->envelope_from)) ) {
401                 SendMsg->mailfrom[0] = '\0';
402                 scan_done = 0;
403                 ptr = ChrPtr(SendMsg->msgtext);
404                 do {
405                         if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
406                                 scan_done = 1;
407                         }
408                         if (!strncasecmp(buf, "From:", 5)) {
409                                 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
410                                 striplt(SendMsg->mailfrom);
411                                 for (i=0; SendMsg->mailfrom[i]; ++i) {
412                                         if (!isprint(SendMsg->mailfrom[i])) {
413                                                 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
414                                                 i=0;
415                                         }
416                                 }
417         
418                                 /* Strip out parenthesized names */
419                                 lp = (-1);
420                                 rp = (-1);
421                                 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
422                                         if (SendMsg->mailfrom[i] == '(') lp = i;
423                                         if (SendMsg->mailfrom[i] == ')') rp = i;
424                                 }
425                                 if ((lp>0)&&(rp>lp)) {
426                                         strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
427                                 }
428         
429                                 /* Prefer brokketized names */
430                                 lp = (-1);
431                                 rp = (-1);
432                                 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
433                                         if (SendMsg->mailfrom[i] == '<') lp = i;
434                                         if (SendMsg->mailfrom[i] == '>') rp = i;
435                                 }
436                                 if ( (lp>=0) && (rp>lp) ) {
437                                         SendMsg->mailfrom[rp] = 0;
438                                         memmove(SendMsg->mailfrom, 
439                                                 &SendMsg->mailfrom[lp + 1], 
440                                                 rp - lp);
441                                 }
442         
443                                 scan_done = 1;
444                         }
445                 } while (scan_done == 0);
446                 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
447                 stripallbut(SendMsg->mailfrom, '<', '>');
448                 SendMsg->envelope_from = SendMsg->mailfrom;
449         }
450
451         return 1;
452 }
453
454
455
456 void smtp_try(OneQueItem *MyQItem, 
457               MailQEntry *MyQEntry, 
458               StrBuf *MsgText, 
459               int KeepMsgText,  /* KeepMsgText allows us to use MsgText as ours. */
460               int MsgCount)
461 {
462         SmtpOutMsg * SendMsg;
463
464         SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
465         memset(SendMsg, 0, sizeof(SmtpOutMsg));
466         SendMsg->IO.sock = (-1);
467         SendMsg->n = MsgCount++;
468         SendMsg->MyQEntry = MyQEntry;
469         SendMsg->MyQItem = MyQItem;
470         SendMsg->IO.Data = SendMsg;
471         if (KeepMsgText)
472                 SendMsg->msgtext = MsgText;
473         else 
474                 SendMsg->msgtext = NewStrBufDup(MsgText);
475
476         if (smtp_resolve_recipients(SendMsg)) {
477                 QueueEventContext(SendMsg, 
478                                   &SendMsg->IO,
479                                   resolve_mx_records);
480         }
481         else {
482                 if ((SendMsg==NULL) || 
483                     (SendMsg->MyQEntry == NULL)) {
484                         SendMsg->MyQEntry->Status = 5;
485                         StrBufPlain(SendMsg->MyQEntry->StatusMessage, 
486                                     HKEY("Invalid Recipient!"));
487                 }
488                 FinalizeMessageSend(SendMsg);
489         }
490 }
491
492
493
494
495
496 /*****************************************************************************/
497 /*                     SMTP CLIENT STATE CALLBACKS                           */
498 /*****************************************************************************/
499 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
500 {
501         /* Process the SMTP greeting from the server */
502         SMTP_DBG_READ();
503
504         if (!SMTP_IS_STATE('2')) {
505                 if (SMTP_IS_STATE('4')) 
506                         SMTP_VERROR(4);
507                 else 
508                         SMTP_VERROR(5);
509         }
510         return eSendReply;
511 }
512
513 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
514 {
515         /* At this point we know we are talking to a real SMTP server */
516
517         /* Do a EHLO command.  If it fails, try the HELO command. */
518         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
519                      "EHLO %s\r\n", config.c_fqdn);
520
521         SMTP_DBG_SEND();
522         return eReadMessage;
523 }
524
525 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
526 {
527         SMTP_DBG_READ();
528
529         if (SMTP_IS_STATE('2')) {
530                 SendMsg->State ++;
531                 if (IsEmptyStr(SendMsg->mx_user))
532                         SendMsg->State ++; /* Skip auth... */
533         }
534         /* else we fall back to 'helo' */
535         return eSendReply;
536 }
537
538 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
539 {
540         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
541                      "HELO %s\r\n", config.c_fqdn);
542
543         SMTP_DBG_SEND();
544         return eReadMessage;
545 }
546
547 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
548 {
549         SMTP_DBG_READ();
550
551         if (!SMTP_IS_STATE('2')) {
552                 if (SMTP_IS_STATE('4'))
553                         SMTP_VERROR(4);
554                 else 
555                         SMTP_VERROR(5);
556         }
557         if (!IsEmptyStr(SendMsg->mx_user))
558                 SendMsg->State ++; /* Skip auth... */
559         return eSendReply;
560 }
561
562 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
563 {
564         char buf[SIZ];
565         char encoded[1024];
566
567         /* Do an AUTH command if necessary */
568         sprintf(buf, "%s%c%s%c%s", 
569                 SendMsg->mx_user, '\0', 
570                 SendMsg->mx_user, '\0', 
571                 SendMsg->mx_pass);
572         CtdlEncodeBase64(encoded, buf, 
573                          strlen(SendMsg->mx_user) + 
574                          strlen(SendMsg->mx_user) + 
575                          strlen(SendMsg->mx_pass) + 2, 0);
576         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
577                      "AUTH PLAIN %s\r\n", encoded);
578         
579         SMTP_DBG_SEND();
580         return eReadMessage;
581 }
582
583 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
584 {
585         /* Do an AUTH command if necessary */
586         
587         SMTP_DBG_READ();
588         
589         if (!SMTP_IS_STATE('2')) {
590                 if (SMTP_IS_STATE('4'))
591                         SMTP_VERROR(4);
592                 else 
593                         SMTP_VERROR(5);
594         }
595         return eSendReply;
596 }
597
598 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
599 {
600         /* previous command succeeded, now try the MAIL FROM: command */
601         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
602                      "MAIL FROM:<%s>\r\n", 
603                      SendMsg->envelope_from);
604
605         SMTP_DBG_SEND();
606         return eReadMessage;
607 }
608
609 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
610 {
611         SMTP_DBG_READ();
612
613         if (!SMTP_IS_STATE('2')) {
614                 if (SMTP_IS_STATE('4'))
615                         SMTP_VERROR(4);
616                 else 
617                         SMTP_VERROR(5);
618         }
619         return eSendReply;
620 }
621
622
623 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
624 {
625         /* MAIL succeeded, now try the RCPT To: command */
626         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
627                      "RCPT TO:<%s@%s>\r\n", 
628                      SendMsg->user, 
629                      SendMsg->node);
630
631         SMTP_DBG_SEND();
632         return eReadMessage;
633 }
634
635 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
636 {
637         SMTP_DBG_READ();
638
639         if (!SMTP_IS_STATE('2')) {
640                 if (SMTP_IS_STATE('4')) 
641                         SMTP_VERROR(4);
642                 else 
643                         SMTP_VERROR(5);
644         }
645         return eSendReply;
646 }
647
648 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
649 {
650         /* RCPT succeeded, now try the DATA command */
651         StrBufPlain(SendMsg->IO.SendBuf.Buf,
652                     HKEY("DATA\r\n"));
653
654         SMTP_DBG_SEND();
655         return eReadMessage;
656 }
657
658 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
659 {
660         SMTP_DBG_READ();
661
662         if (!SMTP_IS_STATE('3')) {
663                 if (SMTP_IS_STATE('4')) 
664                         SMTP_VERROR(3);
665                 else 
666                         SMTP_VERROR(5);
667         }
668         return eSendReply;
669 }
670
671 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
672 {
673         StrBuf *Buf;
674         /* If we reach this point, the server is expecting data.*/
675
676         Buf = SendMsg->IO.SendBuf.Buf;
677         SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
678         SendMsg->msgtext = Buf;
679         //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
680         SendMsg->State ++;
681
682         return eSendMore;
683 }
684
685 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
686 {
687         StrBuf *Buf;
688
689         Buf = SendMsg->IO.SendBuf.Buf;
690         SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
691         SendMsg->msgtext = Buf;
692
693         StrBufPlain(SendMsg->IO.SendBuf.Buf,
694                     HKEY(".\r\n"));
695
696         return eReadMessage;
697
698 }
699
700 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
701 {
702         SMTP_DBG_READ();
703
704         if (!SMTP_IS_STATE('2')) {
705                 if (SMTP_IS_STATE('4'))
706                         SMTP_VERROR(4);
707                 else 
708                         SMTP_VERROR(5);
709         }
710
711         /* We did it! */
712         StrBufPlain(SendMsg->MyQEntry->StatusMessage, 
713                     &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
714                     StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
715         SendMsg->MyQEntry->Status = 2;
716         return eSendReply;
717 }
718
719 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
720 {
721         StrBufPlain(SendMsg->IO.SendBuf.Buf,
722                     HKEY("QUIT\r\n"));
723
724         SMTP_DBG_SEND();
725         return eReadMessage;
726 }
727
728 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
729 {
730         SMTP_DBG_READ();
731
732         CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
733                       SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
734         return eTerminateConnection;
735 }
736
737 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
738 {
739         return eSendReply;
740 }
741
742 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
743 {
744         return eReadMessage;
745 }
746
747
748 /*****************************************************************************/
749 /*                     SMTP CLIENT DISPATCHER                                */
750 /*****************************************************************************/
751 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
752         SMTPC_read_greeting,
753         SMTPC_read_EHLO_reply,
754         SMTPC_read_HELO_reply,
755         SMTPC_read_auth_reply,
756         SMTPC_read_FROM_reply,
757         SMTPC_read_RCPT_reply,
758         SMTPC_read_DATAcmd_reply,
759         SMTPC_read_dummy,
760         SMTPC_read_data_body_reply,
761         SMTPC_read_QUIT_reply
762 };
763 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
764         SMTPC_send_dummy, /* we don't send a greeting, the server does... */
765         SMTPC_send_EHLO,
766         STMPC_send_HELO,
767         SMTPC_send_auth,
768         SMTPC_send_FROM,
769         SMTPC_send_RCPT,
770         SMTPC_send_DATAcmd,
771         SMTPC_send_data_body,
772         SMTPC_send_terminate_data_body,
773         SMTPC_send_QUIT
774 };
775 eNextState SMTP_C_DispatchReadDone(void *Data)
776 {
777         SmtpOutMsg *pMsg = Data;
778         eNextState rc = ReadHandlers[pMsg->State](pMsg);
779         pMsg->State++;
780         return rc;
781 }
782 eNextState SMTP_C_DispatchWriteDone(void *Data)
783 {
784         SmtpOutMsg *pMsg = Data;
785         return SendHandlers[pMsg->State](pMsg); 
786 }
787
788
789 /*****************************************************************************/
790 /*                     SMTP CLIENT ERROR CATCHERS                            */
791 /*****************************************************************************/
792 eNextState SMTP_C_Terminate(void *Data)
793 {
794         AsyncIO *IO = Data;
795         SmtpOutMsg *pMsg = IO->Data;
796         FinalizeMessageSend(pMsg);
797         return 0;
798 }
799 eNextState SMTP_C_Timeout(void *Data)
800 {
801         AsyncIO *IO = Data;
802         SmtpOutMsg *pMsg = IO->Data;
803         FinalizeMessageSend(pMsg);
804         return 0;
805 }
806 eNextState SMTP_C_ConnFail(void *Data)
807 {
808         AsyncIO *IO = Data;
809         SmtpOutMsg *pMsg = IO->Data;
810         FinalizeMessageSend(pMsg);
811         return 0;
812 }
813
814
815 /**
816  * @brief lineread Handler; understands when to read more SMTP lines, and when this is a one-lined reply.
817  */
818 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
819 {
820         eReadState Finished = eBufferNotEmpty; 
821
822         while (Finished == eBufferNotEmpty) {
823                 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
824                 
825                 switch (Finished) {
826                 case eMustReadMore: /// read new from socket... 
827                         return Finished;
828                         break;
829                 case eBufferNotEmpty: /* shouldn't happen... */
830                 case eReadSuccess: /// done for now...
831                         if (StrLength(IO->IOBuf) < 4)
832                                 continue;
833                         if (ChrPtr(IO->IOBuf)[3] == '-')
834                                 Finished = eBufferNotEmpty;
835                         else 
836                                 return Finished;
837                         break;
838                 case eReadFail: /// WHUT?
839                         ///todo: shut down! 
840                         break;
841                 }
842         }
843         return Finished;
844 }
845
846
847 #endif
848 CTDL_MODULE_INIT(smtp_eventclient)
849 {
850         return "smtpeventclient";
851 }