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