libevent Migration
[citadel.git] / citadel / modules / smtp / serv_smtpeventclient.c
1 /*
2  * This module is an SMTP and ESMTP implementation for the Citadel system.
3  * It is compliant with all of the following:
4  *
5  * RFC  821 - Simple Mail Transfer Protocol
6  * RFC  876 - Survey of SMTP Implementations
7  * RFC 1047 - Duplicate messages and SMTP
8  * RFC 1652 - 8 bit MIME
9  * RFC 1869 - Extended Simple Mail Transfer Protocol
10  * RFC 1870 - SMTP Service Extension for Message Size Declaration
11  * RFC 2033 - Local Mail Transfer Protocol
12  * RFC 2197 - SMTP Service Extension for Command Pipelining
13  * RFC 2476 - Message Submission
14  * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
15  * RFC 2554 - SMTP Service Extension for Authentication
16  * RFC 2821 - Simple Mail Transfer Protocol
17  * RFC 2822 - Internet Message Format
18  * RFC 2920 - SMTP Service Extension for Command Pipelining
19  *  
20  * The VRFY and EXPN commands have been removed from this implementation
21  * because nobody uses these commands anymore, except for spammers.
22  *
23  * Copyright (c) 1998-2009 by the citadel.org team
24  *
25  *  This program is free software; you can redistribute it and/or modify
26  *  it under the terms of the GNU General Public License as published by
27  *  the Free Software Foundation; either version 3 of the License, or
28  *  (at your option) any later version.
29  *
30  *  This program is distributed in the hope that it will be useful,
31  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
32  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
33  *  GNU General Public License for more details.
34  *
35  *  You should have received a copy of the GNU General Public License
36  *  along with this program; if not, write to the Free Software
37  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
38  */
39
40 #include "sysdep.h"
41 #include <stdlib.h>
42 #include <unistd.h>
43 #include <stdio.h>
44 #include <termios.h>
45 #include <fcntl.h>
46 #include <signal.h>
47 #include <pwd.h>
48 #include <errno.h>
49 #include <sys/types.h>
50 #include <syslog.h>
51
52 #if TIME_WITH_SYS_TIME
53 # include <sys/time.h>
54 # include <time.h>
55 #else
56 # if HAVE_SYS_TIME_H
57 #  include <sys/time.h>
58 # else
59 #  include <time.h>
60 # endif
61 #endif
62 #include <sys/wait.h>
63 #include <ctype.h>
64 #include <string.h>
65 #include <limits.h>
66 #include <sys/socket.h>
67 #include <netinet/in.h>
68 #include <arpa/inet.h>
69 #include <libcitadel.h>
70 #include "citadel.h"
71 #include "server.h"
72 #include "citserver.h"
73 #include "support.h"
74 #include "config.h"
75 #include "control.h"
76 #include "user_ops.h"
77 #include "database.h"
78 #include "msgbase.h"
79 #include "internet_addressing.h"
80 #include "genstamp.h"
81 #include "domain.h"
82 #include "clientsocket.h"
83 #include "locate_host.h"
84 #include "citadel_dirs.h"
85
86 #include "ctdl_module.h"
87
88 #include "smtp_util.h"
89 #include "event_client.h"
90
91 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
92 HashList *QItemHandlers = NULL;
93
94 citthread_mutex_t ActiveQItemsLock;
95 HashList *ActiveQItems = NULL;
96
97 int run_queue_now = 0;  /* Set to 1 to ignore SMTP send retry times */
98 int MsgCount = 0;
99 /*****************************************************************************/
100 /*               SMTP CLIENT (Queue Management) STUFF                        */
101 /*****************************************************************************/
102
103 #define MaxAttempts 15
104 typedef struct _delivery_attempt {
105         time_t when;
106         time_t retry;
107 }DeliveryAttempt;
108
109 typedef struct _mailq_entry {
110         DeliveryAttempt Attempts[MaxAttempts];
111         int nAttempts;
112         StrBuf *Recipient;
113         StrBuf *StatusMessage;
114         int Status;
115         int n;
116         int Active;
117 }MailQEntry;
118 void FreeMailQEntry(void *qv)
119 {
120         MailQEntry *Q = qv;
121         FreeStrBuf(&Q->Recipient);
122         FreeStrBuf(&Q->StatusMessage);
123         free(Q);
124 }
125
126 typedef struct queueitem {
127         long MessageID;
128         long QueMsgID;
129         int FailNow;
130         HashList *MailQEntries;
131         MailQEntry *Current; /* copy of the currently parsed item in the MailQEntries list; if null add a new one. */
132         DeliveryAttempt LastAttempt;
133         long ActiveDeliveries;
134         StrBuf *EnvelopeFrom;
135         StrBuf *BounceTo;
136 } OneQueItem;
137 typedef void (*QItemHandler)(OneQueItem *Item, StrBuf *Line, const char **Pos);
138
139 /*****************************************************************************/
140 /*               SMTP CLIENT (OUTBOUND PROCESSING) STUFF                     */
141 /*****************************************************************************/
142
143 typedef enum _eSMTP_C_States {
144         eConnect, 
145         eEHLO,
146         eHELO,
147         eSMTPAuth,
148         eFROM,
149         eRCPT,
150         eDATA,
151         eDATABody,
152         eDATATerminateBody,
153         eQUIT,
154         eMaxSMTPC
155 } eSMTP_C_States;
156
157 const long SMTP_C_ReadTimeouts[eMaxSMTPC] = {
158         90, /* Greeting... */
159         30, /* EHLO */
160         30, /* HELO */
161         30, /* Auth */
162         30, /* From */
163         30, /* RCPT */
164         30, /* DATA */
165         90, /* DATABody */
166         900, /* end of body... */
167         30  /* QUIT */
168 };
169 /*
170 const long SMTP_C_SendTimeouts[eMaxSMTPC] = {
171
172 }; */
173 const char *ReadErrors[eMaxSMTPC] = {
174         "Connection broken during SMTP conversation",
175         "Connection broken during SMTP EHLO",
176         "Connection broken during SMTP HELO",
177         "Connection broken during SMTP AUTH",
178         "Connection broken during SMTP MAIL FROM",
179         "Connection broken during SMTP RCPT",
180         "Connection broken during SMTP DATA",
181         "Connection broken during SMTP message transmit",
182         ""/* quit reply, don't care. */
183 };
184
185
186 typedef struct _stmp_out_msg {
187         MailQEntry *MyQEntry;
188         OneQueItem *MyQItem;
189         long n;
190         AsyncIO IO;
191
192         eSMTP_C_States State;
193
194         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 eNextState SMTP_C_Terminate(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                 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_Terminate,
671                     SMTP_C_ReadServerStatus,
672                     1);
673         return 0;
674 }
675
676 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
677 {
678         /* Process the SMTP greeting from the server */
679         SMTP_DBG_READ();
680
681         if (!SMTP_IS_STATE('2')) {
682                 if (SMTP_IS_STATE('4')) 
683                         SMTP_VERROR(4)
684                 else 
685                         SMTP_VERROR(5)
686         }
687         return eSendReply;
688 }
689
690 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
691 {
692         /* At this point we know we are talking to a real SMTP server */
693
694         /* Do a EHLO command.  If it fails, try the HELO command. */
695         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
696                      "EHLO %s\r\n", config.c_fqdn);
697
698         SMTP_DBG_SEND();
699         return eReadMessage;
700 }
701
702 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
703 {
704         SMTP_DBG_READ();
705
706         if (SMTP_IS_STATE('2')) {
707                 SendMsg->State ++;
708                 if (IsEmptyStr(SendMsg->mx_user))
709                         SendMsg->State ++; /* Skip auth... */
710         }
711         /* else we fall back to 'helo' */
712         return eSendReply;
713 }
714
715 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
716 {
717         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
718                      "HELO %s\r\n", config.c_fqdn);
719
720         SMTP_DBG_SEND();
721         return eReadMessage;
722 }
723
724 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
725 {
726         SMTP_DBG_READ();
727
728         if (!SMTP_IS_STATE('2')) {
729                 if (SMTP_IS_STATE('4'))
730                         SMTP_VERROR(4)
731                 else 
732                         SMTP_VERROR(5)
733         }
734         if (!IsEmptyStr(SendMsg->mx_user))
735                 SendMsg->State ++; /* Skip auth... */
736         return eSendReply;
737 }
738
739 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
740 {
741         char buf[SIZ];
742         char encoded[1024];
743
744         /* Do an AUTH command if necessary */
745         sprintf(buf, "%s%c%s%c%s", 
746                 SendMsg->mx_user, '\0', 
747                 SendMsg->mx_user, '\0', 
748                 SendMsg->mx_pass);
749         CtdlEncodeBase64(encoded, buf, 
750                          strlen(SendMsg->mx_user) + 
751                          strlen(SendMsg->mx_user) + 
752                          strlen(SendMsg->mx_pass) + 2, 0);
753         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
754                      "AUTH PLAIN %s\r\n", encoded);
755         
756         SMTP_DBG_SEND();
757         return eReadMessage;
758 }
759
760 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
761 {
762         /* Do an AUTH command if necessary */
763         
764         SMTP_DBG_READ();
765         
766         if (!SMTP_IS_STATE('2')) {
767                 if (SMTP_IS_STATE('4'))
768                         SMTP_VERROR(4)
769                 else 
770                         SMTP_VERROR(5)
771         }
772         return eSendReply;
773 }
774
775 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
776 {
777         /* previous command succeeded, now try the MAIL FROM: command */
778         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
779                      "MAIL FROM:<%s>\r\n", 
780                      SendMsg->envelope_from);
781
782         SMTP_DBG_SEND();
783         return eReadMessage;
784 }
785
786 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
787 {
788         SMTP_DBG_READ();
789
790         if (!SMTP_IS_STATE('2')) {
791                 if (SMTP_IS_STATE('4'))
792                         SMTP_VERROR(4)
793                 else 
794                         SMTP_VERROR(5)
795         }
796         return eSendReply;
797 }
798
799
800 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
801 {
802         /* MAIL succeeded, now try the RCPT To: command */
803         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
804                      "RCPT TO:<%s@%s>\r\n", 
805                      SendMsg->user, 
806                      SendMsg->node);
807
808         SMTP_DBG_SEND();
809         return eReadMessage;
810 }
811
812 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
813 {
814         SMTP_DBG_READ();
815
816         if (!SMTP_IS_STATE('2')) {
817                 if (SMTP_IS_STATE('4')) 
818                         SMTP_VERROR(4)
819                 else 
820                         SMTP_VERROR(5)
821         }
822         return eSendReply;
823 }
824
825 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
826 {
827         /* RCPT succeeded, now try the DATA command */
828         StrBufPlain(SendMsg->IO.SendBuf.Buf,
829                     HKEY("DATA\r\n"));
830
831         SMTP_DBG_SEND();
832         return eReadMessage;
833 }
834
835 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
836 {
837         SMTP_DBG_READ();
838
839         if (!SMTP_IS_STATE('3')) {
840                 if (SMTP_IS_STATE('4')) 
841                         SMTP_VERROR(3)
842                 else 
843                         SMTP_VERROR(5)
844         }
845         return eSendReply;
846 }
847
848 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
849 {
850         StrBuf *Buf;
851         /* If we reach this point, the server is expecting data.*/
852
853         Buf = SendMsg->IO.SendBuf.Buf;
854         SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
855         SendMsg->msgtext = Buf;
856         //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
857         SendMsg->State ++;
858
859         return eSendMore;
860 }
861
862 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
863 {
864         StrBuf *Buf;
865
866         Buf = SendMsg->IO.SendBuf.Buf;
867         SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
868         SendMsg->msgtext = Buf;
869
870         StrBufPlain(SendMsg->IO.SendBuf.Buf,
871                     HKEY(".\r\n"));
872
873         return eReadMessage;
874
875 }
876
877 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
878 {
879         SMTP_DBG_READ();
880
881         if (!SMTP_IS_STATE('2')) {
882                 if (SMTP_IS_STATE('4'))
883                         SMTP_VERROR(4)
884                 else 
885                         SMTP_VERROR(5)
886         }
887
888         /* We did it! */
889         StrBufPlain(SendMsg->MyQEntry->StatusMessage, 
890                     &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
891                     StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
892         SendMsg->SMTPstatus = 2;
893         return eSendReply;
894 }
895
896 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
897 {
898         StrBufPlain(SendMsg->IO.SendBuf.Buf,
899                     HKEY("QUIT\r\n"));
900
901         SMTP_DBG_SEND();
902         return eReadMessage;
903 }
904
905 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
906 {
907         SMTP_DBG_READ();
908
909         CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
910                       SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
911         return eTerminateConnection;
912 }
913
914 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
915 {
916         return eSendReply;
917 }
918
919 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
920 {
921         return eReadMessage;
922 }
923
924 void smtp_try(OneQueItem *MyQItem, 
925               MailQEntry *MyQEntry, 
926               StrBuf *MsgText, 
927               int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
928 {
929         SmtpOutMsg * SendMsg;
930
931         SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
932         memset(SendMsg, 0, sizeof(SmtpOutMsg));
933         SendMsg->IO.sock = (-1);
934         SendMsg->n = MsgCount++;
935         SendMsg->MyQEntry = MyQEntry;
936         SendMsg->MyQItem = MyQItem;
937         SendMsg->msgtext = MsgText;
938
939         smtp_resolve_recipients(SendMsg);
940         resolve_mx_hosts(SendMsg);
941         connect_one_smtpsrv(SendMsg);
942         QueueEventContext(SendMsg, 
943                           &SendMsg->IO,
944                           connect_one_smtpsrv_xamine_result);
945 }
946
947
948
949 void NewMailQEntry(OneQueItem *Item)
950 {
951         Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
952         memset(Item->Current, 0, sizeof(MailQEntry));
953
954         if (Item->MailQEntries == NULL)
955                 Item->MailQEntries = NewHash(1, Flathash);
956         Item->Current->n = GetCount(Item->MailQEntries);
957         Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
958 }
959
960 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
961 {
962         Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
963 }
964
965 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
966 {
967         if (Item->EnvelopeFrom == NULL)
968                 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
969         StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
970 }
971
972 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
973 {
974         if (Item->BounceTo == NULL)
975                 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
976         StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
977 }
978
979 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
980 {
981         if (Item->Current == NULL)
982                 NewMailQEntry(Item);
983         if (Item->Current->Recipient == NULL)
984                 Item->Current->Recipient =  NewStrBufPlain(NULL, StrLength(Line));
985         StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
986         Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
987         StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
988 }
989
990
991 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
992 {
993         if (Item->Current == NULL)
994                 NewMailQEntry(Item);
995         if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
996                 Item->Current->nAttempts++;
997         if (Item->Current->nAttempts > MaxAttempts) {
998                 Item->FailNow = 1;
999                 return;
1000         }
1001         Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1002 }
1003
1004 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1005 {
1006         if (Item->Current == NULL)
1007                 NewMailQEntry(Item);
1008         if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1009                 Item->Current->nAttempts++;
1010         if (Item->Current->nAttempts > MaxAttempts) {
1011                 Item->FailNow = 1;
1012                 return;
1013         }
1014                 
1015         Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1016         if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1017         {
1018                 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1019                 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1020                 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1021                         Item->LastAttempt.retry = SMTP_RETRY_MAX;
1022         }
1023 }
1024
1025
1026
1027
1028 /*
1029  * smtp_do_procmsg()
1030  *
1031  * Called by smtp_do_queue() to handle an individual message.
1032  */
1033 void smtp_do_procmsg(long msgnum, void *userdata) {
1034         struct CtdlMessage *msg = NULL;
1035         char *instr = NULL;     
1036         StrBuf *PlainQItem;
1037         OneQueItem *MyQItem;
1038         char *pch;
1039         HashPos  *It;
1040         void *vQE;
1041         long len;
1042         const char *Key;
1043
1044         CtdlLogPrintf(CTDL_DEBUG, "SMTP client: smtp_do_procmsg(%ld)\n", msgnum);
1045         ///strcpy(envelope_from, "");
1046
1047         msg = CtdlFetchMessage(msgnum, 1);
1048         if (msg == NULL) {
1049                 CtdlLogPrintf(CTDL_ERR, "SMTP client: tried %ld but no such message!\n", msgnum);
1050                 return;
1051         }
1052
1053         pch = instr = msg->cm_fields['M'];
1054
1055         /* Strip out the headers (no not amd any other non-instruction) line */
1056         while (pch != NULL) {
1057                 pch = strchr(pch, '\n');
1058                 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1059                         instr = pch + 2;
1060                         pch = NULL;
1061                 }
1062         }
1063         PlainQItem = NewStrBufPlain(instr, -1);
1064         CtdlFreeMessage(msg);
1065         MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1066         FreeStrBuf(&PlainQItem);
1067
1068         if (MyQItem == NULL)
1069                 return; /* s.b. else is already processing... */
1070
1071
1072         /*
1073          * Postpone delivery if we've already tried recently.
1074          * /
1075         if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1076                 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1077
1078                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1079                 citthread_mutex_lock(&ActiveQItemsLock);
1080                 {
1081                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1082                         DeleteEntryFromHash(ActiveQItems, It);
1083                 }
1084                 citthread_mutex_unlock(&ActiveQItemsLock);
1085                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1086                 DeleteHashPos(&It);
1087                 return;
1088         }// TODO: reenable me.*/
1089
1090         /*
1091          * Bail out if there's no actual message associated with this
1092          */
1093         if (MyQItem->MessageID < 0L) {
1094                 CtdlLogPrintf(CTDL_ERR, "SMTP client: no 'msgid' directive found!\n");
1095                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1096                 citthread_mutex_lock(&ActiveQItemsLock);
1097                 {
1098                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1099                         DeleteEntryFromHash(ActiveQItems, It);
1100                 }
1101                 citthread_mutex_unlock(&ActiveQItemsLock);
1102                 DeleteHashPos(&It);
1103                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1104                 return;
1105         }
1106
1107         CountActiveQueueEntries(MyQItem);
1108         if (MyQItem->ActiveDeliveries > 0)
1109         {
1110                 int i = 1;
1111                 StrBuf *Msg = smtp_load_msg(MyQItem);
1112                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1113                 while ((i <= MyQItem->ActiveDeliveries) && 
1114                        (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1115                 {
1116                         MailQEntry *ThisItem = vQE;
1117                         if (ThisItem->Active == 1) {
1118                                 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1119                                 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1120                                 i++;
1121                         }
1122                 }
1123                 DeleteHashPos(&It);
1124         }
1125         else 
1126         {
1127                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1128                 citthread_mutex_lock(&ActiveQItemsLock);
1129                 {
1130                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1131                         DeleteEntryFromHash(ActiveQItems, It);
1132                 }
1133                 citthread_mutex_unlock(&ActiveQItemsLock);
1134                 DeleteHashPos(&It);
1135                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1136
1137 // TODO: bounce & delete?
1138
1139         }
1140 }
1141
1142
1143 /*****************************************************************************/
1144 /*                          SMTP UTILITY COMMANDS                            */
1145 /*****************************************************************************/
1146
1147 void cmd_smtp(char *argbuf) {
1148         char cmd[64];
1149         char node[256];
1150         char buf[1024];
1151         int i;
1152         int num_mxhosts;
1153
1154         if (CtdlAccessCheck(ac_aide)) return;
1155
1156         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1157
1158         if (!strcasecmp(cmd, "mx")) {
1159                 extract_token(node, argbuf, 1, '|', sizeof node);
1160                 num_mxhosts = getmx(buf, node);
1161                 cprintf("%d %d MX hosts listed for %s\n",
1162                         LISTING_FOLLOWS, num_mxhosts, node);
1163                 for (i=0; i<num_mxhosts; ++i) {
1164                         extract_token(node, buf, i, '|', sizeof node);
1165                         cprintf("%s\n", node);
1166                 }
1167                 cprintf("000\n");
1168                 return;
1169         }
1170
1171         else if (!strcasecmp(cmd, "runqueue")) {
1172                 run_queue_now = 1;
1173                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1174                 return;
1175         }
1176
1177         else {
1178                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1179         }
1180
1181 }
1182
1183
1184 /*
1185  * smtp_queue_thread()
1186  * 
1187  * Run through the queue sending out messages.
1188  */
1189 void *smtp_queue_thread(void *arg) {
1190         int num_processed = 0;
1191         struct CitContext smtp_queue_CC;
1192
1193         CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1194         citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1195         CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1196
1197         while (!CtdlThreadCheckStop()) {
1198                 
1199                 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1200
1201                 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1202                         CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1203                 }
1204                 else {
1205                         num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1206                 }
1207                 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1208                 CtdlThreadSleep(60);
1209         }
1210
1211         CtdlClearSystemContext();
1212         return(NULL);
1213 }
1214
1215
1216 /*
1217  * Initialize the SMTP outbound queue
1218  */
1219 void smtp_init_spoolout(void) {
1220         struct ctdlroom qrbuf;
1221
1222         /*
1223          * Create the room.  This will silently fail if the room already
1224          * exists, and that's perfectly ok, because we want it to exist.
1225          */
1226         CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1227
1228         /*
1229          * Make sure it's set to be a "system room" so it doesn't show up
1230          * in the <K>nown rooms list for Aides.
1231          */
1232         if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1233                 qrbuf.QRflags2 |= QR2_SYSTEM;
1234                 CtdlPutRoomLock(&qrbuf);
1235         }
1236 }
1237
1238
1239 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1240         SMTPC_read_greeting,
1241         SMTPC_read_EHLO_reply,
1242         SMTPC_read_HELO_reply,
1243         SMTPC_read_auth_reply,
1244         SMTPC_read_FROM_reply,
1245         SMTPC_read_RCPT_reply,
1246         SMTPC_read_DATAcmd_reply,
1247         SMTPC_read_dummy,
1248         SMTPC_read_data_body_reply,
1249         SMTPC_read_QUIT_reply
1250 };
1251
1252 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1253         SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1254         SMTPC_send_EHLO,
1255         STMPC_send_HELO,
1256         SMTPC_send_auth,
1257         SMTPC_send_FROM,
1258         SMTPC_send_RCPT,
1259         SMTPC_send_DATAcmd,
1260         SMTPC_send_data_body,
1261         SMTPC_send_terminate_data_body,
1262         SMTPC_send_QUIT
1263 };
1264
1265 eNextState SMTP_C_Terminate(void *Data)
1266 {
1267         SmtpOutMsg *pMsg = Data;
1268         FinalizeMessageSend(pMsg);
1269
1270 }
1271 eNextState SMTP_C_DispatchReadDone(void *Data)
1272 {
1273         SmtpOutMsg *pMsg = Data;
1274         eNextState rc = ReadHandlers[pMsg->State](pMsg);
1275         pMsg->State++;
1276         return rc;
1277 }
1278
1279 eNextState SMTP_C_DispatchWriteDone(void *Data)
1280 {
1281         SmtpOutMsg *pMsg = Data;
1282         return SendHandlers[pMsg->State](pMsg);
1283         
1284 }
1285
1286
1287 #endif
1288 CTDL_MODULE_INIT(smtp_eventclient)
1289 {
1290 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1291         if (!threading)
1292         {
1293                 ActiveQItems = NewHash(1, Flathash);
1294                 citthread_mutex_init(&ActiveQItemsLock, NULL);
1295
1296                 QItemHandlers = NewHash(0, NULL);
1297
1298                 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1299                 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1300                 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1301                 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1302                 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1303                 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1304 ///submitted /TODO: flush qitemhandlers on exit
1305
1306
1307                 smtp_init_spoolout();
1308                 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1309
1310                 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1311         }
1312 #endif
1313         
1314         /* return our Subversion id for the Log */
1315         return "smtpeventclient";
1316 }