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