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