Libev/libc-ares migration: cleanup
[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
582                 SendMsg->IO.HEnt = hostent;
583                 InitEventIO(IO, SendMsg, 
584                             SMTP_C_DispatchReadDone, 
585                             SMTP_C_DispatchWriteDone, 
586                             SMTP_C_Terminate,
587                             SMTP_C_Timeout,
588                             SMTP_C_ConnFail,
589                             SMTP_C_ReadServerStatus,
590                             1);
591
592         }
593 }
594
595 const unsigned short DefaultMXPort = 25;
596 void connect_one_smtpsrv(SmtpOutMsg *SendMsg)
597 {
598         //char *endpart;
599         //char buf[SIZ];
600
601         SendMsg->IO.dport = DefaultMXPort;
602
603
604 /* TODO: Relay!
605         *SendMsg->mx_user =  '\0';
606         *SendMsg->mx_pass = '\0';
607         if (num_tokens(buf, '@') > 1) {
608                 strcpy (SendMsg->mx_user, buf);
609                 endpart = strrchr(SendMsg->mx_user, '@');
610                 *endpart = '\0';
611                 strcpy (SendMsg->mx_host, endpart + 1);
612                 endpart = strrchr(SendMsg->mx_user, ':');
613                 if (endpart != NULL) {
614                         strcpy(SendMsg->mx_pass, endpart+1);
615                         *endpart = '\0';
616                 }
617
618         endpart = strrchr(SendMsg->mx_host, ':');
619         if (endpart != 0){
620                 *endpart = '\0';
621                 strcpy(SendMsg->mx_port, endpart + 1);
622         }               
623         }
624         else
625 */
626         SendMsg->mx_host = SendMsg->CurrMX->host;
627         SendMsg->CurrMX = SendMsg->CurrMX->next;
628
629         CtdlLogPrintf(CTDL_DEBUG, 
630                       "SMTP client[%ld]: connecting to %s : %d ...\n", 
631                       SendMsg->n, 
632                       SendMsg->mx_host, 
633                       SendMsg->IO.dport);
634
635         ares_gethostbyname(SendMsg->IO.DNSChannel,
636                            SendMsg->mx_host,   
637                            AF_INET6, /* it falls back to ipv4 in doubt... */
638                            get_one_mx_host_name_done,
639                            &SendMsg->IO);
640 /*
641         if (!QueueQuery(ns_t_a, 
642                         SendMsg->mx_host, 
643                         &SendMsg->IO, 
644                         connect_one_smtpsrv_xamine_result))
645         {
646                 /// TODO: abort
647         }
648 */
649 }
650
651
652 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
653 {
654         /* Process the SMTP greeting from the server */
655         SMTP_DBG_READ();
656
657         if (!SMTP_IS_STATE('2')) {
658                 if (SMTP_IS_STATE('4')) 
659                         SMTP_VERROR(4)
660                 else 
661                         SMTP_VERROR(5)
662         }
663         return eSendReply;
664 }
665
666 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
667 {
668         /* At this point we know we are talking to a real SMTP server */
669
670         /* Do a EHLO command.  If it fails, try the HELO command. */
671         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
672                      "EHLO %s\r\n", config.c_fqdn);
673
674         SMTP_DBG_SEND();
675         return eReadMessage;
676 }
677
678 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
679 {
680         SMTP_DBG_READ();
681
682         if (SMTP_IS_STATE('2')) {
683                 SendMsg->State ++;
684                 if (IsEmptyStr(SendMsg->mx_user))
685                         SendMsg->State ++; /* Skip auth... */
686         }
687         /* else we fall back to 'helo' */
688         return eSendReply;
689 }
690
691 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
692 {
693         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
694                      "HELO %s\r\n", config.c_fqdn);
695
696         SMTP_DBG_SEND();
697         return eReadMessage;
698 }
699
700 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
701 {
702         SMTP_DBG_READ();
703
704         if (!SMTP_IS_STATE('2')) {
705                 if (SMTP_IS_STATE('4'))
706                         SMTP_VERROR(4)
707                 else 
708                         SMTP_VERROR(5)
709         }
710         if (!IsEmptyStr(SendMsg->mx_user))
711                 SendMsg->State ++; /* Skip auth... */
712         return eSendReply;
713 }
714
715 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
716 {
717         char buf[SIZ];
718         char encoded[1024];
719
720         /* Do an AUTH command if necessary */
721         sprintf(buf, "%s%c%s%c%s", 
722                 SendMsg->mx_user, '\0', 
723                 SendMsg->mx_user, '\0', 
724                 SendMsg->mx_pass);
725         CtdlEncodeBase64(encoded, buf, 
726                          strlen(SendMsg->mx_user) + 
727                          strlen(SendMsg->mx_user) + 
728                          strlen(SendMsg->mx_pass) + 2, 0);
729         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
730                      "AUTH PLAIN %s\r\n", encoded);
731         
732         SMTP_DBG_SEND();
733         return eReadMessage;
734 }
735
736 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
737 {
738         /* Do an AUTH command if necessary */
739         
740         SMTP_DBG_READ();
741         
742         if (!SMTP_IS_STATE('2')) {
743                 if (SMTP_IS_STATE('4'))
744                         SMTP_VERROR(4)
745                 else 
746                         SMTP_VERROR(5)
747         }
748         return eSendReply;
749 }
750
751 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
752 {
753         /* previous command succeeded, now try the MAIL FROM: command */
754         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
755                      "MAIL FROM:<%s>\r\n", 
756                      SendMsg->envelope_from);
757
758         SMTP_DBG_SEND();
759         return eReadMessage;
760 }
761
762 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
763 {
764         SMTP_DBG_READ();
765
766         if (!SMTP_IS_STATE('2')) {
767                 if (SMTP_IS_STATE('4'))
768                         SMTP_VERROR(4)
769                 else 
770                         SMTP_VERROR(5)
771         }
772         return eSendReply;
773 }
774
775
776 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
777 {
778         /* MAIL succeeded, now try the RCPT To: command */
779         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
780                      "RCPT TO:<%s@%s>\r\n", 
781                      SendMsg->user, 
782                      SendMsg->node);
783
784         SMTP_DBG_SEND();
785         return eReadMessage;
786 }
787
788 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
789 {
790         SMTP_DBG_READ();
791
792         if (!SMTP_IS_STATE('2')) {
793                 if (SMTP_IS_STATE('4')) 
794                         SMTP_VERROR(4)
795                 else 
796                         SMTP_VERROR(5)
797         }
798         return eSendReply;
799 }
800
801 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
802 {
803         /* RCPT succeeded, now try the DATA command */
804         StrBufPlain(SendMsg->IO.SendBuf.Buf,
805                     HKEY("DATA\r\n"));
806
807         SMTP_DBG_SEND();
808         return eReadMessage;
809 }
810
811 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
812 {
813         SMTP_DBG_READ();
814
815         if (!SMTP_IS_STATE('3')) {
816                 if (SMTP_IS_STATE('4')) 
817                         SMTP_VERROR(3)
818                 else 
819                         SMTP_VERROR(5)
820         }
821         return eSendReply;
822 }
823
824 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
825 {
826         StrBuf *Buf;
827         /* If we reach this point, the server is expecting data.*/
828
829         Buf = SendMsg->IO.SendBuf.Buf;
830         SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
831         SendMsg->msgtext = Buf;
832         //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
833         SendMsg->State ++;
834
835         return eSendMore;
836 }
837
838 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
839 {
840         StrBuf *Buf;
841
842         Buf = SendMsg->IO.SendBuf.Buf;
843         SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
844         SendMsg->msgtext = Buf;
845
846         StrBufPlain(SendMsg->IO.SendBuf.Buf,
847                     HKEY(".\r\n"));
848
849         return eReadMessage;
850
851 }
852
853 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
854 {
855         SMTP_DBG_READ();
856
857         if (!SMTP_IS_STATE('2')) {
858                 if (SMTP_IS_STATE('4'))
859                         SMTP_VERROR(4)
860                 else 
861                         SMTP_VERROR(5)
862         }
863
864         /* We did it! */
865         StrBufPlain(SendMsg->MyQEntry->StatusMessage, 
866                     &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
867                     StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
868         SendMsg->MyQEntry->Status = 2;
869         return eSendReply;
870 }
871
872 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
873 {
874         StrBufPlain(SendMsg->IO.SendBuf.Buf,
875                     HKEY("QUIT\r\n"));
876
877         SMTP_DBG_SEND();
878         return eReadMessage;
879 }
880
881 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
882 {
883         SMTP_DBG_READ();
884
885         CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
886                       SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
887         return eTerminateConnection;
888 }
889
890 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
891 {
892         return eSendReply;
893 }
894
895 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
896 {
897         return eReadMessage;
898 }
899
900 eNextState smtp_resolve_mx_done(void *data)
901 {/// VParsedDNSReply
902         AsyncIO *IO = data;
903         SmtpOutMsg * SendMsg = IO->Data;
904
905         SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
906         SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
907         SendMsg->IO.IOBuf = NewStrBuf();
908         SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
909
910         //// connect_one_smtpsrv_xamine_result
911         SendMsg->CurrMX = SendMsg->AllMX = IO->VParsedDNSReply;
912         //// TODO: should we remove the current ares context???
913         connect_one_smtpsrv(SendMsg);
914         return 0;
915 }
916
917
918
919 int resolve_mx_records(void *Ctx)
920 {
921         SmtpOutMsg * SendMsg = Ctx;
922
923         if (!QueueQuery(ns_t_mx, 
924                         SendMsg->node, 
925                         &SendMsg->IO, 
926                         smtp_resolve_mx_done))
927         {
928                 SendMsg->MyQEntry->Status = 5;
929                 StrBufPrintf(SendMsg->MyQEntry->StatusMessage, 
930                              "No MX hosts found for <%s>", SendMsg->node);
931                 return 0; ///////TODO: abort!
932         }
933         return 0;
934 }
935
936 void smtp_try(OneQueItem *MyQItem, 
937               MailQEntry *MyQEntry, 
938               StrBuf *MsgText, 
939               int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
940 {
941         SmtpOutMsg * SendMsg;
942
943         SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
944         memset(SendMsg, 0, sizeof(SmtpOutMsg));
945         SendMsg->IO.sock = (-1);
946         SendMsg->n = MsgCount++;
947         SendMsg->MyQEntry = MyQEntry;
948         SendMsg->MyQItem = MyQItem;
949         SendMsg->IO.Data = SendMsg;
950         if (KeepMsgText)
951                 SendMsg->msgtext = MsgText;
952         else 
953                 SendMsg->msgtext = NewStrBufDup(MsgText);
954
955         smtp_resolve_recipients(SendMsg);
956
957         QueueEventContext(SendMsg, 
958                           &SendMsg->IO,
959                           resolve_mx_records);
960 }
961
962
963
964 void NewMailQEntry(OneQueItem *Item)
965 {
966         Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
967         memset(Item->Current, 0, sizeof(MailQEntry));
968
969         if (Item->MailQEntries == NULL)
970                 Item->MailQEntries = NewHash(1, Flathash);
971         Item->Current->n = GetCount(Item->MailQEntries);
972         Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
973 }
974
975 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
976 {
977         Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
978 }
979
980 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
981 {
982         if (Item->EnvelopeFrom == NULL)
983                 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
984         StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
985 }
986
987 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
988 {
989         if (Item->BounceTo == NULL)
990                 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
991         StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
992 }
993
994 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
995 {
996         if (Item->Current == NULL)
997                 NewMailQEntry(Item);
998         if (Item->Current->Recipient == NULL)
999                 Item->Current->Recipient =  NewStrBufPlain(NULL, StrLength(Line));
1000         StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
1001         Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
1002         StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
1003         Item->Current = NULL; // TODO: is this always right?
1004 }
1005
1006
1007 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
1008 {
1009         if (Item->Current == NULL)
1010                 NewMailQEntry(Item);
1011         if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
1012                 Item->Current->nAttempts++;
1013         if (Item->Current->nAttempts > MaxAttempts) {
1014                 Item->FailNow = 1;
1015                 return;
1016         }
1017         Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1018 }
1019
1020 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1021 {
1022         if (Item->Current == NULL)
1023                 NewMailQEntry(Item);
1024         if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1025                 Item->Current->nAttempts++;
1026         if (Item->Current->nAttempts > MaxAttempts) {
1027                 Item->FailNow = 1;
1028                 return;
1029         }
1030                 
1031         Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1032         if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1033         {
1034                 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1035                 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1036                 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1037                         Item->LastAttempt.retry = SMTP_RETRY_MAX;
1038         }
1039 }
1040
1041
1042
1043
1044 /*
1045  * smtp_do_procmsg()
1046  *
1047  * Called by smtp_do_queue() to handle an individual message.
1048  */
1049 void smtp_do_procmsg(long msgnum, void *userdata) {
1050         struct CtdlMessage *msg = NULL;
1051         char *instr = NULL;     
1052         StrBuf *PlainQItem;
1053         OneQueItem *MyQItem;
1054         char *pch;
1055         HashPos  *It;
1056         void *vQE;
1057         long len;
1058         const char *Key;
1059
1060         CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
1061         ///strcpy(envelope_from, "");
1062
1063         msg = CtdlFetchMessage(msgnum, 1);
1064         if (msg == NULL) {
1065                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
1066                 return;
1067         }
1068
1069         pch = instr = msg->cm_fields['M'];
1070
1071         /* Strip out the headers (no not amd any other non-instruction) line */
1072         while (pch != NULL) {
1073                 pch = strchr(pch, '\n');
1074                 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1075                         instr = pch + 2;
1076                         pch = NULL;
1077                 }
1078         }
1079         PlainQItem = NewStrBufPlain(instr, -1);
1080         CtdlFreeMessage(msg);
1081         MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1082         FreeStrBuf(&PlainQItem);
1083
1084         if (MyQItem == NULL) {
1085                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);              
1086                 return; /* s.b. else is already processing... */
1087         }
1088
1089         /*
1090          * Postpone delivery if we've already tried recently.
1091          * /
1092         if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1093                 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1094
1095                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1096                 citthread_mutex_lock(&ActiveQItemsLock);
1097                 {
1098                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1099                         DeleteEntryFromHash(ActiveQItems, It);
1100                 }
1101                 citthread_mutex_unlock(&ActiveQItemsLock);
1102                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1103                 DeleteHashPos(&It);
1104                 return;
1105         }// TODO: reenable me.*/
1106
1107         /*
1108          * Bail out if there's no actual message associated with this
1109          */
1110         if (MyQItem->MessageID < 0L) {
1111                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
1112                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1113                 citthread_mutex_lock(&ActiveQItemsLock);
1114                 {
1115                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1116                         DeleteEntryFromHash(ActiveQItems, It);
1117                 }
1118                 citthread_mutex_unlock(&ActiveQItemsLock);
1119                 DeleteHashPos(&It);
1120                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1121                 return;
1122         }
1123
1124         It = GetNewHashPos(MyQItem->MailQEntries, 0);
1125         while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
1126         {
1127                 MailQEntry *ThisItem = vQE;
1128                 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
1129         }
1130         DeleteHashPos(&It);
1131
1132         CountActiveQueueEntries(MyQItem);
1133         if (MyQItem->ActiveDeliveries > 0)
1134         {
1135                 int i = 1;
1136                 StrBuf *Msg = smtp_load_msg(MyQItem);
1137                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1138                 while ((i <= MyQItem->ActiveDeliveries) && 
1139                        (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1140                 {
1141                         MailQEntry *ThisItem = vQE;
1142                         if (ThisItem->Active == 1) {
1143                                 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1144                                 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1145                                 i++;
1146                         }
1147                 }
1148                 DeleteHashPos(&It);
1149         }
1150         else 
1151         {
1152                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1153                 citthread_mutex_lock(&ActiveQItemsLock);
1154                 {
1155                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1156                         DeleteEntryFromHash(ActiveQItems, It);
1157                 }
1158                 citthread_mutex_unlock(&ActiveQItemsLock);
1159                 DeleteHashPos(&It);
1160                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1161
1162 // TODO: bounce & delete?
1163
1164         }
1165 }
1166
1167
1168 /*****************************************************************************/
1169 /*                          SMTP UTILITY COMMANDS                            */
1170 /*****************************************************************************/
1171
1172 void cmd_smtp(char *argbuf) {
1173         char cmd[64];
1174         char node[256];
1175         char buf[1024];
1176         int i;
1177         int num_mxhosts;
1178
1179         if (CtdlAccessCheck(ac_aide)) return;
1180
1181         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1182
1183         if (!strcasecmp(cmd, "mx")) {
1184                 extract_token(node, argbuf, 1, '|', sizeof node);
1185                 num_mxhosts = getmx(buf, node);
1186                 cprintf("%d %d MX hosts listed for %s\n",
1187                         LISTING_FOLLOWS, num_mxhosts, node);
1188                 for (i=0; i<num_mxhosts; ++i) {
1189                         extract_token(node, buf, i, '|', sizeof node);
1190                         cprintf("%s\n", node);
1191                 }
1192                 cprintf("000\n");
1193                 return;
1194         }
1195
1196         else if (!strcasecmp(cmd, "runqueue")) {
1197                 run_queue_now = 1;
1198                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1199                 return;
1200         }
1201
1202         else {
1203                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1204         }
1205
1206 }
1207
1208
1209 /*
1210  * smtp_queue_thread()
1211  * 
1212  * Run through the queue sending out messages.
1213  */
1214 void *smtp_queue_thread(void *arg) {
1215         int num_processed = 0;
1216         struct CitContext smtp_queue_CC;
1217
1218         CtdlThreadSleep(10);
1219
1220         CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1221         citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1222         CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1223
1224         while (!CtdlThreadCheckStop()) {
1225                 
1226                 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1227
1228                 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1229                         CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1230                 }
1231                 else {
1232                         num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1233                 }
1234                 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1235                 CtdlThreadSleep(60);
1236         }
1237
1238         CtdlClearSystemContext();
1239         return(NULL);
1240 }
1241
1242
1243 /*
1244  * Initialize the SMTP outbound queue
1245  */
1246 void smtp_init_spoolout(void) {
1247         struct ctdlroom qrbuf;
1248
1249         /*
1250          * Create the room.  This will silently fail if the room already
1251          * exists, and that's perfectly ok, because we want it to exist.
1252          */
1253         CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1254
1255         /*
1256          * Make sure it's set to be a "system room" so it doesn't show up
1257          * in the <K>nown rooms list for Aides.
1258          */
1259         if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1260                 qrbuf.QRflags2 |= QR2_SYSTEM;
1261                 CtdlPutRoomLock(&qrbuf);
1262         }
1263 }
1264
1265
1266 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1267         SMTPC_read_greeting,
1268         SMTPC_read_EHLO_reply,
1269         SMTPC_read_HELO_reply,
1270         SMTPC_read_auth_reply,
1271         SMTPC_read_FROM_reply,
1272         SMTPC_read_RCPT_reply,
1273         SMTPC_read_DATAcmd_reply,
1274         SMTPC_read_dummy,
1275         SMTPC_read_data_body_reply,
1276         SMTPC_read_QUIT_reply
1277 };
1278
1279 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1280         SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1281         SMTPC_send_EHLO,
1282         STMPC_send_HELO,
1283         SMTPC_send_auth,
1284         SMTPC_send_FROM,
1285         SMTPC_send_RCPT,
1286         SMTPC_send_DATAcmd,
1287         SMTPC_send_data_body,
1288         SMTPC_send_terminate_data_body,
1289         SMTPC_send_QUIT
1290 };
1291
1292 eNextState SMTP_C_Terminate(void *Data)
1293 {
1294         SmtpOutMsg *pMsg = Data;
1295         FinalizeMessageSend(pMsg);
1296         return 0;
1297 }
1298
1299 eNextState SMTP_C_Timeout(void *Data)
1300 {
1301         SmtpOutMsg *pMsg = Data;
1302         FinalizeMessageSend(pMsg);
1303         return 0;
1304 }
1305
1306 eNextState SMTP_C_ConnFail(void *Data)
1307 {
1308         SmtpOutMsg *pMsg = Data;
1309         FinalizeMessageSend(pMsg);
1310         return 0;
1311 }
1312
1313 eNextState SMTP_C_DispatchReadDone(void *Data)
1314 {
1315         SmtpOutMsg *pMsg = Data;
1316         eNextState rc = ReadHandlers[pMsg->State](pMsg);
1317         pMsg->State++;
1318         return rc;
1319 }
1320
1321 eNextState SMTP_C_DispatchWriteDone(void *Data)
1322 {
1323         SmtpOutMsg *pMsg = Data;
1324         return SendHandlers[pMsg->State](pMsg);
1325         
1326 }
1327
1328 void smtp_evc_cleanup(void)
1329 {
1330         DeleteHash(&QItemHandlers);
1331         DeleteHash(&ActiveQItems);
1332 }
1333
1334 #endif
1335 CTDL_MODULE_INIT(smtp_eventclient)
1336 {
1337 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1338         if (!threading)
1339         {
1340                 ActiveQItems = NewHash(1, Flathash);
1341                 citthread_mutex_init(&ActiveQItemsLock, NULL);
1342
1343                 QItemHandlers = NewHash(0, NULL);
1344
1345                 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1346                 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1347                 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1348                 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1349                 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1350                 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1351 ///submitted /TODO: flush qitemhandlers on exit
1352
1353
1354                 smtp_init_spoolout();
1355
1356                 CtdlRegisterCleanupHook(smtp_evc_cleanup);
1357                 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1358
1359                 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1360         }
1361 #endif
1362         
1363         /* return our Subversion id for the Log */
1364         return "smtpeventclient";
1365 }