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