libev migration
[citadel.git] / citadel / modules / smtp / serv_smtpeventclient.c
1 /*
2  * This module is an SMTP and ESMTP implementation for the Citadel system.
3  * It is compliant with all of the following:
4  *
5  * RFC  821 - Simple Mail Transfer Protocol
6  * RFC  876 - Survey of SMTP Implementations
7  * RFC 1047 - Duplicate messages and SMTP
8  * RFC 1652 - 8 bit MIME
9  * RFC 1869 - Extended Simple Mail Transfer Protocol
10  * RFC 1870 - SMTP Service Extension for Message Size Declaration
11  * RFC 2033 - Local Mail Transfer Protocol
12  * RFC 2197 - SMTP Service Extension for Command Pipelining
13  * RFC 2476 - Message Submission
14  * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
15  * RFC 2554 - SMTP Service Extension for Authentication
16  * RFC 2821 - Simple Mail Transfer Protocol
17  * RFC 2822 - Internet Message Format
18  * RFC 2920 - SMTP Service Extension for Command Pipelining
19  *  
20  * The VRFY and EXPN commands have been removed from this implementation
21  * because nobody uses these commands anymore, except for spammers.
22  *
23  * Copyright (c) 1998-2009 by the citadel.org team
24  *
25  *  This program is free software; you can redistribute it and/or modify
26  *  it under the terms of the GNU General Public License as published by
27  *  the Free Software Foundation; either version 3 of the License, or
28  *  (at your option) any later version.
29  *
30  *  This program is distributed in the hope that it will be useful,
31  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
32  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
33  *  GNU General Public License for more details.
34  *
35  *  You should have received a copy of the GNU General Public License
36  *  along with this program; if not, write to the Free Software
37  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
38  */
39
40 #include "sysdep.h"
41 #include <stdlib.h>
42 #include <unistd.h>
43 #include <stdio.h>
44 #include <termios.h>
45 #include <fcntl.h>
46 #include <signal.h>
47 #include <pwd.h>
48 #include <errno.h>
49 #include <sys/types.h>
50 #include <syslog.h>
51
52 #if TIME_WITH_SYS_TIME
53 # include <sys/time.h>
54 # include <time.h>
55 #else
56 # if HAVE_SYS_TIME_H
57 #  include <sys/time.h>
58 # else
59 #  include <time.h>
60 # endif
61 #endif
62 #include <sys/wait.h>
63 #include <ctype.h>
64 #include <string.h>
65 #include <limits.h>
66 #include <sys/socket.h>
67 #include <netinet/in.h>
68 #include <arpa/inet.h>
69 #include <libcitadel.h>
70 #include "citadel.h"
71 #include "server.h"
72 #include "citserver.h"
73 #include "support.h"
74 #include "config.h"
75 #include "control.h"
76 #include "user_ops.h"
77 #include "database.h"
78 #include "msgbase.h"
79 #include "internet_addressing.h"
80 #include "genstamp.h"
81 #include "domain.h"
82 #include "clientsocket.h"
83 #include "locate_host.h"
84 #include "citadel_dirs.h"
85
86 #include "ctdl_module.h"
87
88 #include "smtp_util.h"
89 #include "event_client.h"
90
91 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
92 HashList *QItemHandlers = NULL;
93
94 citthread_mutex_t ActiveQItemsLock;
95 HashList *ActiveQItems = NULL;
96
97 int run_queue_now = 0;  /* Set to 1 to ignore SMTP send retry times */
98 int MsgCount = 0;
99 /*****************************************************************************/
100 /*               SMTP CLIENT (Queue Management) STUFF                        */
101 /*****************************************************************************/
102
103 #define MaxAttempts 15
104 typedef struct _delivery_attempt {
105         time_t when;
106         time_t retry;
107 }DeliveryAttempt;
108
109 typedef struct _mailq_entry {
110         DeliveryAttempt Attempts[MaxAttempts];
111         int nAttempts;
112         StrBuf *Recipient;
113         StrBuf *StatusMessage;
114         int Status;
115         int n;
116         int Active;
117 }MailQEntry;
118 void FreeMailQEntry(void *qv)
119 {
120         MailQEntry *Q = qv;
121         FreeStrBuf(&Q->Recipient);
122         FreeStrBuf(&Q->StatusMessage);
123         free(Q);
124 }
125
126 typedef struct queueitem {
127         long MessageID;
128         long QueMsgID;
129         int FailNow;
130         HashList *MailQEntries;
131         MailQEntry *Current; /* copy of the currently parsed item in the MailQEntries list; if null add a new one. */
132         DeliveryAttempt LastAttempt;
133         long ActiveDeliveries;
134         StrBuf *EnvelopeFrom;
135         StrBuf *BounceTo;
136 } OneQueItem;
137 typedef void (*QItemHandler)(OneQueItem *Item, StrBuf *Line, const char **Pos);
138
139 /*****************************************************************************/
140 /*               SMTP CLIENT (OUTBOUND PROCESSING) STUFF                     */
141 /*****************************************************************************/
142
143 typedef enum _eSMTP_C_States {
144         eConnect, 
145         eEHLO,
146         eHELO,
147         eSMTPAuth,
148         eFROM,
149         eRCPT,
150         eDATA,
151         eDATABody,
152         eDATATerminateBody,
153         eQUIT,
154         eMaxSMTPC
155 } eSMTP_C_States;
156
157 const long SMTP_C_ReadTimeouts[eMaxSMTPC] = {
158         90, /* Greeting... */
159         30, /* EHLO */
160         30, /* HELO */
161         30, /* Auth */
162         30, /* From */
163         30, /* RCPT */
164         30, /* DATA */
165         90, /* DATABody */
166         900, /* end of body... */
167         30  /* QUIT */
168 };
169 /*
170 const long SMTP_C_SendTimeouts[eMaxSMTPC] = {
171
172 }; */
173 const char *ReadErrors[eMaxSMTPC] = {
174         "Connection broken during SMTP conversation",
175         "Connection broken during SMTP EHLO",
176         "Connection broken during SMTP HELO",
177         "Connection broken during SMTP AUTH",
178         "Connection broken during SMTP MAIL FROM",
179         "Connection broken during SMTP RCPT",
180         "Connection broken during SMTP DATA",
181         "Connection broken during SMTP message transmit",
182         ""/* quit reply, don't care. */
183 };
184
185
186 typedef struct _stmp_out_msg {
187         MailQEntry *MyQEntry;
188         OneQueItem *MyQItem;
189         long n;
190         AsyncIO IO;
191
192         eSMTP_C_States State;
193
194         struct ares_mx_reply *AllMX;
195         struct ares_mx_reply *CurrMX;
196         const char *mx_port;
197         const char *mx_host;
198
199         struct hostent *OneMX;
200
201         char mx_user[1024];
202         char mx_pass[1024];
203         StrBuf *msgtext;
204         char *envelope_from;
205         char user[1024];
206         char node[1024];
207         char name[1024];
208         char mailfrom[1024];
209 } SmtpOutMsg;
210 void DeleteSmtpOutMsg(void *v)
211 {
212         SmtpOutMsg *Msg = v;
213
214         ares_free_data(Msg->AllMX);
215         FreeStrBuf(&Msg->msgtext);
216         FreeAsyncIOContents(&Msg->IO);
217         free(Msg);
218 }
219
220 eNextState SMTP_C_Timeout(void *Data);
221 eNextState SMTP_C_ConnFail(void *Data);
222 eNextState SMTP_C_DispatchReadDone(void *Data);
223 eNextState SMTP_C_DispatchWriteDone(void *Data);
224 eNextState SMTP_C_Terminate(void *Data);
225 eNextState SMTP_C_MXLookup(void *Data);
226
227 typedef eNextState (*SMTPReadHandler)(SmtpOutMsg *Msg);
228 typedef eNextState (*SMTPSendHandler)(SmtpOutMsg *Msg);
229
230
231
232
233 void FreeQueItem(OneQueItem **Item)
234 {
235         DeleteHash(&(*Item)->MailQEntries);
236         FreeStrBuf(&(*Item)->EnvelopeFrom);
237         FreeStrBuf(&(*Item)->BounceTo);
238         free(*Item);
239         Item = NULL;
240 }
241 void HFreeQueItem(void *Item)
242 {
243         FreeQueItem((OneQueItem**)&Item);
244 }
245
246
247 /* inspect recipients with a status of: 
248  * - 0 (no delivery yet attempted) 
249  * - 3/4 (transient errors
250  *        were experienced and it's time to try again)
251  */
252 int CountActiveQueueEntries(OneQueItem *MyQItem)
253 {
254         HashPos  *It;
255         long len;
256         const char *Key;
257         void *vQE;
258
259         MyQItem->ActiveDeliveries = 0;
260         It = GetNewHashPos(MyQItem->MailQEntries, 0);
261         while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
262         {
263                 MailQEntry *ThisItem = vQE;
264                 if ((ThisItem->Status == 0) || 
265                     (ThisItem->Status == 3) ||
266                     (ThisItem->Status == 4))
267                 {
268                         MyQItem->ActiveDeliveries++;
269                         ThisItem->Active = 1;
270                 }
271                 else 
272                         ThisItem->Active = 0;
273         }
274         DeleteHashPos(&It);
275         return MyQItem->ActiveDeliveries;
276 }
277
278 OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
279 {
280         OneQueItem *Item;
281         const char *pLine = NULL;
282         StrBuf *Line;
283         StrBuf *Token;
284         void *v;
285
286         Item = (OneQueItem*)malloc(sizeof(OneQueItem));
287         memset(Item, 0, sizeof(OneQueItem));
288         Item->LastAttempt.retry = SMTP_RETRY_INTERVAL;
289         Item->MessageID = -1;
290         Item->QueMsgID = QueMsgID;
291
292         citthread_mutex_lock(&ActiveQItemsLock);
293         if (GetHash(ActiveQItems, 
294                     IKEY(Item->QueMsgID), 
295                     &v))
296         {
297                 /* WHOOPS. somebody else is already working on this. */
298                 citthread_mutex_unlock(&ActiveQItemsLock);
299                 FreeQueItem(&Item);
300                 return NULL;
301         }
302         else {
303                 /* mark our claim on this. */
304                 Put(ActiveQItems, 
305                     IKEY(Item->QueMsgID),
306                     Item,
307                     HFreeQueItem);
308                 citthread_mutex_unlock(&ActiveQItemsLock);
309         }
310
311         Token = NewStrBuf();
312         Line = NewStrBufPlain(NULL, 128);
313         while (pLine != StrBufNOTNULL) {
314                 const char *pItemPart = NULL;
315                 void *vHandler;
316
317                 StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
318                 if (StrLength(Line) == 0) continue;
319                 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
320                 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
321                 {
322                         QItemHandler H;
323                         H = (QItemHandler) vHandler;
324                         H(Item, Line, &pItemPart);
325                 }
326         }
327         FreeStrBuf(&Line);
328         FreeStrBuf(&Token);
329         return Item;
330 }
331
332 StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
333 {
334         StrBuf *QMessage;
335         HashPos  *It;
336         const char *Key;
337         long len;
338         void *vQE;
339
340         QMessage = NewStrBufPlain(NULL, SIZ);
341         StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
342
343 //                 "attempted|%ld\n"  "retry|%ld\n",, (long)time(NULL), (long)retry );
344         StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
345         StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
346
347         if (StrLength(MyQItem->BounceTo) > 0) {
348                 StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
349                 StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
350         }
351
352         if (StrLength(MyQItem->EnvelopeFrom) > 0) {
353                 StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
354                 StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
355         }
356
357         It = GetNewHashPos(MyQItem->MailQEntries, 0);
358         while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
359         {
360                 MailQEntry *ThisItem = vQE;
361                 int i;
362
363                 if (!ThisItem->Active)
364                         continue; /* skip already sent ones from the spoolfile. */
365
366                 for (i=0; i < ThisItem->nAttempts; i++) {
367                         StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
368                         StrBufAppendPrintf(QMessage, "%ld", 
369                                            ThisItem->Attempts[i].retry);
370
371                         StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
372                         StrBufAppendPrintf(QMessage, "%ld", 
373                                            ThisItem->Attempts[i].when);
374                 }
375                 StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
376                 StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
377                 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
378                 StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
379                 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
380                 StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
381         }
382         DeleteHashPos(&It);
383         StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);  
384         return QMessage;
385 }
386
387 void FinalizeMessageSend(SmtpOutMsg *Msg)
388 {
389         int IDestructQueItem;
390         HashPos  *It;
391
392         citthread_mutex_lock(&ActiveQItemsLock);
393         Msg->MyQItem->ActiveDeliveries--;
394         IDestructQueItem = Msg->MyQItem->ActiveDeliveries == 0;
395         citthread_mutex_unlock(&ActiveQItemsLock);
396
397         if (IDestructQueItem) {
398                 int nRemain;
399                 StrBuf *MsgData;
400
401                 nRemain = CountActiveQueueEntries(Msg->MyQItem);
402
403                 if (nRemain > 0) 
404                         MsgData = SerializeQueueItem(Msg->MyQItem);
405                 /*
406                  * Uncompleted delivery instructions remain, so delete the old
407                  * instructions and replace with the updated ones.
408                  */
409                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, "");
410
411         /* Generate 'bounce' messages * /
412            smtp_do_bounce(instr); */
413                 if (nRemain > 0) {
414                         struct CtdlMessage *msg;
415                         msg = malloc(sizeof(struct CtdlMessage));
416                         memset(msg, 0, sizeof(struct CtdlMessage));
417                         msg->cm_magic = CTDLMESSAGE_MAGIC;
418                         msg->cm_anon_type = MES_NORMAL;
419                         msg->cm_format_type = FMT_RFC822;
420                         msg->cm_fields['M'] = SmashStrBuf(&MsgData);
421
422                         CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
423                         CtdlFreeMessage(msg);
424                 }
425                 It = GetNewHashPos(Msg->MyQItem->MailQEntries, 0);
426                 citthread_mutex_lock(&ActiveQItemsLock);
427                 {
428                         GetHashPosFromKey(ActiveQItems, IKEY(Msg->MyQItem->MessageID), It);
429                         DeleteEntryFromHash(ActiveQItems, It);
430                 }
431                 citthread_mutex_unlock(&ActiveQItemsLock);
432                 DeleteHashPos(&It);
433         }
434         
435 /// TODO : else free message...
436         close(Msg->IO.sock);
437         DeleteSmtpOutMsg(Msg);
438 }
439
440 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
441 {
442         eReadState Finished = eBufferNotEmpty; 
443
444         while (Finished == eBufferNotEmpty) {
445                 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
446                 
447                 switch (Finished) {
448                 case eMustReadMore: /// read new from socket... 
449                         return Finished;
450                         break;
451                 case eBufferNotEmpty: /* shouldn't happen... */
452                 case eReadSuccess: /// done for now...
453                         if (StrLength(IO->IOBuf) < 4)
454                                 continue;
455                         if (ChrPtr(IO->IOBuf)[3] == '-')
456                                 Finished = eBufferNotEmpty;
457                         else 
458                                 return Finished;
459                         break;
460                 case eReadFail: /// WHUT?
461                         ///todo: shut down! 
462                         break;
463                 }
464         }
465         return Finished;
466 }
467
468 /**
469  * this one has to have the context for loading the message via the redirect buffer...
470  */
471 StrBuf *smtp_load_msg(OneQueItem *MyQItem)
472 {
473         CitContext *CCC=CC;
474         StrBuf *SendMsg;
475         
476         CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
477         CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
478         SendMsg = CCC->redirect_buffer;
479         CCC->redirect_buffer = NULL;
480         if ((StrLength(SendMsg) > 0) && 
481             ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
482                 CtdlLogPrintf(CTDL_WARNING, 
483                               "SMTP client[%ld]: Possible problem: message did not "
484                               "correctly terminate. (expecting 0x10, got 0x%02x)\n",
485                               MsgCount, //yes uncool, but best choice here... 
486                               ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
487                 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
488         }
489         return SendMsg;
490 }
491
492
493 int smtp_resolve_recipients(SmtpOutMsg *SendMsg)
494 {
495         const char *ptr;
496         char buf[1024];
497         int scan_done;
498         int lp, rp;
499         int i;
500
501         /* Parse out the host portion of the recipient address */
502         process_rfc822_addr(ChrPtr(SendMsg->MyQEntry->Recipient), 
503                             SendMsg->user, 
504                             SendMsg->node, 
505                             SendMsg->name);
506
507         CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
508                       SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
509         /* If no envelope_from is supplied, extract one from the message */
510         if ( (SendMsg->envelope_from == NULL) || 
511              (IsEmptyStr(SendMsg->envelope_from)) ) {
512                 SendMsg->mailfrom[0] = '\0';
513                 scan_done = 0;
514                 ptr = ChrPtr(SendMsg->msgtext);
515                 do {
516                         if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
517                                 scan_done = 1;
518                         }
519                         if (!strncasecmp(buf, "From:", 5)) {
520                                 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
521                                 striplt(SendMsg->mailfrom);
522                                 for (i=0; SendMsg->mailfrom[i]; ++i) {
523                                         if (!isprint(SendMsg->mailfrom[i])) {
524                                                 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
525                                                 i=0;
526                                         }
527                                 }
528         
529                                 /* Strip out parenthesized names */
530                                 lp = (-1);
531                                 rp = (-1);
532                                 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
533                                         if (SendMsg->mailfrom[i] == '(') lp = i;
534                                         if (SendMsg->mailfrom[i] == ')') rp = i;
535                                 }
536                                 if ((lp>0)&&(rp>lp)) {
537                                         strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
538                                 }
539         
540                                 /* Prefer brokketized names */
541                                 lp = (-1);
542                                 rp = (-1);
543                                 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
544                                         if (SendMsg->mailfrom[i] == '<') lp = i;
545                                         if (SendMsg->mailfrom[i] == '>') rp = i;
546                                 }
547                                 if ( (lp>=0) && (rp>lp) ) {
548                                         SendMsg->mailfrom[rp] = 0;
549                                         memmove(SendMsg->mailfrom, 
550                                                 &SendMsg->mailfrom[lp + 1], 
551                                                 rp - lp);
552                                 }
553         
554                                 scan_done = 1;
555                         }
556                 } while (scan_done == 0);
557                 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
558                 stripallbut(SendMsg->mailfrom, '<', '>');
559                 SendMsg->envelope_from = SendMsg->mailfrom;
560         }
561
562         return 0;
563 }
564
565
566 #define SMTP_ERROR(WHICH_ERR, ERRSTR) {SendMsg->MyQEntry->Status = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, HKEY(ERRSTR), 0); return eAbort; }
567 #define SMTP_VERROR(WHICH_ERR) { SendMsg->MyQEntry->Status = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, &ChrPtr(SendMsg->IO.IOBuf)[4], -1, 0); return eAbort; }
568 #define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(SendMsg->IO.IOBuf)[0] == WHICH_STATE)
569
570 #define SMTP_DBG_SEND() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: > %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
571 #define SMTP_DBG_READ() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: < %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
572
573 void get_one_mx_host_name_done(void *Ctx, 
574                                int status,
575                                int timeouts,
576                                struct hostent *hostent)
577 {
578         AsyncIO *IO = Ctx;
579         SmtpOutMsg *SendMsg = IO->Data;
580         if ((status == ARES_SUCCESS) && (hostent != NULL) ) {
581                 CtdlLogPrintf(CTDL_DEBUG, 
582                               "SMTP client[%ld]: connecting to %s : %d ...\n", 
583                               SendMsg->n, 
584                               SendMsg->mx_host, 
585                               SendMsg->IO.dport);
586
587
588                 SendMsg->IO.HEnt = hostent;
589                 InitEventIO(IO, SendMsg, 
590                             SMTP_C_DispatchReadDone, 
591                             SMTP_C_DispatchWriteDone, 
592                             SMTP_C_Terminate,
593                             SMTP_C_Timeout,
594                             SMTP_C_ConnFail,
595                             SMTP_C_ReadServerStatus,
596                             1);
597
598         }
599 }
600
601 const unsigned short DefaultMXPort = 25;
602 void connect_one_smtpsrv(SmtpOutMsg *SendMsg)
603 {
604         //char *endpart;
605         //char buf[SIZ];
606
607         SendMsg->IO.dport = DefaultMXPort;
608
609
610 /* TODO: Relay!
611         *SendMsg->mx_user =  '\0';
612         *SendMsg->mx_pass = '\0';
613         if (num_tokens(buf, '@') > 1) {
614                 strcpy (SendMsg->mx_user, buf);
615                 endpart = strrchr(SendMsg->mx_user, '@');
616                 *endpart = '\0';
617                 strcpy (SendMsg->mx_host, endpart + 1);
618                 endpart = strrchr(SendMsg->mx_user, ':');
619                 if (endpart != NULL) {
620                         strcpy(SendMsg->mx_pass, endpart+1);
621                         *endpart = '\0';
622                 }
623
624         endpart = strrchr(SendMsg->mx_host, ':');
625         if (endpart != 0){
626                 *endpart = '\0';
627                 strcpy(SendMsg->mx_port, endpart + 1);
628         }               
629         }
630         else
631 */
632         SendMsg->mx_host = SendMsg->CurrMX->host;
633         SendMsg->CurrMX = SendMsg->CurrMX->next;
634
635         CtdlLogPrintf(CTDL_DEBUG, 
636                       "SMTP client[%ld]: looking up %s : %d ...\n", 
637                       SendMsg->n, 
638                       SendMsg->mx_host);
639
640         ares_gethostbyname(SendMsg->IO.DNSChannel,
641                            SendMsg->mx_host,   
642                            AF_INET6, /* it falls back to ipv4 in doubt... */
643                            get_one_mx_host_name_done,
644                            &SendMsg->IO);
645 }
646
647
648 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
649 {
650         /* Process the SMTP greeting from the server */
651         SMTP_DBG_READ();
652
653         if (!SMTP_IS_STATE('2')) {
654                 if (SMTP_IS_STATE('4')) 
655                         SMTP_VERROR(4)
656                 else 
657                         SMTP_VERROR(5)
658         }
659         return eSendReply;
660 }
661
662 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
663 {
664         /* At this point we know we are talking to a real SMTP server */
665
666         /* Do a EHLO command.  If it fails, try the HELO command. */
667         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
668                      "EHLO %s\r\n", config.c_fqdn);
669
670         SMTP_DBG_SEND();
671         return eReadMessage;
672 }
673
674 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
675 {
676         SMTP_DBG_READ();
677
678         if (SMTP_IS_STATE('2')) {
679                 SendMsg->State ++;
680                 if (IsEmptyStr(SendMsg->mx_user))
681                         SendMsg->State ++; /* Skip auth... */
682         }
683         /* else we fall back to 'helo' */
684         return eSendReply;
685 }
686
687 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
688 {
689         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
690                      "HELO %s\r\n", config.c_fqdn);
691
692         SMTP_DBG_SEND();
693         return eReadMessage;
694 }
695
696 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
697 {
698         SMTP_DBG_READ();
699
700         if (!SMTP_IS_STATE('2')) {
701                 if (SMTP_IS_STATE('4'))
702                         SMTP_VERROR(4)
703                 else 
704                         SMTP_VERROR(5)
705         }
706         if (!IsEmptyStr(SendMsg->mx_user))
707                 SendMsg->State ++; /* Skip auth... */
708         return eSendReply;
709 }
710
711 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
712 {
713         char buf[SIZ];
714         char encoded[1024];
715
716         /* Do an AUTH command if necessary */
717         sprintf(buf, "%s%c%s%c%s", 
718                 SendMsg->mx_user, '\0', 
719                 SendMsg->mx_user, '\0', 
720                 SendMsg->mx_pass);
721         CtdlEncodeBase64(encoded, buf, 
722                          strlen(SendMsg->mx_user) + 
723                          strlen(SendMsg->mx_user) + 
724                          strlen(SendMsg->mx_pass) + 2, 0);
725         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
726                      "AUTH PLAIN %s\r\n", encoded);
727         
728         SMTP_DBG_SEND();
729         return eReadMessage;
730 }
731
732 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
733 {
734         /* Do an AUTH command if necessary */
735         
736         SMTP_DBG_READ();
737         
738         if (!SMTP_IS_STATE('2')) {
739                 if (SMTP_IS_STATE('4'))
740                         SMTP_VERROR(4)
741                 else 
742                         SMTP_VERROR(5)
743         }
744         return eSendReply;
745 }
746
747 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
748 {
749         /* previous command succeeded, now try the MAIL FROM: command */
750         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
751                      "MAIL FROM:<%s>\r\n", 
752                      SendMsg->envelope_from);
753
754         SMTP_DBG_SEND();
755         return eReadMessage;
756 }
757
758 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
759 {
760         SMTP_DBG_READ();
761
762         if (!SMTP_IS_STATE('2')) {
763                 if (SMTP_IS_STATE('4'))
764                         SMTP_VERROR(4)
765                 else 
766                         SMTP_VERROR(5)
767         }
768         return eSendReply;
769 }
770
771
772 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
773 {
774         /* MAIL succeeded, now try the RCPT To: command */
775         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
776                      "RCPT TO:<%s@%s>\r\n", 
777                      SendMsg->user, 
778                      SendMsg->node);
779
780         SMTP_DBG_SEND();
781         return eReadMessage;
782 }
783
784 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
785 {
786         SMTP_DBG_READ();
787
788         if (!SMTP_IS_STATE('2')) {
789                 if (SMTP_IS_STATE('4')) 
790                         SMTP_VERROR(4)
791                 else 
792                         SMTP_VERROR(5)
793         }
794         return eSendReply;
795 }
796
797 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
798 {
799         /* RCPT succeeded, now try the DATA command */
800         StrBufPlain(SendMsg->IO.SendBuf.Buf,
801                     HKEY("DATA\r\n"));
802
803         SMTP_DBG_SEND();
804         return eReadMessage;
805 }
806
807 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
808 {
809         SMTP_DBG_READ();
810
811         if (!SMTP_IS_STATE('3')) {
812                 if (SMTP_IS_STATE('4')) 
813                         SMTP_VERROR(3)
814                 else 
815                         SMTP_VERROR(5)
816         }
817         return eSendReply;
818 }
819
820 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
821 {
822         StrBuf *Buf;
823         /* If we reach this point, the server is expecting data.*/
824
825         Buf = SendMsg->IO.SendBuf.Buf;
826         SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
827         SendMsg->msgtext = Buf;
828         //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
829         SendMsg->State ++;
830
831         return eSendMore;
832 }
833
834 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
835 {
836         StrBuf *Buf;
837
838         Buf = SendMsg->IO.SendBuf.Buf;
839         SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
840         SendMsg->msgtext = Buf;
841
842         StrBufPlain(SendMsg->IO.SendBuf.Buf,
843                     HKEY(".\r\n"));
844
845         return eReadMessage;
846
847 }
848
849 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
850 {
851         SMTP_DBG_READ();
852
853         if (!SMTP_IS_STATE('2')) {
854                 if (SMTP_IS_STATE('4'))
855                         SMTP_VERROR(4)
856                 else 
857                         SMTP_VERROR(5)
858         }
859
860         /* We did it! */
861         StrBufPlain(SendMsg->MyQEntry->StatusMessage, 
862                     &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
863                     StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
864         SendMsg->MyQEntry->Status = 2;
865         return eSendReply;
866 }
867
868 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
869 {
870         StrBufPlain(SendMsg->IO.SendBuf.Buf,
871                     HKEY("QUIT\r\n"));
872
873         SMTP_DBG_SEND();
874         return eReadMessage;
875 }
876
877 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
878 {
879         SMTP_DBG_READ();
880
881         CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
882                       SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
883         return eTerminateConnection;
884 }
885
886 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
887 {
888         return eSendReply;
889 }
890
891 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
892 {
893         return eReadMessage;
894 }
895
896 eNextState smtp_resolve_mx_done(void *data)
897 {/// VParsedDNSReply
898         AsyncIO *IO = data;
899         SmtpOutMsg * SendMsg = IO->Data;
900
901         SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
902         SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
903         SendMsg->IO.IOBuf = NewStrBuf();
904         SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
905
906         //// connect_one_smtpsrv_xamine_result
907         SendMsg->CurrMX = SendMsg->AllMX = IO->VParsedDNSReply;
908         //// TODO: should we remove the current ares context???
909         connect_one_smtpsrv(SendMsg);
910         return 0;
911 }
912
913
914
915 int resolve_mx_records(void *Ctx)
916 {
917         SmtpOutMsg * SendMsg = Ctx;
918
919         if (!QueueQuery(ns_t_mx, 
920                         SendMsg->node, 
921                         &SendMsg->IO, 
922                         smtp_resolve_mx_done))
923         {
924                 SendMsg->MyQEntry->Status = 5;
925                 StrBufPrintf(SendMsg->MyQEntry->StatusMessage, 
926                              "No MX hosts found for <%s>", SendMsg->node);
927                 return 0; ///////TODO: abort!
928         }
929         return 0;
930 }
931
932 void smtp_try(OneQueItem *MyQItem, 
933               MailQEntry *MyQEntry, 
934               StrBuf *MsgText, 
935               int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
936 {
937         SmtpOutMsg * SendMsg;
938
939         SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
940         memset(SendMsg, 0, sizeof(SmtpOutMsg));
941         SendMsg->IO.sock = (-1);
942         SendMsg->n = MsgCount++;
943         SendMsg->MyQEntry = MyQEntry;
944         SendMsg->MyQItem = MyQItem;
945         SendMsg->IO.Data = SendMsg;
946         if (KeepMsgText)
947                 SendMsg->msgtext = MsgText;
948         else 
949                 SendMsg->msgtext = NewStrBufDup(MsgText);
950
951         smtp_resolve_recipients(SendMsg);
952
953         QueueEventContext(SendMsg, 
954                           &SendMsg->IO,
955                           resolve_mx_records);
956 }
957
958
959
960 void NewMailQEntry(OneQueItem *Item)
961 {
962         Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
963         memset(Item->Current, 0, sizeof(MailQEntry));
964
965         if (Item->MailQEntries == NULL)
966                 Item->MailQEntries = NewHash(1, Flathash);
967         Item->Current->n = GetCount(Item->MailQEntries);
968         Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
969 }
970
971 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
972 {
973         Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
974 }
975
976 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
977 {
978         if (Item->EnvelopeFrom == NULL)
979                 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
980         StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
981 }
982
983 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
984 {
985         if (Item->BounceTo == NULL)
986                 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
987         StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
988 }
989
990 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
991 {
992         if (Item->Current == NULL)
993                 NewMailQEntry(Item);
994         if (Item->Current->Recipient == NULL)
995                 Item->Current->Recipient =  NewStrBufPlain(NULL, StrLength(Line));
996         StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
997         Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
998         StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
999         Item->Current = NULL; // TODO: is this always right?
1000 }
1001
1002
1003 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
1004 {
1005         if (Item->Current == NULL)
1006                 NewMailQEntry(Item);
1007         if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
1008                 Item->Current->nAttempts++;
1009         if (Item->Current->nAttempts > MaxAttempts) {
1010                 Item->FailNow = 1;
1011                 return;
1012         }
1013         Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1014 }
1015
1016 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1017 {
1018         if (Item->Current == NULL)
1019                 NewMailQEntry(Item);
1020         if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1021                 Item->Current->nAttempts++;
1022         if (Item->Current->nAttempts > MaxAttempts) {
1023                 Item->FailNow = 1;
1024                 return;
1025         }
1026                 
1027         Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1028         if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1029         {
1030                 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1031                 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1032                 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1033                         Item->LastAttempt.retry = SMTP_RETRY_MAX;
1034         }
1035 }
1036
1037
1038
1039
1040 /*
1041  * smtp_do_procmsg()
1042  *
1043  * Called by smtp_do_queue() to handle an individual message.
1044  */
1045 void smtp_do_procmsg(long msgnum, void *userdata) {
1046         struct CtdlMessage *msg = NULL;
1047         char *instr = NULL;     
1048         StrBuf *PlainQItem;
1049         OneQueItem *MyQItem;
1050         char *pch;
1051         HashPos  *It;
1052         void *vQE;
1053         long len;
1054         const char *Key;
1055
1056         CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
1057         ///strcpy(envelope_from, "");
1058
1059         msg = CtdlFetchMessage(msgnum, 1);
1060         if (msg == NULL) {
1061                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
1062                 return;
1063         }
1064
1065         pch = instr = msg->cm_fields['M'];
1066
1067         /* Strip out the headers (no not amd any other non-instruction) line */
1068         while (pch != NULL) {
1069                 pch = strchr(pch, '\n');
1070                 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1071                         instr = pch + 2;
1072                         pch = NULL;
1073                 }
1074         }
1075         PlainQItem = NewStrBufPlain(instr, -1);
1076         CtdlFreeMessage(msg);
1077         MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1078         FreeStrBuf(&PlainQItem);
1079
1080         if (MyQItem == NULL) {
1081                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);              
1082                 return; /* s.b. else is already processing... */
1083         }
1084
1085         /*
1086          * Postpone delivery if we've already tried recently.
1087          * /
1088         if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1089                 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1090
1091                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1092                 citthread_mutex_lock(&ActiveQItemsLock);
1093                 {
1094                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1095                         DeleteEntryFromHash(ActiveQItems, It);
1096                 }
1097                 citthread_mutex_unlock(&ActiveQItemsLock);
1098                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1099                 DeleteHashPos(&It);
1100                 return;
1101         }// TODO: reenable me.*/
1102
1103         /*
1104          * Bail out if there's no actual message associated with this
1105          */
1106         if (MyQItem->MessageID < 0L) {
1107                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
1108                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1109                 citthread_mutex_lock(&ActiveQItemsLock);
1110                 {
1111                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1112                         DeleteEntryFromHash(ActiveQItems, It);
1113                 }
1114                 citthread_mutex_unlock(&ActiveQItemsLock);
1115                 DeleteHashPos(&It);
1116                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1117                 return;
1118         }
1119
1120         It = GetNewHashPos(MyQItem->MailQEntries, 0);
1121         while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
1122         {
1123                 MailQEntry *ThisItem = vQE;
1124                 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
1125         }
1126         DeleteHashPos(&It);
1127
1128         CountActiveQueueEntries(MyQItem);
1129         if (MyQItem->ActiveDeliveries > 0)
1130         {
1131                 int i = 1;
1132                 StrBuf *Msg = smtp_load_msg(MyQItem);
1133                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1134                 while ((i <= MyQItem->ActiveDeliveries) && 
1135                        (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1136                 {
1137                         MailQEntry *ThisItem = vQE;
1138                         if (ThisItem->Active == 1) {
1139                                 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1140                                 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1141                                 i++;
1142                         }
1143                 }
1144                 DeleteHashPos(&It);
1145         }
1146         else 
1147         {
1148                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1149                 citthread_mutex_lock(&ActiveQItemsLock);
1150                 {
1151                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1152                         DeleteEntryFromHash(ActiveQItems, It);
1153                 }
1154                 citthread_mutex_unlock(&ActiveQItemsLock);
1155                 DeleteHashPos(&It);
1156                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1157
1158 // TODO: bounce & delete?
1159
1160         }
1161 }
1162
1163
1164 /*****************************************************************************/
1165 /*                          SMTP UTILITY COMMANDS                            */
1166 /*****************************************************************************/
1167
1168 void cmd_smtp(char *argbuf) {
1169         char cmd[64];
1170         char node[256];
1171         char buf[1024];
1172         int i;
1173         int num_mxhosts;
1174
1175         if (CtdlAccessCheck(ac_aide)) return;
1176
1177         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1178
1179         if (!strcasecmp(cmd, "mx")) {
1180                 extract_token(node, argbuf, 1, '|', sizeof node);
1181                 num_mxhosts = getmx(buf, node);
1182                 cprintf("%d %d MX hosts listed for %s\n",
1183                         LISTING_FOLLOWS, num_mxhosts, node);
1184                 for (i=0; i<num_mxhosts; ++i) {
1185                         extract_token(node, buf, i, '|', sizeof node);
1186                         cprintf("%s\n", node);
1187                 }
1188                 cprintf("000\n");
1189                 return;
1190         }
1191
1192         else if (!strcasecmp(cmd, "runqueue")) {
1193                 run_queue_now = 1;
1194                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1195                 return;
1196         }
1197
1198         else {
1199                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1200         }
1201
1202 }
1203
1204
1205 /*
1206  * smtp_queue_thread()
1207  * 
1208  * Run through the queue sending out messages.
1209  */
1210 void *smtp_queue_thread(void *arg) {
1211         int num_processed = 0;
1212         struct CitContext smtp_queue_CC;
1213
1214         CtdlThreadSleep(10);
1215
1216         CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1217         citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1218         CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1219
1220         while (!CtdlThreadCheckStop()) {
1221                 
1222                 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1223
1224                 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1225                         CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1226                 }
1227                 else {
1228                         num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1229                 }
1230                 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1231                 CtdlThreadSleep(60);
1232         }
1233
1234         CtdlClearSystemContext();
1235         return(NULL);
1236 }
1237
1238
1239 /*
1240  * Initialize the SMTP outbound queue
1241  */
1242 void smtp_init_spoolout(void) {
1243         struct ctdlroom qrbuf;
1244
1245         /*
1246          * Create the room.  This will silently fail if the room already
1247          * exists, and that's perfectly ok, because we want it to exist.
1248          */
1249         CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1250
1251         /*
1252          * Make sure it's set to be a "system room" so it doesn't show up
1253          * in the <K>nown rooms list for Aides.
1254          */
1255         if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1256                 qrbuf.QRflags2 |= QR2_SYSTEM;
1257                 CtdlPutRoomLock(&qrbuf);
1258         }
1259 }
1260
1261
1262 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1263         SMTPC_read_greeting,
1264         SMTPC_read_EHLO_reply,
1265         SMTPC_read_HELO_reply,
1266         SMTPC_read_auth_reply,
1267         SMTPC_read_FROM_reply,
1268         SMTPC_read_RCPT_reply,
1269         SMTPC_read_DATAcmd_reply,
1270         SMTPC_read_dummy,
1271         SMTPC_read_data_body_reply,
1272         SMTPC_read_QUIT_reply
1273 };
1274
1275 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1276         SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1277         SMTPC_send_EHLO,
1278         STMPC_send_HELO,
1279         SMTPC_send_auth,
1280         SMTPC_send_FROM,
1281         SMTPC_send_RCPT,
1282         SMTPC_send_DATAcmd,
1283         SMTPC_send_data_body,
1284         SMTPC_send_terminate_data_body,
1285         SMTPC_send_QUIT
1286 };
1287
1288 eNextState SMTP_C_Terminate(void *Data)
1289 {
1290         SmtpOutMsg *pMsg = Data;
1291         FinalizeMessageSend(pMsg);
1292         return 0;
1293 }
1294
1295 eNextState SMTP_C_Timeout(void *Data)
1296 {
1297         SmtpOutMsg *pMsg = Data;
1298         FinalizeMessageSend(pMsg);
1299         return 0;
1300 }
1301
1302 eNextState SMTP_C_ConnFail(void *Data)
1303 {
1304         SmtpOutMsg *pMsg = Data;
1305         FinalizeMessageSend(pMsg);
1306         return 0;
1307 }
1308
1309 eNextState SMTP_C_DispatchReadDone(void *Data)
1310 {
1311         SmtpOutMsg *pMsg = Data;
1312         eNextState rc = ReadHandlers[pMsg->State](pMsg);
1313         pMsg->State++;
1314         return rc;
1315 }
1316
1317 eNextState SMTP_C_DispatchWriteDone(void *Data)
1318 {
1319         SmtpOutMsg *pMsg = Data;
1320         return SendHandlers[pMsg->State](pMsg);
1321         
1322 }
1323
1324 void smtp_evc_cleanup(void)
1325 {
1326         DeleteHash(&QItemHandlers);
1327         DeleteHash(&ActiveQItems);
1328 }
1329
1330 #endif
1331 CTDL_MODULE_INIT(smtp_eventclient)
1332 {
1333 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1334         if (!threading)
1335         {
1336                 ActiveQItems = NewHash(1, Flathash);
1337                 citthread_mutex_init(&ActiveQItemsLock, NULL);
1338
1339                 QItemHandlers = NewHash(0, NULL);
1340
1341                 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1342                 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1343                 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1344                 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1345                 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1346                 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1347 ///submitted /TODO: flush qitemhandlers on exit
1348
1349
1350                 smtp_init_spoolout();
1351
1352                 CtdlRegisterCleanupHook(smtp_evc_cleanup);
1353                 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1354
1355                 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1356         }
1357 #endif
1358         
1359         /* return our Subversion id for the Log */
1360         return "smtpeventclient";
1361 }