Libev / libc-ares 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_ConnTimeout = 300; /* wail 5 minutes 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
241                 RemoveQItem(Msg->MyQItem);
242         }
243         
244         close(Msg->IO.sock);
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                 CtdlLogPrintf(CTDL_DEBUG, 
260                               "SMTP client[%ld]: connecting to %s [ip]: %d ...\n", 
261                               SendMsg->n, 
262                               SendMsg->mx_host, 
263                               SendMsg->IO.dport);
264
265                 SendMsg->MyQEntry->Status = 5; 
266                 StrBufPrintf(SendMsg->MyQEntry->StatusMessage, 
267                              "Timeout while connecting %s", 
268                              SendMsg->mx_host);
269
270                 SendMsg->IO.HEnt = hostent;
271                 InitEventIO(IO, SendMsg, 
272                             SMTP_C_DispatchReadDone, 
273                             SMTP_C_DispatchWriteDone, 
274                             SMTP_C_Terminate,
275                             SMTP_C_Timeout,
276                             SMTP_C_ConnFail,
277                             SMTP_C_ReadServerStatus,
278                             SMTP_C_ConnTimeout, 
279                             SMTP_C_ReadTimeouts[0],
280                             1);
281
282         }
283 }
284
285 const unsigned short DefaultMXPort = 25;
286 void get_one_mx_host_ip(SmtpOutMsg *SendMsg)
287 {
288         //char *endpart;
289         //char buf[SIZ];
290
291         SendMsg->IO.dport = DefaultMXPort;
292
293
294 /* TODO: Relay!
295         *SendMsg->mx_user =  '\0';
296         *SendMsg->mx_pass = '\0';
297         if (num_tokens(buf, '@') > 1) {
298                 strcpy (SendMsg->mx_user, buf);
299                 endpart = strrchr(SendMsg->mx_user, '@');
300                 *endpart = '\0';
301                 strcpy (SendMsg->mx_host, endpart + 1);
302                 endpart = strrchr(SendMsg->mx_user, ':');
303                 if (endpart != NULL) {
304                         strcpy(SendMsg->mx_pass, endpart+1);
305                         *endpart = '\0';
306                 }
307
308         endpart = strrchr(SendMsg->mx_host, ':');
309         if (endpart != 0){
310                 *endpart = '\0';
311                 strcpy(SendMsg->mx_port, endpart + 1);
312         }               
313         }
314         else
315 */
316         SendMsg->mx_host = SendMsg->CurrMX->host;
317         SendMsg->CurrMX = SendMsg->CurrMX->next;
318
319         CtdlLogPrintf(CTDL_DEBUG, 
320                       "SMTP client[%ld]: looking up %s : %d ...\n", 
321                       SendMsg->n, 
322                       SendMsg->mx_host);
323
324         ares_gethostbyname(SendMsg->IO.DNSChannel,
325                            SendMsg->mx_host,   
326                            AF_INET6, /* it falls back to ipv4 in doubt... */
327                            get_one_mx_host_ip_done,
328                            &SendMsg->IO);
329 }
330
331
332 eNextState smtp_resolve_mx_done(void *data)
333 {
334         AsyncIO *IO = data;
335         SmtpOutMsg * SendMsg = IO->Data;
336
337         SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
338         SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
339         SendMsg->IO.IOBuf = NewStrBuf();
340         SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
341
342         SendMsg->CurrMX = SendMsg->AllMX = IO->VParsedDNSReply;
343         //// TODO: should we remove the current ares context???
344         get_one_mx_host_ip(SendMsg);
345         return 0;
346 }
347
348
349 int resolve_mx_records(void *Ctx)
350 {
351         SmtpOutMsg * SendMsg = Ctx;
352
353         if (!QueueQuery(ns_t_mx, 
354                         SendMsg->node, 
355                         &SendMsg->IO, 
356                         smtp_resolve_mx_done))
357         {
358                 SendMsg->MyQEntry->Status = 5;
359                 StrBufPrintf(SendMsg->MyQEntry->StatusMessage, 
360                              "No MX hosts found for <%s>", SendMsg->node);
361                 return 0; ///////TODO: abort!
362         }
363         return 0;
364 }
365
366
367 int smtp_resolve_recipients(SmtpOutMsg *SendMsg)
368 {
369         const char *ptr;
370         char buf[1024];
371         int scan_done;
372         int lp, rp;
373         int i;
374
375         /* Parse out the host portion of the recipient address */
376         process_rfc822_addr(ChrPtr(SendMsg->MyQEntry->Recipient), 
377                             SendMsg->user, 
378                             SendMsg->node, 
379                             SendMsg->name);
380
381         CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
382                       SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
383         /* If no envelope_from is supplied, extract one from the message */
384         if ( (SendMsg->envelope_from == NULL) || 
385              (IsEmptyStr(SendMsg->envelope_from)) ) {
386                 SendMsg->mailfrom[0] = '\0';
387                 scan_done = 0;
388                 ptr = ChrPtr(SendMsg->msgtext);
389                 do {
390                         if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
391                                 scan_done = 1;
392                         }
393                         if (!strncasecmp(buf, "From:", 5)) {
394                                 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
395                                 striplt(SendMsg->mailfrom);
396                                 for (i=0; SendMsg->mailfrom[i]; ++i) {
397                                         if (!isprint(SendMsg->mailfrom[i])) {
398                                                 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
399                                                 i=0;
400                                         }
401                                 }
402         
403                                 /* Strip out parenthesized names */
404                                 lp = (-1);
405                                 rp = (-1);
406                                 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
407                                         if (SendMsg->mailfrom[i] == '(') lp = i;
408                                         if (SendMsg->mailfrom[i] == ')') rp = i;
409                                 }
410                                 if ((lp>0)&&(rp>lp)) {
411                                         strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
412                                 }
413         
414                                 /* Prefer brokketized names */
415                                 lp = (-1);
416                                 rp = (-1);
417                                 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
418                                         if (SendMsg->mailfrom[i] == '<') lp = i;
419                                         if (SendMsg->mailfrom[i] == '>') rp = i;
420                                 }
421                                 if ( (lp>=0) && (rp>lp) ) {
422                                         SendMsg->mailfrom[rp] = 0;
423                                         memmove(SendMsg->mailfrom, 
424                                                 &SendMsg->mailfrom[lp + 1], 
425                                                 rp - lp);
426                                 }
427         
428                                 scan_done = 1;
429                         }
430                 } while (scan_done == 0);
431                 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
432                 stripallbut(SendMsg->mailfrom, '<', '>');
433                 SendMsg->envelope_from = SendMsg->mailfrom;
434         }
435
436         return 0;
437 }
438
439
440
441 void smtp_try(OneQueItem *MyQItem, 
442               MailQEntry *MyQEntry, 
443               StrBuf *MsgText, 
444               int KeepMsgText,  /* KeepMsgText allows us to use MsgText as ours. */
445               int MsgCount)
446 {
447         SmtpOutMsg * SendMsg;
448
449         SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
450         memset(SendMsg, 0, sizeof(SmtpOutMsg));
451         SendMsg->IO.sock = (-1);
452         SendMsg->n = MsgCount++;
453         SendMsg->MyQEntry = MyQEntry;
454         SendMsg->MyQItem = MyQItem;
455         SendMsg->IO.Data = SendMsg;
456         if (KeepMsgText)
457                 SendMsg->msgtext = MsgText;
458         else 
459                 SendMsg->msgtext = NewStrBufDup(MsgText);
460
461         smtp_resolve_recipients(SendMsg);
462
463         QueueEventContext(SendMsg, 
464                           &SendMsg->IO,
465                           resolve_mx_records);
466 }
467
468
469
470
471
472 /*****************************************************************************/
473 /*                     SMTP CLIENT STATE CALLBACKS                           */
474 /*****************************************************************************/
475 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
476 {
477         /* Process the SMTP greeting from the server */
478         SMTP_DBG_READ();
479
480         if (!SMTP_IS_STATE('2')) {
481                 if (SMTP_IS_STATE('4')) 
482                         SMTP_VERROR(4);
483                 else 
484                         SMTP_VERROR(5);
485         }
486         return eSendReply;
487 }
488
489 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
490 {
491         /* At this point we know we are talking to a real SMTP server */
492
493         /* Do a EHLO command.  If it fails, try the HELO command. */
494         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
495                      "EHLO %s\r\n", config.c_fqdn);
496
497         SMTP_DBG_SEND();
498         return eReadMessage;
499 }
500
501 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
502 {
503         SMTP_DBG_READ();
504
505         if (SMTP_IS_STATE('2')) {
506                 SendMsg->State ++;
507                 if (IsEmptyStr(SendMsg->mx_user))
508                         SendMsg->State ++; /* Skip auth... */
509         }
510         /* else we fall back to 'helo' */
511         return eSendReply;
512 }
513
514 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
515 {
516         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
517                      "HELO %s\r\n", config.c_fqdn);
518
519         SMTP_DBG_SEND();
520         return eReadMessage;
521 }
522
523 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
524 {
525         SMTP_DBG_READ();
526
527         if (!SMTP_IS_STATE('2')) {
528                 if (SMTP_IS_STATE('4'))
529                         SMTP_VERROR(4);
530                 else 
531                         SMTP_VERROR(5);
532         }
533         if (!IsEmptyStr(SendMsg->mx_user))
534                 SendMsg->State ++; /* Skip auth... */
535         return eSendReply;
536 }
537
538 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
539 {
540         char buf[SIZ];
541         char encoded[1024];
542
543         /* Do an AUTH command if necessary */
544         sprintf(buf, "%s%c%s%c%s", 
545                 SendMsg->mx_user, '\0', 
546                 SendMsg->mx_user, '\0', 
547                 SendMsg->mx_pass);
548         CtdlEncodeBase64(encoded, buf, 
549                          strlen(SendMsg->mx_user) + 
550                          strlen(SendMsg->mx_user) + 
551                          strlen(SendMsg->mx_pass) + 2, 0);
552         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
553                      "AUTH PLAIN %s\r\n", encoded);
554         
555         SMTP_DBG_SEND();
556         return eReadMessage;
557 }
558
559 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
560 {
561         /* Do an AUTH command if necessary */
562         
563         SMTP_DBG_READ();
564         
565         if (!SMTP_IS_STATE('2')) {
566                 if (SMTP_IS_STATE('4'))
567                         SMTP_VERROR(4);
568                 else 
569                         SMTP_VERROR(5);
570         }
571         return eSendReply;
572 }
573
574 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
575 {
576         /* previous command succeeded, now try the MAIL FROM: command */
577         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
578                      "MAIL FROM:<%s>\r\n", 
579                      SendMsg->envelope_from);
580
581         SMTP_DBG_SEND();
582         return eReadMessage;
583 }
584
585 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
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
599 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
600 {
601         /* MAIL succeeded, now try the RCPT To: command */
602         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
603                      "RCPT TO:<%s@%s>\r\n", 
604                      SendMsg->user, 
605                      SendMsg->node);
606
607         SMTP_DBG_SEND();
608         return eReadMessage;
609 }
610
611 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
612 {
613         SMTP_DBG_READ();
614
615         if (!SMTP_IS_STATE('2')) {
616                 if (SMTP_IS_STATE('4')) 
617                         SMTP_VERROR(4);
618                 else 
619                         SMTP_VERROR(5);
620         }
621         return eSendReply;
622 }
623
624 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
625 {
626         /* RCPT succeeded, now try the DATA command */
627         StrBufPlain(SendMsg->IO.SendBuf.Buf,
628                     HKEY("DATA\r\n"));
629
630         SMTP_DBG_SEND();
631         return eReadMessage;
632 }
633
634 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
635 {
636         SMTP_DBG_READ();
637
638         if (!SMTP_IS_STATE('3')) {
639                 if (SMTP_IS_STATE('4')) 
640                         SMTP_VERROR(3);
641                 else 
642                         SMTP_VERROR(5);
643         }
644         return eSendReply;
645 }
646
647 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
648 {
649         StrBuf *Buf;
650         /* If we reach this point, the server is expecting data.*/
651
652         Buf = SendMsg->IO.SendBuf.Buf;
653         SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
654         SendMsg->msgtext = Buf;
655         //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
656         SendMsg->State ++;
657
658         return eSendMore;
659 }
660
661 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
662 {
663         StrBuf *Buf;
664
665         Buf = SendMsg->IO.SendBuf.Buf;
666         SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
667         SendMsg->msgtext = Buf;
668
669         StrBufPlain(SendMsg->IO.SendBuf.Buf,
670                     HKEY(".\r\n"));
671
672         return eReadMessage;
673
674 }
675
676 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
677 {
678         SMTP_DBG_READ();
679
680         if (!SMTP_IS_STATE('2')) {
681                 if (SMTP_IS_STATE('4'))
682                         SMTP_VERROR(4);
683                 else 
684                         SMTP_VERROR(5);
685         }
686
687         /* We did it! */
688         StrBufPlain(SendMsg->MyQEntry->StatusMessage, 
689                     &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
690                     StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
691         SendMsg->MyQEntry->Status = 2;
692         return eSendReply;
693 }
694
695 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
696 {
697         StrBufPlain(SendMsg->IO.SendBuf.Buf,
698                     HKEY("QUIT\r\n"));
699
700         SMTP_DBG_SEND();
701         return eReadMessage;
702 }
703
704 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
705 {
706         SMTP_DBG_READ();
707
708         CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
709                       SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
710         return eTerminateConnection;
711 }
712
713 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
714 {
715         return eSendReply;
716 }
717
718 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
719 {
720         return eReadMessage;
721 }
722
723
724 /*****************************************************************************/
725 /*                     SMTP CLIENT DISPATCHER                                */
726 /*****************************************************************************/
727 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
728         SMTPC_read_greeting,
729         SMTPC_read_EHLO_reply,
730         SMTPC_read_HELO_reply,
731         SMTPC_read_auth_reply,
732         SMTPC_read_FROM_reply,
733         SMTPC_read_RCPT_reply,
734         SMTPC_read_DATAcmd_reply,
735         SMTPC_read_dummy,
736         SMTPC_read_data_body_reply,
737         SMTPC_read_QUIT_reply
738 };
739 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
740         SMTPC_send_dummy, /* we don't send a greeting, the server does... */
741         SMTPC_send_EHLO,
742         STMPC_send_HELO,
743         SMTPC_send_auth,
744         SMTPC_send_FROM,
745         SMTPC_send_RCPT,
746         SMTPC_send_DATAcmd,
747         SMTPC_send_data_body,
748         SMTPC_send_terminate_data_body,
749         SMTPC_send_QUIT
750 };
751 eNextState SMTP_C_DispatchReadDone(void *Data)
752 {
753         SmtpOutMsg *pMsg = Data;
754         eNextState rc = ReadHandlers[pMsg->State](pMsg);
755         pMsg->State++;
756         return rc;
757 }
758 eNextState SMTP_C_DispatchWriteDone(void *Data)
759 {
760         SmtpOutMsg *pMsg = Data;
761         return SendHandlers[pMsg->State](pMsg); 
762 }
763
764
765 /*****************************************************************************/
766 /*                     SMTP CLIENT ERROR CATCHERS                            */
767 /*****************************************************************************/
768 eNextState SMTP_C_Terminate(void *Data)
769 {
770         SmtpOutMsg *pMsg = Data;
771         FinalizeMessageSend(pMsg);
772         return 0;
773 }
774 eNextState SMTP_C_Timeout(void *Data)
775 {
776         SmtpOutMsg *pMsg = Data;
777         FinalizeMessageSend(pMsg);
778         return 0;
779 }
780 eNextState SMTP_C_ConnFail(void *Data)
781 {
782         SmtpOutMsg *pMsg = Data;
783         FinalizeMessageSend(pMsg);
784         return 0;
785 }
786
787
788 /**
789  * @brief lineread Handler; understands when to read more SMTP lines, and when this is a one-lined reply.
790  */
791 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
792 {
793         eReadState Finished = eBufferNotEmpty; 
794
795         while (Finished == eBufferNotEmpty) {
796                 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
797                 
798                 switch (Finished) {
799                 case eMustReadMore: /// read new from socket... 
800                         return Finished;
801                         break;
802                 case eBufferNotEmpty: /* shouldn't happen... */
803                 case eReadSuccess: /// done for now...
804                         if (StrLength(IO->IOBuf) < 4)
805                                 continue;
806                         if (ChrPtr(IO->IOBuf)[3] == '-')
807                                 Finished = eBufferNotEmpty;
808                         else 
809                                 return Finished;
810                         break;
811                 case eReadFail: /// WHUT?
812                         ///todo: shut down! 
813                         break;
814                 }
815         }
816         return Finished;
817 }
818
819
820 #endif
821 CTDL_MODULE_INIT(smtp_eventclient)
822 {
823         return "smtpeventclient";
824 }