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