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