validate_recipients() now strips out duplicate recipients ... probably some 15 years...
[citadel] / webcit / smtpqueue.c
1 /* 
2  * Display the outbound SMTP queue
3  */
4
5 #include "webcit.h"
6 CtxType CTX_MAILQITEM = CTX_NONE;
7 CtxType CTX_MAILQ_RCPT = CTX_NONE;
8 HashList *QItemHandlers = NULL;
9
10
11
12 typedef struct _mailq_entry {
13         StrBuf *Recipient;
14         StrBuf *StatusMessage;
15         int Status;
16         /**<
17          * 0 = No delivery has yet been attempted
18          * 2 = Delivery was successful
19          * 3 = Transient error like connection problem. Try next remote if available.
20          * 4 = A transient error was experienced ... try again later
21          * 5 = Delivery to this address failed permanently.  The error message
22          *     should be placed in the fourth field so that a bounce message may
23          *     be generated.
24          */
25
26         int n;
27         int Active;
28 }MailQEntry;
29
30 typedef struct queueitem {
31         long MessageID;
32         long QueMsgID;
33         long Submitted;
34         int FailNow;
35         HashList *MailQEntries;
36 /* copy of the currently parsed item in the MailQEntries list;
37  * if null add a new one.
38  */
39         MailQEntry *Current;
40         time_t ReattemptWhen;
41         time_t Retry;
42
43         long ActiveDeliveries;
44         StrBuf *EnvelopeFrom;
45         StrBuf *BounceTo;
46         StrBuf *SenderRoom;
47         ParsedURL *URL;
48         ParsedURL *FallBackHost;
49 } OneQueItem;
50
51
52 typedef void (*QItemHandler)(OneQueItem *Item, StrBuf *Line, const char **Pos);
53
54 typedef struct __QItemHandlerStruct {
55         QItemHandler H;
56 } QItemHandlerStruct;
57
58 void RegisterQItemHandler(const char *Key, long Len, QItemHandler H)
59 {
60         QItemHandlerStruct *HS = (QItemHandlerStruct*)malloc(sizeof(QItemHandlerStruct));
61         HS->H = H;
62         Put(QItemHandlers, Key, Len, HS, NULL);
63 }
64
65 void FreeMailQEntry(void *qv)
66 {
67         MailQEntry *Q = qv;
68         FreeStrBuf(&Q->Recipient);
69         FreeStrBuf(&Q->StatusMessage);
70         free(Q);
71 }
72 void FreeQueItem(OneQueItem **Item)
73 {
74         DeleteHash(&(*Item)->MailQEntries);
75         FreeStrBuf(&(*Item)->EnvelopeFrom);
76         FreeStrBuf(&(*Item)->BounceTo);
77         FreeStrBuf(&(*Item)->SenderRoom);
78         FreeURL(&(*Item)->URL);
79         free(*Item);
80         Item = NULL;
81 }
82 void HFreeQueItem(void *Item)
83 {
84         FreeQueItem((OneQueItem**)&Item);
85 }
86
87
88 OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
89 {
90         OneQueItem *Item;
91         const char *pLine = NULL;
92         StrBuf *Line;
93         StrBuf *Token;
94         
95         Item = (OneQueItem*)malloc(sizeof(OneQueItem));
96         memset(Item, 0, sizeof(OneQueItem));
97         Item->Retry = 0;
98         Item->MessageID = -1;
99         Item->QueMsgID = QueMsgID;
100
101         Token = NewStrBuf();
102         Line = NewStrBufPlain(NULL, 128);
103         while (pLine != StrBufNOTNULL) {
104                 const char *pItemPart = NULL;
105                 void *vHandler;
106
107                 StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
108                 if (StrLength(Line) == 0) continue;
109                 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
110                 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
111                 {
112                         QItemHandlerStruct *HS;
113                         HS = (QItemHandlerStruct*) vHandler;
114                         HS->H(Item, Line, &pItemPart);
115                 }
116         }
117         FreeStrBuf(&Line);
118         FreeStrBuf(&Token);
119 /*
120         Put(ActiveQItems,
121             LKEY(Item->MessageID),
122             Item,
123             HFreeQueItem);
124 */      
125
126         return Item;
127 }
128
129 void tmplput_MailQID(StrBuf *Target, WCTemplputParams *TP)
130 {
131         OneQueItem *Item = (OneQueItem*) CTX(CTX_MAILQITEM);
132         StrBufAppendPrintf(Target, "%ld", Item->QueMsgID);;
133 }
134 void tmplput_MailQPayloadID(StrBuf *Target, WCTemplputParams *TP)
135 {
136         OneQueItem *Item = (OneQueItem*) CTX(CTX_MAILQITEM);
137         StrBufAppendPrintf(Target, "%ld", Item->MessageID);
138 }
139 void tmplput_MailQBounceTo(StrBuf *Target, WCTemplputParams *TP)
140 {
141         OneQueItem *Item = (OneQueItem*) CTX(CTX_MAILQITEM);
142         StrBufAppendTemplate(Target, TP, Item->BounceTo, 0);
143 }
144 void tmplput_MailQAttempted(StrBuf *Target, WCTemplputParams *TP)
145 {
146         char datebuf[64];
147         OneQueItem *Item = (OneQueItem*) CTX(CTX_MAILQITEM);
148         webcit_fmt_date(datebuf, 64, Item->ReattemptWhen, DATEFMT_BRIEF);
149         StrBufAppendBufPlain(Target, datebuf, -1, 0);
150 }
151 void tmplput_MailQSubmitted(StrBuf *Target, WCTemplputParams *TP)
152 {
153         char datebuf[64];
154         OneQueItem *Item = (OneQueItem*) CTX(CTX_MAILQITEM);
155         webcit_fmt_date(datebuf, 64, Item->Submitted, DATEFMT_BRIEF);
156         StrBufAppendBufPlain(Target, datebuf, -1, 0);
157 }
158 void tmplput_MailQEnvelopeFrom(StrBuf *Target, WCTemplputParams *TP)
159 {
160         OneQueItem *Item = (OneQueItem*) CTX(CTX_MAILQITEM);
161         StrBufAppendTemplate(Target, TP, Item->EnvelopeFrom, 0);
162 }
163 void tmplput_MailQSourceRoom(StrBuf *Target, WCTemplputParams *TP)
164 {
165         OneQueItem *Item = (OneQueItem*) CTX(CTX_MAILQITEM);
166         StrBufAppendTemplate(Target, TP, Item->SenderRoom, 0);
167 }
168
169 int Conditional_MailQ_HaveSourceRoom(StrBuf *Target, WCTemplputParams *TP)
170 {
171         OneQueItem *Item = (OneQueItem*) CTX(CTX_MAILQITEM);
172         return StrLength(Item->SenderRoom) > 0;
173 }
174
175 void tmplput_MailQRetry(StrBuf *Target, WCTemplputParams *TP)
176 {
177         char datebuf[64];
178         OneQueItem *Item = (OneQueItem*) CTX(CTX_MAILQITEM);
179
180         if (Item->Retry == 0) {
181                 StrBufAppendBufPlain(Target, _("First Attempt pending"), -1, 0);
182         }
183         else {
184                 webcit_fmt_date(datebuf, sizeof(datebuf), Item->Retry, DATEFMT_BRIEF);
185                 StrBufAppendBufPlain(Target, datebuf, -1, 0);
186         }
187 }
188
189 void tmplput_MailQRCPT(StrBuf *Target, WCTemplputParams *TP)
190 {
191         MailQEntry *Entry = (MailQEntry*) CTX(CTX_MAILQ_RCPT);
192         StrBufAppendTemplate(Target, TP, Entry->Recipient, 0);
193 }
194 void tmplput_MailQRCPTStatus(StrBuf *Target, WCTemplputParams *TP)
195 {
196         MailQEntry *Entry = (MailQEntry*) CTX(CTX_MAILQ_RCPT);
197         StrBufAppendPrintf(Target, "%ld", Entry->Status);
198 }
199 void tmplput_MailQStatusMsg(StrBuf *Target, WCTemplputParams *TP)
200 {
201         MailQEntry *Entry = (MailQEntry*) CTX(CTX_MAILQ_RCPT);
202         StrBufAppendTemplate(Target, TP, Entry->StatusMessage, 0);
203 }
204
205 HashList *iterate_get_Recipients(StrBuf *Target, WCTemplputParams *TP)
206 {
207         OneQueItem *Item = (OneQueItem*) CTX(CTX_MAILQITEM);
208         return Item->MailQEntries;
209 }
210
211
212 void NewMailQEntry(OneQueItem *Item)
213 {
214         Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
215         memset(Item->Current, 0, sizeof(MailQEntry));
216
217         if (Item->MailQEntries == NULL)
218                 Item->MailQEntries = NewHash(1, Flathash);
219         Item->Current->StatusMessage = NewStrBuf();
220         Item->Current->n = GetCount(Item->MailQEntries);
221         Put(Item->MailQEntries,
222             IKEY(Item->Current->n),
223             Item->Current,
224             FreeMailQEntry);
225 }
226
227 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
228 {
229         Item->MessageID = StrBufExtractNext_long(Line, Pos, '|');
230 }
231
232 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
233 {
234         if (Item->EnvelopeFrom == NULL)
235                 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
236         StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
237 }
238
239 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
240 {
241         if (Item->BounceTo == NULL)
242                 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
243         StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
244 }
245
246 void QItem_Handle_SenderRoom(OneQueItem *Item, StrBuf *Line, const char **Pos)
247 {
248         if (Item->SenderRoom == NULL)
249                 Item->SenderRoom = NewStrBufPlain(NULL, StrLength(Line));
250         StrBufExtract_NextToken(Item->SenderRoom, Line, Pos, '|');
251 }
252
253 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
254 {
255         const char *pch;
256         if (Item->Current == NULL)
257                 NewMailQEntry(Item);
258         if (Item->Current->Recipient == NULL)
259                 Item->Current->Recipient=NewStrBufPlain(NULL, StrLength(Line));
260         StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
261         Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
262         StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
263
264         pch = ChrPtr(Item->Current->StatusMessage);
265         while ((pch != NULL) && (*pch != '\0')) {
266                 pch = strchr(pch, ';');
267                 if (pch != NULL) {
268                         pch ++;
269                         if (*pch == ' ') {
270                                 StrBufPeek(Item->Current->StatusMessage,
271                                            pch, -1, '\n');
272                         }
273                 }
274         }
275         Item->Current = NULL; // TODO: is this always right?
276 }
277
278
279 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
280 {
281         Item->Retry = StrBufExtractNext_int(Line, Pos, '|');
282 }
283
284
285 void QItem_Handle_Submitted(OneQueItem *Item, StrBuf *Line, const char **Pos)
286 {
287         Item->Submitted = atol(*Pos);
288
289 }
290
291 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
292 {
293         Item->ReattemptWhen = StrBufExtractNext_int(Line, Pos, '|');
294 }
295
296
297
298
299 void render_QUEUE(StrBuf *Target, WCTemplputParams *TP, StrBuf *FoundCharset)
300 {
301         wc_mime_attachment *Mime = CTX(CTX_MIME_ATACH);
302         WCTemplputParams SubTP;
303         OneQueItem* Context;
304
305         Context = DeserializeQueueItem(Mime->Data, Mime->msgnum);
306         StackContext(TP, &SubTP, Context, CTX_MAILQITEM, 0, TP->Tokens);
307         {
308                 DoTemplate(HKEY("view_mailq_message"), NULL, &SubTP);
309         }
310         UnStackContext(&SubTP);
311
312         FreeQueItem (&Context);
313 }
314
315 void
316 ServerShutdownModule_SMTP_QUEUE
317 (void)
318 {
319         DeleteHash(&QItemHandlers);
320 }
321 void
322 ServerStartModule_SMTP_QUEUE
323 (void)
324 {
325         QItemHandlers = NewHash(0, NULL);
326 }
327
328 int qview_PrintPageHeader(SharedMessageStatus *Stat, void **ViewSpecific)
329 {
330         if (yesbstr("ListOnly"))
331                 output_headers(1, 0, 0, 0, 0, 0);
332         else
333                 output_headers(1, 1, 1, 0, 0, 0);
334         return 0;
335 }
336
337 int qview_GetParamsGetServerCall(SharedMessageStatus *Stat,
338                                  void **ViewSpecific,
339                                  long oper,
340                                  char *cmd,
341                                  long len,
342                                  char *filter,
343                                  long flen)
344 {
345         if (!WC->is_aide)
346         {
347                 DoTemplate(HKEY("aide_required"), NULL, NULL);
348                 end_burst();
349
350                 return 300;
351         }
352         else {
353                 snprintf(cmd, len, "MSGS ALL|0|1");
354                 snprintf(filter, flen, "SUBJ|QMSG");
355                 if (yesbstr("ListOnly"))
356                         DoTemplate(HKEY("view_mailq_table"), NULL, NULL);
357                 else
358                         DoTemplate(HKEY("view_mailq_header"), NULL, NULL);
359                 return 200;
360         }
361 }
362
363 /*
364  * Display task view
365  */
366 int qview_LoadMsgFromServer(SharedMessageStatus *Stat, 
367                             void **ViewSpecific, 
368                             message_summary* Msg, 
369                             int is_new, 
370                             int i)
371 {
372         wcsession *WCC = WC;
373         const StrBuf *Mime;
374
375         /* Not (yet?) needed here? calview *c = (calview *) *ViewSpecific; */
376         read_message(WCC->WBuf, HKEY("view_mailq_message_bearer"), Msg->msgnum, NULL, &Mime, NULL);
377
378         return 0;
379 }
380
381
382 int qview_RenderView_or_Tail(SharedMessageStatus *Stat, 
383                              void **ViewSpecific, 
384                              long oper)
385 {
386         wcsession *WCC = WC;
387         WCTemplputParams SubTP;
388
389         memset(&SubTP, 0, sizeof(WCTemplputParams));
390         if (yesbstr("ListOnly"))
391                 DoTemplate(HKEY("view_mailq_footer_listonly"),NULL, &SubTP);
392         else
393         {
394                 if (GetCount(WCC->summ) == 0)
395                         DoTemplate(HKEY("view_mailq_footer_empty"),NULL, &SubTP);
396                 else
397                         DoTemplate(HKEY("view_mailq_footer"),NULL, &SubTP);
398         }
399
400         return 0;
401 }
402 int qview_Cleanup(void **ViewSpecific)
403 {
404         
405         wDumpContent(yesbstr("ListOnly")?0:1);
406         return 0;
407 }
408
409 void 
410 InitModule_SMTP_QUEUE
411 (void)
412 {
413         RegisterCTX(CTX_MAILQITEM);
414         RegisterCTX(CTX_MAILQ_RCPT);
415
416         RegisterQItemHandler(HKEY("msgid"),             QItem_Handle_MsgID);
417         RegisterQItemHandler(HKEY("envelope_from"),     QItem_Handle_EnvelopeFrom);
418         RegisterQItemHandler(HKEY("retry"),             QItem_Handle_retry);
419         RegisterQItemHandler(HKEY("attempted"),         QItem_Handle_Attempted);
420         RegisterQItemHandler(HKEY("remote"),            QItem_Handle_Recipient);
421         RegisterQItemHandler(HKEY("bounceto"),          QItem_Handle_BounceTo);
422         RegisterQItemHandler(HKEY("source_room"),       QItem_Handle_SenderRoom);
423         RegisterQItemHandler(HKEY("submitted"),         QItem_Handle_Submitted);
424         RegisterMimeRenderer(HKEY("application/x-citadel-delivery-list"), render_QUEUE, 1, 9000);
425         RegisterNamespace("MAILQ:ID", 0, 0, tmplput_MailQID, NULL, CTX_MAILQITEM);
426         RegisterNamespace("MAILQ:PAYLOAD:ID", 0, 0, tmplput_MailQPayloadID, NULL, CTX_MAILQITEM);
427         RegisterNamespace("MAILQ:BOUNCETO", 0, 1, tmplput_MailQBounceTo, NULL, CTX_MAILQITEM);
428         RegisterNamespace("MAILQ:ATTEMPTED", 0, 0, tmplput_MailQAttempted, NULL, CTX_MAILQITEM);
429         RegisterNamespace("MAILQ:SUBMITTED", 0, 0, tmplput_MailQSubmitted, NULL, CTX_MAILQITEM);
430         RegisterNamespace("MAILQ:ENVELOPEFROM", 0, 1, tmplput_MailQEnvelopeFrom, NULL, CTX_MAILQITEM);
431         RegisterNamespace("MAILQ:SRCROOM", 0, 1, tmplput_MailQSourceRoom, NULL, CTX_MAILQITEM);
432         RegisterConditional("COND:MAILQ:HAVESRCROOM", 0, Conditional_MailQ_HaveSourceRoom,  CTX_MAILQITEM);
433         RegisterNamespace("MAILQ:RETRY", 0, 0, tmplput_MailQRetry, NULL, CTX_MAILQITEM);
434
435         RegisterNamespace("MAILQ:RCPT:ADDR", 0, 1, tmplput_MailQRCPT, NULL, CTX_MAILQ_RCPT);
436         RegisterNamespace("MAILQ:RCPT:STATUS", 0, 0, tmplput_MailQRCPTStatus, NULL, CTX_MAILQ_RCPT);
437         RegisterNamespace("MAILQ:RCPT:STATUSMSG", 0, 1, tmplput_MailQStatusMsg, NULL, CTX_MAILQ_RCPT);
438
439         RegisterIterator("MAILQ:RCPT", 0, NULL, iterate_get_Recipients, 
440                          NULL, NULL, CTX_MAILQ_RCPT, CTX_MAILQITEM, IT_NOFLAG);
441
442
443         RegisterReadLoopHandlerset(
444                 VIEW_QUEUE,
445                 qview_GetParamsGetServerCall,
446                 qview_PrintPageHeader,
447                 NULL, /* TODO: is this right? */
448                 NULL,
449                 qview_LoadMsgFromServer,
450                 qview_RenderView_or_Tail,
451                 qview_Cleanup,
452                 NULL);
453
454 }