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