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