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