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