b4ab587407bf325871d50ed0375229d79b0eaf16
[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                         free(url);
653                         return 0; /* invalid syntax, no ipv6 */
654                 }
655                 if (*(pEndHost + 1) == ':'){
656                         StrBufPeek(url->URL, pEndHost + 1, 0, '\0');
657                         pPort = pEndHost + 2;
658                 }
659                 url->af = AF_INET6;
660         }
661         else {
662                 pPort = strchr(url->Host, ':');
663                 if (pPort != NULL) {
664                         StrBufPeek(url->URL, pPort, 0, '\0');
665                         pPort ++;
666                 }
667         }
668         if (pPort != NULL)
669                 url->Port = atol(pPort);
670         url->IsIP = inet_pton(url->af, url->Host, &url->Addr);
671         *Url = url;
672         return 1;
673 }
674 /*
675 {
676
677         if (threadding)
678                 n_smarthosts =  get_hosts(char *mxbuf, char *rectype);
679 }
680 */
681 /*
682  * smtp_do_procmsg()
683  *
684  * Called by smtp_do_queue() to handle an individual message.
685  */
686 void smtp_do_procmsg(long msgnum, void *userdata) {
687         struct CtdlMessage *msg = NULL;
688         char *instr = NULL;     
689         StrBuf *PlainQItem;
690         OneQueItem *MyQItem;
691         char *pch;
692         HashPos  *It;
693         void *vQE;
694         long len;
695         const char *Key;
696         int nRelays = 0;
697         ParsedURL *RelayUrls = NULL;
698         int HaveBuffers = 0;
699         StrBuf *Msg =NULL;
700         
701         CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
702         ///strcpy(envelope_from, "");
703
704         msg = CtdlFetchMessage(msgnum, 1);
705         if (msg == NULL) {
706                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
707                 return;
708         }
709
710         pch = instr = msg->cm_fields['M'];
711
712         /* Strip out the headers (no not amd any other non-instruction) line */
713         while (pch != NULL) {
714                 pch = strchr(pch, '\n');
715                 if ((pch != NULL) && (*(pch + 1) == '\n')) {
716                         instr = pch + 2;
717                         pch = NULL;
718                 }
719         }
720         PlainQItem = NewStrBufPlain(instr, -1);
721         CtdlFreeMessage(msg);
722         MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
723         FreeStrBuf(&PlainQItem);
724
725         if (MyQItem == NULL) {
726                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);              
727                 return; /* s.b. else is already processing... */
728         }
729
730         /*
731          * Postpone delivery if we've already tried recently.
732          * /
733         if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
734                 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
735
736                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
737                 citthread_mutex_lock(&ActiveQItemsLock);
738                 {
739                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
740                         DeleteEntryFromHash(ActiveQItems, It);
741                 }
742                 citthread_mutex_unlock(&ActiveQItemsLock);
743                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
744                 DeleteHashPos(&It);
745                 return;
746         }// TODO: reenable me.*/
747
748         /*
749          * Bail out if there's no actual message associated with this
750          */
751         if (MyQItem->MessageID < 0L) {
752                 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
753                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
754                 citthread_mutex_lock(&ActiveQItemsLock);
755                 {
756                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
757                         DeleteEntryFromHash(ActiveQItems, It);
758                 }
759                 citthread_mutex_unlock(&ActiveQItemsLock);
760                 DeleteHashPos(&It);
761                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
762                 return;
763         }
764
765         {
766                 char mxbuf[SIZ];
767                 ParsedURL **Url = &MyQItem->URL;
768                 nRelays = get_hosts(mxbuf, "smarthost");
769                 if (nRelays > 0) {
770                         StrBuf *All;
771                         StrBuf *One;
772                         const char *Pos = NULL;
773                         All = NewStrBufPlain(mxbuf, -1);
774                         One = NewStrBufPlain(NULL, StrLength(All) + 1);
775                         
776                         while ((Pos != StrBufNOTNULL) && ((Pos == NULL) || !IsEmptyStr(Pos))) {
777                                 StrBufExtract_NextToken(One, All, &Pos, '|');
778                                 if (!ParseURL(Url, One, 25))
779                                         CtdlLogPrintf(CTDL_DEBUG, "Failed to parse: %s\n", ChrPtr(One));
780                                 else 
781                                         Url = &(*Url)->Next;
782                         }
783                         FreeStrBuf(&All);
784                         FreeStrBuf(&One);
785                 }
786         }
787
788         It = GetNewHashPos(MyQItem->MailQEntries, 0);
789         while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
790         {
791                 MailQEntry *ThisItem = vQE;
792                 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
793         }
794         DeleteHashPos(&It);
795
796         CountActiveQueueEntries(MyQItem);
797         if (MyQItem->ActiveDeliveries > 0)
798         {
799                 int n = MsgCount++;
800                 int i = 1;
801                 Msg = smtp_load_msg(MyQItem, n);
802                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
803                 while ((i <= MyQItem->ActiveDeliveries) && 
804                        (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
805                 {
806                         MailQEntry *ThisItem = vQE;
807                         if (ThisItem->Active == 1) {
808                                 int KeepBuffers = (i == MyQItem->ActiveDeliveries);
809                                 if (i > 1) n = MsgCount++;
810                                 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
811                                 smtp_try(MyQItem, ThisItem, Msg, KeepBuffers, n, RelayUrls);
812                                 if (KeepBuffers) HaveBuffers = 1;
813                                 i++;
814                         }
815                 }
816                 DeleteHashPos(&It);
817         }
818         else 
819         {
820                 It = GetNewHashPos(MyQItem->MailQEntries, 0);
821                 citthread_mutex_lock(&ActiveQItemsLock);
822                 {
823                         GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
824                         DeleteEntryFromHash(ActiveQItems, It);
825                 }
826                 citthread_mutex_unlock(&ActiveQItemsLock);
827                 DeleteHashPos(&It);
828                 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
829
830 // TODO: bounce & delete?
831
832         }
833         if (!HaveBuffers) {
834                 FreeStrBuf (&Msg);
835 // TODO : free RelayUrls
836         }
837 }
838
839
840
841 /*
842  * smtp_queue_thread()
843  * 
844  * Run through the queue sending out messages.
845  */
846 void *smtp_queue_thread(void *arg) {
847         int num_processed = 0;
848         struct CitContext smtp_queue_CC;
849
850         CtdlThreadSleep(10);
851
852         CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
853         citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
854         CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
855
856         while (!CtdlThreadCheckStop()) {
857                 
858                 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
859
860                 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
861                         CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
862                 }
863                 else {
864                         num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
865                 }
866                 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
867                 CtdlThreadSleep(60);
868         }
869
870         CtdlClearSystemContext();
871         return(NULL);
872 }
873
874
875
876 /*
877  * Initialize the SMTP outbound queue
878  */
879 void smtp_init_spoolout(void) {
880         struct ctdlroom qrbuf;
881
882         /*
883          * Create the room.  This will silently fail if the room already
884          * exists, and that's perfectly ok, because we want it to exist.
885          */
886         CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
887
888         /*
889          * Make sure it's set to be a "system room" so it doesn't show up
890          * in the <K>nown rooms list for Aides.
891          */
892         if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
893                 qrbuf.QRflags2 |= QR2_SYSTEM;
894                 CtdlPutRoomLock(&qrbuf);
895         }
896 }
897
898
899
900
901 /*****************************************************************************/
902 /*                          SMTP UTILITY COMMANDS                            */
903 /*****************************************************************************/
904
905 void cmd_smtp(char *argbuf) {
906         char cmd[64];
907         char node[256];
908         char buf[1024];
909         int i;
910         int num_mxhosts;
911
912         if (CtdlAccessCheck(ac_aide)) return;
913
914         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
915
916         if (!strcasecmp(cmd, "mx")) {
917                 extract_token(node, argbuf, 1, '|', sizeof node);
918                 num_mxhosts = getmx(buf, node);
919                 cprintf("%d %d MX hosts listed for %s\n",
920                         LISTING_FOLLOWS, num_mxhosts, node);
921                 for (i=0; i<num_mxhosts; ++i) {
922                         extract_token(node, buf, i, '|', sizeof node);
923                         cprintf("%s\n", node);
924                 }
925                 cprintf("000\n");
926                 return;
927         }
928
929         else if (!strcasecmp(cmd, "runqueue")) {
930                 run_queue_now = 1;
931                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
932                 return;
933         }
934
935         else {
936                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
937         }
938
939 }
940
941
942
943
944 CTDL_MODULE_INIT(smtp_queu)
945 {
946 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
947         if (!threading)
948         {
949                 ActiveQItems = NewHash(1, Flathash);
950                 citthread_mutex_init(&ActiveQItemsLock, NULL);
951
952                 QItemHandlers = NewHash(0, NULL);
953
954                 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
955                 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
956                 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
957                 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
958                 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
959                 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
960                 Put(QItemHandlers, HKEY("submitted"), QItem_Handle_Submitted, reference_free_handler);
961 ////TODO: flush qitemhandlers on exit
962                 smtp_init_spoolout();
963
964                 CtdlRegisterCleanupHook(smtp_evq_cleanup);
965                 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
966
967                 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
968         }
969 #endif
970         
971         /* return our Subversion id for the Log */
972         return "smtpeventclient";
973 }