Libev Migration; fix aborting on connection establishing problems
[citadel.git] / citadel / modules / smtp / serv_smtpeventclient.c
1 /*
2  * This module is an SMTP and ESMTP implementation for the Citadel system.
3  * It is compliant with all of the following:
4  *
5  * RFC  821 - Simple Mail Transfer Protocol
6  * RFC  876 - Survey of SMTP Implementations
7  * RFC 1047 - Duplicate messages and SMTP
8  * RFC 1652 - 8 bit MIME
9  * RFC 1869 - Extended Simple Mail Transfer Protocol
10  * RFC 1870 - SMTP Service Extension for Message Size Declaration
11  * RFC 2033 - Local Mail Transfer Protocol
12  * RFC 2197 - SMTP Service Extension for Command Pipelining
13  * RFC 2476 - Message Submission
14  * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
15  * RFC 2554 - SMTP Service Extension for Authentication
16  * RFC 2821 - Simple Mail Transfer Protocol
17  * RFC 2822 - Internet Message Format
18  * RFC 2920 - SMTP Service Extension for Command Pipelining
19  *  
20  * The VRFY and EXPN commands have been removed from this implementation
21  * because nobody uses these commands anymore, except for spammers.
22  *
23  * Copyright (c) 1998-2009 by the citadel.org team
24  *
25  *  This program is free software; you can redistribute it and/or modify
26  *  it under the terms of the GNU General Public License as published by
27  *  the Free Software Foundation; either version 3 of the License, or
28  *  (at your option) any later version.
29  *
30  *  This program is distributed in the hope that it will be useful,
31  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
32  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
33  *  GNU General Public License for more details.
34  *
35  *  You should have received a copy of the GNU General Public License
36  *  along with this program; if not, write to the Free Software
37  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
38  */
39
40 #include "sysdep.h"
41 #include <stdlib.h>
42 #include <unistd.h>
43 #include <stdio.h>
44 #include <termios.h>
45 #include <fcntl.h>
46 #include <signal.h>
47 #include <pwd.h>
48 #include <errno.h>
49 #include <sys/types.h>
50 #include <syslog.h>
51
52 #if TIME_WITH_SYS_TIME
53 # include <sys/time.h>
54 # include <time.h>
55 #else
56 # if HAVE_SYS_TIME_H
57 #  include <sys/time.h>
58 # else
59 #  include <time.h>
60 # endif
61 #endif
62 #include <sys/wait.h>
63 #include <ctype.h>
64 #include <string.h>
65 #include <limits.h>
66 #include <sys/socket.h>
67 #include <netinet/in.h>
68 #include <arpa/inet.h>
69 #include <libcitadel.h>
70 #include "citadel.h"
71 #include "server.h"
72 #include "citserver.h"
73 #include "support.h"
74 #include "config.h"
75 #include "control.h"
76 #include "user_ops.h"
77 #include "database.h"
78 #include "msgbase.h"
79 #include "internet_addressing.h"
80 #include "genstamp.h"
81 #include "domain.h"
82 #include "clientsocket.h"
83 #include "locate_host.h"
84 #include "citadel_dirs.h"
85
86 #include "ctdl_module.h"
87
88 #include "smtp_util.h"
89 #include "event_client.h"
90 #include "smtpqueue.h"
91
92 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
93 /*****************************************************************************/
94 /*               SMTP CLIENT (OUTBOUND PROCESSING) STUFF                     */
95 /*****************************************************************************/
96
97 typedef enum _eSMTP_C_States {
98         eConnect, 
99         eEHLO,
100         eHELO,
101         eSMTPAuth,
102         eFROM,
103         eRCPT,
104         eDATA,
105         eDATABody,
106         eDATATerminateBody,
107         eQUIT,
108         eMaxSMTPC
109 } eSMTP_C_States;
110
111 const long SMTP_C_ConnTimeout = 60; /* wail 1 minute for connections... */
112 const long SMTP_C_ReadTimeouts[eMaxSMTPC] = {
113         90, /* Greeting... */
114         30, /* EHLO */
115         30, /* HELO */
116         30, /* Auth */
117         30, /* From */
118         30, /* RCPT */
119         30, /* DATA */
120         90, /* DATABody */
121         900, /* end of body... */
122         30  /* QUIT */
123 };
124 /*
125 const long SMTP_C_SendTimeouts[eMaxSMTPC] = {
126
127 }; */
128 const char *ReadErrors[eMaxSMTPC] = {
129         "Connection broken during SMTP conversation",
130         "Connection broken during SMTP EHLO",
131         "Connection broken during SMTP HELO",
132         "Connection broken during SMTP AUTH",
133         "Connection broken during SMTP MAIL FROM",
134         "Connection broken during SMTP RCPT",
135         "Connection broken during SMTP DATA",
136         "Connection broken during SMTP message transmit",
137         ""/* quit reply, don't care. */
138 };
139
140
141 typedef struct _stmp_out_msg {
142         MailQEntry *MyQEntry;
143         OneQueItem *MyQItem;
144         long n;
145         AsyncIO IO;
146
147         eSMTP_C_States State;
148
149         struct ares_mx_reply *AllMX;
150         struct ares_mx_reply *CurrMX;
151         const char *mx_port;
152         const char *mx_host;
153
154         struct hostent *OneMX;
155
156         char mx_user[1024];
157         char mx_pass[1024];
158         StrBuf *msgtext;
159         char *envelope_from;
160         char user[1024];
161         char node[1024];
162         char name[1024];
163         char mailfrom[1024];
164 } SmtpOutMsg;
165
166 void DeleteSmtpOutMsg(void *v)
167 {
168         SmtpOutMsg *Msg = v;
169
170         ares_free_data(Msg->AllMX);
171         
172         FreeStrBuf(&Msg->msgtext);
173         FreeAsyncIOContents(&Msg->IO);
174         free(Msg);
175 }
176
177 eNextState SMTP_C_Timeout(void *Data);
178 eNextState SMTP_C_ConnFail(void *Data);
179 eNextState SMTP_C_DispatchReadDone(void *Data);
180 eNextState SMTP_C_DispatchWriteDone(void *Data);
181 eNextState SMTP_C_Terminate(void *Data);
182 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO);
183
184 typedef eNextState (*SMTPReadHandler)(SmtpOutMsg *Msg);
185 typedef eNextState (*SMTPSendHandler)(SmtpOutMsg *Msg);
186
187
188 #define SMTP_ERROR(WHICH_ERR, ERRSTR) do {\
189                 SendMsg->MyQEntry->Status = WHICH_ERR; \
190                 StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, HKEY(ERRSTR), 0); \
191                 return eAbort; } \
192         while (0)
193
194 #define SMTP_VERROR(WHICH_ERR) do {\
195                 SendMsg->MyQEntry->Status = WHICH_ERR; \
196                 StrBufPlain(SendMsg->MyQEntry->StatusMessage, \
197                             ChrPtr(SendMsg->IO.IOBuf) + 4, \
198                             StrLength(SendMsg->IO.IOBuf) - 4); \
199                 return eAbort; } \
200         while (0)
201
202 #define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(SendMsg->IO.IOBuf)[0] == WHICH_STATE)
203
204 #define SMTP_DBG_SEND() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: > %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
205 #define SMTP_DBG_READ() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: < %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
206
207
208 void FinalizeMessageSend(SmtpOutMsg *Msg)
209 {
210         if (DecreaseQReference(Msg->MyQItem)) 
211         {
212                 int nRemain;
213                 StrBuf *MsgData;
214
215                 nRemain = CountActiveQueueEntries(Msg->MyQItem);
216
217                 MsgData = SerializeQueueItem(Msg->MyQItem);
218                 /*
219                  * Uncompleted delivery instructions remain, so delete the old
220                  * instructions and replace with the updated ones.
221                  */
222                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, "");
223                 smtpq_do_bounce(Msg->MyQItem,
224                                Msg->msgtext); 
225                 if (nRemain > 0) {
226                         struct CtdlMessage *msg;
227                         msg = malloc(sizeof(struct CtdlMessage));
228                         memset(msg, 0, sizeof(struct CtdlMessage));
229                         msg->cm_magic = CTDLMESSAGE_MAGIC;
230                         msg->cm_anon_type = MES_NORMAL;
231                         msg->cm_format_type = FMT_RFC822;
232                         msg->cm_fields['M'] = SmashStrBuf(&MsgData);
233                         /* Generate 'bounce' messages */
234                         smtp_do_bounce(msg->cm_fields['M'],
235                                        Msg->msgtext); 
236
237                         CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
238                         CtdlFreeMessage(msg);
239                 }
240                 else 
241                         CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->MessageID, 1, "");
242
243                 RemoveQItem(Msg->MyQItem);
244         }
245         DeleteSmtpOutMsg(Msg);
246 }
247
248
249
250
251 void get_one_mx_host_ip_done(void *Ctx, 
252                              int status,
253                              int timeouts,
254                              struct hostent *hostent)
255 {
256         AsyncIO *IO = Ctx;
257         SmtpOutMsg *SendMsg = IO->Data;
258         if ((status == ARES_SUCCESS) && (hostent != NULL) ) {
259                 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         if ((SendMsg==NULL) || 
376             (SendMsg->MyQEntry == NULL) || 
377             (StrLength(SendMsg->MyQEntry->Recipient) == 0)) {
378                 return 0;
379         }
380
381         /* Parse out the host portion of the recipient address */
382         process_rfc822_addr(ChrPtr(SendMsg->MyQEntry->Recipient), 
383                             SendMsg->user, 
384                             SendMsg->node, 
385                             SendMsg->name);
386
387         CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
388                       SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
389         /* If no envelope_from is supplied, extract one from the message */
390         if ( (SendMsg->envelope_from == NULL) || 
391              (IsEmptyStr(SendMsg->envelope_from)) ) {
392                 SendMsg->mailfrom[0] = '\0';
393                 scan_done = 0;
394                 ptr = ChrPtr(SendMsg->msgtext);
395                 do {
396                         if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
397                                 scan_done = 1;
398                         }
399                         if (!strncasecmp(buf, "From:", 5)) {
400                                 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
401                                 striplt(SendMsg->mailfrom);
402                                 for (i=0; SendMsg->mailfrom[i]; ++i) {
403                                         if (!isprint(SendMsg->mailfrom[i])) {
404                                                 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
405                                                 i=0;
406                                         }
407                                 }
408         
409                                 /* Strip out parenthesized names */
410                                 lp = (-1);
411                                 rp = (-1);
412                                 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
413                                         if (SendMsg->mailfrom[i] == '(') lp = i;
414                                         if (SendMsg->mailfrom[i] == ')') rp = i;
415                                 }
416                                 if ((lp>0)&&(rp>lp)) {
417                                         strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
418                                 }
419         
420                                 /* Prefer brokketized names */
421                                 lp = (-1);
422                                 rp = (-1);
423                                 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
424                                         if (SendMsg->mailfrom[i] == '<') lp = i;
425                                         if (SendMsg->mailfrom[i] == '>') rp = i;
426                                 }
427                                 if ( (lp>=0) && (rp>lp) ) {
428                                         SendMsg->mailfrom[rp] = 0;
429                                         memmove(SendMsg->mailfrom, 
430                                                 &SendMsg->mailfrom[lp + 1], 
431                                                 rp - lp);
432                                 }
433         
434                                 scan_done = 1;
435                         }
436                 } while (scan_done == 0);
437                 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
438                 stripallbut(SendMsg->mailfrom, '<', '>');
439                 SendMsg->envelope_from = SendMsg->mailfrom;
440         }
441
442         return 1;
443 }
444
445
446
447 void smtp_try(OneQueItem *MyQItem, 
448               MailQEntry *MyQEntry, 
449               StrBuf *MsgText, 
450               int KeepMsgText,  /* KeepMsgText allows us to use MsgText as ours. */
451               int MsgCount)
452 {
453         SmtpOutMsg * SendMsg;
454
455         SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
456         memset(SendMsg, 0, sizeof(SmtpOutMsg));
457         SendMsg->IO.sock = (-1);
458         SendMsg->n = MsgCount++;
459         SendMsg->MyQEntry = MyQEntry;
460         SendMsg->MyQItem = MyQItem;
461         SendMsg->IO.Data = SendMsg;
462         if (KeepMsgText)
463                 SendMsg->msgtext = MsgText;
464         else 
465                 SendMsg->msgtext = NewStrBufDup(MsgText);
466
467         if (smtp_resolve_recipients(SendMsg)) {
468                 QueueEventContext(SendMsg, 
469                                   &SendMsg->IO,
470                                   resolve_mx_records);
471         }
472         else {
473                 if ((SendMsg==NULL) || 
474                     (SendMsg->MyQEntry == NULL)) {
475                         SendMsg->MyQEntry->Status = 5;
476                         StrBufPlain(SendMsg->MyQEntry->StatusMessage, 
477                                     HKEY("Invalid Recipient!"));
478                 }
479                 FinalizeMessageSend(SendMsg);
480         }
481 }
482
483
484
485
486
487 /*****************************************************************************/
488 /*                     SMTP CLIENT STATE CALLBACKS                           */
489 /*****************************************************************************/
490 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
491 {
492         /* Process the SMTP greeting from the server */
493         SMTP_DBG_READ();
494
495         if (!SMTP_IS_STATE('2')) {
496                 if (SMTP_IS_STATE('4')) 
497                         SMTP_VERROR(4);
498                 else 
499                         SMTP_VERROR(5);
500         }
501         return eSendReply;
502 }
503
504 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
505 {
506         /* At this point we know we are talking to a real SMTP server */
507
508         /* Do a EHLO command.  If it fails, try the HELO command. */
509         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
510                      "EHLO %s\r\n", config.c_fqdn);
511
512         SMTP_DBG_SEND();
513         return eReadMessage;
514 }
515
516 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
517 {
518         SMTP_DBG_READ();
519
520         if (SMTP_IS_STATE('2')) {
521                 SendMsg->State ++;
522                 if (IsEmptyStr(SendMsg->mx_user))
523                         SendMsg->State ++; /* Skip auth... */
524         }
525         /* else we fall back to 'helo' */
526         return eSendReply;
527 }
528
529 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
530 {
531         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
532                      "HELO %s\r\n", config.c_fqdn);
533
534         SMTP_DBG_SEND();
535         return eReadMessage;
536 }
537
538 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
539 {
540         SMTP_DBG_READ();
541
542         if (!SMTP_IS_STATE('2')) {
543                 if (SMTP_IS_STATE('4'))
544                         SMTP_VERROR(4);
545                 else 
546                         SMTP_VERROR(5);
547         }
548         if (!IsEmptyStr(SendMsg->mx_user))
549                 SendMsg->State ++; /* Skip auth... */
550         return eSendReply;
551 }
552
553 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
554 {
555         char buf[SIZ];
556         char encoded[1024];
557
558         /* Do an AUTH command if necessary */
559         sprintf(buf, "%s%c%s%c%s", 
560                 SendMsg->mx_user, '\0', 
561                 SendMsg->mx_user, '\0', 
562                 SendMsg->mx_pass);
563         CtdlEncodeBase64(encoded, buf, 
564                          strlen(SendMsg->mx_user) + 
565                          strlen(SendMsg->mx_user) + 
566                          strlen(SendMsg->mx_pass) + 2, 0);
567         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
568                      "AUTH PLAIN %s\r\n", encoded);
569         
570         SMTP_DBG_SEND();
571         return eReadMessage;
572 }
573
574 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
575 {
576         /* Do an AUTH command if necessary */
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         return eSendReply;
587 }
588
589 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
590 {
591         /* previous command succeeded, now try the MAIL FROM: command */
592         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
593                      "MAIL FROM:<%s>\r\n", 
594                      SendMsg->envelope_from);
595
596         SMTP_DBG_SEND();
597         return eReadMessage;
598 }
599
600 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
601 {
602         SMTP_DBG_READ();
603
604         if (!SMTP_IS_STATE('2')) {
605                 if (SMTP_IS_STATE('4'))
606                         SMTP_VERROR(4);
607                 else 
608                         SMTP_VERROR(5);
609         }
610         return eSendReply;
611 }
612
613
614 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
615 {
616         /* MAIL succeeded, now try the RCPT To: command */
617         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
618                      "RCPT TO:<%s@%s>\r\n", 
619                      SendMsg->user, 
620                      SendMsg->node);
621
622         SMTP_DBG_SEND();
623         return eReadMessage;
624 }
625
626 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
627 {
628         SMTP_DBG_READ();
629
630         if (!SMTP_IS_STATE('2')) {
631                 if (SMTP_IS_STATE('4')) 
632                         SMTP_VERROR(4);
633                 else 
634                         SMTP_VERROR(5);
635         }
636         return eSendReply;
637 }
638
639 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
640 {
641         /* RCPT succeeded, now try the DATA command */
642         StrBufPlain(SendMsg->IO.SendBuf.Buf,
643                     HKEY("DATA\r\n"));
644
645         SMTP_DBG_SEND();
646         return eReadMessage;
647 }
648
649 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
650 {
651         SMTP_DBG_READ();
652
653         if (!SMTP_IS_STATE('3')) {
654                 if (SMTP_IS_STATE('4')) 
655                         SMTP_VERROR(3);
656                 else 
657                         SMTP_VERROR(5);
658         }
659         return eSendReply;
660 }
661
662 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
663 {
664         StrBuf *Buf;
665         /* If we reach this point, the server is expecting data.*/
666
667         Buf = SendMsg->IO.SendBuf.Buf;
668         SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
669         SendMsg->msgtext = Buf;
670         //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
671         SendMsg->State ++;
672
673         return eSendMore;
674 }
675
676 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
677 {
678         StrBuf *Buf;
679
680         Buf = SendMsg->IO.SendBuf.Buf;
681         SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
682         SendMsg->msgtext = Buf;
683
684         StrBufPlain(SendMsg->IO.SendBuf.Buf,
685                     HKEY(".\r\n"));
686
687         return eReadMessage;
688
689 }
690
691 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
692 {
693         SMTP_DBG_READ();
694
695         if (!SMTP_IS_STATE('2')) {
696                 if (SMTP_IS_STATE('4'))
697                         SMTP_VERROR(4);
698                 else 
699                         SMTP_VERROR(5);
700         }
701
702         /* We did it! */
703         StrBufPlain(SendMsg->MyQEntry->StatusMessage, 
704                     &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
705                     StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
706         SendMsg->MyQEntry->Status = 2;
707         return eSendReply;
708 }
709
710 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
711 {
712         StrBufPlain(SendMsg->IO.SendBuf.Buf,
713                     HKEY("QUIT\r\n"));
714
715         SMTP_DBG_SEND();
716         return eReadMessage;
717 }
718
719 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
720 {
721         SMTP_DBG_READ();
722
723         CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
724                       SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
725         return eTerminateConnection;
726 }
727
728 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
729 {
730         return eSendReply;
731 }
732
733 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
734 {
735         return eReadMessage;
736 }
737
738
739 /*****************************************************************************/
740 /*                     SMTP CLIENT DISPATCHER                                */
741 /*****************************************************************************/
742 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
743         SMTPC_read_greeting,
744         SMTPC_read_EHLO_reply,
745         SMTPC_read_HELO_reply,
746         SMTPC_read_auth_reply,
747         SMTPC_read_FROM_reply,
748         SMTPC_read_RCPT_reply,
749         SMTPC_read_DATAcmd_reply,
750         SMTPC_read_dummy,
751         SMTPC_read_data_body_reply,
752         SMTPC_read_QUIT_reply
753 };
754 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
755         SMTPC_send_dummy, /* we don't send a greeting, the server does... */
756         SMTPC_send_EHLO,
757         STMPC_send_HELO,
758         SMTPC_send_auth,
759         SMTPC_send_FROM,
760         SMTPC_send_RCPT,
761         SMTPC_send_DATAcmd,
762         SMTPC_send_data_body,
763         SMTPC_send_terminate_data_body,
764         SMTPC_send_QUIT
765 };
766 eNextState SMTP_C_DispatchReadDone(void *Data)
767 {
768         SmtpOutMsg *pMsg = Data;
769         eNextState rc = ReadHandlers[pMsg->State](pMsg);
770         pMsg->State++;
771         return rc;
772 }
773 eNextState SMTP_C_DispatchWriteDone(void *Data)
774 {
775         SmtpOutMsg *pMsg = Data;
776         return SendHandlers[pMsg->State](pMsg); 
777 }
778
779
780 /*****************************************************************************/
781 /*                     SMTP CLIENT ERROR CATCHERS                            */
782 /*****************************************************************************/
783 eNextState SMTP_C_Terminate(void *Data)
784 {
785         AsyncIO *IO = Data;
786         SmtpOutMsg *pMsg = IO->Data;
787         FinalizeMessageSend(pMsg);
788         return 0;
789 }
790 eNextState SMTP_C_Timeout(void *Data)
791 {
792         AsyncIO *IO = Data;
793         SmtpOutMsg *pMsg = IO->Data;
794         FinalizeMessageSend(pMsg);
795         return 0;
796 }
797 eNextState SMTP_C_ConnFail(void *Data)
798 {
799         AsyncIO *IO = Data;
800         SmtpOutMsg *pMsg = IO->Data;
801         FinalizeMessageSend(pMsg);
802         return 0;
803 }
804
805
806 /**
807  * @brief lineread Handler; understands when to read more SMTP lines, and when this is a one-lined reply.
808  */
809 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
810 {
811         eReadState Finished = eBufferNotEmpty; 
812
813         while (Finished == eBufferNotEmpty) {
814                 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
815                 
816                 switch (Finished) {
817                 case eMustReadMore: /// read new from socket... 
818                         return Finished;
819                         break;
820                 case eBufferNotEmpty: /* shouldn't happen... */
821                 case eReadSuccess: /// done for now...
822                         if (StrLength(IO->IOBuf) < 4)
823                                 continue;
824                         if (ChrPtr(IO->IOBuf)[3] == '-')
825                                 Finished = eBufferNotEmpty;
826                         else 
827                                 return Finished;
828                         break;
829                 case eReadFail: /// WHUT?
830                         ///todo: shut down! 
831                         break;
832                 }
833         }
834         return Finished;
835 }
836
837
838 #endif
839 CTDL_MODULE_INIT(smtp_eventclient)
840 {
841         return "smtpeventclient";
842 }