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