FOREVER is of very bad taste, use for (;;)
[apps/madmutt.git] / remailer.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 /*
11  * Mixmaster support for Mutt
12  */
13
14 #if HAVE_CONFIG_H
15 # include "config.h"
16 #endif
17
18 #include <lib-lib/mem.h>
19 #include <lib-lib/str.h>
20 #include <lib-lib/macros.h>
21
22 #include "mutt.h"
23 #include "recvattach.h"
24 #include "mutt_curses.h"
25 #include "mutt_menu.h"
26 #include "mapping.h"
27
28 #include "remailer.h"
29
30 #include "lib/rx.h"
31
32 #include <stdio.h>
33 #include <string.h>
34 #include <stdlib.h>
35
36 #include <sys/types.h>
37 #include <sys/file.h>
38 #include <fcntl.h>
39
40 #define SW              (option(OPTMBOXPANE)?SidebarWidth:0)
41
42 #ifdef MIXMASTER
43
44 struct coord {
45   short r, c;
46 };
47
48 static REMAILER **mix_type2_list (size_t * l);
49 static REMAILER *mix_new_remailer (void);
50 static const char *mix_format_caps (REMAILER * r);
51 static int mix_chain_add (MIXCHAIN * chain, const char *s,
52                           REMAILER ** type2_list);
53 static int mix_get_caps (const char *capstr);
54 static void mix_add_entry (REMAILER ***, REMAILER *, size_t *, size_t *);
55 static void mix_entry (char *b, size_t blen, MUTTMENU * menu, int num);
56 static void mix_free_remailer (REMAILER ** r);
57 static void mix_free_type2_list (REMAILER *** ttlp);
58 static void mix_redraw_ce (REMAILER ** type2_list, struct coord *coords,
59                            MIXCHAIN * chain, int i, short selected);
60 static void mix_redraw_chain (REMAILER ** type2_list, struct coord *coords,
61                               MIXCHAIN * chain, int cur);
62 static void mix_redraw_head (MIXCHAIN *);
63 static void mix_screen_coordinates (REMAILER ** type2_list, struct coord **,
64                                     MIXCHAIN *, int);
65
66 static int mix_get_caps (const char *capstr)
67 {
68   int caps = 0;
69
70   while (*capstr) {
71     switch (*capstr) {
72     case 'C':
73       caps |= MIX_CAP_COMPRESS;
74       break;
75
76     case 'M':
77       caps |= MIX_CAP_MIDDLEMAN;
78       break;
79
80     case 'N':
81       {
82         switch (*++capstr) {
83         case 'm':
84           caps |= MIX_CAP_NEWSMAIL;
85           break;
86
87         case 'p':
88           caps |= MIX_CAP_NEWSPOST;
89           break;
90
91         }
92       }
93     }
94
95     if (*capstr)
96       capstr++;
97   }
98
99   return caps;
100 }
101
102 static void mix_add_entry (REMAILER *** type2_list, REMAILER * entry,
103                            size_t * slots, size_t * used)
104 {
105   if (*used == *slots) {
106     *slots += 5;
107     p_realloc(type2_list, *slots);
108   }
109
110   (*type2_list)[(*used)++] = entry;
111   if (entry)
112     entry->num = *used;
113 }
114
115 static REMAILER *mix_new_remailer (void)
116 {
117   return p_new(REMAILER, 1);
118 }
119
120 static void mix_free_remailer (REMAILER ** r)
121 {
122   p_delete(&(*r)->shortname);
123   p_delete(&(*r)->addr);
124   p_delete(&(*r)->ver);
125
126   p_delete(r);
127 }
128
129 /* parse the type2.list as given by mixmaster -T */
130
131 static REMAILER **mix_type2_list (size_t * l)
132 {
133   FILE *fp;
134   pid_t mm_pid;
135   int devnull;
136
137   char cmd[HUGE_STRING + _POSIX_PATH_MAX];
138   char line[HUGE_STRING];
139   char *t;
140
141   REMAILER **type2_list = NULL, *p;
142   size_t slots = 0, used = 0;
143
144   if (!l)
145     return NULL;
146
147   if ((devnull = open ("/dev/null", O_RDWR)) == -1)
148     return NULL;
149
150   snprintf (cmd, sizeof (cmd), "%s -T", Mixmaster);
151
152   if ((mm_pid =
153        mutt_create_filter_fd (cmd, NULL, &fp, NULL, devnull, -1,
154                               devnull)) == -1) {
155     close (devnull);
156     return NULL;
157   }
158
159   /* first, generate the "random" remailer */
160
161   p = mix_new_remailer ();
162   p->shortname = m_strdup("<random>");
163   mix_add_entry (&type2_list, p, &slots, &used);
164
165   while (fgets (line, sizeof (line), fp)) {
166     p = mix_new_remailer ();
167
168     if (!(t = strtok (line, " \t\n")))
169       goto problem;
170
171     p->shortname = m_strdup(t);
172
173     if (!(t = strtok (NULL, " \t\n")))
174       goto problem;
175
176     p->addr = m_strdup(t);
177
178     if (!(t = strtok (NULL, " \t\n")))
179       goto problem;
180
181     if (!(t = strtok (NULL, " \t\n")))
182       goto problem;
183
184     p->ver = m_strdup(t);
185
186     if (!(t = strtok (NULL, " \t\n")))
187       goto problem;
188
189     p->caps = mix_get_caps (t);
190
191     mix_add_entry (&type2_list, p, &slots, &used);
192     continue;
193
194   problem:
195     mix_free_remailer (&p);
196   }
197
198   *l = used;
199
200   mix_add_entry (&type2_list, NULL, &slots, &used);
201   mutt_wait_filter (mm_pid);
202
203   close (devnull);
204
205   return type2_list;
206 }
207
208 static void mix_free_type2_list (REMAILER *** ttlp)
209 {
210   int i;
211   REMAILER **type2_list = *ttlp;
212
213   for (i = 0; type2_list[i]; i++)
214     mix_free_remailer (&type2_list[i]);
215
216   p_delete(type2_list);
217 }
218
219
220 #define MIX_HOFFSET 2
221 #define MIX_VOFFSET (LINES - 6)
222 #define MIX_MAXROW  (LINES - 3)
223
224
225 static void mix_screen_coordinates (REMAILER ** type2_list,
226                                     struct coord **coordsp,
227                                     MIXCHAIN * chain, int i)
228 {
229   short c, r, oc;
230   struct coord *coords;
231
232   if (!chain->cl)
233     return;
234
235   p_realloc(coordsp, chain->cl);
236
237   coords = *coordsp;
238
239   if (i) {
240     c =
241       coords[i - 1].c + m_strlen(type2_list[chain->ch[i - 1]]->shortname) + 2;
242     r = coords[i - 1].r;
243   }
244   else {
245     r = MIX_VOFFSET;
246     c = MIX_HOFFSET;
247   }
248
249
250   for (; i < chain->cl; i++) {
251     oc = c;
252     c += m_strlen(type2_list[chain->ch[i]]->shortname) + 2;
253
254     if (c >= COLS) {
255       oc = c = MIX_HOFFSET;
256       r++;
257     }
258
259     coords[i].c = oc;
260     coords[i].r = r;
261
262   }
263
264 }
265
266 static void mix_redraw_ce (REMAILER ** type2_list,
267                            struct coord *coords,
268                            MIXCHAIN * chain, int i, short selected)
269 {
270   if (!coords || !chain)
271     return;
272
273   if (coords[i].r < MIX_MAXROW) {
274
275     if (selected)
276       SETCOLOR (MT_COLOR_INDICATOR);
277     else
278       SETCOLOR (MT_COLOR_NORMAL);
279
280     mvaddstr (coords[i].r, coords[i].c, type2_list[chain->ch[i]]->shortname);
281     SETCOLOR (MT_COLOR_NORMAL);
282
283     if (i + 1 < chain->cl)
284       addstr (", ");
285   }
286 }
287
288 static void mix_redraw_chain (REMAILER ** type2_list,
289                               struct coord *coords, MIXCHAIN * chain, int cur)
290 {
291   int i;
292
293   SETCOLOR (MT_COLOR_NORMAL);
294   BKGDSET (MT_COLOR_NORMAL);
295
296   for (i = MIX_VOFFSET; i < MIX_MAXROW; i++) {
297     move (i, 0);
298     clrtoeol ();
299   }
300
301   for (i = 0; i < chain->cl; i++)
302     mix_redraw_ce (type2_list, coords, chain, i, i == cur);
303 }
304
305 static void mix_redraw_head (MIXCHAIN * chain)
306 {
307   SETCOLOR (MT_COLOR_STATUS);
308   mvprintw (MIX_VOFFSET - 1, 0, "-- Remailer chain [Length: %d]",
309             chain ? chain->cl : 0);
310
311   BKGDSET (MT_COLOR_STATUS);
312   clrtoeol ();
313
314   BKGDSET (MT_COLOR_NORMAL);
315   SETCOLOR (MT_COLOR_NORMAL);
316 }
317
318 static const char *mix_format_caps (REMAILER * r)
319 {
320   static char capbuff[10];
321   char *t = capbuff;
322
323   if (r->caps & MIX_CAP_COMPRESS)
324     *t++ = 'C';
325   else
326     *t++ = ' ';
327
328   if (r->caps & MIX_CAP_MIDDLEMAN)
329     *t++ = 'M';
330   else
331     *t++ = ' ';
332
333   if (r->caps & MIX_CAP_NEWSPOST) {
334     *t++ = 'N';
335     *t++ = 'p';
336   }
337   else {
338     *t++ = ' ';
339     *t++ = ' ';
340   }
341
342   if (r->caps & MIX_CAP_NEWSMAIL) {
343     *t++ = 'N';
344     *t++ = 'm';
345   }
346   else {
347     *t++ = ' ';
348     *t++ = ' ';
349   }
350
351   *t = '\0';
352
353   return capbuff;
354 }
355
356 /*
357  * Format an entry for the remailer menu.
358  * 
359  * %n   number
360  * %c   capabilities
361  * %s   short name
362  * %a   address
363  *
364  */
365
366 static const char *mix_entry_fmt (char *dest,
367                                   size_t destlen,
368                                   char op,
369                                   const char *src,
370                                   const char *prefix,
371                                   const char *ifstring,
372                                   const char *elsestring,
373                                   unsigned long data, format_flag flags)
374 {
375   char fmt[16];
376   REMAILER *remailer = (REMAILER *) data;
377   int optional = (flags & M_FORMAT_OPTIONAL);
378
379   switch (op) {
380   case 'n':
381     if (!optional) {
382       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
383       snprintf (dest, destlen, fmt, remailer->num);
384     }
385     break;
386   case 'c':
387     if (!optional) {
388       snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
389       snprintf (dest, destlen, fmt, mix_format_caps (remailer));
390     }
391     break;
392   case 's':
393     if (!optional) {
394       snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
395       snprintf (dest, destlen, fmt, NONULL (remailer->shortname));
396     }
397     else if (!remailer->shortname)
398       optional = 0;
399     break;
400   case 'a':
401     if (!optional) {
402       snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
403       snprintf (dest, destlen, fmt, NONULL (remailer->addr));
404     }
405     else if (!remailer->addr)
406       optional = 0;
407     break;
408
409   default:
410     *dest = '\0';
411   }
412
413   if (optional)
414     mutt_FormatString (dest, destlen, ifstring, mutt_attach_fmt, data, 0);
415   else if (flags & M_FORMAT_OPTIONAL)
416     mutt_FormatString (dest, destlen, elsestring, mutt_attach_fmt, data, 0);
417   return (src);
418 }
419
420
421
422 static void mix_entry (char *b, size_t blen, MUTTMENU * menu, int num)
423 {
424   REMAILER **type2_list = (REMAILER **) menu->data;
425   int w=(COLS-SW)>blen?blen:(COLS-SW);
426
427   mutt_FormatString (b, w, NONULL (MixEntryFormat), mix_entry_fmt,
428                      (unsigned long) type2_list[num], M_FORMAT_ARROWCURSOR);
429 }
430
431 static int mix_chain_add (MIXCHAIN * chain, const char *s,
432                           REMAILER ** type2_list)
433 {
434   int i;
435
436   if (chain->cl >= MAXMIXES)
437     return -1;
438
439   if (!m_strcmp(s, "0") || !ascii_strcasecmp (s, "<random>")) {
440     chain->ch[chain->cl++] = 0;
441     return 0;
442   }
443
444   for (i = 0; type2_list[i]; i++) {
445     if (!ascii_strcasecmp (s, type2_list[i]->shortname)) {
446       chain->ch[chain->cl++] = i;
447       return 0;
448     }
449   }
450
451   /* replace unknown remailers by <random> */
452
453   if (!type2_list[i])
454     chain->ch[chain->cl++] = 0;
455
456   return 0;
457 }
458
459 static struct mapping_t RemailerHelp[] = {
460   {N_("Append"), OP_MIX_APPEND},
461   {N_("Insert"), OP_MIX_INSERT},
462   {N_("Delete"), OP_MIX_DELETE},
463   {N_("Abort"), OP_EXIT},
464   {N_("OK"), OP_MIX_USE},
465   {NULL}
466 };
467
468
469 void mix_make_chain (LIST ** chainp, int *redraw)
470 {
471   LIST *p;
472   MIXCHAIN *chain;
473   int c_cur = 0, c_old = 0;
474   int m_len;
475   short c_redraw = 1;
476
477   REMAILER **type2_list = NULL;
478   size_t ttll = 0;
479
480   struct coord *coords = NULL;
481
482   MUTTMENU *menu;
483   char helpstr[SHORT_STRING];
484   short loop = 1;
485   int op;
486
487   int i, j;
488   const char *t;
489
490   if (!(type2_list = mix_type2_list (&ttll))) {
491     mutt_error _("Can't get mixmaster's type2.list!");
492
493     return;
494   }
495
496   *redraw = REDRAW_FULL;
497
498   chain = p_new(MIXCHAIN, 1);
499   for (p = *chainp; p; p = p->next)
500     mix_chain_add (chain, (char *) p->data, type2_list);
501
502   mutt_free_list (chainp);
503
504   /* safety check */
505   for (i = 0; i < chain->cl; i++) {
506     if (chain->ch[i] >= ttll)
507       chain->ch[i] = 0;
508   }
509
510   mix_screen_coordinates (type2_list, &coords, chain, 0);
511
512   menu = mutt_new_menu ();
513   menu->menu = MENU_MIX;
514   menu->max = ttll;
515   menu->make_entry = mix_entry;
516   menu->tag = NULL;
517   menu->title = _("Select a remailer chain.");
518   menu->data = type2_list;
519   menu->help =
520     mutt_compile_help (helpstr, sizeof (helpstr), MENU_MIX, RemailerHelp);
521
522   m_len = menu->pagelen = MIX_VOFFSET - menu->offset - 1;
523
524   while (loop) {
525     if (menu->pagelen != m_len) {
526       menu->pagelen = m_len;
527       menu->redraw = REDRAW_FULL;
528     }
529
530     if (c_redraw) {
531       mix_redraw_head (chain);
532       mix_redraw_chain (type2_list, coords, chain, c_cur);
533       c_redraw = 0;
534     }
535     else if (c_cur != c_old) {
536       mix_redraw_ce (type2_list, coords, chain, c_old, 0);
537       mix_redraw_ce (type2_list, coords, chain, c_cur, 1);
538     }
539
540     c_old = c_cur;
541
542     switch ((op = mutt_menuLoop (menu))) {
543     case OP_REDRAW:
544       {
545         menu_redraw_status (menu);
546         mix_redraw_head (chain);
547         mix_screen_coordinates (type2_list, &coords, chain, 0);
548         mix_redraw_chain (type2_list, coords, chain, c_cur);
549         menu->pagelen = m_len = MIX_VOFFSET - menu->offset - 1;
550         break;
551       }
552
553     case OP_EXIT:
554       {
555         chain->cl = 0;
556         loop = 0;
557         break;
558       }
559
560     case OP_MIX_USE:
561       {
562         if (!chain->cl) {
563           chain->cl++;
564           chain->ch[0] = menu->current;
565           mix_screen_coordinates (type2_list, &coords, chain, c_cur);
566           c_redraw = 1;
567         }
568
569         if (chain->cl && chain->ch[chain->cl - 1] &&
570             (type2_list[chain->ch[chain->cl - 1]]->caps & MIX_CAP_MIDDLEMAN))
571         {
572           mutt_error (_
573                       ("Error: %s can't be used as the final remailer of a chain."),
574                       type2_list[chain->ch[chain->cl - 1]]->shortname);
575         }
576         else {
577           loop = 0;
578         }
579         break;
580       }
581
582     case OP_GENERIC_SELECT_ENTRY:
583     case OP_MIX_APPEND:
584       {
585         if (chain->cl < MAXMIXES && c_cur < chain->cl)
586           c_cur++;
587       }
588       /* fallthrough */
589     case OP_MIX_INSERT:
590       {
591         if (chain->cl < MAXMIXES) {
592           chain->cl++;
593           for (i = chain->cl - 1; i > c_cur; i--)
594             chain->ch[i] = chain->ch[i - 1];
595
596           chain->ch[c_cur] = menu->current;
597           mix_screen_coordinates (type2_list, &coords, chain, c_cur);
598           c_redraw = 1;
599         }
600         else
601           mutt_error (_("Mixmaster chains are limited to %d elements."),
602                       MAXMIXES);
603
604         break;
605       }
606
607     case OP_MIX_DELETE:
608       {
609         if (chain->cl) {
610           chain->cl--;
611
612           for (i = c_cur; i < chain->cl; i++)
613             chain->ch[i] = chain->ch[i + 1];
614
615           if (c_cur == chain->cl && c_cur)
616             c_cur--;
617
618           mix_screen_coordinates (type2_list, &coords, chain, c_cur);
619           c_redraw = 1;
620         }
621         else {
622           mutt_error _("The remailer chain is already empty.");
623         }
624         break;
625       }
626
627     case OP_MIX_CHAIN_PREV:
628       {
629         if (c_cur)
630           c_cur--;
631         else
632           mutt_error _("You already have the first chain element selected.");
633
634         break;
635       }
636
637     case OP_MIX_CHAIN_NEXT:
638       {
639         if (chain->cl && c_cur < chain->cl - 1)
640           c_cur++;
641         else
642           mutt_error _("You already have the last chain element selected.");
643
644         break;
645       }
646     }
647   }
648
649   mutt_menuDestroy (&menu);
650
651   /* construct the remailer list */
652
653   if (chain->cl) {
654     for (i = 0; i < chain->cl; i++) {
655       if ((j = chain->ch[i]))
656         t = type2_list[j]->shortname;
657       else
658         t = "*";
659
660       *chainp = mutt_add_list (*chainp, t);
661     }
662   }
663
664   mix_free_type2_list (&type2_list);
665   p_delete(&coords);
666   p_delete(&chain);
667 }
668
669 /* some safety checks before piping the message to mixmaster */
670
671 int mix_check_message (HEADER * msg)
672 {
673   const char *fqdn;
674   short need_hostname = 0;
675   ADDRESS *p;
676
677   if (msg->env->cc || msg->env->bcc) {
678     mutt_error _("Mixmaster doesn't accept Cc or Bcc headers.");
679
680     return -1;
681   }
682
683   /* When using mixmaster, we MUST qualify any addresses since
684    * the message will be delivered through remote systems.
685    * 
686    * use_domain won't be respected at this point, hidden_host will.
687    */
688
689   for (p = msg->env->to; p; p = p->next) {
690     if (!p->group && strchr (p->mailbox, '@') == NULL) {
691       need_hostname = 1;
692       break;
693     }
694   }
695
696   if (need_hostname) {
697
698     if (!(fqdn = mutt_fqdn (1))) {
699       mutt_error
700         _
701         ("Please set the hostname variable to a proper value when using mixmaster!");
702       return (-1);
703     }
704
705     /* Cc and Bcc are empty at this point. */
706     rfc822_qualify (msg->env->to, fqdn);
707     rfc822_qualify (msg->env->reply_to, fqdn);
708     rfc822_qualify (msg->env->mail_followup_to, fqdn);
709   }
710
711   return 0;
712 }
713
714 int mix_send_message (LIST * chain, const char *tempfile)
715 {
716   char cmd[HUGE_STRING];
717   char tmp[HUGE_STRING];
718   char cd_quoted[STRING];
719   int i;
720
721   snprintf (cmd, sizeof (cmd), "cat %s | %s -m ", tempfile, Mixmaster);
722
723   for (i = 0; chain; chain = chain->next, i = 1) {
724     strfcpy (tmp, cmd, sizeof (tmp));
725     mutt_quote_filename (cd_quoted, sizeof (cd_quoted), (char *) chain->data);
726     snprintf (cmd, sizeof (cmd), "%s%s%s", tmp, i ? "," : " -l ", cd_quoted);
727   }
728
729   if (!option (OPTNOCURSES))
730     mutt_endwin (NULL);
731
732   if ((i = mutt_system (cmd))) {
733     fprintf (stderr, _("Error sending message, child exited %d.\n"), i);
734     if (!option (OPTNOCURSES)) {
735       mutt_any_key_to_continue (NULL);
736       mutt_error _("Error sending message.");
737     }
738   }
739
740   unlink (tempfile);
741   return i;
742 }
743
744
745 #endif