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