65cde8f15354eff3c15ed6e2066ab9bce9b4389e
[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                                         Url = &(*Url)->Next;
700                         }
701                         FreeStrBuf(&All);
702                         FreeStrBuf(&One);
703                 }
704
705                 Url = &MyQItem->FallBackHost;
706                 nRelays = get_hosts(mxbuf, "fallbackhost");
707                 if (nRelays > 0) {
708                         StrBuf *All;
709                         StrBuf *One;
710                         const char *Pos = NULL;
711                         All = NewStrBufPlain(mxbuf, -1);
712                         One = NewStrBufPlain(NULL, StrLength(All) + 1);
713                         
714                         while ((Pos != StrBufNOTNULL) && ((Pos == NULL) || !IsEmptyStr(Pos))) {
715                                 StrBufExtract_NextToken(One, All, &Pos, '|');
716                                 if (!ParseURL(Url, One, 25))
717                                         CtdlLogPrintf(CTDL_DEBUG, "Failed to parse: %s\n", ChrPtr(One));
718                                 else 
719                                         Url = &(*Url)->Next;
720                         }
721                         FreeStrBuf(&All);
722                         FreeStrBuf(&One);
723                 }
724         }
725
726         It = GetNewHashPos(MyQItem->MailQEntries, 0);
727         while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
728         {
729                 MailQEntry *ThisItem = vQE;
730                 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
731         }
732         DeleteHashPos(&It);
733
734         CountActiveQueueEntries(MyQItem);
735         if (MyQItem->ActiveDeliveries > 0)
736         {
737                 int n = MsgCount++;
738                 int m = MyQItem->ActiveDeliveries;
739                 int i = 1;
740                 Msg = smtp_load_msg(MyQItem, n);
741                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
742                 while ((i <= m) && 
743                        (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
744                 {
745                         MailQEntry *ThisItem = vQE;
746                         if (ThisItem->Active == 1) {
747                                 int KeepBuffers = (i == m);
748                                 if (i > 1) n = MsgCount++;
749                                 CtdlLogPrintf(CTDL_DEBUG, 
750                                               "SMTP Queue: Trying <%s> %d / %d \n", 
751                                               ChrPtr(ThisItem->Recipient), 
752                                               i, 
753                                               m);
754                                 smtp_try_one_queue_entry(MyQItem, 
755                                                          ThisItem, 
756                                                          Msg, 
757                                                          KeepBuffers, 
758                                                          n, 
759                                                          RelayUrls);
760                                 if (KeepBuffers) HaveBuffers = 1;
761                                 i++;
762                         }
763                 }
764                 DeleteHashPos(&It);
765         }
766         else 
767         {
768                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
769                 citthread_mutex_lock(&ActiveQItemsLock);
770                 {
771                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
772                         DeleteEntryFromHash(ActiveQItems, It);
773                 }
774                 citthread_mutex_unlock(&ActiveQItemsLock);
775                 DeleteHashPos(&It);
776                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
777
778 // TODO: bounce & delete?
779
780         }
781         if (!HaveBuffers) {
782                 FreeStrBuf (&Msg);
783 // TODO : free RelayUrls
784         }
785 }
786
787
788
789 /*
790  * smtp_queue_thread()
791  * 
792  * Run through the queue sending out messages.
793  */
794 void *smtp_queue_thread(void *arg) {
795         int num_processed = 0;
796         struct CitContext smtp_queue_CC;
797
798         CtdlThreadSleep(10);
799
800         CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
801         citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
802         CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
803
804         while (!CtdlThreadCheckStop()) {
805                 
806                 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
807
808                 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
809                         CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
810                 }
811                 else {
812                         num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
813                 }
814                 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
815                 CtdlThreadSleep(60);
816         }
817
818         CtdlClearSystemContext();
819         return(NULL);
820 }
821
822
823
824 /*
825  * Initialize the SMTP outbound queue
826  */
827 void smtp_init_spoolout(void) {
828         struct ctdlroom qrbuf;
829
830         /*
831          * Create the room.  This will silently fail if the room already
832          * exists, and that's perfectly ok, because we want it to exist.
833          */
834         CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
835
836         /*
837          * Make sure it's set to be a "system room" so it doesn't show up
838          * in the <K>nown rooms list for Aides.
839          */
840         if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
841                 qrbuf.QRflags2 |= QR2_SYSTEM;
842                 CtdlPutRoomLock(&qrbuf);
843         }
844 }
845
846
847
848
849 /*****************************************************************************/
850 /*                          SMTP UTILITY COMMANDS                            */
851 /*****************************************************************************/
852
853 void cmd_smtp(char *argbuf) {
854         char cmd[64];
855         char node[256];
856         char buf[1024];
857         int i;
858         int num_mxhosts;
859
860         if (CtdlAccessCheck(ac_aide)) return;
861
862         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
863
864         if (!strcasecmp(cmd, "mx")) {
865                 extract_token(node, argbuf, 1, '|', sizeof node);
866                 num_mxhosts = getmx(buf, node);
867                 cprintf("%d %d MX hosts listed for %s\n",
868                         LISTING_FOLLOWS, num_mxhosts, node);
869                 for (i=0; i<num_mxhosts; ++i) {
870                         extract_token(node, buf, i, '|', sizeof node);
871                         cprintf("%s\n", node);
872                 }
873                 cprintf("000\n");
874                 return;
875         }
876
877         else if (!strcasecmp(cmd, "runqueue")) {
878                 run_queue_now = 1;
879                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
880                 return;
881         }
882
883         else {
884                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
885         }
886
887 }
888
889
890
891
892 CTDL_MODULE_INIT(smtp_queu)
893 {
894 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
895         if (!threading)
896         {
897                 ActiveQItems = NewHash(1, Flathash);
898                 citthread_mutex_init(&ActiveQItemsLock, NULL);
899
900                 QItemHandlers = NewHash(0, NULL);
901
902                 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
903                 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
904                 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
905                 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
906                 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
907                 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
908                 Put(QItemHandlers, HKEY("submitted"), QItem_Handle_Submitted, reference_free_handler);
909 ////TODO: flush qitemhandlers on exit
910                 smtp_init_spoolout();
911
912                 CtdlRegisterCleanupHook(smtp_evq_cleanup);
913                 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
914
915                 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
916         }
917 #endif
918         
919         /* return our Subversion id for the Log */
920         return "smtpeventclient";
921 }