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