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