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