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