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