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