Fix bouncing of messages in smtp-queue
[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         StrBuf *boundary, *Msg = NULL; 
431         int num_bounces = 0;
432         struct CtdlMessage *bmsg = NULL;
433         int give_up = 0;
434         struct recptypes *valid;
435         int successful_bounce = 0;
436         static int seq = 0;
437         StrBuf *BounceMB;
438         HashPos  *It;
439         void *vQE;
440         long len;
441         const char *Key;
442
443         CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
444         
445         if ( (time(NULL) - MyQItem->Submitted) > SMTP_GIVE_UP ) {
446                 give_up = 1;/// TODO: replace time by libevq timer get
447         }
448
449         /*
450          * Now go through the instructions checking for stuff.
451          */
452         It = GetNewHashPos(MyQItem->MailQEntries, 0);
453         while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
454         {
455                 MailQEntry *ThisItem = vQE;
456                 if ((ThisItem->Status == 5) || /* failed now? */
457                     ((give_up == 1) && (ThisItem->Status != 2))) /* giving up after failed attempts... */
458                 {
459                         if (num_bounces == 0)
460                                 Msg = NewStrBufPlain(NULL, 1024);
461                         ++num_bounces;
462         
463                         StrBufAppendBuf(Msg, ThisItem->Recipient, 0);
464                         StrBufAppendBufPlain(Msg, HKEY(": "), 0);
465                         StrBufAppendBuf(Msg, ThisItem->StatusMessage, 0);
466                         StrBufAppendBufPlain(Msg, HKEY("\r\n"), 0);
467                 }
468         }
469         DeleteHashPos(&It);
470
471         /* Deliver the bounce if there's anything worth mentioning */
472         CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
473
474         if (num_bounces == 0) {
475                 FreeStrBuf(&Msg);
476                 return;
477         }
478
479         boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
480         StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
481
482         /* Start building our bounce message; go shopping for memory first. */
483         BounceMB = NewStrBufPlain(NULL, 
484                                   1024 + /* mime stuff.... */
485                                   StrLength(Msg) +  /* the bounce information... */
486                                   StrLength(OMsgTxt)); /* the original message */
487         if (BounceMB == NULL) {
488                 FreeStrBuf(&boundary);
489                 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
490
491                 return;
492         }
493
494         bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
495         if (bmsg == NULL) {
496                 FreeStrBuf(&boundary);
497                 FreeStrBuf(&BounceMB);
498                 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
499
500                 return;
501         }
502         memset(bmsg, 0, sizeof(struct CtdlMessage));
503
504
505         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
506         StrBufAppendBuf(BounceMB, boundary, 0);
507         StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
508         StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
509         StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
510         StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
511         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
512         StrBufAppendBuf(BounceMB, boundary, 0);
513         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
514         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
515
516         if (give_up) 
517                 StrBufAppendBufPlain(
518                         BounceMB, 
519                         HKEY(
520                                 "A message you sent could not be delivered to some or all of its recipients\n"
521                                 "due to prolonged unavailability of its destination(s).\n"
522                                 "Giving up on the following addresses:\n\n"
523                                 ), 0);
524         else 
525                 StrBufAppendBufPlain(
526                         BounceMB, 
527                         HKEY(
528                                 "A message you sent could not be delivered to some or all of its recipients.\n"
529                                 "The following addresses were undeliverable:\n\n"
530                                 ), 0);
531
532         StrBufAppendBuf(BounceMB, Msg, 0);
533         FreeStrBuf(&Msg);
534
535         /* Attach the original message */
536         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
537         StrBufAppendBuf(BounceMB, boundary, 0);
538         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
539         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
540         StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
541         StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
542         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
543         StrBufAppendBuf(BounceMB, OMsgTxt, 0);
544
545         /* Close the multipart MIME scope */
546         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
547         StrBufAppendBuf(BounceMB, boundary, 0);
548         StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
549
550
551
552         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
553         bmsg->cm_anon_type = MES_NORMAL;
554         bmsg->cm_format_type = FMT_RFC822;
555
556         bmsg->cm_fields['O'] = strdup(MAILROOM);
557         bmsg->cm_fields['A'] = strdup("Citadel");
558         bmsg->cm_fields['N'] = strdup(config.c_nodename);
559         bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
560         bmsg->cm_fields['M'] = SmashStrBuf(&BounceMB);
561
562         /* First try the user who sent the message */
563         if (StrLength(MyQItem->BounceTo) == 0) 
564                 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
565         else
566                 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", ChrPtr(MyQItem->BounceTo));
567
568         /* Can we deliver the bounce to the original sender? */
569         valid = validate_recipients(ChrPtr(MyQItem->BounceTo), NULL, 0);
570         if ((valid != NULL) && (valid->num_error == 0)) {
571                 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
572                 successful_bounce = 1;
573         }
574
575         /* If not, post it in the Aide> room */
576         if (successful_bounce == 0) {
577                 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
578         }
579
580         /* Free up the memory we used */
581         free_recipients(valid);
582         FreeStrBuf(&boundary);
583         CtdlFreeMessage(bmsg);
584         CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
585 }
586
587 /*
588  * smtp_do_procmsg()
589  *
590  * Called by smtp_do_queue() to handle an individual message.
591  */
592 void smtp_do_procmsg(long msgnum, void *userdata) {
593         struct CtdlMessage *msg = NULL;
594         char *instr = NULL;     
595         StrBuf *PlainQItem;
596         OneQueItem *MyQItem;
597         char *pch;
598         HashPos  *It;
599         void *vQE;
600         long len;
601         const char *Key;
602
603         CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
604         ///strcpy(envelope_from, "");
605
606         msg = CtdlFetchMessage(msgnum, 1);
607         if (msg == NULL) {
608                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
609                 return;
610         }
611
612         pch = instr = msg->cm_fields['M'];
613
614         /* Strip out the headers (no not amd any other non-instruction) line */
615         while (pch != NULL) {
616                 pch = strchr(pch, '\n');
617                 if ((pch != NULL) && (*(pch + 1) == '\n')) {
618                         instr = pch + 2;
619                         pch = NULL;
620                 }
621         }
622         PlainQItem = NewStrBufPlain(instr, -1);
623         CtdlFreeMessage(msg);
624         MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
625         FreeStrBuf(&PlainQItem);
626
627         if (MyQItem == NULL) {
628                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);              
629                 return; /* s.b. else is already processing... */
630         }
631
632         /*
633          * Postpone delivery if we've already tried recently.
634          * /
635         if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
636                 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
637
638                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
639                 citthread_mutex_lock(&ActiveQItemsLock);
640                 {
641                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
642                         DeleteEntryFromHash(ActiveQItems, It);
643                 }
644                 citthread_mutex_unlock(&ActiveQItemsLock);
645                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
646                 DeleteHashPos(&It);
647                 return;
648         }// TODO: reenable me.*/
649
650         /*
651          * Bail out if there's no actual message associated with this
652          */
653         if (MyQItem->MessageID < 0L) {
654                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
655                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
656                 citthread_mutex_lock(&ActiveQItemsLock);
657                 {
658                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
659                         DeleteEntryFromHash(ActiveQItems, It);
660                 }
661                 citthread_mutex_unlock(&ActiveQItemsLock);
662                 DeleteHashPos(&It);
663                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
664                 return;
665         }
666
667         It = GetNewHashPos(MyQItem->MailQEntries, 0);
668         while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
669         {
670                 MailQEntry *ThisItem = vQE;
671                 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
672         }
673         DeleteHashPos(&It);
674
675         CountActiveQueueEntries(MyQItem);
676         if (MyQItem->ActiveDeliveries > 0)
677         {
678                 int n = MsgCount++;
679                 int i = 1;
680                 StrBuf *Msg = smtp_load_msg(MyQItem, n);
681                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
682                 while ((i <= MyQItem->ActiveDeliveries) && 
683                        (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
684                 {
685                         MailQEntry *ThisItem = vQE;
686                         if (ThisItem->Active == 1) {
687                                 if (i > 1) n = MsgCount++;
688                                 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
689                                 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries), n);
690                                 i++;
691                         }
692                 }
693                 DeleteHashPos(&It);
694         }
695         else 
696         {
697                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
698                 citthread_mutex_lock(&ActiveQItemsLock);
699                 {
700                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
701                         DeleteEntryFromHash(ActiveQItems, It);
702                 }
703                 citthread_mutex_unlock(&ActiveQItemsLock);
704                 DeleteHashPos(&It);
705                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
706
707 // TODO: bounce & delete?
708
709         }
710 }
711
712
713
714 /*
715  * smtp_queue_thread()
716  * 
717  * Run through the queue sending out messages.
718  */
719 void *smtp_queue_thread(void *arg) {
720         int num_processed = 0;
721         struct CitContext smtp_queue_CC;
722
723         CtdlThreadSleep(10);
724
725         CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
726         citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
727         CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
728
729         while (!CtdlThreadCheckStop()) {
730                 
731                 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
732
733                 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
734                         CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
735                 }
736                 else {
737                         num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
738                 }
739                 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
740                 CtdlThreadSleep(60);
741         }
742
743         CtdlClearSystemContext();
744         return(NULL);
745 }
746
747
748
749 /*
750  * Initialize the SMTP outbound queue
751  */
752 void smtp_init_spoolout(void) {
753         struct ctdlroom qrbuf;
754
755         /*
756          * Create the room.  This will silently fail if the room already
757          * exists, and that's perfectly ok, because we want it to exist.
758          */
759         CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
760
761         /*
762          * Make sure it's set to be a "system room" so it doesn't show up
763          * in the <K>nown rooms list for Aides.
764          */
765         if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
766                 qrbuf.QRflags2 |= QR2_SYSTEM;
767                 CtdlPutRoomLock(&qrbuf);
768         }
769 }
770
771
772
773
774 /*****************************************************************************/
775 /*                          SMTP UTILITY COMMANDS                            */
776 /*****************************************************************************/
777
778 void cmd_smtp(char *argbuf) {
779         char cmd[64];
780         char node[256];
781         char buf[1024];
782         int i;
783         int num_mxhosts;
784
785         if (CtdlAccessCheck(ac_aide)) return;
786
787         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
788
789         if (!strcasecmp(cmd, "mx")) {
790                 extract_token(node, argbuf, 1, '|', sizeof node);
791                 num_mxhosts = getmx(buf, node);
792                 cprintf("%d %d MX hosts listed for %s\n",
793                         LISTING_FOLLOWS, num_mxhosts, node);
794                 for (i=0; i<num_mxhosts; ++i) {
795                         extract_token(node, buf, i, '|', sizeof node);
796                         cprintf("%s\n", node);
797                 }
798                 cprintf("000\n");
799                 return;
800         }
801
802         else if (!strcasecmp(cmd, "runqueue")) {
803                 run_queue_now = 1;
804                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
805                 return;
806         }
807
808         else {
809                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
810         }
811
812 }
813
814
815
816
817 CTDL_MODULE_INIT(smtp_queu)
818 {
819 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
820         if (!threading)
821         {
822                 ActiveQItems = NewHash(1, Flathash);
823                 citthread_mutex_init(&ActiveQItemsLock, NULL);
824
825                 QItemHandlers = NewHash(0, NULL);
826
827                 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
828                 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
829                 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
830                 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
831                 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
832                 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
833                 Put(QItemHandlers, HKEY("submitted"), QItem_Handle_Submitted, reference_free_handler);
834 ////TODO: flush qitemhandlers on exit
835                 smtp_init_spoolout();
836
837                 CtdlRegisterCleanupHook(smtp_evq_cleanup);
838                 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
839
840                 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
841         }
842 #endif
843         
844         /* return our Subversion id for the Log */
845         return "smtpeventclient";
846 }