libev migration
[citadel.git] / citadel / modules / smtp / serv_smtpeventclient.c
1 /*
2  * This module is an SMTP and ESMTP implementation for the Citadel system.
3  * It is compliant with all of the following:
4  *
5  * RFC  821 - Simple Mail Transfer Protocol
6  * RFC  876 - Survey of SMTP Implementations
7  * RFC 1047 - Duplicate messages and SMTP
8  * RFC 1652 - 8 bit MIME
9  * RFC 1869 - Extended Simple Mail Transfer Protocol
10  * RFC 1870 - SMTP Service Extension for Message Size Declaration
11  * RFC 2033 - Local Mail Transfer Protocol
12  * RFC 2197 - SMTP Service Extension for Command Pipelining
13  * RFC 2476 - Message Submission
14  * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
15  * RFC 2554 - SMTP Service Extension for Authentication
16  * RFC 2821 - Simple Mail Transfer Protocol
17  * RFC 2822 - Internet Message Format
18  * RFC 2920 - SMTP Service Extension for Command Pipelining
19  *  
20  * The VRFY and EXPN commands have been removed from this implementation
21  * because nobody uses these commands anymore, except for spammers.
22  *
23  * Copyright (c) 1998-2009 by the citadel.org team
24  *
25  *  This program is free software; you can redistribute it and/or modify
26  *  it under the terms of the GNU General Public License as published by
27  *  the Free Software Foundation; either version 3 of the License, or
28  *  (at your option) any later version.
29  *
30  *  This program is distributed in the hope that it will be useful,
31  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
32  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
33  *  GNU General Public License for more details.
34  *
35  *  You should have received a copy of the GNU General Public License
36  *  along with this program; if not, write to the Free Software
37  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
38  */
39
40 #include "sysdep.h"
41 #include <stdlib.h>
42 #include <unistd.h>
43 #include <stdio.h>
44 #include <termios.h>
45 #include <fcntl.h>
46 #include <signal.h>
47 #include <pwd.h>
48 #include <errno.h>
49 #include <sys/types.h>
50 #include <syslog.h>
51
52 #if TIME_WITH_SYS_TIME
53 # include <sys/time.h>
54 # include <time.h>
55 #else
56 # if HAVE_SYS_TIME_H
57 #  include <sys/time.h>
58 # else
59 #  include <time.h>
60 # endif
61 #endif
62 #include <sys/wait.h>
63 #include <ctype.h>
64 #include <string.h>
65 #include <limits.h>
66 #include <sys/socket.h>
67 #include <netinet/in.h>
68 #include <arpa/inet.h>
69 #include <libcitadel.h>
70 #include "citadel.h"
71 #include "server.h"
72 #include "citserver.h"
73 #include "support.h"
74 #include "config.h"
75 #include "control.h"
76 #include "user_ops.h"
77 #include "database.h"
78 #include "msgbase.h"
79 #include "internet_addressing.h"
80 #include "genstamp.h"
81 #include "domain.h"
82 #include "clientsocket.h"
83 #include "locate_host.h"
84 #include "citadel_dirs.h"
85
86 #include "ctdl_module.h"
87
88 #include "smtp_util.h"
89 #include "event_client.h"
90
91 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
92 HashList *QItemHandlers = NULL;
93
94 citthread_mutex_t ActiveQItemsLock;
95 HashList *ActiveQItems = NULL;
96
97 int run_queue_now = 0;  /* Set to 1 to ignore SMTP send retry times */
98 int MsgCount = 0;
99 /*****************************************************************************/
100 /*               SMTP CLIENT (Queue Management) STUFF                        */
101 /*****************************************************************************/
102
103 #define MaxAttempts 15
104 typedef struct _delivery_attempt {
105         time_t when;
106         time_t retry;
107 }DeliveryAttempt;
108
109 typedef struct _mailq_entry {
110         DeliveryAttempt Attempts[MaxAttempts];
111         int nAttempts;
112         StrBuf *Recipient;
113         StrBuf *StatusMessage;
114         int Status;
115         int n;
116         int Active;
117 }MailQEntry;
118 void FreeMailQEntry(void *qv)
119 {
120         MailQEntry *Q = qv;
121         FreeStrBuf(&Q->Recipient);
122         FreeStrBuf(&Q->StatusMessage);
123         free(Q);
124 }
125
126 typedef struct queueitem {
127         long MessageID;
128         long QueMsgID;
129         int FailNow;
130         HashList *MailQEntries;
131         MailQEntry *Current; /* copy of the currently parsed item in the MailQEntries list; if null add a new one. */
132         DeliveryAttempt LastAttempt;
133         long ActiveDeliveries;
134         StrBuf *EnvelopeFrom;
135         StrBuf *BounceTo;
136 } OneQueItem;
137 typedef void (*QItemHandler)(OneQueItem *Item, StrBuf *Line, const char **Pos);
138
139 /*****************************************************************************/
140 /*               SMTP CLIENT (OUTBOUND PROCESSING) STUFF                     */
141 /*****************************************************************************/
142
143 typedef enum _eSMTP_C_States {
144         eConnect, 
145         eEHLO,
146         eHELO,
147         eSMTPAuth,
148         eFROM,
149         eRCPT,
150         eDATA,
151         eDATABody,
152         eDATATerminateBody,
153         eQUIT,
154         eMaxSMTPC
155 } eSMTP_C_States;
156
157 const long SMTP_C_ReadTimeouts[eMaxSMTPC] = {
158         90, /* Greeting... */
159         30, /* EHLO */
160         30, /* HELO */
161         30, /* Auth */
162         30, /* From */
163         30, /* RCPT */
164         30, /* DATA */
165         90, /* DATABody */
166         900, /* end of body... */
167         30  /* QUIT */
168 };
169 /*
170 const long SMTP_C_SendTimeouts[eMaxSMTPC] = {
171
172 }; */
173 const char *ReadErrors[eMaxSMTPC] = {
174         "Connection broken during SMTP conversation",
175         "Connection broken during SMTP EHLO",
176         "Connection broken during SMTP HELO",
177         "Connection broken during SMTP AUTH",
178         "Connection broken during SMTP MAIL FROM",
179         "Connection broken during SMTP RCPT",
180         "Connection broken during SMTP DATA",
181         "Connection broken during SMTP message transmit",
182         ""/* quit reply, don't care. */
183 };
184
185
186 typedef struct _stmp_out_msg {
187         MailQEntry *MyQEntry;
188         OneQueItem *MyQItem;
189         long n;
190         AsyncIO IO;
191
192         eSMTP_C_States State;
193
194         struct ares_mx_reply *AllMX;
195         struct ares_mx_reply *CurrMX;
196         const char *mx_port;
197         const char *mx_host;
198
199         struct hostent *OneMX;
200
201
202         char mx_user[1024];
203         char mx_pass[1024];
204         StrBuf *msgtext;
205         char *envelope_from;
206         char user[1024];
207         char node[1024];
208         char name[1024];
209         char mailfrom[1024];
210 } SmtpOutMsg;
211 void DeleteSmtpOutMsg(void *v)
212 {
213         SmtpOutMsg *Msg = v;
214         FreeStrBuf(&Msg->msgtext);
215         FreeAsyncIOContents(&Msg->IO);
216         free(Msg);
217 }
218
219 eNextState SMTP_C_Timeout(void *Data);
220 eNextState SMTP_C_ConnFail(void *Data);
221 eNextState SMTP_C_DispatchReadDone(void *Data);
222 eNextState SMTP_C_DispatchWriteDone(void *Data);
223 eNextState SMTP_C_Terminate(void *Data);
224 eNextState SMTP_C_MXLookup(void *Data);
225
226 typedef eNextState (*SMTPReadHandler)(SmtpOutMsg *Msg);
227 typedef eNextState (*SMTPSendHandler)(SmtpOutMsg *Msg);
228
229
230
231
232 void FreeQueItem(OneQueItem **Item)
233 {
234         DeleteHash(&(*Item)->MailQEntries);
235         FreeStrBuf(&(*Item)->EnvelopeFrom);
236         FreeStrBuf(&(*Item)->BounceTo);
237         free(*Item);
238         Item = NULL;
239 }
240 void HFreeQueItem(void *Item)
241 {
242         FreeQueItem((OneQueItem**)&Item);
243 }
244
245
246 /* inspect recipients with a status of: 
247  * - 0 (no delivery yet attempted) 
248  * - 3/4 (transient errors
249  *        were experienced and it's time to try again)
250  */
251 int CountActiveQueueEntries(OneQueItem *MyQItem)
252 {
253         HashPos  *It;
254         long len;
255         const char *Key;
256         void *vQE;
257
258         MyQItem->ActiveDeliveries = 0;
259         It = GetNewHashPos(MyQItem->MailQEntries, 0);
260         while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
261         {
262                 MailQEntry *ThisItem = vQE;
263                 if ((ThisItem->Status == 0) || 
264                     (ThisItem->Status == 3) ||
265                     (ThisItem->Status == 4))
266                 {
267                         MyQItem->ActiveDeliveries++;
268                         ThisItem->Active = 1;
269                 }
270                 else 
271                         ThisItem->Active = 0;
272         }
273         DeleteHashPos(&It);
274         return MyQItem->ActiveDeliveries;
275 }
276
277 OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
278 {
279         OneQueItem *Item;
280         const char *pLine = NULL;
281         StrBuf *Line;
282         StrBuf *Token;
283         void *v;
284
285         Item = (OneQueItem*)malloc(sizeof(OneQueItem));
286         memset(Item, 0, sizeof(OneQueItem));
287         Item->LastAttempt.retry = SMTP_RETRY_INTERVAL;
288         Item->MessageID = -1;
289         Item->QueMsgID = QueMsgID;
290
291         citthread_mutex_lock(&ActiveQItemsLock);
292         if (GetHash(ActiveQItems, 
293                     IKEY(Item->QueMsgID), 
294                     &v))
295         {
296                 /* WHOOPS. somebody else is already working on this. */
297                 citthread_mutex_unlock(&ActiveQItemsLock);
298                 FreeQueItem(&Item);
299                 return NULL;
300         }
301         else {
302                 /* mark our claim on this. */
303                 Put(ActiveQItems, 
304                     IKEY(Item->QueMsgID),
305                     Item,
306                     HFreeQueItem);
307                 citthread_mutex_unlock(&ActiveQItemsLock);
308         }
309
310         Token = NewStrBuf();
311         Line = NewStrBufPlain(NULL, 128);
312         while (pLine != StrBufNOTNULL) {
313                 const char *pItemPart = NULL;
314                 void *vHandler;
315
316                 StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
317                 if (StrLength(Line) == 0) continue;
318                 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
319                 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
320                 {
321                         QItemHandler H;
322                         H = (QItemHandler) vHandler;
323                         H(Item, Line, &pItemPart);
324                 }
325         }
326         FreeStrBuf(&Line);
327         FreeStrBuf(&Token);
328         return Item;
329 }
330
331 StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
332 {
333         StrBuf *QMessage;
334         HashPos  *It;
335         const char *Key;
336         long len;
337         void *vQE;
338
339         QMessage = NewStrBufPlain(NULL, SIZ);
340         StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
341
342 //                 "attempted|%ld\n"  "retry|%ld\n",, (long)time(NULL), (long)retry );
343         StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
344         StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
345
346         if (StrLength(MyQItem->BounceTo) > 0) {
347                 StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
348                 StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
349         }
350
351         if (StrLength(MyQItem->EnvelopeFrom) > 0) {
352                 StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
353                 StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
354         }
355
356         It = GetNewHashPos(MyQItem->MailQEntries, 0);
357         while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
358         {
359                 MailQEntry *ThisItem = vQE;
360                 int i;
361
362                 if (!ThisItem->Active)
363                         continue; /* skip already sent ones from the spoolfile. */
364
365                 for (i=0; i < ThisItem->nAttempts; i++) {
366                         StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
367                         StrBufAppendPrintf(QMessage, "%ld", 
368                                            ThisItem->Attempts[i].retry);
369
370                         StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
371                         StrBufAppendPrintf(QMessage, "%ld", 
372                                            ThisItem->Attempts[i].when);
373                 }
374                 StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
375                 StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
376                 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
377                 StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
378                 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
379                 StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
380         }
381         DeleteHashPos(&It);
382         StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);  
383         return QMessage;
384 }
385
386 void FinalizeMessageSend(SmtpOutMsg *Msg)
387 {
388         int IDestructQueItem;
389         HashPos  *It;
390
391         citthread_mutex_lock(&ActiveQItemsLock);
392         Msg->MyQItem->ActiveDeliveries--;
393         IDestructQueItem = Msg->MyQItem->ActiveDeliveries == 0;
394         citthread_mutex_unlock(&ActiveQItemsLock);
395
396         if (IDestructQueItem) {
397                 int nRemain;
398                 StrBuf *MsgData;
399
400                 nRemain = CountActiveQueueEntries(Msg->MyQItem);
401
402                 if (nRemain > 0) 
403                         MsgData = SerializeQueueItem(Msg->MyQItem);
404                 /*
405                  * Uncompleted delivery instructions remain, so delete the old
406                  * instructions and replace with the updated ones.
407                  */
408                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, "");
409
410         /* Generate 'bounce' messages * /
411            smtp_do_bounce(instr); */
412                 if (nRemain > 0) {
413                         struct CtdlMessage *msg;
414                         msg = malloc(sizeof(struct CtdlMessage));
415                         memset(msg, 0, sizeof(struct CtdlMessage));
416                         msg->cm_magic = CTDLMESSAGE_MAGIC;
417                         msg->cm_anon_type = MES_NORMAL;
418                         msg->cm_format_type = FMT_RFC822;
419                         msg->cm_fields['M'] = SmashStrBuf(&MsgData);
420
421                         CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
422                         CtdlFreeMessage(msg);
423                 }
424                 It = GetNewHashPos(Msg->MyQItem->MailQEntries, 0);
425                 citthread_mutex_lock(&ActiveQItemsLock);
426                 {
427                         GetHashPosFromKey(ActiveQItems, IKEY(Msg->MyQItem->MessageID), It);
428                         DeleteEntryFromHash(ActiveQItems, It);
429                 }
430                 citthread_mutex_unlock(&ActiveQItemsLock);
431                 DeleteHashPos(&It);
432         }
433         
434 /// TODO : else free message...
435         close(Msg->IO.sock);
436         DeleteSmtpOutMsg(Msg);
437 }
438
439 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
440 {
441         eReadState Finished = eBufferNotEmpty; 
442
443         while (Finished == eBufferNotEmpty) {
444                 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
445                 
446                 switch (Finished) {
447                 case eMustReadMore: /// read new from socket... 
448                         return Finished;
449                         break;
450                 case eBufferNotEmpty: /* shouldn't happen... */
451                 case eReadSuccess: /// done for now...
452                         if (StrLength(IO->IOBuf) < 4)
453                                 continue;
454                         if (ChrPtr(IO->IOBuf)[3] == '-')
455                                 Finished = eBufferNotEmpty;
456                         else 
457                                 return Finished;
458                         break;
459                 case eReadFail: /// WHUT?
460                         ///todo: shut down! 
461                         break;
462                 }
463         }
464         return Finished;
465 }
466
467 /**
468  * this one has to have the context for loading the message via the redirect buffer...
469  */
470 StrBuf *smtp_load_msg(OneQueItem *MyQItem)
471 {
472         CitContext *CCC=CC;
473         StrBuf *SendMsg;
474         
475         CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
476         CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
477         SendMsg = CCC->redirect_buffer;
478         CCC->redirect_buffer = NULL;
479         if ((StrLength(SendMsg) > 0) && 
480             ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
481                 CtdlLogPrintf(CTDL_WARNING, 
482                               "SMTP client[%ld]: Possible problem: message did not "
483                               "correctly terminate. (expecting 0x10, got 0x%02x)\n",
484                               MsgCount, //yes uncool, but best choice here... 
485                               ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
486                 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
487         }
488         return SendMsg;
489 }
490
491
492 int smtp_resolve_recipients(SmtpOutMsg *SendMsg)
493 {
494         const char *ptr;
495         char buf[1024];
496         int scan_done;
497         int lp, rp;
498         int i;
499
500         /* Parse out the host portion of the recipient address */
501         process_rfc822_addr(ChrPtr(SendMsg->MyQEntry->Recipient), 
502                             SendMsg->user, 
503                             SendMsg->node, 
504                             SendMsg->name);
505
506         CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
507                       SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
508         /* If no envelope_from is supplied, extract one from the message */
509         if ( (SendMsg->envelope_from == NULL) || 
510              (IsEmptyStr(SendMsg->envelope_from)) ) {
511                 SendMsg->mailfrom[0] = '\0';
512                 scan_done = 0;
513                 ptr = ChrPtr(SendMsg->msgtext);
514                 do {
515                         if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
516                                 scan_done = 1;
517                         }
518                         if (!strncasecmp(buf, "From:", 5)) {
519                                 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
520                                 striplt(SendMsg->mailfrom);
521                                 for (i=0; SendMsg->mailfrom[i]; ++i) {
522                                         if (!isprint(SendMsg->mailfrom[i])) {
523                                                 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
524                                                 i=0;
525                                         }
526                                 }
527         
528                                 /* Strip out parenthesized names */
529                                 lp = (-1);
530                                 rp = (-1);
531                                 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
532                                         if (SendMsg->mailfrom[i] == '(') lp = i;
533                                         if (SendMsg->mailfrom[i] == ')') rp = i;
534                                 }
535                                 if ((lp>0)&&(rp>lp)) {
536                                         strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
537                                 }
538         
539                                 /* Prefer brokketized names */
540                                 lp = (-1);
541                                 rp = (-1);
542                                 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
543                                         if (SendMsg->mailfrom[i] == '<') lp = i;
544                                         if (SendMsg->mailfrom[i] == '>') rp = i;
545                                 }
546                                 if ( (lp>=0) && (rp>lp) ) {
547                                         SendMsg->mailfrom[rp] = 0;
548                                         memmove(SendMsg->mailfrom, 
549                                                 &SendMsg->mailfrom[lp + 1], 
550                                                 rp - lp);
551                                 }
552         
553                                 scan_done = 1;
554                         }
555                 } while (scan_done == 0);
556                 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
557                 stripallbut(SendMsg->mailfrom, '<', '>');
558                 SendMsg->envelope_from = SendMsg->mailfrom;
559         }
560
561         return 0;
562 }
563
564
565 #define SMTP_ERROR(WHICH_ERR, ERRSTR) {SendMsg->MyQEntry->Status = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, HKEY(ERRSTR), 0); return eAbort; }
566 #define SMTP_VERROR(WHICH_ERR) { SendMsg->MyQEntry->Status = WHICH_ERR; StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, &ChrPtr(SendMsg->IO.IOBuf)[4], -1, 0); return eAbort; }
567 #define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(SendMsg->IO.IOBuf)[0] == WHICH_STATE)
568
569 #define SMTP_DBG_SEND() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: > %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
570 #define SMTP_DBG_READ() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: < %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
571
572 /*
573 void connect_one_smtpsrv_xamine_result(void *Ctx, 
574                                        int status,
575                                        int timeouts,
576                                        struct hostent *hostent)
577 {
578         SmtpOutMsg *SendMsg = Ctx;
579
580         CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connecting [%s:%s]!\n", 
581                       SendMsg->n, SendMsg->mx_host, SendMsg->mx_port);
582
583         SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
584         SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
585         SendMsg->IO.IOBuf = NewStrBuf();
586         SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
587
588
589         SendMsg->IO.SendBuf.fd = 
590         SendMsg->IO.RecvBuf.fd = 
591         SendMsg->IO.sock = sock_connect(SendMsg->mx_host, SendMsg->mx_port);
592
593         StrBufPrintf(SendMsg->MyQEntry->StatusMessage, 
594                      "Could not connect: %s", strerror(errno));
595
596
597         if (SendMsg->IO.sock < 0) {
598                 if (errno > 0) {
599                         StrBufPlain(SendMsg->MyQEntry->StatusMessage, 
600                                     strerror(errno), -1);
601                 }
602                 else {
603                         StrBufPrintf(SendMsg->MyQEntry->StatusMessage, 
604                                      "Unable to connect to %s : %s\n", 
605                                      SendMsg->mx_host, SendMsg->mx_port);
606                 }
607         }
608         /// hier: naechsten mx ausprobieren.
609         if (SendMsg->IO.sock < 0) {
610                 SendMsg->MyQEntry->Status = 4;  /* dsn is already filled in * /
611                 //// hier: abbrechen & bounce.
612                 return;
613         }
614 /*
615
616         InitEventIO(&SendMsg->IO, SendMsg, 
617                     SMTP_C_DispatchReadDone, 
618                     SMTP_C_DispatchWriteDone, 
619                     SMTP_C_Terminate,
620                     SMTP_C_Timeout,
621                     SMTP_C_ConnFail,
622                     SMTP_C_MXLookup,
623                     SMTP_C_ReadServerStatus,
624                     1);
625 * /
626         return;
627 }
628 */
629
630 void get_one_mx_host_name_done(void *Ctx, 
631                                int status,
632                                int timeouts,
633                                struct hostent *hostent)
634 {
635         SmtpOutMsg *SendMsg = Ctx;
636         if ((status == ARES_SUCCESS) && (hostent != NULL) ) {
637
638                         SendMsg->IO.HEnt = hostent;
639                         InitEventIO(&SendMsg->IO, SendMsg, 
640                                     SMTP_C_DispatchReadDone, 
641                                     SMTP_C_DispatchWriteDone, 
642                                     SMTP_C_Terminate,
643                                     SMTP_C_Timeout,
644                                     SMTP_C_ConnFail,
645                                     SMTP_C_ReadServerStatus,
646                                     1);
647
648         }
649 }
650
651 const char *DefaultMXPort = "25";
652 void connect_one_smtpsrv(SmtpOutMsg *SendMsg)
653 {
654         //char *endpart;
655         //char buf[SIZ];
656
657         SendMsg->mx_port = DefaultMXPort;
658
659         SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
660         SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
661         SendMsg->IO.IOBuf = NewStrBuf();
662         SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
663
664 /* TODO: Relay!
665         *SendMsg->mx_user =  '\0';
666         *SendMsg->mx_pass = '\0';
667         if (num_tokens(buf, '@') > 1) {
668                 strcpy (SendMsg->mx_user, buf);
669                 endpart = strrchr(SendMsg->mx_user, '@');
670                 *endpart = '\0';
671                 strcpy (SendMsg->mx_host, endpart + 1);
672                 endpart = strrchr(SendMsg->mx_user, ':');
673                 if (endpart != NULL) {
674                         strcpy(SendMsg->mx_pass, endpart+1);
675                         *endpart = '\0';
676                 }
677
678         endpart = strrchr(SendMsg->mx_host, ':');
679         if (endpart != 0){
680                 *endpart = '\0';
681                 strcpy(SendMsg->mx_port, endpart + 1);
682         }               
683         }
684         else
685 */
686         SendMsg->mx_host = SendMsg->CurrMX->host;
687         SendMsg->CurrMX = SendMsg->CurrMX->next;
688
689         CtdlLogPrintf(CTDL_DEBUG, 
690                       "SMTP client[%ld]: connecting to %s : %s ...\n", 
691                       SendMsg->n, 
692                       SendMsg->mx_host, 
693                       SendMsg->mx_port);
694
695         ares_gethostbyname(SendMsg->IO.DNSChannel,
696                            SendMsg->mx_host,   
697                            AF_INET6, /* it falls back to ipv4 in doubt... */
698                            get_one_mx_host_name_done,
699                            &SendMsg->IO);
700 /*
701         if (!QueueQuery(ns_t_a, 
702                         SendMsg->mx_host, 
703                         &SendMsg->IO, 
704                         connect_one_smtpsrv_xamine_result))
705         {
706                 /// TODO: abort
707         }
708 */
709 }
710
711
712 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
713 {
714         /* Process the SMTP greeting from the server */
715         SMTP_DBG_READ();
716
717         if (!SMTP_IS_STATE('2')) {
718                 if (SMTP_IS_STATE('4')) 
719                         SMTP_VERROR(4)
720                 else 
721                         SMTP_VERROR(5)
722         }
723         return eSendReply;
724 }
725
726 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
727 {
728         /* At this point we know we are talking to a real SMTP server */
729
730         /* Do a EHLO command.  If it fails, try the HELO command. */
731         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
732                      "EHLO %s\r\n", config.c_fqdn);
733
734         SMTP_DBG_SEND();
735         return eReadMessage;
736 }
737
738 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
739 {
740         SMTP_DBG_READ();
741
742         if (SMTP_IS_STATE('2')) {
743                 SendMsg->State ++;
744                 if (IsEmptyStr(SendMsg->mx_user))
745                         SendMsg->State ++; /* Skip auth... */
746         }
747         /* else we fall back to 'helo' */
748         return eSendReply;
749 }
750
751 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
752 {
753         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
754                      "HELO %s\r\n", config.c_fqdn);
755
756         SMTP_DBG_SEND();
757         return eReadMessage;
758 }
759
760 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
761 {
762         SMTP_DBG_READ();
763
764         if (!SMTP_IS_STATE('2')) {
765                 if (SMTP_IS_STATE('4'))
766                         SMTP_VERROR(4)
767                 else 
768                         SMTP_VERROR(5)
769         }
770         if (!IsEmptyStr(SendMsg->mx_user))
771                 SendMsg->State ++; /* Skip auth... */
772         return eSendReply;
773 }
774
775 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
776 {
777         char buf[SIZ];
778         char encoded[1024];
779
780         /* Do an AUTH command if necessary */
781         sprintf(buf, "%s%c%s%c%s", 
782                 SendMsg->mx_user, '\0', 
783                 SendMsg->mx_user, '\0', 
784                 SendMsg->mx_pass);
785         CtdlEncodeBase64(encoded, buf, 
786                          strlen(SendMsg->mx_user) + 
787                          strlen(SendMsg->mx_user) + 
788                          strlen(SendMsg->mx_pass) + 2, 0);
789         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
790                      "AUTH PLAIN %s\r\n", encoded);
791         
792         SMTP_DBG_SEND();
793         return eReadMessage;
794 }
795
796 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
797 {
798         /* Do an AUTH command if necessary */
799         
800         SMTP_DBG_READ();
801         
802         if (!SMTP_IS_STATE('2')) {
803                 if (SMTP_IS_STATE('4'))
804                         SMTP_VERROR(4)
805                 else 
806                         SMTP_VERROR(5)
807         }
808         return eSendReply;
809 }
810
811 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
812 {
813         /* previous command succeeded, now try the MAIL FROM: command */
814         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
815                      "MAIL FROM:<%s>\r\n", 
816                      SendMsg->envelope_from);
817
818         SMTP_DBG_SEND();
819         return eReadMessage;
820 }
821
822 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
823 {
824         SMTP_DBG_READ();
825
826         if (!SMTP_IS_STATE('2')) {
827                 if (SMTP_IS_STATE('4'))
828                         SMTP_VERROR(4)
829                 else 
830                         SMTP_VERROR(5)
831         }
832         return eSendReply;
833 }
834
835
836 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
837 {
838         /* MAIL succeeded, now try the RCPT To: command */
839         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
840                      "RCPT TO:<%s@%s>\r\n", 
841                      SendMsg->user, 
842                      SendMsg->node);
843
844         SMTP_DBG_SEND();
845         return eReadMessage;
846 }
847
848 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
849 {
850         SMTP_DBG_READ();
851
852         if (!SMTP_IS_STATE('2')) {
853                 if (SMTP_IS_STATE('4')) 
854                         SMTP_VERROR(4)
855                 else 
856                         SMTP_VERROR(5)
857         }
858         return eSendReply;
859 }
860
861 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
862 {
863         /* RCPT succeeded, now try the DATA command */
864         StrBufPlain(SendMsg->IO.SendBuf.Buf,
865                     HKEY("DATA\r\n"));
866
867         SMTP_DBG_SEND();
868         return eReadMessage;
869 }
870
871 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
872 {
873         SMTP_DBG_READ();
874
875         if (!SMTP_IS_STATE('3')) {
876                 if (SMTP_IS_STATE('4')) 
877                         SMTP_VERROR(3)
878                 else 
879                         SMTP_VERROR(5)
880         }
881         return eSendReply;
882 }
883
884 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
885 {
886         StrBuf *Buf;
887         /* If we reach this point, the server is expecting data.*/
888
889         Buf = SendMsg->IO.SendBuf.Buf;
890         SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
891         SendMsg->msgtext = Buf;
892         //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
893         SendMsg->State ++;
894
895         return eSendMore;
896 }
897
898 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
899 {
900         StrBuf *Buf;
901
902         Buf = SendMsg->IO.SendBuf.Buf;
903         SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
904         SendMsg->msgtext = Buf;
905
906         StrBufPlain(SendMsg->IO.SendBuf.Buf,
907                     HKEY(".\r\n"));
908
909         return eReadMessage;
910
911 }
912
913 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
914 {
915         SMTP_DBG_READ();
916
917         if (!SMTP_IS_STATE('2')) {
918                 if (SMTP_IS_STATE('4'))
919                         SMTP_VERROR(4)
920                 else 
921                         SMTP_VERROR(5)
922         }
923
924         /* We did it! */
925         StrBufPlain(SendMsg->MyQEntry->StatusMessage, 
926                     &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
927                     StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
928         SendMsg->MyQEntry->Status = 2;
929         return eSendReply;
930 }
931
932 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
933 {
934         StrBufPlain(SendMsg->IO.SendBuf.Buf,
935                     HKEY("QUIT\r\n"));
936
937         SMTP_DBG_SEND();
938         return eReadMessage;
939 }
940
941 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
942 {
943         SMTP_DBG_READ();
944
945         CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
946                       SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
947         return eTerminateConnection;
948 }
949
950 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
951 {
952         return eSendReply;
953 }
954
955 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
956 {
957         return eReadMessage;
958 }
959
960 eNextState smtp_resolve_mx_done(void *data)
961 {/// VParsedDNSReply
962         AsyncIO *IO = data;
963         SmtpOutMsg * SendMsg = IO->Data;
964
965         //// connect_one_smtpsrv_xamine_result
966         SendMsg->CurrMX = SendMsg->AllMX = IO->VParsedDNSReply;
967         //// TODO: should we remove the current ares context???
968         connect_one_smtpsrv(SendMsg);
969         return 0;
970 }
971
972
973
974 int resolve_mx_records(void *Ctx)
975 {
976         SmtpOutMsg * SendMsg = Ctx;
977 ///TMP
978         SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
979         SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
980         SendMsg->IO.IOBuf = NewStrBuf();
981         SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
982
983         InitEventIO(&SendMsg->IO, SendMsg, 
984                                     SMTP_C_DispatchReadDone, 
985                                     SMTP_C_DispatchWriteDone, 
986                                     SMTP_C_Terminate,
987                                     SMTP_C_Timeout,
988                                     SMTP_C_ConnFail,
989                                     SMTP_C_ReadServerStatus,
990                                     1);
991                                     return 0;
992 /// END TMP */
993         if (!QueueQuery(ns_t_mx, 
994                         SendMsg->node, 
995                         &SendMsg->IO, 
996                         smtp_resolve_mx_done))
997         {
998                 SendMsg->MyQEntry->Status = 5;
999                 StrBufPrintf(SendMsg->MyQEntry->StatusMessage, 
1000                              "No MX hosts found for <%s>", SendMsg->node);
1001                 return 0; ///////TODO: abort!
1002         }
1003         return 0;
1004 }
1005
1006 void smtp_try(OneQueItem *MyQItem, 
1007               MailQEntry *MyQEntry, 
1008               StrBuf *MsgText, 
1009               int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
1010 {
1011         SmtpOutMsg * SendMsg;
1012
1013         SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
1014         memset(SendMsg, 0, sizeof(SmtpOutMsg));
1015         SendMsg->IO.sock = (-1);
1016         SendMsg->n = MsgCount++;
1017         SendMsg->MyQEntry = MyQEntry;
1018         SendMsg->MyQItem = MyQItem;
1019         SendMsg->IO.Data = SendMsg;
1020         if (KeepMsgText)
1021                 SendMsg->msgtext = MsgText;
1022         else 
1023                 SendMsg->msgtext = NewStrBufDup(MsgText);
1024
1025         smtp_resolve_recipients(SendMsg);
1026
1027         QueueEventContext(SendMsg, 
1028                           &SendMsg->IO,
1029                           resolve_mx_records);
1030
1031
1032 }
1033
1034
1035
1036 void NewMailQEntry(OneQueItem *Item)
1037 {
1038         Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
1039         memset(Item->Current, 0, sizeof(MailQEntry));
1040
1041         if (Item->MailQEntries == NULL)
1042                 Item->MailQEntries = NewHash(1, Flathash);
1043         Item->Current->n = GetCount(Item->MailQEntries);
1044         Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
1045 }
1046
1047 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
1048 {
1049         Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
1050 }
1051
1052 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
1053 {
1054         if (Item->EnvelopeFrom == NULL)
1055                 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
1056         StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
1057 }
1058
1059 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
1060 {
1061         if (Item->BounceTo == NULL)
1062                 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
1063         StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
1064 }
1065
1066 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
1067 {
1068         if (Item->Current == NULL)
1069                 NewMailQEntry(Item);
1070         if (Item->Current->Recipient == NULL)
1071                 Item->Current->Recipient =  NewStrBufPlain(NULL, StrLength(Line));
1072         StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
1073         Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
1074         StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
1075         Item->Current = NULL; // TODO: is this always right?
1076 }
1077
1078
1079 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
1080 {
1081         if (Item->Current == NULL)
1082                 NewMailQEntry(Item);
1083         if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
1084                 Item->Current->nAttempts++;
1085         if (Item->Current->nAttempts > MaxAttempts) {
1086                 Item->FailNow = 1;
1087                 return;
1088         }
1089         Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1090 }
1091
1092 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1093 {
1094         if (Item->Current == NULL)
1095                 NewMailQEntry(Item);
1096         if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1097                 Item->Current->nAttempts++;
1098         if (Item->Current->nAttempts > MaxAttempts) {
1099                 Item->FailNow = 1;
1100                 return;
1101         }
1102                 
1103         Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1104         if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1105         {
1106                 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1107                 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1108                 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1109                         Item->LastAttempt.retry = SMTP_RETRY_MAX;
1110         }
1111 }
1112
1113
1114
1115
1116 /*
1117  * smtp_do_procmsg()
1118  *
1119  * Called by smtp_do_queue() to handle an individual message.
1120  */
1121 void smtp_do_procmsg(long msgnum, void *userdata) {
1122         struct CtdlMessage *msg = NULL;
1123         char *instr = NULL;     
1124         StrBuf *PlainQItem;
1125         OneQueItem *MyQItem;
1126         char *pch;
1127         HashPos  *It;
1128         void *vQE;
1129         long len;
1130         const char *Key;
1131
1132         CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
1133         ///strcpy(envelope_from, "");
1134
1135         msg = CtdlFetchMessage(msgnum, 1);
1136         if (msg == NULL) {
1137                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
1138                 return;
1139         }
1140
1141         pch = instr = msg->cm_fields['M'];
1142
1143         /* Strip out the headers (no not amd any other non-instruction) line */
1144         while (pch != NULL) {
1145                 pch = strchr(pch, '\n');
1146                 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1147                         instr = pch + 2;
1148                         pch = NULL;
1149                 }
1150         }
1151         PlainQItem = NewStrBufPlain(instr, -1);
1152         CtdlFreeMessage(msg);
1153         MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1154         FreeStrBuf(&PlainQItem);
1155
1156         if (MyQItem == NULL) {
1157                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);              
1158                 return; /* s.b. else is already processing... */
1159         }
1160
1161         /*
1162          * Postpone delivery if we've already tried recently.
1163          * /
1164         if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1165                 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1166
1167                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1168                 citthread_mutex_lock(&ActiveQItemsLock);
1169                 {
1170                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1171                         DeleteEntryFromHash(ActiveQItems, It);
1172                 }
1173                 citthread_mutex_unlock(&ActiveQItemsLock);
1174                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1175                 DeleteHashPos(&It);
1176                 return;
1177         }// TODO: reenable me.*/
1178
1179         /*
1180          * Bail out if there's no actual message associated with this
1181          */
1182         if (MyQItem->MessageID < 0L) {
1183                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
1184                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1185                 citthread_mutex_lock(&ActiveQItemsLock);
1186                 {
1187                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1188                         DeleteEntryFromHash(ActiveQItems, It);
1189                 }
1190                 citthread_mutex_unlock(&ActiveQItemsLock);
1191                 DeleteHashPos(&It);
1192                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1193                 return;
1194         }
1195
1196         It = GetNewHashPos(MyQItem->MailQEntries, 0);
1197         while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
1198         {
1199                 MailQEntry *ThisItem = vQE;
1200                 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
1201         }
1202         DeleteHashPos(&It);
1203
1204         CountActiveQueueEntries(MyQItem);
1205         if (MyQItem->ActiveDeliveries > 0)
1206         {
1207                 int i = 1;
1208                 StrBuf *Msg = smtp_load_msg(MyQItem);
1209                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1210                 while ((i <= MyQItem->ActiveDeliveries) && 
1211                        (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1212                 {
1213                         MailQEntry *ThisItem = vQE;
1214                         if (ThisItem->Active == 1) {
1215                                 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1216                                 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1217                                 i++;
1218                         }
1219                 }
1220                 DeleteHashPos(&It);
1221         }
1222         else 
1223         {
1224                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1225                 citthread_mutex_lock(&ActiveQItemsLock);
1226                 {
1227                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1228                         DeleteEntryFromHash(ActiveQItems, It);
1229                 }
1230                 citthread_mutex_unlock(&ActiveQItemsLock);
1231                 DeleteHashPos(&It);
1232                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1233
1234 // TODO: bounce & delete?
1235
1236         }
1237 }
1238
1239
1240 /*****************************************************************************/
1241 /*                          SMTP UTILITY COMMANDS                            */
1242 /*****************************************************************************/
1243
1244 void cmd_smtp(char *argbuf) {
1245         char cmd[64];
1246         char node[256];
1247         char buf[1024];
1248         int i;
1249         int num_mxhosts;
1250
1251         if (CtdlAccessCheck(ac_aide)) return;
1252
1253         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1254
1255         if (!strcasecmp(cmd, "mx")) {
1256                 extract_token(node, argbuf, 1, '|', sizeof node);
1257                 num_mxhosts = getmx(buf, node);
1258                 cprintf("%d %d MX hosts listed for %s\n",
1259                         LISTING_FOLLOWS, num_mxhosts, node);
1260                 for (i=0; i<num_mxhosts; ++i) {
1261                         extract_token(node, buf, i, '|', sizeof node);
1262                         cprintf("%s\n", node);
1263                 }
1264                 cprintf("000\n");
1265                 return;
1266         }
1267
1268         else if (!strcasecmp(cmd, "runqueue")) {
1269                 run_queue_now = 1;
1270                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1271                 return;
1272         }
1273
1274         else {
1275                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1276         }
1277
1278 }
1279
1280
1281 /*
1282  * smtp_queue_thread()
1283  * 
1284  * Run through the queue sending out messages.
1285  */
1286 void *smtp_queue_thread(void *arg) {
1287         int num_processed = 0;
1288         struct CitContext smtp_queue_CC;
1289
1290         CtdlThreadSleep(10);
1291
1292         CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1293         citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1294         CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1295
1296         while (!CtdlThreadCheckStop()) {
1297                 
1298                 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1299
1300                 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1301                         CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1302                 }
1303                 else {
1304                         num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1305                 }
1306                 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1307                 CtdlThreadSleep(60);
1308         }
1309
1310         CtdlClearSystemContext();
1311         return(NULL);
1312 }
1313
1314
1315 /*
1316  * Initialize the SMTP outbound queue
1317  */
1318 void smtp_init_spoolout(void) {
1319         struct ctdlroom qrbuf;
1320
1321         /*
1322          * Create the room.  This will silently fail if the room already
1323          * exists, and that's perfectly ok, because we want it to exist.
1324          */
1325         CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1326
1327         /*
1328          * Make sure it's set to be a "system room" so it doesn't show up
1329          * in the <K>nown rooms list for Aides.
1330          */
1331         if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1332                 qrbuf.QRflags2 |= QR2_SYSTEM;
1333                 CtdlPutRoomLock(&qrbuf);
1334         }
1335 }
1336
1337
1338 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1339         SMTPC_read_greeting,
1340         SMTPC_read_EHLO_reply,
1341         SMTPC_read_HELO_reply,
1342         SMTPC_read_auth_reply,
1343         SMTPC_read_FROM_reply,
1344         SMTPC_read_RCPT_reply,
1345         SMTPC_read_DATAcmd_reply,
1346         SMTPC_read_dummy,
1347         SMTPC_read_data_body_reply,
1348         SMTPC_read_QUIT_reply
1349 };
1350
1351 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1352         SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1353         SMTPC_send_EHLO,
1354         STMPC_send_HELO,
1355         SMTPC_send_auth,
1356         SMTPC_send_FROM,
1357         SMTPC_send_RCPT,
1358         SMTPC_send_DATAcmd,
1359         SMTPC_send_data_body,
1360         SMTPC_send_terminate_data_body,
1361         SMTPC_send_QUIT
1362 };
1363
1364 eNextState SMTP_C_Terminate(void *Data)
1365 {
1366         SmtpOutMsg *pMsg = Data;
1367         FinalizeMessageSend(pMsg);
1368         return 0;
1369 }
1370
1371 eNextState SMTP_C_Timeout(void *Data)
1372 {
1373         SmtpOutMsg *pMsg = Data;
1374         FinalizeMessageSend(pMsg);
1375         return 0;
1376 }
1377
1378 eNextState SMTP_C_ConnFail(void *Data)
1379 {
1380         SmtpOutMsg *pMsg = Data;
1381         FinalizeMessageSend(pMsg);
1382         return 0;
1383 }
1384
1385 eNextState SMTP_C_DispatchReadDone(void *Data)
1386 {
1387         SmtpOutMsg *pMsg = Data;
1388         eNextState rc = ReadHandlers[pMsg->State](pMsg);
1389         pMsg->State++;
1390         return rc;
1391 }
1392
1393 eNextState SMTP_C_DispatchWriteDone(void *Data)
1394 {
1395         SmtpOutMsg *pMsg = Data;
1396         return SendHandlers[pMsg->State](pMsg);
1397         
1398 }
1399
1400 #endif
1401 CTDL_MODULE_INIT(smtp_eventclient)
1402 {
1403 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1404         if (!threading)
1405         {
1406                 ActiveQItems = NewHash(1, Flathash);
1407                 citthread_mutex_init(&ActiveQItemsLock, NULL);
1408
1409                 QItemHandlers = NewHash(0, NULL);
1410
1411                 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1412                 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1413                 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1414                 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1415                 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1416                 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1417 ///submitted /TODO: flush qitemhandlers on exit
1418
1419
1420                 smtp_init_spoolout();
1421                 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1422
1423                 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1424         }
1425 #endif
1426         
1427         /* return our Subversion id for the Log */
1428         return "smtpeventclient";
1429 }