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