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