libev migration: reinstantiate MX-Relay; unfinished.
[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
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         static int seq = 0;
432
433         struct CtdlMessage *bmsg = NULL;
434         StrBuf *boundary;
435         StrBuf *Msg = NULL; 
436         StrBuf *BounceMB;
437         struct recptypes *valid;
438         
439         HashPos *It;
440         void *vQE;
441         long len;
442         const char *Key;
443
444         int successful_bounce = 0;
445         int num_bounces = 0;
446         int give_up = 0;
447
448         CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
449
450         if ( (ev_time() - MyQItem->Submitted) > SMTP_GIVE_UP ) {
451                 give_up = 1;/// TODO: replace time by libevq timer get
452         }
453
454         /*
455          * Now go through the instructions checking for stuff.
456          */
457         It = GetNewHashPos(MyQItem->MailQEntries, 0);
458         while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
459         {
460                 MailQEntry *ThisItem = vQE;
461                 if ((ThisItem->Status == 5) || /* failed now? */
462                     ((give_up == 1) && (ThisItem->Status != 2))) /* giving up after failed attempts... */
463                 {
464                         if (num_bounces == 0)
465                                 Msg = NewStrBufPlain(NULL, 1024);
466                         ++num_bounces;
467         
468                         StrBufAppendBuf(Msg, ThisItem->Recipient, 0);
469                         StrBufAppendBufPlain(Msg, HKEY(": "), 0);
470                         StrBufAppendBuf(Msg, ThisItem->StatusMessage, 0);
471                         StrBufAppendBufPlain(Msg, HKEY("\r\n"), 0);
472                 }
473         }
474         DeleteHashPos(&It);
475
476         /* Deliver the bounce if there's anything worth mentioning */
477         CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
478
479         if (num_bounces == 0) {
480                 FreeStrBuf(&Msg);
481                 return;
482         }
483
484         boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
485         StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
486
487         /* Start building our bounce message; go shopping for memory first. */
488         BounceMB = NewStrBufPlain(NULL, 
489                                   1024 + /* mime stuff.... */
490                                   StrLength(Msg) +  /* the bounce information... */
491                                   StrLength(OMsgTxt)); /* the original message */
492         if (BounceMB == NULL) {
493                 FreeStrBuf(&boundary);
494                 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
495
496                 return;
497         }
498
499         bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
500         if (bmsg == NULL) {
501                 FreeStrBuf(&boundary);
502                 FreeStrBuf(&BounceMB);
503                 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
504
505                 return;
506         }
507         memset(bmsg, 0, sizeof(struct CtdlMessage));
508
509
510         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
511         StrBufAppendBuf(BounceMB, boundary, 0);
512         StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
513         StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
514         StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
515         StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
516         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
517         StrBufAppendBuf(BounceMB, boundary, 0);
518         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
519         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
520
521         if (give_up) 
522                 StrBufAppendBufPlain(
523                         BounceMB, 
524                         HKEY(
525                                 "A message you sent could not be delivered to some or all of its recipients\n"
526                                 "due to prolonged unavailability of its destination(s).\n"
527                                 "Giving up on the following addresses:\n\n"
528                                 ), 0);
529         else 
530                 StrBufAppendBufPlain(
531                         BounceMB, 
532                         HKEY(
533                                 "A message you sent could not be delivered to some or all of its recipients.\n"
534                                 "The following addresses were undeliverable:\n\n"
535                                 ), 0);
536
537         StrBufAppendBuf(BounceMB, Msg, 0);
538         FreeStrBuf(&Msg);
539
540         /* Attach the original message */
541         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
542         StrBufAppendBuf(BounceMB, boundary, 0);
543         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
544         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
545         StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
546         StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
547         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
548         StrBufAppendBuf(BounceMB, OMsgTxt, 0);
549
550         /* Close the multipart MIME scope */
551         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
552         StrBufAppendBuf(BounceMB, boundary, 0);
553         StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
554
555
556
557         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
558         bmsg->cm_anon_type = MES_NORMAL;
559         bmsg->cm_format_type = FMT_RFC822;
560
561         bmsg->cm_fields['O'] = strdup(MAILROOM);
562         bmsg->cm_fields['A'] = strdup("Citadel");
563         bmsg->cm_fields['N'] = strdup(config.c_nodename);
564         bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
565         bmsg->cm_fields['M'] = SmashStrBuf(&BounceMB);
566
567         /* First try the user who sent the message */
568         if (StrLength(MyQItem->BounceTo) == 0) 
569                 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
570         else
571                 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", ChrPtr(MyQItem->BounceTo));
572
573         /* Can we deliver the bounce to the original sender? */
574         valid = validate_recipients(ChrPtr(MyQItem->BounceTo), NULL, 0);
575         if ((valid != NULL) && (valid->num_error == 0)) {
576                 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
577                 successful_bounce = 1;
578         }
579
580         /* If not, post it in the Aide> room */
581         if (successful_bounce == 0) {
582                 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
583         }
584
585         /* Free up the memory we used */
586         free_recipients(valid);
587         FreeStrBuf(&boundary);
588         CtdlFreeMessage(bmsg);
589         CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
590 }
591
592
593
594 int ParseURL(ParsedURL **Url, StrBuf *UrlStr, short DefaultPort)
595 {
596         const char *pch, *pStartHost, *pEndHost, *pPort, *pCredEnd, *pUserEnd;
597         ParsedURL *url = (ParsedURL *)malloc(sizeof(ParsedURL));
598         memset(url, 0, sizeof(ParsedURL));
599
600         url->af = AF_INET;
601         url->Port =  DefaultPort;
602         /*
603          * http://username:passvoid@[ipv6]:port/url 
604          */
605         url->URL = NewStrBufDup(UrlStr);
606         pStartHost = pch = ChrPtr(url->URL);
607         url->LocalPart = strchr(pch, '/');
608         if (url->LocalPart != NULL) {
609                 if ((*(url->LocalPart + 1) == '/') && 
610                     (*(url->LocalPart + 2) == ':')) { /* TODO: find default port for this protocol... */
611                         pStartHost = url->LocalPart + 3;
612                         url->LocalPart = strchr(pStartHost, '/');
613                 }
614         }
615         if (url->LocalPart == NULL) {
616                 url->LocalPart = pch + StrLength(UrlStr);
617         }
618
619         pCredEnd = strchr(pch, '@');
620         if (pCredEnd >= url->LocalPart)
621                 pCredEnd = NULL;
622         if (pCredEnd != NULL)
623         {
624                 url->User = pStartHost;
625                 pStartHost = pCredEnd + 1;
626                 pUserEnd = strchr(url->User, ':');
627                 
628                 if (pUserEnd > pCredEnd)
629                         pUserEnd = pCredEnd;
630                 else {
631                         url->Pass = pUserEnd + 1;
632                 }
633                 StrBufPeek(UrlStr, pUserEnd, 0, '\0');
634                 StrBufPeek(UrlStr, pCredEnd, 0, '\0');          
635         }
636         
637         pPort = NULL;
638         if (*pStartHost == '[') {
639                 pStartHost ++;
640                 pEndHost = strchr(pStartHost, ']');
641                 if (pEndHost == NULL) {
642                         free(url);
643                         return 0; /* invalid syntax, no ipv6 */
644                 }
645                 if (*(pEndHost + 1) == ':')
646                         pPort = pEndHost + 2;
647                 url->af = AF_INET6;
648         }
649         else {
650                 pPort = strchr(pStartHost, ':');
651                 if (pPort != NULL)
652                         pPort ++;
653         }
654         if (pPort != NULL)
655                 url->Port = atol(pPort);
656         url->IsIP = inet_pton(url->af, pStartHost, &url->Addr); 
657         return 1;
658 }
659 /*
660 {
661
662         if (threadding)
663                 n_smarthosts =  get_hosts(char *mxbuf, char *rectype);
664 }
665 */
666 /*
667  * smtp_do_procmsg()
668  *
669  * Called by smtp_do_queue() to handle an individual message.
670  */
671 void smtp_do_procmsg(long msgnum, void *userdata) {
672         struct CtdlMessage *msg = NULL;
673         char *instr = NULL;     
674         StrBuf *PlainQItem;
675         OneQueItem *MyQItem;
676         char *pch;
677         HashPos  *It;
678         void *vQE;
679         long len;
680         const char *Key;
681         int nRelays = 0;
682         ParsedURL *RelayUrls = NULL;
683         int HaveBuffers = 0;
684         StrBuf *Msg =NULL;
685         
686         CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
687         ///strcpy(envelope_from, "");
688
689         msg = CtdlFetchMessage(msgnum, 1);
690         if (msg == NULL) {
691                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
692                 return;
693         }
694
695         pch = instr = msg->cm_fields['M'];
696
697         /* Strip out the headers (no not amd any other non-instruction) line */
698         while (pch != NULL) {
699                 pch = strchr(pch, '\n');
700                 if ((pch != NULL) && (*(pch + 1) == '\n')) {
701                         instr = pch + 2;
702                         pch = NULL;
703                 }
704         }
705         PlainQItem = NewStrBufPlain(instr, -1);
706         CtdlFreeMessage(msg);
707         MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
708         FreeStrBuf(&PlainQItem);
709
710         if (MyQItem == NULL) {
711                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);              
712                 return; /* s.b. else is already processing... */
713         }
714
715         /*
716          * Postpone delivery if we've already tried recently.
717          * /
718         if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
719                 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
720
721                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
722                 citthread_mutex_lock(&ActiveQItemsLock);
723                 {
724                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
725                         DeleteEntryFromHash(ActiveQItems, It);
726                 }
727                 citthread_mutex_unlock(&ActiveQItemsLock);
728                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
729                 DeleteHashPos(&It);
730                 return;
731         }// TODO: reenable me.*/
732
733         /*
734          * Bail out if there's no actual message associated with this
735          */
736         if (MyQItem->MessageID < 0L) {
737                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
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                 DeleteHashPos(&It);
746                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
747                 return;
748         }
749
750         {
751                 char mxbuf[SIZ];
752                 ParsedURL **Url = &RelayUrls; ///&MyQItem->Relay;
753                 nRelays = get_hosts(mxbuf, "smarthost");
754                 if (nRelays > 0) {
755                         StrBuf *All;
756                         StrBuf *One;
757                         const char *Pos = NULL;
758                         All = NewStrBufPlain(mxbuf, -1);
759                         One = NewStrBufPlain(NULL, StrLength(All) + 1);
760                         
761                         while (Pos != StrBufNOTNULL) {
762                                 StrBufExtract_NextToken(One, All, &Pos, '|');
763                                 if (!ParseURL(Url, One, 25))
764                                         CtdlLogPrintf(CTDL_DEBUG, "Failed to parse: %s\n", ChrPtr(One));
765                                 else 
766                                         Url = &(*Url)->Next;
767                         }
768                 }
769         }
770
771         It = GetNewHashPos(MyQItem->MailQEntries, 0);
772         while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
773         {
774                 MailQEntry *ThisItem = vQE;
775                 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
776         }
777         DeleteHashPos(&It);
778
779         CountActiveQueueEntries(MyQItem);
780         if (MyQItem->ActiveDeliveries > 0)
781         {
782                 int n = MsgCount++;
783                 int i = 1;
784                 Msg = smtp_load_msg(MyQItem, n);
785                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
786                 while ((i <= MyQItem->ActiveDeliveries) && 
787                        (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
788                 {
789                         MailQEntry *ThisItem = vQE;
790                         if (ThisItem->Active == 1) {
791                                 int KeepBuffers = (i == MyQItem->ActiveDeliveries);
792                                 if (i > 1) n = MsgCount++;
793                                 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
794                                 smtp_try(MyQItem, ThisItem, Msg, KeepBuffers, n, RelayUrls);
795                                 if (KeepBuffers) HaveBuffers = 1;
796                                 i++;
797                         }
798                 }
799                 DeleteHashPos(&It);
800         }
801         else 
802         {
803                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
804                 citthread_mutex_lock(&ActiveQItemsLock);
805                 {
806                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
807                         DeleteEntryFromHash(ActiveQItems, It);
808                 }
809                 citthread_mutex_unlock(&ActiveQItemsLock);
810                 DeleteHashPos(&It);
811                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
812
813 // TODO: bounce & delete?
814
815         }
816         if (!HaveBuffers) {
817                 FreeStrBuf (&Msg);
818 // TODO : free RelayUrls
819         }
820 }
821
822
823
824 /*
825  * smtp_queue_thread()
826  * 
827  * Run through the queue sending out messages.
828  */
829 void *smtp_queue_thread(void *arg) {
830         int num_processed = 0;
831         struct CitContext smtp_queue_CC;
832
833         CtdlThreadSleep(10);
834
835         CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
836         citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
837         CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
838
839         while (!CtdlThreadCheckStop()) {
840                 
841                 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
842
843                 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
844                         CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
845                 }
846                 else {
847                         num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
848                 }
849                 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
850                 CtdlThreadSleep(60);
851         }
852
853         CtdlClearSystemContext();
854         return(NULL);
855 }
856
857
858
859 /*
860  * Initialize the SMTP outbound queue
861  */
862 void smtp_init_spoolout(void) {
863         struct ctdlroom qrbuf;
864
865         /*
866          * Create the room.  This will silently fail if the room already
867          * exists, and that's perfectly ok, because we want it to exist.
868          */
869         CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
870
871         /*
872          * Make sure it's set to be a "system room" so it doesn't show up
873          * in the <K>nown rooms list for Aides.
874          */
875         if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
876                 qrbuf.QRflags2 |= QR2_SYSTEM;
877                 CtdlPutRoomLock(&qrbuf);
878         }
879 }
880
881
882
883
884 /*****************************************************************************/
885 /*                          SMTP UTILITY COMMANDS                            */
886 /*****************************************************************************/
887
888 void cmd_smtp(char *argbuf) {
889         char cmd[64];
890         char node[256];
891         char buf[1024];
892         int i;
893         int num_mxhosts;
894
895         if (CtdlAccessCheck(ac_aide)) return;
896
897         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
898
899         if (!strcasecmp(cmd, "mx")) {
900                 extract_token(node, argbuf, 1, '|', sizeof node);
901                 num_mxhosts = getmx(buf, node);
902                 cprintf("%d %d MX hosts listed for %s\n",
903                         LISTING_FOLLOWS, num_mxhosts, node);
904                 for (i=0; i<num_mxhosts; ++i) {
905                         extract_token(node, buf, i, '|', sizeof node);
906                         cprintf("%s\n", node);
907                 }
908                 cprintf("000\n");
909                 return;
910         }
911
912         else if (!strcasecmp(cmd, "runqueue")) {
913                 run_queue_now = 1;
914                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
915                 return;
916         }
917
918         else {
919                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
920         }
921
922 }
923
924
925
926
927 CTDL_MODULE_INIT(smtp_queu)
928 {
929 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
930         if (!threading)
931         {
932                 ActiveQItems = NewHash(1, Flathash);
933                 citthread_mutex_init(&ActiveQItemsLock, NULL);
934
935                 QItemHandlers = NewHash(0, NULL);
936
937                 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
938                 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
939                 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
940                 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
941                 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
942                 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
943                 Put(QItemHandlers, HKEY("submitted"), QItem_Handle_Submitted, reference_free_handler);
944 ////TODO: flush qitemhandlers on exit
945                 smtp_init_spoolout();
946
947                 CtdlRegisterCleanupHook(smtp_evq_cleanup);
948                 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
949
950                 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
951         }
952 #endif
953         
954         /* return our Subversion id for the Log */
955         return "smtpeventclient";
956 }