Libevent Integration
[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         int SMTPstatus;
195
196         int i_mx;
197         int n_mx;
198         int num_mxhosts;
199         char mx_user[1024];
200         char mx_pass[1024];
201         char mx_host[1024];
202         char mx_port[1024];
203         char mxhosts[SIZ];
204
205         StrBuf *msgtext;
206         char *envelope_from;
207         char user[1024];
208         char node[1024];
209         char name[1024];
210 ///     char addr[SIZ]; -> MyQEntry->Recipient
211 ///     char dsn[1024]; -> MyQEntry->StatusMessage
212 ///     char envelope_from_buf[1024]; MyQItem->EnvelopeFrom
213         char mailfrom[1024];
214 } SmtpOutMsg;
215 void DeleteSmtpOutMsg(void *v)
216 {
217         SmtpOutMsg *Msg = v;
218         FreeStrBuf(&Msg->msgtext);
219         FreeAsyncIOContents(&Msg->IO);
220         free(Msg);
221 }
222
223 eNextState SMTP_C_DispatchReadDone(void *Data);
224 eNextState SMTP_C_DispatchWriteDone(void *Data);
225
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                 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
319                 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
320                 {
321                         QItemHandler H;
322                         H = (QItemHandler) vHandler;
323                         H(Item, Line, &pItemPart);
324                 }
325         }
326         FreeStrBuf(&Line);
327         FreeStrBuf(&Token);
328         return Item;
329 }
330
331 StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
332 {
333         StrBuf *QMessage;
334         HashPos  *It;
335         const char *Key;
336         long len;
337         void *vQE;
338
339         QMessage = NewStrBufPlain(NULL, SIZ);
340         StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
341
342 //                 "attempted|%ld\n"  "retry|%ld\n",, (long)time(NULL), (long)retry );
343         StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
344         StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
345
346         if (StrLength(MyQItem->BounceTo) > 0) {
347                 StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
348                 StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
349         }
350
351         if (StrLength(MyQItem->EnvelopeFrom) > 0) {
352                 StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
353                 StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
354         }
355
356         It = GetNewHashPos(MyQItem->MailQEntries, 0);
357         while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
358         {
359                 MailQEntry *ThisItem = vQE;
360                 int i;
361
362                 if (!ThisItem->Active)
363                         continue; /* skip already sent ones from the spoolfile. */
364
365                 for (i=0; i < ThisItem->nAttempts; i++) {
366                         StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
367                         StrBufAppendPrintf(QMessage, "%ld", 
368                                            ThisItem->Attempts[i].retry);
369
370                         StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
371                         StrBufAppendPrintf(QMessage, "%ld", 
372                                            ThisItem->Attempts[i].when);
373                 }
374                 StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
375                 StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
376                 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
377                 StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
378                 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
379                 StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
380         }
381         DeleteHashPos(&It);
382         StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);  
383         return QMessage;
384 }
385
386 void FinalizeMessageSend(SmtpOutMsg *Msg)
387 {
388         int IDestructQueItem;
389         HashPos  *It;
390
391         citthread_mutex_lock(&ActiveQItemsLock);
392         Msg->MyQItem->ActiveDeliveries--;
393         IDestructQueItem = Msg->MyQItem->ActiveDeliveries == 0;
394         citthread_mutex_unlock(&ActiveQItemsLock);
395
396         if (IDestructQueItem) {
397                 int nRemain;
398                 StrBuf *MsgData;
399
400                 nRemain = CountActiveQueueEntries(Msg->MyQItem);
401
402                 if (nRemain > 0) 
403                         MsgData = SerializeQueueItem(Msg->MyQItem);
404                 /*
405                  * Uncompleted delivery instructions remain, so delete the old
406                  * instructions and replace with the updated ones.
407                  */
408                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, "");
409
410         /* Generate 'bounce' messages * /
411            smtp_do_bounce(instr); */
412                 if (nRemain > 0) {
413                         struct CtdlMessage *msg;
414                         msg = malloc(sizeof(struct CtdlMessage));
415                         memset(msg, 0, sizeof(struct CtdlMessage));
416                         msg->cm_magic = CTDLMESSAGE_MAGIC;
417                         msg->cm_anon_type = MES_NORMAL;
418                         msg->cm_format_type = FMT_RFC822;
419                         msg->cm_fields['M'] = SmashStrBuf(&MsgData);
420
421                         CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
422                         CtdlFreeMessage(msg);
423                 }
424                 It = GetNewHashPos(Msg->MyQItem->MailQEntries, 0);
425                 citthread_mutex_lock(&ActiveQItemsLock);
426                 {
427                         GetHashPosFromKey(ActiveQItems, IKEY(Msg->MyQItem->MessageID), It);
428                         DeleteEntryFromHash(ActiveQItems, It);
429                 }
430                 citthread_mutex_unlock(&ActiveQItemsLock);
431                 DeleteHashPos(&It);
432         }
433         
434 /// TODO : else free message...
435         DeleteSmtpOutMsg(Msg);
436 }
437
438 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
439 {
440         eReadState Finished = eBufferNotEmpty; 
441
442         while (Finished == eBufferNotEmpty) {
443                 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
444                 
445                 switch (Finished) {
446                 case eMustReadMore: /// read new from socket... 
447                         return Finished;
448                         break;
449                 case eBufferNotEmpty: /* shouldn't happen... */
450                 case eReadSuccess: /// done for now...
451                         if (StrLength(IO->IOBuf) < 4)
452                                 continue;
453                         if (ChrPtr(IO->IOBuf)[3] == '-')
454                                 Finished = eBufferNotEmpty;
455                         else 
456                                 return Finished;
457                         break;
458                 case eReadFail: /// WHUT?
459                         ///todo: shut down! 
460                         break;
461                 }
462         }
463         return Finished;
464 }
465
466 /**
467  * this one has to have the context for loading the message via the redirect buffer...
468  */
469 StrBuf *smtp_load_msg(OneQueItem *MyQItem)
470 {
471         CitContext *CCC=CC;
472         StrBuf *SendMsg;
473         
474         CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
475         CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
476         SendMsg = CCC->redirect_buffer;
477         CCC->redirect_buffer = NULL;
478         if ((StrLength(SendMsg) > 0) && 
479             ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
480                 CtdlLogPrintf(CTDL_WARNING, 
481                               "SMTP client[%ld]: Possible problem: message did not "
482                               "correctly terminate. (expecting 0x10, got 0x%02x)\n",
483                               MsgCount, //yes uncool, but best choice here... 
484                               ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
485                 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
486         }
487         return SendMsg;
488 }
489
490
491 int smtp_resolve_recipients(SmtpOutMsg *SendMsg)
492 {
493         const char *ptr;
494         char buf[1024];
495         int scan_done;
496         int lp, rp;
497         int i;
498
499         /* Parse out the host portion of the recipient address */
500         process_rfc822_addr(ChrPtr(SendMsg->MyQEntry->Recipient), 
501                             SendMsg->user, 
502                             SendMsg->node, 
503                             SendMsg->name);
504
505         CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
506                       SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
507         /* If no envelope_from is supplied, extract one from the message */
508         if ( (SendMsg->envelope_from == NULL) || 
509              (IsEmptyStr(SendMsg->envelope_from)) ) {
510                 SendMsg->mailfrom[0] = '\0';
511                 scan_done = 0;
512                 ptr = ChrPtr(SendMsg->msgtext);
513                 do {
514                         if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
515                                 scan_done = 1;
516                         }
517                         if (!strncasecmp(buf, "From:", 5)) {
518                                 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
519                                 striplt(SendMsg->mailfrom);
520                                 for (i=0; SendMsg->mailfrom[i]; ++i) {
521                                         if (!isprint(SendMsg->mailfrom[i])) {
522                                                 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
523                                                 i=0;
524                                         }
525                                 }
526         
527                                 /* Strip out parenthesized names */
528                                 lp = (-1);
529                                 rp = (-1);
530                                 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
531                                         if (SendMsg->mailfrom[i] == '(') lp = i;
532                                         if (SendMsg->mailfrom[i] == ')') rp = i;
533                                 }
534                                 if ((lp>0)&&(rp>lp)) {
535                                         strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
536                                 }
537         
538                                 /* Prefer brokketized names */
539                                 lp = (-1);
540                                 rp = (-1);
541                                 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
542                                         if (SendMsg->mailfrom[i] == '<') lp = i;
543                                         if (SendMsg->mailfrom[i] == '>') rp = i;
544                                 }
545                                 if ( (lp>=0) && (rp>lp) ) {
546                                         SendMsg->mailfrom[rp] = 0;
547                                         memmove(SendMsg->mailfrom, 
548                                                 &SendMsg->mailfrom[lp + 1], 
549                                                 rp - lp);
550                                 }
551         
552                                 scan_done = 1;
553                         }
554                 } while (scan_done == 0);
555                 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
556                 stripallbut(SendMsg->mailfrom, '<', '>');
557                 SendMsg->envelope_from = SendMsg->mailfrom;
558         }
559
560         return 0;
561 }
562
563 void resolve_mx_hosts(SmtpOutMsg *SendMsg)
564 {
565         /// well this is blocking and sux, but libevent jsut supports async dns since v2
566         /* Figure out what mail exchanger host we have to connect to */
567         SendMsg->num_mxhosts = getmx(SendMsg->mxhosts, SendMsg->node);
568         CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Number of MX hosts for <%s> is %d [%s]\n", 
569                       SendMsg->n, SendMsg->node, SendMsg->num_mxhosts, SendMsg->mxhosts);
570         if (SendMsg->num_mxhosts < 1) {
571                 SendMsg->SMTPstatus = 5;
572                 StrBufPrintf(SendMsg->MyQEntry->StatusMessage, 
573                              "No MX hosts found for <%s>", SendMsg->node);
574                 return; ///////TODO: abort!
575         }
576
577 }
578 /* TODO: abort... */
579 #define SMTP_ERROR(WHICH_ERR, ERRSTR) {SendMsg->SMTPstatus = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, HKEY(ERRSTR), 0); return eAbort; }
580 #define SMTP_VERROR(WHICH_ERR) { SendMsg->SMTPstatus = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, &ChrPtr(SendMsg->IO.IOBuf)[4], -1, 0); return eAbort; }
581 #define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(SendMsg->IO.IOBuf)[0] == WHICH_STATE)
582
583 #define SMTP_DBG_SEND() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: > %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
584 #define SMTP_DBG_READ() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: < %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
585
586 void connect_one_smtpsrv(SmtpOutMsg *SendMsg)
587 {
588         char *endpart;
589         char buf[SIZ];
590
591         extract_token(buf, SendMsg->mxhosts, SendMsg->n_mx, '|', sizeof(buf));
592         strcpy(SendMsg->mx_user, "");
593         strcpy(SendMsg->mx_pass, "");
594         if (num_tokens(buf, '@') > 1) {
595                 strcpy (SendMsg->mx_user, buf);
596                 endpart = strrchr(SendMsg->mx_user, '@');
597                 *endpart = '\0';
598                 strcpy (SendMsg->mx_host, endpart + 1);
599                 endpart = strrchr(SendMsg->mx_user, ':');
600                 if (endpart != NULL) {
601                         strcpy(SendMsg->mx_pass, endpart+1);
602                         *endpart = '\0';
603                 }
604         }
605         else
606                 strcpy (SendMsg->mx_host, buf);
607         endpart = strrchr(SendMsg->mx_host, ':');
608         if (endpart != 0){
609                 *endpart = '\0';
610                 strcpy(SendMsg->mx_port, endpart + 1);
611         }               
612         else {
613                 strcpy(SendMsg->mx_port, "25");
614         }
615         CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connecting to %s : %s ...\n", 
616                       SendMsg->n, SendMsg->mx_host, SendMsg->mx_port);
617
618 }
619
620
621 int connect_one_smtpsrv_xamine_result(void *Ctx)
622 {
623         SmtpOutMsg *SendMsg = Ctx;
624         SendMsg->IO.SendBuf.fd = 
625         SendMsg->IO.RecvBuf.fd = 
626         SendMsg->IO.sock = sock_connect(SendMsg->mx_host, SendMsg->mx_port);
627
628         StrBufPrintf(SendMsg->MyQEntry->StatusMessage, 
629                      "Could not connect: %s", strerror(errno));
630         if (SendMsg->IO.sock >= 0) 
631         {
632                 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connected!\n", SendMsg->n);
633                 int fdflags; 
634                 fdflags = fcntl(SendMsg->IO.sock, F_GETFL);
635                 if (fdflags < 0)
636                         CtdlLogPrintf(CTDL_DEBUG,
637                                       "SMTP client[%ld]: unable to get socket flags! %s \n",
638                                       SendMsg->n, strerror(errno));
639                 fdflags = fdflags | O_NONBLOCK;
640                 if (fcntl(SendMsg->IO.sock, F_SETFL, fdflags) < 0)
641                         CtdlLogPrintf(CTDL_DEBUG,
642                                       "SMTP client[%ld]: unable to set socket nonblocking flags! %s \n",
643                                       SendMsg->n, strerror(errno));
644         }
645         if (SendMsg->IO.sock < 0) {
646                 if (errno > 0) {
647                         StrBufPlain(SendMsg->MyQEntry->StatusMessage, 
648                                     strerror(errno), -1);
649                 }
650                 else {
651                         StrBufPrintf(SendMsg->MyQEntry->StatusMessage, 
652                                      "Unable to connect to %s : %s\n", 
653                                      SendMsg->mx_host, SendMsg->mx_port);
654                 }
655         }
656         /// hier: naechsten mx ausprobieren.
657         if (SendMsg->IO.sock < 0) {
658                 SendMsg->SMTPstatus = 4;        /* dsn is already filled in */
659                 //// hier: abbrechen & bounce.
660                 return -1;
661         }
662
663
664         SendMsg->IO.SendBuf.Buf = NewStrBuf();
665         SendMsg->IO.RecvBuf.Buf = NewStrBuf();
666         SendMsg->IO.IOBuf = NewStrBuf();
667         InitEventIO(&SendMsg->IO, SendMsg, 
668                     SMTP_C_DispatchReadDone, 
669                     SMTP_C_DispatchWriteDone, 
670                     SMTP_C_ReadServerStatus,
671                     1);
672         return 0;
673 }
674
675 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
676 {
677         /* Process the SMTP greeting from the server */
678         SMTP_DBG_READ();
679
680         if (!SMTP_IS_STATE('2')) {
681                 if (SMTP_IS_STATE('4')) 
682                         SMTP_VERROR(4)
683                 else 
684                         SMTP_VERROR(5)
685         }
686         return eSendReply;
687 }
688
689 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
690 {
691         /* At this point we know we are talking to a real SMTP server */
692
693         /* Do a EHLO command.  If it fails, try the HELO command. */
694         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
695                      "EHLO %s\r\n", config.c_fqdn);
696
697         SMTP_DBG_SEND();
698         return eReadMessage;
699 }
700
701 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
702 {
703         SMTP_DBG_READ();
704
705         if (SMTP_IS_STATE('2')) {
706                 SendMsg->State ++;
707                 if (IsEmptyStr(SendMsg->mx_user))
708                         SendMsg->State ++; /* Skip auth... */
709         }
710         /* else we fall back to 'helo' */
711         return eSendReply;
712 }
713
714 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
715 {
716         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
717                      "HELO %s\r\n", config.c_fqdn);
718
719         SMTP_DBG_SEND();
720         return eReadMessage;
721 }
722
723 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
724 {
725         SMTP_DBG_READ();
726
727         if (!SMTP_IS_STATE('2')) {
728                 if (SMTP_IS_STATE('4'))
729                         SMTP_VERROR(4)
730                 else 
731                         SMTP_VERROR(5)
732         }
733         if (!IsEmptyStr(SendMsg->mx_user))
734                 SendMsg->State ++; /* Skip auth... */
735         return eSendReply;
736 }
737
738 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
739 {
740         char buf[SIZ];
741         char encoded[1024];
742
743         /* Do an AUTH command if necessary */
744         sprintf(buf, "%s%c%s%c%s", 
745                 SendMsg->mx_user, '\0', 
746                 SendMsg->mx_user, '\0', 
747                 SendMsg->mx_pass);
748         CtdlEncodeBase64(encoded, buf, 
749                          strlen(SendMsg->mx_user) + 
750                          strlen(SendMsg->mx_user) + 
751                          strlen(SendMsg->mx_pass) + 2, 0);
752         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
753                      "AUTH PLAIN %s\r\n", encoded);
754         
755         SMTP_DBG_SEND();
756         return eReadMessage;
757 }
758
759 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
760 {
761         /* Do an AUTH command if necessary */
762         
763         SMTP_DBG_READ();
764         
765         if (!SMTP_IS_STATE('2')) {
766                 if (SMTP_IS_STATE('4'))
767                         SMTP_VERROR(4)
768                 else 
769                         SMTP_VERROR(5)
770         }
771         return eSendReply;
772 }
773
774 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
775 {
776         /* previous command succeeded, now try the MAIL FROM: command */
777         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
778                      "MAIL FROM:<%s>\r\n", 
779                      SendMsg->envelope_from);
780
781         SMTP_DBG_SEND();
782         return eReadMessage;
783 }
784
785 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
786 {
787         SMTP_DBG_READ();
788
789         if (!SMTP_IS_STATE('2')) {
790                 if (SMTP_IS_STATE('4'))
791                         SMTP_VERROR(4)
792                 else 
793                         SMTP_VERROR(5)
794         }
795         return eSendReply;
796 }
797
798
799 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
800 {
801         /* MAIL succeeded, now try the RCPT To: command */
802         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
803                      "RCPT TO:<%s@%s>\r\n", 
804                      SendMsg->user, 
805                      SendMsg->node);
806
807         SMTP_DBG_SEND();
808         return eReadMessage;
809 }
810
811 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
812 {
813         SMTP_DBG_READ();
814
815         if (!SMTP_IS_STATE('2')) {
816                 if (SMTP_IS_STATE('4')) 
817                         SMTP_VERROR(4)
818                 else 
819                         SMTP_VERROR(5)
820         }
821         return eSendReply;
822 }
823
824 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
825 {
826         /* RCPT succeeded, now try the DATA command */
827         StrBufPlain(SendMsg->IO.SendBuf.Buf,
828                     HKEY("DATA\r\n"));
829
830         SMTP_DBG_SEND();
831         return eReadMessage;
832 }
833
834 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
835 {
836         SMTP_DBG_READ();
837
838         if (!SMTP_IS_STATE('3')) {
839                 if (SMTP_IS_STATE('4')) 
840                         SMTP_VERROR(3)
841                 else 
842                         SMTP_VERROR(5)
843         }
844         return eSendReply;
845 }
846
847 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
848 {
849         StrBuf *Buf;
850         /* If we reach this point, the server is expecting data.*/
851
852         Buf = SendMsg->IO.SendBuf.Buf;
853         SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
854         SendMsg->msgtext = Buf;
855         //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
856         SendMsg->State ++;
857
858         return eSendMore;
859 }
860
861 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
862 {
863         StrBuf *Buf;
864
865         Buf = SendMsg->IO.SendBuf.Buf;
866         SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
867         SendMsg->msgtext = Buf;
868
869         StrBufPlain(SendMsg->IO.SendBuf.Buf,
870                     HKEY(".\r\n"));
871
872         return eReadMessage;
873
874 }
875
876 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
877 {
878         SMTP_DBG_READ();
879
880         if (!SMTP_IS_STATE('2')) {
881                 if (SMTP_IS_STATE('4'))
882                         SMTP_VERROR(4)
883                 else 
884                         SMTP_VERROR(5)
885         }
886
887         /* We did it! */
888         StrBufPlain(SendMsg->MyQEntry->StatusMessage, 
889                     &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
890                     StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
891         SendMsg->SMTPstatus = 2;
892         return eSendReply;
893 }
894
895 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
896 {
897         StrBufPlain(SendMsg->IO.SendBuf.Buf,
898                     HKEY("QUIT\r\n"));
899
900         SMTP_DBG_SEND();
901         return eReadMessage;
902 }
903
904 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
905 {
906         SMTP_DBG_READ();
907
908         CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
909                       SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
910         return eSendReply;
911 }
912
913 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
914 {
915         return eSendReply;
916 }
917
918 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
919 {
920         return eReadMessage;
921 }
922
923 void smtp_try(OneQueItem *MyQItem, 
924               MailQEntry *MyQEntry, 
925               StrBuf *MsgText, 
926               int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
927 {
928         SmtpOutMsg * SendMsg;
929
930         SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
931         memset(SendMsg, 0, sizeof(SmtpOutMsg));
932         SendMsg->IO.sock = (-1);
933         SendMsg->n = MsgCount++;
934         SendMsg->MyQEntry = MyQEntry;
935         SendMsg->MyQItem = MyQItem;
936         SendMsg->msgtext = MsgText;
937
938         smtp_resolve_recipients(SendMsg);
939         resolve_mx_hosts(SendMsg);
940         connect_one_smtpsrv(SendMsg);
941         QueueEventContext(SendMsg, 
942                           &SendMsg->IO,
943                           connect_one_smtpsrv_xamine_result);
944 }
945
946
947
948 void NewMailQEntry(OneQueItem *Item)
949 {
950         Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
951         memset(Item->Current, 0, sizeof(MailQEntry));
952
953         if (Item->MailQEntries == NULL)
954                 Item->MailQEntries = NewHash(1, Flathash);
955         Item->Current->n = GetCount(Item->MailQEntries);
956         Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
957 }
958
959 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
960 {
961         Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
962 }
963
964 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
965 {
966         if (Item->EnvelopeFrom == NULL)
967                 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
968         StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
969 }
970
971 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
972 {
973         if (Item->BounceTo == NULL)
974                 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
975         StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
976 }
977
978 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
979 {
980         if (Item->Current == NULL)
981                 NewMailQEntry(Item);
982         if (Item->Current->Recipient == NULL)
983                 Item->Current->Recipient =  NewStrBufPlain(NULL, StrLength(Line));
984         StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
985         Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
986         StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
987 }
988
989
990 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
991 {
992         if (Item->Current == NULL)
993                 NewMailQEntry(Item);
994         if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
995                 Item->Current->nAttempts++;
996         if (Item->Current->nAttempts > MaxAttempts) {
997                 Item->FailNow = 1;
998                 return;
999         }
1000         Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1001 }
1002
1003 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1004 {
1005         if (Item->Current == NULL)
1006                 NewMailQEntry(Item);
1007         if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1008                 Item->Current->nAttempts++;
1009         if (Item->Current->nAttempts > MaxAttempts) {
1010                 Item->FailNow = 1;
1011                 return;
1012         }
1013                 
1014         Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1015         if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1016         {
1017                 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1018                 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1019                 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1020                         Item->LastAttempt.retry = SMTP_RETRY_MAX;
1021         }
1022 }
1023
1024
1025
1026
1027 /*
1028  * smtp_do_procmsg()
1029  *
1030  * Called by smtp_do_queue() to handle an individual message.
1031  */
1032 void smtp_do_procmsg(long msgnum, void *userdata) {
1033         struct CtdlMessage *msg = NULL;
1034         char *instr = NULL;     
1035         StrBuf *PlainQItem;
1036         OneQueItem *MyQItem;
1037         char *pch;
1038         HashPos  *It;
1039         void *vQE;
1040         long len;
1041         const char *Key;
1042
1043         CtdlLogPrintf(CTDL_DEBUG, "SMTP client: smtp_do_procmsg(%ld)\n", msgnum);
1044         ///strcpy(envelope_from, "");
1045
1046         msg = CtdlFetchMessage(msgnum, 1);
1047         if (msg == NULL) {
1048                 CtdlLogPrintf(CTDL_ERR, "SMTP client: tried %ld but no such message!\n", msgnum);
1049                 return;
1050         }
1051
1052         pch = instr = msg->cm_fields['M'];
1053
1054         /* Strip out the headers (no not amd any other non-instruction) line */
1055         while (pch != NULL) {
1056                 pch = strchr(pch, '\n');
1057                 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1058                         instr = pch + 2;
1059                         pch = NULL;
1060                 }
1061         }
1062         PlainQItem = NewStrBufPlain(instr, -1);
1063         CtdlFreeMessage(msg);
1064         MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1065         FreeStrBuf(&PlainQItem);
1066
1067         if (MyQItem == NULL)
1068                 return; /* s.b. else is already processing... */
1069
1070
1071         /*
1072          * Postpone delivery if we've already tried recently.
1073          * /
1074         if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1075                 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1076
1077                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1078                 citthread_mutex_lock(&ActiveQItemsLock);
1079                 {
1080                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1081                         DeleteEntryFromHash(ActiveQItems, It);
1082                 }
1083                 citthread_mutex_unlock(&ActiveQItemsLock);
1084                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1085                 DeleteHashPos(&It);
1086                 return;
1087         }// TODO: reenable me.*/
1088
1089         /*
1090          * Bail out if there's no actual message associated with this
1091          */
1092         if (MyQItem->MessageID < 0L) {
1093                 CtdlLogPrintf(CTDL_ERR, "SMTP client: no 'msgid' directive found!\n");
1094                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1095                 citthread_mutex_lock(&ActiveQItemsLock);
1096                 {
1097                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1098                         DeleteEntryFromHash(ActiveQItems, It);
1099                 }
1100                 citthread_mutex_unlock(&ActiveQItemsLock);
1101                 DeleteHashPos(&It);
1102                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1103                 return;
1104         }
1105
1106         CountActiveQueueEntries(MyQItem);
1107         if (MyQItem->ActiveDeliveries > 0)
1108         {
1109                 int i = 1;
1110                 StrBuf *Msg = smtp_load_msg(MyQItem);
1111                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1112                 while ((i <= MyQItem->ActiveDeliveries) && 
1113                        (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1114                 {
1115                         MailQEntry *ThisItem = vQE;
1116                         if (ThisItem->Active == 1) {
1117                                 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1118                                 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1119                                 i++;
1120                         }
1121                 }
1122                 DeleteHashPos(&It);
1123         }
1124         else 
1125         {
1126                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1127                 citthread_mutex_lock(&ActiveQItemsLock);
1128                 {
1129                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1130                         DeleteEntryFromHash(ActiveQItems, It);
1131                 }
1132                 citthread_mutex_unlock(&ActiveQItemsLock);
1133                 DeleteHashPos(&It);
1134                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1135
1136 // TODO: bounce & delete?
1137
1138         }
1139 }
1140
1141
1142 /*****************************************************************************/
1143 /*                          SMTP UTILITY COMMANDS                            */
1144 /*****************************************************************************/
1145
1146 void cmd_smtp(char *argbuf) {
1147         char cmd[64];
1148         char node[256];
1149         char buf[1024];
1150         int i;
1151         int num_mxhosts;
1152
1153         if (CtdlAccessCheck(ac_aide)) return;
1154
1155         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1156
1157         if (!strcasecmp(cmd, "mx")) {
1158                 extract_token(node, argbuf, 1, '|', sizeof node);
1159                 num_mxhosts = getmx(buf, node);
1160                 cprintf("%d %d MX hosts listed for %s\n",
1161                         LISTING_FOLLOWS, num_mxhosts, node);
1162                 for (i=0; i<num_mxhosts; ++i) {
1163                         extract_token(node, buf, i, '|', sizeof node);
1164                         cprintf("%s\n", node);
1165                 }
1166                 cprintf("000\n");
1167                 return;
1168         }
1169
1170         else if (!strcasecmp(cmd, "runqueue")) {
1171                 run_queue_now = 1;
1172                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1173                 return;
1174         }
1175
1176         else {
1177                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1178         }
1179
1180 }
1181
1182
1183 /*
1184  * smtp_queue_thread()
1185  * 
1186  * Run through the queue sending out messages.
1187  */
1188 void *smtp_queue_thread(void *arg) {
1189         int num_processed = 0;
1190         struct CitContext smtp_queue_CC;
1191
1192         CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1193         citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1194         CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1195
1196         while (!CtdlThreadCheckStop()) {
1197                 
1198                 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1199
1200                 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1201                         CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1202                 }
1203                 else {
1204                         num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1205                 }
1206                 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1207                 CtdlThreadSleep(60);
1208         }
1209
1210         CtdlClearSystemContext();
1211         return(NULL);
1212 }
1213
1214
1215 /*
1216  * Initialize the SMTP outbound queue
1217  */
1218 void smtp_init_spoolout(void) {
1219         struct ctdlroom qrbuf;
1220
1221         /*
1222          * Create the room.  This will silently fail if the room already
1223          * exists, and that's perfectly ok, because we want it to exist.
1224          */
1225         CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1226
1227         /*
1228          * Make sure it's set to be a "system room" so it doesn't show up
1229          * in the <K>nown rooms list for Aides.
1230          */
1231         if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1232                 qrbuf.QRflags2 |= QR2_SYSTEM;
1233                 CtdlPutRoomLock(&qrbuf);
1234         }
1235 }
1236
1237
1238 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1239         SMTPC_read_greeting,
1240         SMTPC_read_EHLO_reply,
1241         SMTPC_read_HELO_reply,
1242         SMTPC_read_auth_reply,
1243         SMTPC_read_FROM_reply,
1244         SMTPC_read_RCPT_reply,
1245         SMTPC_read_DATAcmd_reply,
1246         SMTPC_read_dummy,
1247         SMTPC_read_data_body_reply,
1248         SMTPC_read_QUIT_reply
1249 };
1250
1251 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1252         SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1253         SMTPC_send_EHLO,
1254         STMPC_send_HELO,
1255         SMTPC_send_auth,
1256         SMTPC_send_FROM,
1257         SMTPC_send_RCPT,
1258         SMTPC_send_DATAcmd,
1259         SMTPC_send_data_body,
1260         SMTPC_send_terminate_data_body,
1261         SMTPC_send_QUIT
1262 };
1263
1264 eNextState SMTP_C_DispatchReadDone(void *Data)
1265 {
1266         SmtpOutMsg *pMsg = Data;
1267         eNextState rc = ReadHandlers[pMsg->State](pMsg);
1268         pMsg->State++;
1269         return rc;
1270 }
1271
1272 eNextState SMTP_C_DispatchWriteDone(void *Data)
1273 {
1274         SmtpOutMsg *pMsg = Data;
1275         return SendHandlers[pMsg->State](pMsg);
1276         
1277 }
1278
1279
1280 #endif
1281 CTDL_MODULE_INIT(smtp_eventclient)
1282 {
1283 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1284         if (!threading)
1285         {
1286                 ActiveQItems = NewHash(1, Flathash);
1287                 citthread_mutex_init(&ActiveQItemsLock, NULL);
1288
1289                 QItemHandlers = NewHash(0, NULL);
1290
1291                 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1292                 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1293                 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1294                 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1295                 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1296                 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1297 ///submitted /TODO: flush qitemhandlers on exit
1298
1299
1300                 smtp_init_spoolout();
1301                 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1302
1303                 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1304         }
1305 #endif
1306         
1307         /* return our Subversion id for the Log */
1308         return "smtpeventclient";
1309 }