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