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