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