16b45cb774b1449408df1188f3d05a9cca9696e9
[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         AsyncIO *IO = Ctx;
636         SmtpOutMsg *SendMsg = IO->Data;
637         if ((status == ARES_SUCCESS) && (hostent != NULL) ) {
638
639                         SendMsg->IO.HEnt = hostent;
640                         InitEventIO(IO, SendMsg, 
641                                     SMTP_C_DispatchReadDone, 
642                                     SMTP_C_DispatchWriteDone, 
643                                     SMTP_C_Terminate,
644                                     SMTP_C_Timeout,
645                                     SMTP_C_ConnFail,
646                                     SMTP_C_ReadServerStatus,
647                                     1);
648
649         }
650 }
651
652 const char *DefaultMXPort = "25";
653 void connect_one_smtpsrv(SmtpOutMsg *SendMsg)
654 {
655         //char *endpart;
656         //char buf[SIZ];
657
658         SendMsg->mx_port = DefaultMXPort;
659
660
661 /* TODO: Relay!
662         *SendMsg->mx_user =  '\0';
663         *SendMsg->mx_pass = '\0';
664         if (num_tokens(buf, '@') > 1) {
665                 strcpy (SendMsg->mx_user, buf);
666                 endpart = strrchr(SendMsg->mx_user, '@');
667                 *endpart = '\0';
668                 strcpy (SendMsg->mx_host, endpart + 1);
669                 endpart = strrchr(SendMsg->mx_user, ':');
670                 if (endpart != NULL) {
671                         strcpy(SendMsg->mx_pass, endpart+1);
672                         *endpart = '\0';
673                 }
674
675         endpart = strrchr(SendMsg->mx_host, ':');
676         if (endpart != 0){
677                 *endpart = '\0';
678                 strcpy(SendMsg->mx_port, endpart + 1);
679         }               
680         }
681         else
682 */
683         SendMsg->mx_host = SendMsg->CurrMX->host;
684         SendMsg->CurrMX = SendMsg->CurrMX->next;
685
686         CtdlLogPrintf(CTDL_DEBUG, 
687                       "SMTP client[%ld]: connecting to %s : %s ...\n", 
688                       SendMsg->n, 
689                       SendMsg->mx_host, 
690                       SendMsg->mx_port);
691
692         ares_gethostbyname(SendMsg->IO.DNSChannel,
693                            SendMsg->mx_host,   
694                            AF_INET6, /* it falls back to ipv4 in doubt... */
695                            get_one_mx_host_name_done,
696                            &SendMsg->IO);
697 /*
698         if (!QueueQuery(ns_t_a, 
699                         SendMsg->mx_host, 
700                         &SendMsg->IO, 
701                         connect_one_smtpsrv_xamine_result))
702         {
703                 /// TODO: abort
704         }
705 */
706 }
707
708
709 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
710 {
711         /* Process the SMTP greeting from the server */
712         SMTP_DBG_READ();
713
714         if (!SMTP_IS_STATE('2')) {
715                 if (SMTP_IS_STATE('4')) 
716                         SMTP_VERROR(4)
717                 else 
718                         SMTP_VERROR(5)
719         }
720         return eSendReply;
721 }
722
723 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
724 {
725         /* At this point we know we are talking to a real SMTP server */
726
727         /* Do a EHLO command.  If it fails, try the HELO command. */
728         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
729                      "EHLO %s\r\n", config.c_fqdn);
730
731         SMTP_DBG_SEND();
732         return eReadMessage;
733 }
734
735 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
736 {
737         SMTP_DBG_READ();
738
739         if (SMTP_IS_STATE('2')) {
740                 SendMsg->State ++;
741                 if (IsEmptyStr(SendMsg->mx_user))
742                         SendMsg->State ++; /* Skip auth... */
743         }
744         /* else we fall back to 'helo' */
745         return eSendReply;
746 }
747
748 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
749 {
750         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
751                      "HELO %s\r\n", config.c_fqdn);
752
753         SMTP_DBG_SEND();
754         return eReadMessage;
755 }
756
757 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
758 {
759         SMTP_DBG_READ();
760
761         if (!SMTP_IS_STATE('2')) {
762                 if (SMTP_IS_STATE('4'))
763                         SMTP_VERROR(4)
764                 else 
765                         SMTP_VERROR(5)
766         }
767         if (!IsEmptyStr(SendMsg->mx_user))
768                 SendMsg->State ++; /* Skip auth... */
769         return eSendReply;
770 }
771
772 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
773 {
774         char buf[SIZ];
775         char encoded[1024];
776
777         /* Do an AUTH command if necessary */
778         sprintf(buf, "%s%c%s%c%s", 
779                 SendMsg->mx_user, '\0', 
780                 SendMsg->mx_user, '\0', 
781                 SendMsg->mx_pass);
782         CtdlEncodeBase64(encoded, buf, 
783                          strlen(SendMsg->mx_user) + 
784                          strlen(SendMsg->mx_user) + 
785                          strlen(SendMsg->mx_pass) + 2, 0);
786         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
787                      "AUTH PLAIN %s\r\n", encoded);
788         
789         SMTP_DBG_SEND();
790         return eReadMessage;
791 }
792
793 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
794 {
795         /* Do an AUTH command if necessary */
796         
797         SMTP_DBG_READ();
798         
799         if (!SMTP_IS_STATE('2')) {
800                 if (SMTP_IS_STATE('4'))
801                         SMTP_VERROR(4)
802                 else 
803                         SMTP_VERROR(5)
804         }
805         return eSendReply;
806 }
807
808 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
809 {
810         /* previous command succeeded, now try the MAIL FROM: command */
811         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
812                      "MAIL FROM:<%s>\r\n", 
813                      SendMsg->envelope_from);
814
815         SMTP_DBG_SEND();
816         return eReadMessage;
817 }
818
819 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
820 {
821         SMTP_DBG_READ();
822
823         if (!SMTP_IS_STATE('2')) {
824                 if (SMTP_IS_STATE('4'))
825                         SMTP_VERROR(4)
826                 else 
827                         SMTP_VERROR(5)
828         }
829         return eSendReply;
830 }
831
832
833 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
834 {
835         /* MAIL succeeded, now try the RCPT To: command */
836         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
837                      "RCPT TO:<%s@%s>\r\n", 
838                      SendMsg->user, 
839                      SendMsg->node);
840
841         SMTP_DBG_SEND();
842         return eReadMessage;
843 }
844
845 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
846 {
847         SMTP_DBG_READ();
848
849         if (!SMTP_IS_STATE('2')) {
850                 if (SMTP_IS_STATE('4')) 
851                         SMTP_VERROR(4)
852                 else 
853                         SMTP_VERROR(5)
854         }
855         return eSendReply;
856 }
857
858 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
859 {
860         /* RCPT succeeded, now try the DATA command */
861         StrBufPlain(SendMsg->IO.SendBuf.Buf,
862                     HKEY("DATA\r\n"));
863
864         SMTP_DBG_SEND();
865         return eReadMessage;
866 }
867
868 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
869 {
870         SMTP_DBG_READ();
871
872         if (!SMTP_IS_STATE('3')) {
873                 if (SMTP_IS_STATE('4')) 
874                         SMTP_VERROR(3)
875                 else 
876                         SMTP_VERROR(5)
877         }
878         return eSendReply;
879 }
880
881 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
882 {
883         StrBuf *Buf;
884         /* If we reach this point, the server is expecting data.*/
885
886         Buf = SendMsg->IO.SendBuf.Buf;
887         SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
888         SendMsg->msgtext = Buf;
889         //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
890         SendMsg->State ++;
891
892         return eSendMore;
893 }
894
895 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
896 {
897         StrBuf *Buf;
898
899         Buf = SendMsg->IO.SendBuf.Buf;
900         SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
901         SendMsg->msgtext = Buf;
902
903         StrBufPlain(SendMsg->IO.SendBuf.Buf,
904                     HKEY(".\r\n"));
905
906         return eReadMessage;
907
908 }
909
910 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
911 {
912         SMTP_DBG_READ();
913
914         if (!SMTP_IS_STATE('2')) {
915                 if (SMTP_IS_STATE('4'))
916                         SMTP_VERROR(4)
917                 else 
918                         SMTP_VERROR(5)
919         }
920
921         /* We did it! */
922         StrBufPlain(SendMsg->MyQEntry->StatusMessage, 
923                     &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
924                     StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
925         SendMsg->MyQEntry->Status = 2;
926         return eSendReply;
927 }
928
929 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
930 {
931         StrBufPlain(SendMsg->IO.SendBuf.Buf,
932                     HKEY("QUIT\r\n"));
933
934         SMTP_DBG_SEND();
935         return eReadMessage;
936 }
937
938 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
939 {
940         SMTP_DBG_READ();
941
942         CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
943                       SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
944         return eTerminateConnection;
945 }
946
947 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
948 {
949         return eSendReply;
950 }
951
952 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
953 {
954         return eReadMessage;
955 }
956
957 eNextState smtp_resolve_mx_done(void *data)
958 {/// VParsedDNSReply
959         AsyncIO *IO = data;
960         SmtpOutMsg * SendMsg = IO->Data;
961
962         SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
963         SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
964         SendMsg->IO.IOBuf = NewStrBuf();
965         SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
966
967         //// connect_one_smtpsrv_xamine_result
968         SendMsg->CurrMX = SendMsg->AllMX = IO->VParsedDNSReply;
969         //// TODO: should we remove the current ares context???
970         connect_one_smtpsrv(SendMsg);
971         return 0;
972 }
973
974
975
976 int resolve_mx_records(void *Ctx)
977 {
978         SmtpOutMsg * SendMsg = Ctx;
979 /*//TMP
980         SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
981         SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
982         SendMsg->IO.IOBuf = NewStrBuf();
983         SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
984
985         InitEventIO(&SendMsg->IO, SendMsg, 
986                                     SMTP_C_DispatchReadDone, 
987                                     SMTP_C_DispatchWriteDone, 
988                                     SMTP_C_Terminate,
989                                     SMTP_C_Timeout,
990                                     SMTP_C_ConnFail,
991                                     SMTP_C_ReadServerStatus,
992                                     1);
993                                     return 0;
994 /// END TMP */
995         if (!QueueQuery(ns_t_mx, 
996                         SendMsg->node, 
997                         &SendMsg->IO, 
998                         smtp_resolve_mx_done))
999         {
1000                 SendMsg->MyQEntry->Status = 5;
1001                 StrBufPrintf(SendMsg->MyQEntry->StatusMessage, 
1002                              "No MX hosts found for <%s>", SendMsg->node);
1003                 return 0; ///////TODO: abort!
1004         }
1005         return 0;
1006 }
1007
1008 void smtp_try(OneQueItem *MyQItem, 
1009               MailQEntry *MyQEntry, 
1010               StrBuf *MsgText, 
1011               int KeepMsgText) /* KeepMsgText allows us to use MsgText as ours. */
1012 {
1013         SmtpOutMsg * SendMsg;
1014
1015         SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
1016         memset(SendMsg, 0, sizeof(SmtpOutMsg));
1017         SendMsg->IO.sock = (-1);
1018         SendMsg->n = MsgCount++;
1019         SendMsg->MyQEntry = MyQEntry;
1020         SendMsg->MyQItem = MyQItem;
1021         SendMsg->IO.Data = SendMsg;
1022         if (KeepMsgText)
1023                 SendMsg->msgtext = MsgText;
1024         else 
1025                 SendMsg->msgtext = NewStrBufDup(MsgText);
1026
1027         smtp_resolve_recipients(SendMsg);
1028
1029         QueueEventContext(SendMsg, 
1030                           &SendMsg->IO,
1031                           resolve_mx_records);
1032
1033
1034 }
1035
1036
1037
1038 void NewMailQEntry(OneQueItem *Item)
1039 {
1040         Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
1041         memset(Item->Current, 0, sizeof(MailQEntry));
1042
1043         if (Item->MailQEntries == NULL)
1044                 Item->MailQEntries = NewHash(1, Flathash);
1045         Item->Current->n = GetCount(Item->MailQEntries);
1046         Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
1047 }
1048
1049 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
1050 {
1051         Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
1052 }
1053
1054 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
1055 {
1056         if (Item->EnvelopeFrom == NULL)
1057                 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
1058         StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
1059 }
1060
1061 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
1062 {
1063         if (Item->BounceTo == NULL)
1064                 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
1065         StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
1066 }
1067
1068 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
1069 {
1070         if (Item->Current == NULL)
1071                 NewMailQEntry(Item);
1072         if (Item->Current->Recipient == NULL)
1073                 Item->Current->Recipient =  NewStrBufPlain(NULL, StrLength(Line));
1074         StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
1075         Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
1076         StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
1077         Item->Current = NULL; // TODO: is this always right?
1078 }
1079
1080
1081 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
1082 {
1083         if (Item->Current == NULL)
1084                 NewMailQEntry(Item);
1085         if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
1086                 Item->Current->nAttempts++;
1087         if (Item->Current->nAttempts > MaxAttempts) {
1088                 Item->FailNow = 1;
1089                 return;
1090         }
1091         Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
1092 }
1093
1094 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
1095 {
1096         if (Item->Current == NULL)
1097                 NewMailQEntry(Item);
1098         if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
1099                 Item->Current->nAttempts++;
1100         if (Item->Current->nAttempts > MaxAttempts) {
1101                 Item->FailNow = 1;
1102                 return;
1103         }
1104                 
1105         Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
1106         if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
1107         {
1108                 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
1109                 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
1110                 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
1111                         Item->LastAttempt.retry = SMTP_RETRY_MAX;
1112         }
1113 }
1114
1115
1116
1117
1118 /*
1119  * smtp_do_procmsg()
1120  *
1121  * Called by smtp_do_queue() to handle an individual message.
1122  */
1123 void smtp_do_procmsg(long msgnum, void *userdata) {
1124         struct CtdlMessage *msg = NULL;
1125         char *instr = NULL;     
1126         StrBuf *PlainQItem;
1127         OneQueItem *MyQItem;
1128         char *pch;
1129         HashPos  *It;
1130         void *vQE;
1131         long len;
1132         const char *Key;
1133
1134         CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
1135         ///strcpy(envelope_from, "");
1136
1137         msg = CtdlFetchMessage(msgnum, 1);
1138         if (msg == NULL) {
1139                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
1140                 return;
1141         }
1142
1143         pch = instr = msg->cm_fields['M'];
1144
1145         /* Strip out the headers (no not amd any other non-instruction) line */
1146         while (pch != NULL) {
1147                 pch = strchr(pch, '\n');
1148                 if ((pch != NULL) && (*(pch + 1) == '\n')) {
1149                         instr = pch + 2;
1150                         pch = NULL;
1151                 }
1152         }
1153         PlainQItem = NewStrBufPlain(instr, -1);
1154         CtdlFreeMessage(msg);
1155         MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
1156         FreeStrBuf(&PlainQItem);
1157
1158         if (MyQItem == NULL) {
1159                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);              
1160                 return; /* s.b. else is already processing... */
1161         }
1162
1163         /*
1164          * Postpone delivery if we've already tried recently.
1165          * /
1166         if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
1167                 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1168
1169                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1170                 citthread_mutex_lock(&ActiveQItemsLock);
1171                 {
1172                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1173                         DeleteEntryFromHash(ActiveQItems, It);
1174                 }
1175                 citthread_mutex_unlock(&ActiveQItemsLock);
1176                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1177                 DeleteHashPos(&It);
1178                 return;
1179         }// TODO: reenable me.*/
1180
1181         /*
1182          * Bail out if there's no actual message associated with this
1183          */
1184         if (MyQItem->MessageID < 0L) {
1185                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
1186                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1187                 citthread_mutex_lock(&ActiveQItemsLock);
1188                 {
1189                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1190                         DeleteEntryFromHash(ActiveQItems, It);
1191                 }
1192                 citthread_mutex_unlock(&ActiveQItemsLock);
1193                 DeleteHashPos(&It);
1194                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1195                 return;
1196         }
1197
1198         It = GetNewHashPos(MyQItem->MailQEntries, 0);
1199         while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
1200         {
1201                 MailQEntry *ThisItem = vQE;
1202                 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
1203         }
1204         DeleteHashPos(&It);
1205
1206         CountActiveQueueEntries(MyQItem);
1207         if (MyQItem->ActiveDeliveries > 0)
1208         {
1209                 int i = 1;
1210                 StrBuf *Msg = smtp_load_msg(MyQItem);
1211                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1212                 while ((i <= MyQItem->ActiveDeliveries) && 
1213                        (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
1214                 {
1215                         MailQEntry *ThisItem = vQE;
1216                         if (ThisItem->Active == 1) {
1217                                 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
1218                                 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries));
1219                                 i++;
1220                         }
1221                 }
1222                 DeleteHashPos(&It);
1223         }
1224         else 
1225         {
1226                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
1227                 citthread_mutex_lock(&ActiveQItemsLock);
1228                 {
1229                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
1230                         DeleteEntryFromHash(ActiveQItems, It);
1231                 }
1232                 citthread_mutex_unlock(&ActiveQItemsLock);
1233                 DeleteHashPos(&It);
1234                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
1235
1236 // TODO: bounce & delete?
1237
1238         }
1239 }
1240
1241
1242 /*****************************************************************************/
1243 /*                          SMTP UTILITY COMMANDS                            */
1244 /*****************************************************************************/
1245
1246 void cmd_smtp(char *argbuf) {
1247         char cmd[64];
1248         char node[256];
1249         char buf[1024];
1250         int i;
1251         int num_mxhosts;
1252
1253         if (CtdlAccessCheck(ac_aide)) return;
1254
1255         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1256
1257         if (!strcasecmp(cmd, "mx")) {
1258                 extract_token(node, argbuf, 1, '|', sizeof node);
1259                 num_mxhosts = getmx(buf, node);
1260                 cprintf("%d %d MX hosts listed for %s\n",
1261                         LISTING_FOLLOWS, num_mxhosts, node);
1262                 for (i=0; i<num_mxhosts; ++i) {
1263                         extract_token(node, buf, i, '|', sizeof node);
1264                         cprintf("%s\n", node);
1265                 }
1266                 cprintf("000\n");
1267                 return;
1268         }
1269
1270         else if (!strcasecmp(cmd, "runqueue")) {
1271                 run_queue_now = 1;
1272                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1273                 return;
1274         }
1275
1276         else {
1277                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1278         }
1279
1280 }
1281
1282
1283 /*
1284  * smtp_queue_thread()
1285  * 
1286  * Run through the queue sending out messages.
1287  */
1288 void *smtp_queue_thread(void *arg) {
1289         int num_processed = 0;
1290         struct CitContext smtp_queue_CC;
1291
1292         CtdlThreadSleep(10);
1293
1294         CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1295         citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1296         CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1297
1298         while (!CtdlThreadCheckStop()) {
1299                 
1300                 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1301
1302                 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1303                         CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1304                 }
1305                 else {
1306                         num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1307                 }
1308                 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1309                 CtdlThreadSleep(60);
1310         }
1311
1312         CtdlClearSystemContext();
1313         return(NULL);
1314 }
1315
1316
1317 /*
1318  * Initialize the SMTP outbound queue
1319  */
1320 void smtp_init_spoolout(void) {
1321         struct ctdlroom qrbuf;
1322
1323         /*
1324          * Create the room.  This will silently fail if the room already
1325          * exists, and that's perfectly ok, because we want it to exist.
1326          */
1327         CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1328
1329         /*
1330          * Make sure it's set to be a "system room" so it doesn't show up
1331          * in the <K>nown rooms list for Aides.
1332          */
1333         if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1334                 qrbuf.QRflags2 |= QR2_SYSTEM;
1335                 CtdlPutRoomLock(&qrbuf);
1336         }
1337 }
1338
1339
1340 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1341         SMTPC_read_greeting,
1342         SMTPC_read_EHLO_reply,
1343         SMTPC_read_HELO_reply,
1344         SMTPC_read_auth_reply,
1345         SMTPC_read_FROM_reply,
1346         SMTPC_read_RCPT_reply,
1347         SMTPC_read_DATAcmd_reply,
1348         SMTPC_read_dummy,
1349         SMTPC_read_data_body_reply,
1350         SMTPC_read_QUIT_reply
1351 };
1352
1353 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1354         SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1355         SMTPC_send_EHLO,
1356         STMPC_send_HELO,
1357         SMTPC_send_auth,
1358         SMTPC_send_FROM,
1359         SMTPC_send_RCPT,
1360         SMTPC_send_DATAcmd,
1361         SMTPC_send_data_body,
1362         SMTPC_send_terminate_data_body,
1363         SMTPC_send_QUIT
1364 };
1365
1366 eNextState SMTP_C_Terminate(void *Data)
1367 {
1368         SmtpOutMsg *pMsg = Data;
1369         FinalizeMessageSend(pMsg);
1370         return 0;
1371 }
1372
1373 eNextState SMTP_C_Timeout(void *Data)
1374 {
1375         SmtpOutMsg *pMsg = Data;
1376         FinalizeMessageSend(pMsg);
1377         return 0;
1378 }
1379
1380 eNextState SMTP_C_ConnFail(void *Data)
1381 {
1382         SmtpOutMsg *pMsg = Data;
1383         FinalizeMessageSend(pMsg);
1384         return 0;
1385 }
1386
1387 eNextState SMTP_C_DispatchReadDone(void *Data)
1388 {
1389         SmtpOutMsg *pMsg = Data;
1390         eNextState rc = ReadHandlers[pMsg->State](pMsg);
1391         pMsg->State++;
1392         return rc;
1393 }
1394
1395 eNextState SMTP_C_DispatchWriteDone(void *Data)
1396 {
1397         SmtpOutMsg *pMsg = Data;
1398         return SendHandlers[pMsg->State](pMsg);
1399         
1400 }
1401
1402 #endif
1403 CTDL_MODULE_INIT(smtp_eventclient)
1404 {
1405 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1406         if (!threading)
1407         {
1408                 ActiveQItems = NewHash(1, Flathash);
1409                 citthread_mutex_init(&ActiveQItemsLock, NULL);
1410
1411                 QItemHandlers = NewHash(0, NULL);
1412
1413                 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
1414                 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
1415                 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
1416                 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
1417                 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
1418                 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
1419 ///submitted /TODO: flush qitemhandlers on exit
1420
1421
1422                 smtp_init_spoolout();
1423                 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1424
1425                 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1426         }
1427 #endif
1428         
1429         /* return our Subversion id for the Log */
1430         return "smtpeventclient";
1431 }