libc-ares 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 eNextState smtp_resolve_one_smtpsrv_start(void *data)
928 {
929         AsyncIO *IO = data;
930         SmtpOutMsg * SendMsg = IO->Data;
931 ///     resolve_mx_hosts(SendMsg);
932         //// connect_one_smtpsrv_xamine_result
933
934         connect_one_smtpsrv(SendMsg);
935 }
936
937 int resolve_mx_records(void *Ctx)
938 {
939         SmtpOutMsg * SendMsg = Ctx;
940         if (!QueueQuery(ns_t_mx, 
941                         SendMsg->node, 
942                         &SendMsg->IO, 
943                         smtp_resolve_one_smtpsrv_start))
944         {
945                 /// TODO: abort
946         }
947 }
948
949 void smtp_try(OneQueItem *MyQItem, 
950               MailQEntry *MyQEntry, 
951               StrBuf *MsgText, 
952               int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
953 {
954         SmtpOutMsg * SendMsg;
955
956         SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
957         memset(SendMsg, 0, sizeof(SmtpOutMsg));
958         SendMsg->IO.sock = (-1);
959         SendMsg->n = MsgCount++;
960         SendMsg->MyQEntry = MyQEntry;
961         SendMsg->MyQItem = MyQItem;
962         if (KeepMsgText)
963                 SendMsg->msgtext = MsgText;
964         else 
965                 SendMsg->msgtext = NewStrBufDup(MsgText);
966
967         smtp_resolve_recipients(SendMsg);
968
969         QueueEventContext(SendMsg, 
970                           &SendMsg->IO,
971                           resolve_mx_records);
972
973
974 }
975
976
977
978 void NewMailQEntry(OneQueItem *Item)
979 {
980         Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
981         memset(Item->Current, 0, sizeof(MailQEntry));
982
983         if (Item->MailQEntries == NULL)
984                 Item->MailQEntries = NewHash(1, Flathash);
985         Item->Current->n = GetCount(Item->MailQEntries);
986         Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
987 }
988
989 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
990 {
991         Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
992 }
993
994 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
995 {
996         if (Item->EnvelopeFrom == NULL)
997                 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
998         StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
999 }
1000
1001 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
1002 {
1003         if (Item->BounceTo == NULL)
1004                 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
1005         StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
1006 }
1007
1008 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
1009 {
1010         if (Item->Current == NULL)
1011                 NewMailQEntry(Item);
1012         if (Item->Current->Recipient == NULL)
1013                 Item->Current->Recipient =  NewStrBufPlain(NULL, StrLength(Line));
1014         StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
1015         Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
1016         StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
1017         Item->Current = NULL; // TODO: is this always right?
1018 }
1019
1020
1021 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
1022 {
1023         if (Item->Current == NULL)
1024                 NewMailQEntry(Item);
1025         if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
1026                 Item->Current->nAttempts++;
1027         if (Item->Current->nAttempts > MaxAttempts) {
1028                 Item->FailNow = 1;
1029                 return;
1030         }
1031         Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1032 }
1033
1034 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1035 {
1036         if (Item->Current == NULL)
1037                 NewMailQEntry(Item);
1038         if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1039                 Item->Current->nAttempts++;
1040         if (Item->Current->nAttempts > MaxAttempts) {
1041                 Item->FailNow = 1;
1042                 return;
1043         }
1044                 
1045         Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1046         if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1047         {
1048                 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1049                 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1050                 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1051                         Item->LastAttempt.retry = SMTP_RETRY_MAX;
1052         }
1053 }
1054
1055
1056
1057
1058 /*
1059  * smtp_do_procmsg()
1060  *
1061  * Called by smtp_do_queue() to handle an individual message.
1062  */
1063 void smtp_do_procmsg(long msgnum, void *userdata) {
1064         struct CtdlMessage *msg = NULL;
1065         char *instr = NULL;     
1066         StrBuf *PlainQItem;
1067         OneQueItem *MyQItem;
1068         char *pch;
1069         HashPos  *It;
1070         void *vQE;
1071         long len;
1072         const char *Key;
1073
1074         CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
1075         ///strcpy(envelope_from, "");
1076
1077         msg = CtdlFetchMessage(msgnum, 1);
1078         if (msg == NULL) {
1079                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
1080                 return;
1081         }
1082
1083         pch = instr = msg->cm_fields['M'];
1084
1085         /* Strip out the headers (no not amd any other non-instruction) line */
1086         while (pch != NULL) {
1087                 pch = strchr(pch, '\n');
1088                 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1089                         instr = pch + 2;
1090                         pch = NULL;
1091                 }
1092         }
1093         PlainQItem = NewStrBufPlain(instr, -1);
1094         CtdlFreeMessage(msg);
1095         MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1096         FreeStrBuf(&PlainQItem);
1097
1098         if (MyQItem == NULL) {
1099                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);              
1100                 return; /* s.b. else is already processing... */
1101         }
1102
1103         /*
1104          * Postpone delivery if we've already tried recently.
1105          * /
1106         if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1107                 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1108
1109                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1110                 citthread_mutex_lock(&ActiveQItemsLock);
1111                 {
1112                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1113                         DeleteEntryFromHash(ActiveQItems, It);
1114                 }
1115                 citthread_mutex_unlock(&ActiveQItemsLock);
1116                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1117                 DeleteHashPos(&It);
1118                 return;
1119         }// TODO: reenable me.*/
1120
1121         /*
1122          * Bail out if there's no actual message associated with this
1123          */
1124         if (MyQItem->MessageID < 0L) {
1125                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
1126                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1127                 citthread_mutex_lock(&ActiveQItemsLock);
1128                 {
1129                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1130                         DeleteEntryFromHash(ActiveQItems, It);
1131                 }
1132                 citthread_mutex_unlock(&ActiveQItemsLock);
1133                 DeleteHashPos(&It);
1134                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1135                 return;
1136         }
1137
1138         It = GetNewHashPos(MyQItem->MailQEntries, 0);
1139         while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
1140         {
1141                 MailQEntry *ThisItem = vQE;
1142                 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
1143         }
1144         DeleteHashPos(&It);
1145
1146         CountActiveQueueEntries(MyQItem);
1147         if (MyQItem->ActiveDeliveries > 0)
1148         {
1149                 int i = 1;
1150                 StrBuf *Msg = smtp_load_msg(MyQItem);
1151                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1152                 while ((i <= MyQItem->ActiveDeliveries) && 
1153                        (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1154                 {
1155                         MailQEntry *ThisItem = vQE;
1156                         if (ThisItem->Active == 1) {
1157                                 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1158                                 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1159                                 i++;
1160                         }
1161                 }
1162                 DeleteHashPos(&It);
1163         }
1164         else 
1165         {
1166                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1167                 citthread_mutex_lock(&ActiveQItemsLock);
1168                 {
1169                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1170                         DeleteEntryFromHash(ActiveQItems, It);
1171                 }
1172                 citthread_mutex_unlock(&ActiveQItemsLock);
1173                 DeleteHashPos(&It);
1174                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1175
1176 // TODO: bounce & delete?
1177
1178         }
1179 }
1180
1181
1182 /*****************************************************************************/
1183 /*                          SMTP UTILITY COMMANDS                            */
1184 /*****************************************************************************/
1185
1186 void cmd_smtp(char *argbuf) {
1187         char cmd[64];
1188         char node[256];
1189         char buf[1024];
1190         int i;
1191         int num_mxhosts;
1192
1193         if (CtdlAccessCheck(ac_aide)) return;
1194
1195         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1196
1197         if (!strcasecmp(cmd, "mx")) {
1198                 extract_token(node, argbuf, 1, '|', sizeof node);
1199                 num_mxhosts = getmx(buf, node);
1200                 cprintf("%d %d MX hosts listed for %s\n",
1201                         LISTING_FOLLOWS, num_mxhosts, node);
1202                 for (i=0; i<num_mxhosts; ++i) {
1203                         extract_token(node, buf, i, '|', sizeof node);
1204                         cprintf("%s\n", node);
1205                 }
1206                 cprintf("000\n");
1207                 return;
1208         }
1209
1210         else if (!strcasecmp(cmd, "runqueue")) {
1211                 run_queue_now = 1;
1212                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1213                 return;
1214         }
1215
1216         else {
1217                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1218         }
1219
1220 }
1221
1222
1223 /*
1224  * smtp_queue_thread()
1225  * 
1226  * Run through the queue sending out messages.
1227  */
1228 void *smtp_queue_thread(void *arg) {
1229         int num_processed = 0;
1230         struct CitContext smtp_queue_CC;
1231
1232         CtdlThreadSleep(10);
1233
1234         CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1235         citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1236         CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1237
1238         while (!CtdlThreadCheckStop()) {
1239                 
1240                 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1241
1242                 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1243                         CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1244                 }
1245                 else {
1246                         num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1247                 }
1248                 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1249                 CtdlThreadSleep(60);
1250         }
1251
1252         CtdlClearSystemContext();
1253         return(NULL);
1254 }
1255
1256
1257 /*
1258  * Initialize the SMTP outbound queue
1259  */
1260 void smtp_init_spoolout(void) {
1261         struct ctdlroom qrbuf;
1262
1263         /*
1264          * Create the room.  This will silently fail if the room already
1265          * exists, and that's perfectly ok, because we want it to exist.
1266          */
1267         CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1268
1269         /*
1270          * Make sure it's set to be a "system room" so it doesn't show up
1271          * in the <K>nown rooms list for Aides.
1272          */
1273         if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1274                 qrbuf.QRflags2 |= QR2_SYSTEM;
1275                 CtdlPutRoomLock(&qrbuf);
1276         }
1277 }
1278
1279
1280 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1281         SMTPC_read_greeting,
1282         SMTPC_read_EHLO_reply,
1283         SMTPC_read_HELO_reply,
1284         SMTPC_read_auth_reply,
1285         SMTPC_read_FROM_reply,
1286         SMTPC_read_RCPT_reply,
1287         SMTPC_read_DATAcmd_reply,
1288         SMTPC_read_dummy,
1289         SMTPC_read_data_body_reply,
1290         SMTPC_read_QUIT_reply
1291 };
1292
1293 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1294         SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1295         SMTPC_send_EHLO,
1296         STMPC_send_HELO,
1297         SMTPC_send_auth,
1298         SMTPC_send_FROM,
1299         SMTPC_send_RCPT,
1300         SMTPC_send_DATAcmd,
1301         SMTPC_send_data_body,
1302         SMTPC_send_terminate_data_body,
1303         SMTPC_send_QUIT
1304 };
1305
1306 eNextState SMTP_C_Terminate(void *Data)
1307 {
1308         SmtpOutMsg *pMsg = Data;
1309         FinalizeMessageSend(pMsg);
1310
1311 }
1312
1313 eNextState SMTP_C_Timeout(void *Data)
1314 {
1315         SmtpOutMsg *pMsg = Data;
1316         FinalizeMessageSend(pMsg);
1317
1318 }
1319
1320 eNextState SMTP_C_ConnFail(void *Data)
1321 {
1322         SmtpOutMsg *pMsg = Data;
1323         FinalizeMessageSend(pMsg);
1324
1325 }
1326
1327 eNextState SMTP_C_DispatchReadDone(void *Data)
1328 {
1329         SmtpOutMsg *pMsg = Data;
1330         eNextState rc = ReadHandlers[pMsg->State](pMsg);
1331         pMsg->State++;
1332         return rc;
1333 }
1334
1335 eNextState SMTP_C_DispatchWriteDone(void *Data)
1336 {
1337         SmtpOutMsg *pMsg = Data;
1338         return SendHandlers[pMsg->State](pMsg);
1339         
1340 }
1341
1342 eNextState SMTP_C_MXLookup(void *Data)
1343 {
1344
1345 }
1346
1347
1348 #endif
1349 CTDL_MODULE_INIT(smtp_eventclient)
1350 {
1351 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1352         if (!threading)
1353         {
1354                 ActiveQItems = NewHash(1, Flathash);
1355                 citthread_mutex_init(&ActiveQItemsLock, NULL);
1356
1357                 QItemHandlers = NewHash(0, NULL);
1358
1359                 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1360                 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1361                 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1362                 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1363                 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1364                 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1365 ///submitted /TODO: flush qitemhandlers on exit
1366
1367
1368                 smtp_init_spoolout();
1369                 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1370
1371                 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1372         }
1373 #endif
1374         
1375         /* return our Subversion id for the Log */
1376         return "smtpeventclient";
1377 }