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