move menu.[hc].
[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 <lib-ui/curses.h>
26 #include <lib-ui/menu.h>
27
28 #include "mutt.h"
29 #include "recvattach.h"
30
31 #include "remailer.h"
32
33 #include "lib/rx.h"
34
35 #include <stdio.h>
36 #include <string.h>
37 #include <stdlib.h>
38
39 #include <sys/types.h>
40 #include <sys/file.h>
41 #include <fcntl.h>
42
43 #define SW              (option(OPTMBOXPANE)?SidebarWidth:0)
44
45 #ifdef MIXMASTER
46
47 struct coord {
48   short r, c;
49 };
50
51 static REMAILER **mix_type2_list (size_t * l);
52 static REMAILER *mix_new_remailer (void);
53 static const char *mix_format_caps (REMAILER * r);
54 static int mix_chain_add (MIXCHAIN * chain, const char *s,
55                           REMAILER ** type2_list);
56 static int mix_get_caps (const char *capstr);
57 static void mix_add_entry (REMAILER ***, REMAILER *, size_t *, size_t *);
58 static void mix_entry (char *b, size_t blen, MUTTMENU * menu, int num);
59 static void mix_free_remailer (REMAILER ** r);
60 static void mix_free_type2_list (REMAILER *** ttlp);
61 static void mix_redraw_ce (REMAILER ** type2_list, struct coord *coords,
62                            MIXCHAIN * chain, int i, short selected);
63 static void mix_redraw_chain (REMAILER ** type2_list, struct coord *coords,
64                               MIXCHAIN * chain, int cur);
65 static void mix_redraw_head (MIXCHAIN *);
66 static void mix_screen_coordinates (REMAILER ** type2_list, struct coord **,
67                                     MIXCHAIN *, int);
68
69 static int mix_get_caps (const char *capstr)
70 {
71   int caps = 0;
72
73   while (*capstr) {
74     switch (*capstr) {
75     case 'C':
76       caps |= MIX_CAP_COMPRESS;
77       break;
78
79     case 'M':
80       caps |= MIX_CAP_MIDDLEMAN;
81       break;
82
83     case 'N':
84       {
85         switch (*++capstr) {
86         case 'm':
87           caps |= MIX_CAP_NEWSMAIL;
88           break;
89
90         case 'p':
91           caps |= MIX_CAP_NEWSPOST;
92           break;
93
94         }
95       }
96     }
97
98     if (*capstr)
99       capstr++;
100   }
101
102   return caps;
103 }
104
105 static void mix_add_entry (REMAILER *** type2_list, REMAILER * entry,
106                            size_t * slots, size_t * used)
107 {
108   if (*used == *slots) {
109     *slots += 5;
110     p_realloc(type2_list, *slots);
111   }
112
113   (*type2_list)[(*used)++] = entry;
114   if (entry)
115     entry->num = *used;
116 }
117
118 static REMAILER *mix_new_remailer (void)
119 {
120   return p_new(REMAILER, 1);
121 }
122
123 static void mix_free_remailer (REMAILER ** r)
124 {
125   p_delete(&(*r)->shortname);
126   p_delete(&(*r)->addr);
127   p_delete(&(*r)->ver);
128
129   p_delete(r);
130 }
131
132 /* parse the type2.list as given by mixmaster -T */
133
134 static REMAILER **mix_type2_list (size_t * l)
135 {
136   FILE *fp;
137   pid_t mm_pid;
138   int devnull;
139
140   char cmd[HUGE_STRING + _POSIX_PATH_MAX];
141   char line[HUGE_STRING];
142   char *t;
143
144   REMAILER **type2_list = NULL, *p;
145   size_t slots = 0, used = 0;
146
147   if (!l)
148     return NULL;
149
150   if ((devnull = open ("/dev/null", O_RDWR)) == -1)
151     return NULL;
152
153   snprintf (cmd, sizeof (cmd), "%s -T", Mixmaster);
154
155   if ((mm_pid =
156        mutt_create_filter_fd (cmd, NULL, &fp, NULL, devnull, -1,
157                               devnull)) == -1) {
158     close (devnull);
159     return NULL;
160   }
161
162   /* first, generate the "random" remailer */
163
164   p = mix_new_remailer ();
165   p->shortname = m_strdup("<random>");
166   mix_add_entry (&type2_list, p, &slots, &used);
167
168   while (fgets (line, sizeof (line), fp)) {
169     p = mix_new_remailer ();
170
171     if (!(t = strtok (line, " \t\n")))
172       goto problem;
173
174     p->shortname = m_strdup(t);
175
176     if (!(t = strtok (NULL, " \t\n")))
177       goto problem;
178
179     p->addr = m_strdup(t);
180
181     if (!(t = strtok (NULL, " \t\n")))
182       goto problem;
183
184     if (!(t = strtok (NULL, " \t\n")))
185       goto problem;
186
187     p->ver = m_strdup(t);
188
189     if (!(t = strtok (NULL, " \t\n")))
190       goto problem;
191
192     p->caps = mix_get_caps (t);
193
194     mix_add_entry (&type2_list, p, &slots, &used);
195     continue;
196
197   problem:
198     mix_free_remailer (&p);
199   }
200
201   *l = used;
202
203   mix_add_entry (&type2_list, NULL, &slots, &used);
204   mutt_wait_filter (mm_pid);
205
206   close (devnull);
207
208   return type2_list;
209 }
210
211 static void mix_free_type2_list (REMAILER *** ttlp)
212 {
213   int i;
214   REMAILER **type2_list = *ttlp;
215
216   for (i = 0; type2_list[i]; i++)
217     mix_free_remailer (&type2_list[i]);
218
219   p_delete(type2_list);
220 }
221
222
223 #define MIX_HOFFSET 2
224 #define MIX_VOFFSET (LINES - 6)
225 #define MIX_MAXROW  (LINES - 3)
226
227
228 static void mix_screen_coordinates (REMAILER ** type2_list,
229                                     struct coord **coordsp,
230                                     MIXCHAIN * chain, int i)
231 {
232   short c, r, oc;
233   struct coord *coords;
234
235   if (!chain->cl)
236     return;
237
238   p_realloc(coordsp, chain->cl);
239
240   coords = *coordsp;
241
242   if (i) {
243     c =
244       coords[i - 1].c + m_strlen(type2_list[chain->ch[i - 1]]->shortname) + 2;
245     r = coords[i - 1].r;
246   }
247   else {
248     r = MIX_VOFFSET;
249     c = MIX_HOFFSET;
250   }
251
252
253   for (; i < chain->cl; i++) {
254     oc = c;
255     c += m_strlen(type2_list[chain->ch[i]]->shortname) + 2;
256
257     if (c >= COLS) {
258       oc = c = MIX_HOFFSET;
259       r++;
260     }
261
262     coords[i].c = oc;
263     coords[i].r = r;
264
265   }
266
267 }
268
269 static void mix_redraw_ce (REMAILER ** type2_list,
270                            struct coord *coords,
271                            MIXCHAIN * chain, int i, short selected)
272 {
273   if (!coords || !chain)
274     return;
275
276   if (coords[i].r < MIX_MAXROW) {
277
278     if (selected)
279       SETCOLOR (MT_COLOR_INDICATOR);
280     else
281       SETCOLOR (MT_COLOR_NORMAL);
282
283     mvaddstr (coords[i].r, coords[i].c, type2_list[chain->ch[i]]->shortname);
284     SETCOLOR (MT_COLOR_NORMAL);
285
286     if (i + 1 < chain->cl)
287       addstr (", ");
288   }
289 }
290
291 static void mix_redraw_chain (REMAILER ** type2_list,
292                               struct coord *coords, MIXCHAIN * chain, int cur)
293 {
294   int i;
295
296   SETCOLOR (MT_COLOR_NORMAL);
297   BKGDSET (MT_COLOR_NORMAL);
298
299   for (i = MIX_VOFFSET; i < MIX_MAXROW; i++) {
300     move (i, 0);
301     clrtoeol ();
302   }
303
304   for (i = 0; i < chain->cl; i++)
305     mix_redraw_ce (type2_list, coords, chain, i, i == cur);
306 }
307
308 static void mix_redraw_head (MIXCHAIN * chain)
309 {
310   SETCOLOR (MT_COLOR_STATUS);
311   mvprintw (MIX_VOFFSET - 1, 0, "-- Remailer chain [Length: %d]",
312             chain ? chain->cl : 0);
313
314   BKGDSET (MT_COLOR_STATUS);
315   clrtoeol ();
316
317   BKGDSET (MT_COLOR_NORMAL);
318   SETCOLOR (MT_COLOR_NORMAL);
319 }
320
321 static const char *mix_format_caps (REMAILER * r)
322 {
323   static char capbuff[10];
324   char *t = capbuff;
325
326   if (r->caps & MIX_CAP_COMPRESS)
327     *t++ = 'C';
328   else
329     *t++ = ' ';
330
331   if (r->caps & MIX_CAP_MIDDLEMAN)
332     *t++ = 'M';
333   else
334     *t++ = ' ';
335
336   if (r->caps & MIX_CAP_NEWSPOST) {
337     *t++ = 'N';
338     *t++ = 'p';
339   }
340   else {
341     *t++ = ' ';
342     *t++ = ' ';
343   }
344
345   if (r->caps & MIX_CAP_NEWSMAIL) {
346     *t++ = 'N';
347     *t++ = 'm';
348   }
349   else {
350     *t++ = ' ';
351     *t++ = ' ';
352   }
353
354   *t = '\0';
355
356   return capbuff;
357 }
358
359 /*
360  * Format an entry for the remailer menu.
361  * 
362  * %n   number
363  * %c   capabilities
364  * %s   short name
365  * %a   address
366  *
367  */
368
369 static const char *mix_entry_fmt (char *dest,
370                                   size_t destlen,
371                                   char op,
372                                   const char *src,
373                                   const char *prefix,
374                                   const char *ifstring,
375                                   const char *elsestring,
376                                   unsigned long data, format_flag flags)
377 {
378   char fmt[16];
379   REMAILER *remailer = (REMAILER *) data;
380   int optional = (flags & M_FORMAT_OPTIONAL);
381
382   switch (op) {
383   case 'n':
384     if (!optional) {
385       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
386       snprintf (dest, destlen, fmt, remailer->num);
387     }
388     break;
389   case 'c':
390     if (!optional) {
391       snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
392       snprintf (dest, destlen, fmt, mix_format_caps (remailer));
393     }
394     break;
395   case 's':
396     if (!optional) {
397       snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
398       snprintf (dest, destlen, fmt, NONULL (remailer->shortname));
399     }
400     else if (!remailer->shortname)
401       optional = 0;
402     break;
403   case 'a':
404     if (!optional) {
405       snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
406       snprintf (dest, destlen, fmt, NONULL (remailer->addr));
407     }
408     else if (!remailer->addr)
409       optional = 0;
410     break;
411
412   default:
413     *dest = '\0';
414   }
415
416   if (optional)
417     mutt_FormatString (dest, destlen, ifstring, mutt_attach_fmt, data, 0);
418   else if (flags & M_FORMAT_OPTIONAL)
419     mutt_FormatString (dest, destlen, elsestring, mutt_attach_fmt, data, 0);
420   return (src);
421 }
422
423
424
425 static void mix_entry (char *b, size_t blen, MUTTMENU * menu, int num)
426 {
427   REMAILER **type2_list = (REMAILER **) menu->data;
428   int w=(COLS-SW)>blen?blen:(COLS-SW);
429
430   mutt_FormatString (b, w, 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 (!m_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, OP_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   const 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 = p_new(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   p_delete(&coords);
669   p_delete(&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_t *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     m_strcpy(tmp, sizeof(tmp), cmd);
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