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