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