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