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