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