libev migration; cleanup bounce; use libev to get the current time.
[citadel.git] / citadel / modules / smtp / serv_smtpqueue.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 "smtpqueue.h"
89 #include "event_client.h"
90
91 HashList *QItemHandlers = NULL;
92
93 citthread_mutex_t ActiveQItemsLock;
94 HashList *ActiveQItems = NULL;
95
96 int MsgCount = 0;
97 int run_queue_now = 0;  /* Set to 1 to ignore SMTP send retry times */
98
99 void smtp_try(OneQueItem *MyQItem, 
100               MailQEntry *MyQEntry, 
101               StrBuf *MsgText, 
102               int KeepMsgText, /* KeepMsgText allows us to use MsgText as ours. */
103               int MsgCount);
104
105
106 void smtp_evq_cleanup(void)
107 {
108         citthread_mutex_lock(&ActiveQItemsLock);
109         DeleteHash(&QItemHandlers);
110         DeleteHash(&ActiveQItems);
111         citthread_mutex_unlock(&ActiveQItemsLock);
112         citthread_mutex_destroy(&ActiveQItemsLock);
113 }
114
115 int DecreaseQReference(OneQueItem *MyQItem)
116 {
117         int IDestructQueItem;
118
119         citthread_mutex_lock(&ActiveQItemsLock);
120         MyQItem->ActiveDeliveries--;
121         IDestructQueItem = MyQItem->ActiveDeliveries == 0;
122         citthread_mutex_unlock(&ActiveQItemsLock);
123         return IDestructQueItem;
124 }
125
126 void RemoveQItem(OneQueItem *MyQItem)
127 {
128         HashPos  *It;
129
130         It = GetNewHashPos(MyQItem->MailQEntries, 0);
131         citthread_mutex_lock(&ActiveQItemsLock);
132         {
133                 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
134                 DeleteEntryFromHash(ActiveQItems, It);
135         }
136         citthread_mutex_unlock(&ActiveQItemsLock);
137         DeleteHashPos(&It);
138 }
139
140
141
142 void FreeMailQEntry(void *qv)
143 {
144         MailQEntry *Q = qv;
145         FreeStrBuf(&Q->Recipient);
146         FreeStrBuf(&Q->StatusMessage);
147         free(Q);
148 }
149 void FreeQueItem(OneQueItem **Item)
150 {
151         DeleteHash(&(*Item)->MailQEntries);
152         FreeStrBuf(&(*Item)->EnvelopeFrom);
153         FreeStrBuf(&(*Item)->BounceTo);
154         free(*Item);
155         Item = NULL;
156 }
157 void HFreeQueItem(void *Item)
158 {
159         FreeQueItem((OneQueItem**)&Item);
160 }
161
162 /* inspect recipients with a status of: 
163  * - 0 (no delivery yet attempted) 
164  * - 3/4 (transient errors
165  *        were experienced and it's time to try again)
166  */
167 int CountActiveQueueEntries(OneQueItem *MyQItem)
168 {
169         HashPos  *It;
170         long len;
171         const char *Key;
172         void *vQE;
173
174         MyQItem->ActiveDeliveries = 0;
175         It = GetNewHashPos(MyQItem->MailQEntries, 0);
176         while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
177         {
178                 MailQEntry *ThisItem = vQE;
179                 if ((ThisItem->Status == 0) || 
180                     (ThisItem->Status == 3) ||
181                     (ThisItem->Status == 4))
182                 {
183                         MyQItem->ActiveDeliveries++;
184                         ThisItem->Active = 1;
185                 }
186                 else 
187                         ThisItem->Active = 0;
188         }
189         DeleteHashPos(&It);
190         return MyQItem->ActiveDeliveries;
191 }
192
193 OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
194 {
195         OneQueItem *Item;
196         const char *pLine = NULL;
197         StrBuf *Line;
198         StrBuf *Token;
199         void *v;
200
201         Item = (OneQueItem*)malloc(sizeof(OneQueItem));
202         memset(Item, 0, sizeof(OneQueItem));
203         Item->LastAttempt.retry = SMTP_RETRY_INTERVAL;
204         Item->MessageID = -1;
205         Item->QueMsgID = QueMsgID;
206
207         citthread_mutex_lock(&ActiveQItemsLock);
208         if (GetHash(ActiveQItems, 
209                     IKEY(Item->QueMsgID), 
210                     &v))
211         {
212                 /* WHOOPS. somebody else is already working on this. */
213                 citthread_mutex_unlock(&ActiveQItemsLock);
214                 FreeQueItem(&Item);
215                 return NULL;
216         }
217         else {
218                 /* mark our claim on this. */
219                 Put(ActiveQItems, 
220                     IKEY(Item->QueMsgID),
221                     Item,
222                     HFreeQueItem);
223                 citthread_mutex_unlock(&ActiveQItemsLock);
224         }
225
226         Token = NewStrBuf();
227         Line = NewStrBufPlain(NULL, 128);
228         while (pLine != StrBufNOTNULL) {
229                 const char *pItemPart = NULL;
230                 void *vHandler;
231
232                 StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
233                 if (StrLength(Line) == 0) continue;
234                 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
235                 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
236                 {
237                         QItemHandler H;
238                         H = (QItemHandler) vHandler;
239                         H(Item, Line, &pItemPart);
240                 }
241         }
242         FreeStrBuf(&Line);
243         FreeStrBuf(&Token);
244         return Item;
245 }
246
247 StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
248 {
249         StrBuf *QMessage;
250         HashPos  *It;
251         const char *Key;
252         long len;
253         void *vQE;
254
255         QMessage = NewStrBufPlain(NULL, SIZ);
256         StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
257
258 //                 "attempted|%ld\n"  "retry|%ld\n",, (long)time(NULL), (long)retry );
259         StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
260         StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
261
262         StrBufAppendBufPlain(QMessage, HKEY("\nsubmitted|"), 0);
263         StrBufAppendPrintf(QMessage, "%ld", MyQItem->Submitted);
264
265         if (StrLength(MyQItem->BounceTo) > 0) {
266                 StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
267                 StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
268         }
269
270         if (StrLength(MyQItem->EnvelopeFrom) > 0) {
271                 StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
272                 StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
273         }
274
275         It = GetNewHashPos(MyQItem->MailQEntries, 0);
276         while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
277         {
278                 MailQEntry *ThisItem = vQE;
279                 int i;
280
281                 if (!ThisItem->Active)
282                         continue; /* skip already sent ones from the spoolfile. */
283
284                 for (i=0; i < ThisItem->nAttempts; i++) {
285                         /* TODO: most probably there is just one retry/attempted per message! */
286                         StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
287                         StrBufAppendPrintf(QMessage, "%ld", 
288                                            ThisItem->Attempts[i].retry);
289
290                         StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
291                         StrBufAppendPrintf(QMessage, "%ld", 
292                                            ThisItem->Attempts[i].when);
293                 }
294                 StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
295                 StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
296                 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
297                 StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
298                 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
299                 StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
300         }
301         DeleteHashPos(&It);
302         StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);  
303         return QMessage;
304 }
305
306
307
308
309
310 void NewMailQEntry(OneQueItem *Item)
311 {
312         Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
313         memset(Item->Current, 0, sizeof(MailQEntry));
314
315         if (Item->MailQEntries == NULL)
316                 Item->MailQEntries = NewHash(1, Flathash);
317         Item->Current->StatusMessage = NewStrBuf();
318         Item->Current->n = GetCount(Item->MailQEntries);
319         Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
320 }
321
322 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
323 {
324         Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
325 }
326
327 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
328 {
329         if (Item->EnvelopeFrom == NULL)
330                 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
331         StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
332 }
333
334 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
335 {
336         if (Item->BounceTo == NULL)
337                 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
338         StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
339 }
340
341 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
342 {
343         if (Item->Current == NULL)
344                 NewMailQEntry(Item);
345         if (Item->Current->Recipient == NULL)
346                 Item->Current->Recipient =  NewStrBufPlain(NULL, StrLength(Line));
347         StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
348         Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
349         StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
350         Item->Current = NULL; // TODO: is this always right?
351 }
352
353
354 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
355 {
356         if (Item->Current == NULL)
357                 NewMailQEntry(Item);
358         if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
359                 Item->Current->nAttempts++;
360         if (Item->Current->nAttempts > MaxAttempts) {
361                 Item->FailNow = 1;
362                 return;
363         }
364         Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
365 }
366
367
368 void QItem_Handle_Submitted(OneQueItem *Item, StrBuf *Line, const char **Pos)
369 {
370         Item->Submitted = atol(*Pos);
371
372 }
373
374 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
375 {
376         if (Item->Current == NULL)
377                 NewMailQEntry(Item);
378         if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
379                 Item->Current->nAttempts++;
380         if (Item->Current->nAttempts > MaxAttempts) {
381                 Item->FailNow = 1;
382                 return;
383         }
384                 
385         Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
386         if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
387         {
388                 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
389                 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
390                 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
391                         Item->LastAttempt.retry = SMTP_RETRY_MAX;
392         }
393 }
394
395
396
397 /**
398  * this one has to have the context for loading the message via the redirect buffer...
399  */
400 StrBuf *smtp_load_msg(OneQueItem *MyQItem, int n)
401 {
402         CitContext *CCC=CC;
403         StrBuf *SendMsg;
404         
405         CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
406         CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
407         SendMsg = CCC->redirect_buffer;
408         CCC->redirect_buffer = NULL;
409         if ((StrLength(SendMsg) > 0) && 
410             ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
411                 CtdlLogPrintf(CTDL_WARNING, 
412                               "SMTP client[%ld]: Possible problem: message did not "
413                               "correctly terminate. (expecting 0x10, got 0x%02x)\n",
414                               MsgCount, //yes uncool, but best choice here... 
415                               ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
416                 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
417         }
418         return SendMsg;
419 }
420
421
422
423 /*
424  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
425  * instructions for "5" codes (permanent fatal errors) and produce/deliver
426  * a "bounce" message (delivery status notification).
427  */
428 void smtpq_do_bounce(OneQueItem *MyQItem, StrBuf *OMsgTxt) 
429 {
430         static int seq = 0;
431
432         struct CtdlMessage *bmsg = NULL;
433         StrBuf *boundary;
434         StrBuf *Msg = NULL; 
435         StrBuf *BounceMB;
436         struct recptypes *valid;
437         
438         HashPos *It;
439         void *vQE;
440         long len;
441         const char *Key;
442
443         int successful_bounce = 0;
444         int num_bounces = 0;
445         int give_up = 0;
446
447         CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
448
449         if ( (ev_time() - MyQItem->Submitted) > SMTP_GIVE_UP ) {
450                 give_up = 1;/// TODO: replace time by libevq timer get
451         }
452
453         /*
454          * Now go through the instructions checking for stuff.
455          */
456         It = GetNewHashPos(MyQItem->MailQEntries, 0);
457         while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
458         {
459                 MailQEntry *ThisItem = vQE;
460                 if ((ThisItem->Status == 5) || /* failed now? */
461                     ((give_up == 1) && (ThisItem->Status != 2))) /* giving up after failed attempts... */
462                 {
463                         if (num_bounces == 0)
464                                 Msg = NewStrBufPlain(NULL, 1024);
465                         ++num_bounces;
466         
467                         StrBufAppendBuf(Msg, ThisItem->Recipient, 0);
468                         StrBufAppendBufPlain(Msg, HKEY(": "), 0);
469                         StrBufAppendBuf(Msg, ThisItem->StatusMessage, 0);
470                         StrBufAppendBufPlain(Msg, HKEY("\r\n"), 0);
471                 }
472         }
473         DeleteHashPos(&It);
474
475         /* Deliver the bounce if there's anything worth mentioning */
476         CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
477
478         if (num_bounces == 0) {
479                 FreeStrBuf(&Msg);
480                 return;
481         }
482
483         boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
484         StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
485
486         /* Start building our bounce message; go shopping for memory first. */
487         BounceMB = NewStrBufPlain(NULL, 
488                                   1024 + /* mime stuff.... */
489                                   StrLength(Msg) +  /* the bounce information... */
490                                   StrLength(OMsgTxt)); /* the original message */
491         if (BounceMB == NULL) {
492                 FreeStrBuf(&boundary);
493                 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
494
495                 return;
496         }
497
498         bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
499         if (bmsg == NULL) {
500                 FreeStrBuf(&boundary);
501                 FreeStrBuf(&BounceMB);
502                 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
503
504                 return;
505         }
506         memset(bmsg, 0, sizeof(struct CtdlMessage));
507
508
509         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
510         StrBufAppendBuf(BounceMB, boundary, 0);
511         StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
512         StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
513         StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
514         StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
515         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
516         StrBufAppendBuf(BounceMB, boundary, 0);
517         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
518         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
519
520         if (give_up) 
521                 StrBufAppendBufPlain(
522                         BounceMB, 
523                         HKEY(
524                                 "A message you sent could not be delivered to some or all of its recipients\n"
525                                 "due to prolonged unavailability of its destination(s).\n"
526                                 "Giving up on the following addresses:\n\n"
527                                 ), 0);
528         else 
529                 StrBufAppendBufPlain(
530                         BounceMB, 
531                         HKEY(
532                                 "A message you sent could not be delivered to some or all of its recipients.\n"
533                                 "The following addresses were undeliverable:\n\n"
534                                 ), 0);
535
536         StrBufAppendBuf(BounceMB, Msg, 0);
537         FreeStrBuf(&Msg);
538
539         /* Attach the original message */
540         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
541         StrBufAppendBuf(BounceMB, boundary, 0);
542         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
543         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
544         StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
545         StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
546         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
547         StrBufAppendBuf(BounceMB, OMsgTxt, 0);
548
549         /* Close the multipart MIME scope */
550         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
551         StrBufAppendBuf(BounceMB, boundary, 0);
552         StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
553
554
555
556         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
557         bmsg->cm_anon_type = MES_NORMAL;
558         bmsg->cm_format_type = FMT_RFC822;
559
560         bmsg->cm_fields['O'] = strdup(MAILROOM);
561         bmsg->cm_fields['A'] = strdup("Citadel");
562         bmsg->cm_fields['N'] = strdup(config.c_nodename);
563         bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
564         bmsg->cm_fields['M'] = SmashStrBuf(&BounceMB);
565
566         /* First try the user who sent the message */
567         if (StrLength(MyQItem->BounceTo) == 0) 
568                 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
569         else
570                 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", ChrPtr(MyQItem->BounceTo));
571
572         /* Can we deliver the bounce to the original sender? */
573         valid = validate_recipients(ChrPtr(MyQItem->BounceTo), NULL, 0);
574         if ((valid != NULL) && (valid->num_error == 0)) {
575                 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
576                 successful_bounce = 1;
577         }
578
579         /* If not, post it in the Aide> room */
580         if (successful_bounce == 0) {
581                 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
582         }
583
584         /* Free up the memory we used */
585         free_recipients(valid);
586         FreeStrBuf(&boundary);
587         CtdlFreeMessage(bmsg);
588         CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
589 }
590
591 /*
592  * smtp_do_procmsg()
593  *
594  * Called by smtp_do_queue() to handle an individual message.
595  */
596 void smtp_do_procmsg(long msgnum, void *userdata) {
597         struct CtdlMessage *msg = NULL;
598         char *instr = NULL;     
599         StrBuf *PlainQItem;
600         OneQueItem *MyQItem;
601         char *pch;
602         HashPos  *It;
603         void *vQE;
604         long len;
605         const char *Key;
606
607         CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
608         ///strcpy(envelope_from, "");
609
610         msg = CtdlFetchMessage(msgnum, 1);
611         if (msg == NULL) {
612                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
613                 return;
614         }
615
616         pch = instr = msg->cm_fields['M'];
617
618         /* Strip out the headers (no not amd any other non-instruction) line */
619         while (pch != NULL) {
620                 pch = strchr(pch, '\n');
621                 if ((pch != NULL) && (*(pch + 1) == '\n')) {
622                         instr = pch + 2;
623                         pch = NULL;
624                 }
625         }
626         PlainQItem = NewStrBufPlain(instr, -1);
627         CtdlFreeMessage(msg);
628         MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
629         FreeStrBuf(&PlainQItem);
630
631         if (MyQItem == NULL) {
632                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);              
633                 return; /* s.b. else is already processing... */
634         }
635
636         /*
637          * Postpone delivery if we've already tried recently.
638          * /
639         if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
640                 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
641
642                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
643                 citthread_mutex_lock(&ActiveQItemsLock);
644                 {
645                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
646                         DeleteEntryFromHash(ActiveQItems, It);
647                 }
648                 citthread_mutex_unlock(&ActiveQItemsLock);
649                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
650                 DeleteHashPos(&It);
651                 return;
652         }// TODO: reenable me.*/
653
654         /*
655          * Bail out if there's no actual message associated with this
656          */
657         if (MyQItem->MessageID < 0L) {
658                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
659                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
660                 citthread_mutex_lock(&ActiveQItemsLock);
661                 {
662                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
663                         DeleteEntryFromHash(ActiveQItems, It);
664                 }
665                 citthread_mutex_unlock(&ActiveQItemsLock);
666                 DeleteHashPos(&It);
667                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
668                 return;
669         }
670
671         It = GetNewHashPos(MyQItem->MailQEntries, 0);
672         while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
673         {
674                 MailQEntry *ThisItem = vQE;
675                 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
676         }
677         DeleteHashPos(&It);
678
679         CountActiveQueueEntries(MyQItem);
680         if (MyQItem->ActiveDeliveries > 0)
681         {
682                 int n = MsgCount++;
683                 int i = 1;
684                 StrBuf *Msg = smtp_load_msg(MyQItem, n);
685                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
686                 while ((i <= MyQItem->ActiveDeliveries) && 
687                        (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
688                 {
689                         MailQEntry *ThisItem = vQE;
690                         if (ThisItem->Active == 1) {
691                                 if (i > 1) n = MsgCount++;
692                                 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
693                                 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries), n);
694                                 i++;
695                         }
696                 }
697                 DeleteHashPos(&It);
698         }
699         else 
700         {
701                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
702                 citthread_mutex_lock(&ActiveQItemsLock);
703                 {
704                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
705                         DeleteEntryFromHash(ActiveQItems, It);
706                 }
707                 citthread_mutex_unlock(&ActiveQItemsLock);
708                 DeleteHashPos(&It);
709                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
710
711 // TODO: bounce & delete?
712
713         }
714 }
715
716
717
718 /*
719  * smtp_queue_thread()
720  * 
721  * Run through the queue sending out messages.
722  */
723 void *smtp_queue_thread(void *arg) {
724         int num_processed = 0;
725         struct CitContext smtp_queue_CC;
726
727         CtdlThreadSleep(10);
728
729         CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
730         citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
731         CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
732
733         while (!CtdlThreadCheckStop()) {
734                 
735                 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
736
737                 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
738                         CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
739                 }
740                 else {
741                         num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
742                 }
743                 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
744                 CtdlThreadSleep(60);
745         }
746
747         CtdlClearSystemContext();
748         return(NULL);
749 }
750
751
752
753 /*
754  * Initialize the SMTP outbound queue
755  */
756 void smtp_init_spoolout(void) {
757         struct ctdlroom qrbuf;
758
759         /*
760          * Create the room.  This will silently fail if the room already
761          * exists, and that's perfectly ok, because we want it to exist.
762          */
763         CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
764
765         /*
766          * Make sure it's set to be a "system room" so it doesn't show up
767          * in the <K>nown rooms list for Aides.
768          */
769         if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
770                 qrbuf.QRflags2 |= QR2_SYSTEM;
771                 CtdlPutRoomLock(&qrbuf);
772         }
773 }
774
775
776
777
778 /*****************************************************************************/
779 /*                          SMTP UTILITY COMMANDS                            */
780 /*****************************************************************************/
781
782 void cmd_smtp(char *argbuf) {
783         char cmd[64];
784         char node[256];
785         char buf[1024];
786         int i;
787         int num_mxhosts;
788
789         if (CtdlAccessCheck(ac_aide)) return;
790
791         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
792
793         if (!strcasecmp(cmd, "mx")) {
794                 extract_token(node, argbuf, 1, '|', sizeof node);
795                 num_mxhosts = getmx(buf, node);
796                 cprintf("%d %d MX hosts listed for %s\n",
797                         LISTING_FOLLOWS, num_mxhosts, node);
798                 for (i=0; i<num_mxhosts; ++i) {
799                         extract_token(node, buf, i, '|', sizeof node);
800                         cprintf("%s\n", node);
801                 }
802                 cprintf("000\n");
803                 return;
804         }
805
806         else if (!strcasecmp(cmd, "runqueue")) {
807                 run_queue_now = 1;
808                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
809                 return;
810         }
811
812         else {
813                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
814         }
815
816 }
817
818
819
820
821 CTDL_MODULE_INIT(smtp_queu)
822 {
823 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
824         if (!threading)
825         {
826                 ActiveQItems = NewHash(1, Flathash);
827                 citthread_mutex_init(&ActiveQItemsLock, NULL);
828
829                 QItemHandlers = NewHash(0, NULL);
830
831                 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
832                 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
833                 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
834                 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
835                 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
836                 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
837                 Put(QItemHandlers, HKEY("submitted"), QItem_Handle_Submitted, reference_free_handler);
838 ////TODO: flush qitemhandlers on exit
839                 smtp_init_spoolout();
840
841                 CtdlRegisterCleanupHook(smtp_evq_cleanup);
842                 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
843
844                 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
845         }
846 #endif
847         
848         /* return our Subversion id for the Log */
849         return "smtpeventclient";
850 }