1 /**
2  * @file
3  * Mixmaster Remailer Dialog
4  *
5  * @authors
6  * Copyright (C) 1999-2001 Thomas Roessler <roessler@does-not-exist.org>
7  * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
8  *
9  * @copyright
10  * This program is free software: you can redistribute it and/or modify it under
11  * the terms of the GNU General Public License as published by the Free Software
12  * Foundation, either version 2 of the License, or (at your option) any later
13  * version.
14  *
15  * This program is distributed in the hope that it will be useful, but WITHOUT
16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License along with
21  * this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 /**
25  * @page neo_remailer Mixmaster Remailer Dialog
26  *
27  * The Mixmaster Remailer Dialog lets the user edit anonymous remailer chain.
28  *
29  * ## Windows
30  *
31  * | Name                      | Type            | See Also                     |
32  * | :------------------------ | :-------------- | :--------------------------- |
33  * | Mixmaster Remailer Dialog | WT_DLG_REMAILER | dlg_select_mixmaster_chain() |
34  *
35  * **Parent**
36  * - @ref gui_dialog
37  *
38  * **Children**
39  * - Hosts:        @ref menu_window
40  * - Chain Bar:    @ref gui_sbar
41  * - Chain:        @ref gui_window
42  * - Remailer Bar: @ref gui_sbar
43  *
44  * ## Data
45  * - #Remailer
46  *
47  * The Mixmaster Remailer Dialog stores its data (#Remailer) in Menu::mdata.
48  *
49  * ## Events
50  *
51  * Once constructed, it is controlled by the following events:
52  *
53  * | Event Type  | Handler                    |
54  * | :---------- | :------------------------- |
55  * | #NT_CONFIG  | remailer_config_observer() |
56  * | #NT_WINDOW  | remailer_window_observer() |
57  *
58  * The Mixmaster Remailer Dialog does not implement MuttWindow::recalc() or MuttWindow::repaint().
59  *
60  * Some other events are handled by the dialog's children.
61  */
62 
63 #include "config.h"
64 #include <fcntl.h>
65 #include <stdbool.h>
66 #include <stdio.h>
67 #include <string.h>
68 #include <unistd.h>
69 #include "mutt/lib.h"
70 #include "address/lib.h"
71 #include "config/lib.h"
72 #include "email/lib.h"
73 #include "core/lib.h"
74 #include "gui/lib.h"
75 #include "color/lib.h"
76 #include "menu/lib.h"
77 #include "send/lib.h"
78 #include "format_flags.h"
79 #include "muttlib.h"
80 #include "opcodes.h"
81 #include "options.h"
82 #include "protos.h"
83 #ifdef MIXMASTER
84 #include "remailer.h"
85 #endif
86 
87 /**
88  * struct Coord - Screen coordinates
89  */
90 struct Coord
91 {
92   short r; ///< row
93   short c; ///< column
94 };
95 
96 /// Help Bar for the Mixmaster dialog
97 static const struct Mapping RemailerHelp[] = {
98   // clang-format off
99   { N_("Append"), OP_MIX_APPEND },
100   { N_("Insert"), OP_MIX_INSERT },
101   { N_("Delete"), OP_MIX_DELETE },
102   { N_("Abort"),  OP_EXIT },
103   { N_("OK"),     OP_MIX_USE },
104   { NULL, 0 },
105   // clang-format on
106 };
107 
108 /**
109  * mix_get_caps - Get Mixmaster Capabilities
110  * @param capstr Capability string to parse
111  * @retval num Capabilities, see #MixCapFlags
112  */
mix_get_caps(const char * capstr)113 static MixCapFlags mix_get_caps(const char *capstr)
114 {
115   MixCapFlags caps = MIX_CAP_NO_FLAGS;
116 
117   while (*capstr)
118   {
119     switch (*capstr)
120     {
121       case 'C':
122         caps |= MIX_CAP_COMPRESS;
123         break;
124 
125       case 'M':
126         caps |= MIX_CAP_MIDDLEMAN;
127         break;
128 
129       case 'N':
130       {
131         switch (*++capstr)
132         {
133           case 'm':
134             caps |= MIX_CAP_NEWSMAIL;
135             break;
136 
137           case 'p':
138             caps |= MIX_CAP_NEWSPOST;
139             break;
140         }
141       }
142     }
143 
144     if (*capstr)
145       capstr++;
146   }
147 
148   return caps;
149 }
150 
151 /**
152  * mix_add_entry - Add an entry to the Remailer list
153  * @param[out] type2_list Remailer list to add to
154  * @param[in]  entry      Remailer to add
155  * @param[out] slots      Total number of slots
156  * @param[out] used       Number of slots used
157  */
mix_add_entry(struct Remailer *** type2_list,struct Remailer * entry,size_t * slots,size_t * used)158 static void mix_add_entry(struct Remailer ***type2_list, struct Remailer *entry,
159                           size_t *slots, size_t *used)
160 {
161   if (*used == *slots)
162   {
163     *slots += 5;
164     mutt_mem_realloc(type2_list, sizeof(struct Remailer *) * (*slots));
165   }
166 
167   (*type2_list)[(*used)++] = entry;
168   if (entry)
169     entry->num = *used;
170 }
171 
172 /**
173  * remailer_new - Create a new Remailer
174  * @retval ptr Newly allocated Remailer
175  */
remailer_new(void)176 static struct Remailer *remailer_new(void)
177 {
178   return mutt_mem_calloc(1, sizeof(struct Remailer));
179 }
180 
181 /**
182  * remailer_free - Free a Remailer
183  * @param[out] ptr Remailer to free
184  */
remailer_free(struct Remailer ** ptr)185 static void remailer_free(struct Remailer **ptr)
186 {
187   if (!ptr || !*ptr)
188     return;
189 
190   struct Remailer *r = *ptr;
191 
192   FREE(&r->shortname);
193   FREE(&r->addr);
194   FREE(&r->ver);
195   FREE(ptr);
196 }
197 
198 /**
199  * mix_type2_list - Parse the type2.list as given by mixmaster -T
200  * @param[out] l Length of list
201  * @retval ptr type2.list
202  */
mix_type2_list(size_t * l)203 static struct Remailer **mix_type2_list(size_t *l)
204 {
205   if (!l)
206     return NULL;
207 
208   FILE *fp = NULL;
209   char line[8192];
210   char *t = NULL;
211 
212   struct Remailer **type2_list = NULL;
213   struct Remailer *p = NULL;
214   size_t slots = 0, used = 0;
215 
216   int fd_null = open("/dev/null", O_RDWR);
217   if (fd_null == -1)
218     return NULL;
219 
220   struct Buffer *cmd = mutt_buffer_pool_get();
221   const char *const c_mixmaster = cs_subset_string(NeoMutt->sub, "mixmaster");
222   mutt_buffer_printf(cmd, "%s -T", c_mixmaster);
223 
224   pid_t mm_pid =
225       filter_create_fd(mutt_buffer_string(cmd), NULL, &fp, NULL, fd_null, -1, fd_null);
226   if (mm_pid == -1)
227   {
228     mutt_buffer_pool_release(&cmd);
229     close(fd_null);
230     return NULL;
231   }
232 
233   mutt_buffer_pool_release(&cmd);
234 
235   /* first, generate the "random" remailer */
236 
237   p = remailer_new();
238   p->shortname = mutt_str_dup(_("<random>"));
239   mix_add_entry(&type2_list, p, &slots, &used);
240 
241   while (fgets(line, sizeof(line), fp))
242   {
243     p = remailer_new();
244 
245     t = strtok(line, " \t\n");
246     if (!t)
247       goto problem;
248 
249     p->shortname = mutt_str_dup(t);
250 
251     t = strtok(NULL, " \t\n");
252     if (!t)
253       goto problem;
254 
255     p->addr = mutt_str_dup(t);
256 
257     t = strtok(NULL, " \t\n");
258     if (!t)
259       goto problem;
260 
261     t = strtok(NULL, " \t\n");
262     if (!t)
263       goto problem;
264 
265     p->ver = mutt_str_dup(t);
266 
267     t = strtok(NULL, " \t\n");
268     if (!t)
269       goto problem;
270 
271     p->caps = mix_get_caps(t);
272 
273     mix_add_entry(&type2_list, p, &slots, &used);
274     continue;
275 
276   problem:
277     remailer_free(&p);
278   }
279 
280   *l = used;
281 
282   mix_add_entry(&type2_list, NULL, &slots, &used);
283   filter_wait(mm_pid);
284 
285   close(fd_null);
286 
287   return type2_list;
288 }
289 
290 /**
291  * mix_type2_list_free - Free a Remailer List
292  * @param[out] ttlp Remailer List to free
293  */
mix_type2_list_free(struct Remailer *** ttlp)294 static void mix_type2_list_free(struct Remailer ***ttlp)
295 {
296   struct Remailer **type2_list = *ttlp;
297 
298   for (int i = 0; type2_list[i]; i++)
299     remailer_free(&type2_list[i]);
300 
301   FREE(ttlp);
302 }
303 
304 /**
305  * mix_screen_coordinates - Get the screen coordinates to place a chain
306  * @param[in]  win        Window
307  * @param[out] type2_list Remailer List
308  * @param[out] coordsp    On screen coordinates
309  * @param[in]  chain      Chain
310  * @param[in]  i          Index in chain
311  */
mix_screen_coordinates(struct MuttWindow * win,struct Remailer ** type2_list,struct Coord ** coordsp,struct MixChain * chain,int i)312 static void mix_screen_coordinates(struct MuttWindow *win, struct Remailer **type2_list,
313                                    struct Coord **coordsp, struct MixChain *chain, int i)
314 {
315   const int wrap_indent = 2;
316 
317   if (!chain->cl)
318     return;
319 
320   short c, r;
321 
322   mutt_mem_realloc(coordsp, sizeof(struct Coord) * chain->cl);
323 
324   struct Coord *coords = *coordsp;
325 
326   if (i == 0)
327   {
328     r = 0;
329     c = 0;
330   }
331   else
332   {
333     c = coords[i - 1].c + strlen(type2_list[chain->ch[i - 1]]->shortname) + 2;
334     r = coords[i - 1].r;
335   }
336 
337   for (; i < chain->cl; i++)
338   {
339     short oc = c;
340     c += strlen(type2_list[chain->ch[i]]->shortname) + 2;
341 
342     if (c >= win->state.cols)
343     {
344       oc = wrap_indent;
345       c = wrap_indent;
346       r++;
347     }
348 
349     coords[i].c = oc;
350     coords[i].r = r;
351   }
352 }
353 
354 /**
355  * mix_redraw_ce - Redraw the Remailer chain
356  * @param[in]  win        Window
357  * @param[out] type2_list Remailer List
358  * @param[in]  coords     Screen Coordinates
359  * @param[in]  chain      Chain
360  * @param[in]  i          Index in chain
361  * @param[in]  selected   true, if this item is selected
362  */
mix_redraw_ce(struct MuttWindow * win,struct Remailer ** type2_list,struct Coord * coords,struct MixChain * chain,int i,bool selected)363 static void mix_redraw_ce(struct MuttWindow *win, struct Remailer **type2_list,
364                           struct Coord *coords, struct MixChain *chain, int i, bool selected)
365 {
366   if (!coords || !chain)
367     return;
368 
369   if (coords[i].r < win->state.rows)
370   {
371     if (selected)
372       mutt_curses_set_color_by_id(MT_COLOR_INDICATOR);
373     else
374       mutt_curses_set_color_by_id(MT_COLOR_NORMAL);
375 
376     mutt_window_mvaddstr(win, coords[i].c, coords[i].r, type2_list[chain->ch[i]]->shortname);
377     mutt_curses_set_color_by_id(MT_COLOR_NORMAL);
378 
379     if (i + 1 < chain->cl)
380       mutt_window_addstr(win, ", ");
381   }
382 }
383 
384 /**
385  * mix_redraw_chain - Redraw the chain on screen
386  * @param[in]  win        Window
387  * @param[out] type2_list Remailer List
388  * @param[in]  coords     Where to draw the list on screen
389  * @param[in]  chain      Chain to display
390  * @param[in]  cur        Chain index of current selection
391  */
mix_redraw_chain(struct MuttWindow * win,struct Remailer ** type2_list,struct Coord * coords,struct MixChain * chain,int cur)392 static void mix_redraw_chain(struct MuttWindow *win, struct Remailer **type2_list,
393                              struct Coord *coords, struct MixChain *chain, int cur)
394 {
395   for (int i = 0; i < win->state.rows; i++)
396   {
397     mutt_window_move(win, 0, i);
398     mutt_window_clrtoeol(win);
399   }
400 
401   for (int i = 0; i < chain->cl; i++)
402     mix_redraw_ce(win, type2_list, coords, chain, i, i == cur);
403 }
404 
405 /**
406  * mix_redraw_head - Redraw the Chain info
407  * @param win   Window
408  * @param chain Chain
409  */
mix_redraw_head(struct MuttWindow * win,struct MixChain * chain)410 static void mix_redraw_head(struct MuttWindow *win, struct MixChain *chain)
411 {
412   char buf[1024];
413   snprintf(buf, sizeof(buf), "-- Remailer chain [Length: %ld]", chain ? chain->cl : 0);
414   sbar_set_title(win, buf);
415 }
416 
417 /**
418  * mix_format_caps - Turn flags into a MixMaster capability string
419  * @param r Remailer to use
420  * @retval ptr Capability string
421  *
422  * @note The string is a static buffer
423  */
mix_format_caps(struct Remailer * r)424 static const char *mix_format_caps(struct Remailer *r)
425 {
426   static char capbuf[10];
427   char *t = capbuf;
428 
429   if (r->caps & MIX_CAP_COMPRESS)
430     *t++ = 'C';
431   else
432     *t++ = ' ';
433 
434   if (r->caps & MIX_CAP_MIDDLEMAN)
435     *t++ = 'M';
436   else
437     *t++ = ' ';
438 
439   if (r->caps & MIX_CAP_NEWSPOST)
440   {
441     *t++ = 'N';
442     *t++ = 'p';
443   }
444   else
445   {
446     *t++ = ' ';
447     *t++ = ' ';
448   }
449 
450   if (r->caps & MIX_CAP_NEWSMAIL)
451   {
452     *t++ = 'N';
453     *t++ = 'm';
454   }
455   else
456   {
457     *t++ = ' ';
458     *t++ = ' ';
459   }
460 
461   *t = '\0';
462 
463   return capbuf;
464 }
465 
466 /**
467  * mix_format_str - Format a string for the remailer menu - Implements ::format_t - @ingroup expando_api
468  *
469  * | Expando | Description
470  * |:--------|:--------------------------------------------------------
471  * | \%a     | The remailer's e-mail address
472  * | \%c     | Remailer capabilities
473  * | \%n     | The running number on the menu
474  * | \%s     | The remailer's short name
475  */
mix_format_str(char * buf,size_t buflen,size_t col,int cols,char op,const char * src,const char * prec,const char * if_str,const char * else_str,intptr_t data,MuttFormatFlags flags)476 static const char *mix_format_str(char *buf, size_t buflen, size_t col, int cols,
477                                   char op, const char *src, const char *prec,
478                                   const char *if_str, const char *else_str,
479                                   intptr_t data, MuttFormatFlags flags)
480 {
481   char fmt[128];
482   struct Remailer *remailer = (struct Remailer *) data;
483   bool optional = (flags & MUTT_FORMAT_OPTIONAL);
484 
485   switch (op)
486   {
487     case 'a':
488       if (!optional)
489       {
490         snprintf(fmt, sizeof(fmt), "%%%ss", prec);
491         snprintf(buf, buflen, fmt, NONULL(remailer->addr));
492       }
493       else if (!remailer->addr)
494         optional = false;
495       break;
496 
497     case 'c':
498       if (optional)
499         break;
500 
501       snprintf(fmt, sizeof(fmt), "%%%ss", prec);
502       snprintf(buf, buflen, fmt, mix_format_caps(remailer));
503       break;
504 
505     case 'n':
506       if (optional)
507         break;
508 
509       snprintf(fmt, sizeof(fmt), "%%%sd", prec);
510       snprintf(buf, buflen, fmt, remailer->num);
511       break;
512 
513     case 's':
514       if (!optional)
515       {
516         snprintf(fmt, sizeof(fmt), "%%%ss", prec);
517         snprintf(buf, buflen, fmt, NONULL(remailer->shortname));
518       }
519       else if (!remailer->shortname)
520         optional = false;
521       break;
522 
523     default:
524       *buf = '\0';
525   }
526 
527   if (optional)
528   {
529     mutt_expando_format(buf, buflen, col, cols, if_str, mix_format_str, data,
530                         MUTT_FORMAT_NO_FLAGS);
531   }
532   else if (flags & MUTT_FORMAT_OPTIONAL)
533   {
534     mutt_expando_format(buf, buflen, col, cols, else_str, mix_format_str, data,
535                         MUTT_FORMAT_NO_FLAGS);
536   }
537 
538   /* We return the format string, unchanged */
539   return src;
540 }
541 
542 /**
543  * mix_make_entry - Format a menu item for the mixmaster chain list - Implements Menu::make_entry() - @ingroup menu_make_entry
544  */
mix_make_entry(struct Menu * menu,char * buf,size_t buflen,int num)545 static void mix_make_entry(struct Menu *menu, char *buf, size_t buflen, int num)
546 {
547   struct Remailer **type2_list = menu->mdata;
548   const char *const c_mix_entry_format =
549       cs_subset_string(NeoMutt->sub, "mix_entry_format");
550   mutt_expando_format(buf, buflen, 0, menu->win->state.cols,
551                       NONULL(c_mix_entry_format), mix_format_str,
552                       (intptr_t) type2_list[num], MUTT_FORMAT_ARROWCURSOR);
553 }
554 
555 /**
556  * mix_chain_add - Add a host to the chain
557  * @param[in]  chain      Chain to add to
558  * @param[in]  s          Hostname
559  * @param[out] type2_list Remailer List
560  * @retval  0 Success
561  * @retval -1 Error
562  */
mix_chain_add(struct MixChain * chain,const char * s,struct Remailer ** type2_list)563 static int mix_chain_add(struct MixChain *chain, const char *s, struct Remailer **type2_list)
564 {
565   int i;
566 
567   if (chain->cl >= MAX_MIXES)
568     return -1;
569 
570   if (mutt_str_equal(s, "0") || mutt_istr_equal(s, "<random>"))
571   {
572     chain->ch[chain->cl++] = 0;
573     return 0;
574   }
575 
576   for (i = 0; type2_list[i]; i++)
577   {
578     if (mutt_istr_equal(s, type2_list[i]->shortname))
579     {
580       chain->ch[chain->cl++] = i;
581       return 0;
582     }
583   }
584 
585   /* replace unknown remailers by <random> */
586 
587   if (!type2_list[i])
588     chain->ch[chain->cl++] = 0;
589 
590   return 0;
591 }
592 
593 /**
594  * remailer_config_observer - Notification that a Config Variable has changed - Implements ::observer_t - @ingroup observer_api
595  */
remailer_config_observer(struct NotifyCallback * nc)596 static int remailer_config_observer(struct NotifyCallback *nc)
597 {
598   if ((nc->event_type != NT_CONFIG) || !nc->global_data || !nc->event_data)
599     return -1;
600 
601   struct EventConfig *ev_c = nc->event_data;
602   if (!mutt_str_equal(ev_c->name, "status_on_top"))
603     return 0;
604 
605   struct MuttWindow *dlg = nc->global_data;
606   window_status_on_top(dlg, NeoMutt->sub);
607   mutt_debug(LL_DEBUG5, "config done\n");
608   return 0;
609 }
610 
611 /**
612  * remailer_window_observer - Notification that a Window has changed - Implements ::observer_t - @ingroup observer_api
613  */
remailer_window_observer(struct NotifyCallback * nc)614 static int remailer_window_observer(struct NotifyCallback *nc)
615 {
616   if ((nc->event_type != NT_WINDOW) || !nc->global_data || !nc->event_data)
617     return -1;
618 
619   if (nc->event_subtype != NT_WINDOW_DELETE)
620     return 0;
621 
622   struct MuttWindow *dlg = nc->global_data;
623   struct EventWindow *ev_w = nc->event_data;
624   if (ev_w->win != dlg)
625     return 0;
626 
627   notify_observer_remove(NeoMutt->notify, remailer_config_observer, dlg);
628   notify_observer_remove(dlg->notify, remailer_window_observer, dlg);
629   mutt_debug(LL_DEBUG5, "window delete done\n");
630 
631   return 0;
632 }
633 
634 /**
635  * dlg_select_mixmaster_chain - Create a Mixmaster chain
636  * @param chainhead List of chain links
637  *
638  * Ask the user to select Mixmaster hosts to create a chain.
639  */
dlg_select_mixmaster_chain(struct ListHead * chainhead)640 void dlg_select_mixmaster_chain(struct ListHead *chainhead)
641 {
642   int c_cur = 0, c_old = 0;
643   bool c_redraw = true;
644   size_t ttll = 0;
645 
646   struct Coord *coords = NULL;
647 
648   struct Menu *menu = NULL;
649   bool loop = true;
650 
651   char *t = NULL;
652 
653   struct Remailer **type2_list = mix_type2_list(&ttll);
654   if (!type2_list)
655   {
656     mutt_error(_("Can't get mixmaster's type2.list"));
657     return;
658   }
659 
660   struct MixChain *chain = mutt_mem_calloc(1, sizeof(struct MixChain));
661 
662   struct ListNode *p = NULL;
663   STAILQ_FOREACH(p, chainhead, entries)
664   {
665     mix_chain_add(chain, p->data, type2_list);
666   }
667   mutt_list_free(chainhead);
668 
669   /* safety check */
670   for (int i = 0; i < chain->cl; i++)
671   {
672     if (chain->ch[i] >= ttll)
673       chain->ch[i] = 0;
674   }
675 
676   struct MuttWindow *dlg =
677       mutt_window_new(WT_DLG_REMAILER, MUTT_WIN_ORIENT_VERTICAL, MUTT_WIN_SIZE_MAXIMISE,
678                       MUTT_WIN_SIZE_UNLIMITED, MUTT_WIN_SIZE_UNLIMITED);
679   dlg->help_menu = MENU_MIX;
680   dlg->help_data = RemailerHelp;
681 
682   struct MuttWindow *win_hosts = menu_new_window(MENU_MIX, NeoMutt->sub);
683   win_hosts->focus = win_hosts;
684 
685   struct MuttWindow *win_chain =
686       mutt_window_new(WT_CUSTOM, MUTT_WIN_ORIENT_VERTICAL, MUTT_WIN_SIZE_FIXED,
687                       MUTT_WIN_SIZE_UNLIMITED, 4);
688 
689   struct MuttWindow *win_cbar = sbar_new();
690   struct MuttWindow *win_rbar = sbar_new();
691 
692   const bool c_status_on_top = cs_subset_bool(NeoMutt->sub, "status_on_top");
693   if (c_status_on_top)
694   {
695     mutt_window_add_child(dlg, win_rbar);
696     mutt_window_add_child(dlg, win_hosts);
697     mutt_window_add_child(dlg, win_cbar);
698     mutt_window_add_child(dlg, win_chain);
699   }
700   else
701   {
702     mutt_window_add_child(dlg, win_hosts);
703     mutt_window_add_child(dlg, win_cbar);
704     mutt_window_add_child(dlg, win_chain);
705     mutt_window_add_child(dlg, win_rbar);
706   }
707   sbar_set_title(win_rbar, _("Select a remailer chain"));
708 
709   mix_screen_coordinates(dlg, type2_list, &coords, chain, 0);
710 
711   menu = win_hosts->wdata;
712   menu->max = ttll;
713   menu->make_entry = mix_make_entry;
714   menu->tag = NULL;
715   menu->mdata = type2_list;
716 
717   notify_observer_add(NeoMutt->notify, NT_CONFIG, remailer_config_observer, dlg);
718   notify_observer_add(dlg->notify, NT_WINDOW, remailer_window_observer, dlg);
719   dialog_push(dlg);
720 
721   while (loop)
722   {
723     if (c_redraw)
724     {
725       mix_redraw_head(win_cbar, chain);
726       mix_redraw_chain(win_chain, type2_list, coords, chain, c_cur);
727       c_redraw = false;
728     }
729     else if (c_cur != c_old)
730     {
731       mix_redraw_ce(win_chain, type2_list, coords, chain, c_old, false);
732       mix_redraw_ce(win_chain, type2_list, coords, chain, c_cur, true);
733     }
734 
735     c_old = c_cur;
736 
737     window_redraw(dlg);
738     const int op = menu_loop(menu);
739     switch (op)
740     {
741       case OP_REDRAW:
742       {
743         mix_redraw_head(win_cbar, chain);
744         mix_screen_coordinates(menu->win, type2_list, &coords, chain, 0);
745         mix_redraw_chain(win_chain, type2_list, coords, chain, c_cur);
746         break;
747       }
748 
749       case OP_EXIT:
750       {
751         chain->cl = 0;
752         loop = false;
753         break;
754       }
755 
756       case OP_MIX_USE:
757       {
758         if (!chain->cl)
759         {
760           chain->cl++;
761           chain->ch[0] = menu_get_index(menu);
762           mix_screen_coordinates(menu->win, type2_list, &coords, chain, c_cur);
763           c_redraw = true;
764         }
765 
766         if (chain->cl && chain->ch[chain->cl - 1] &&
767             (type2_list[chain->ch[chain->cl - 1]]->caps & MIX_CAP_MIDDLEMAN))
768         {
769           mutt_error(
770               _("Error: %s can't be used as the final remailer of a chain"),
771               type2_list[chain->ch[chain->cl - 1]]->shortname);
772         }
773         else
774         {
775           loop = false;
776         }
777         break;
778       }
779 
780       case OP_GENERIC_SELECT_ENTRY:
781       case OP_MIX_APPEND:
782       {
783         if ((chain->cl < MAX_MIXES) && (c_cur < chain->cl))
784           c_cur++;
785       }
786       /* fallthrough */
787       case OP_MIX_INSERT:
788       {
789         if (chain->cl < MAX_MIXES)
790         {
791           chain->cl++;
792           for (int i = chain->cl - 1; i > c_cur; i--)
793             chain->ch[i] = chain->ch[i - 1];
794 
795           chain->ch[c_cur] = menu_get_index(menu);
796           mix_screen_coordinates(menu->win, type2_list, &coords, chain, c_cur);
797           c_redraw = true;
798         }
799         else
800         {
801           /* L10N The '%d' here hard-coded to 19 */
802           mutt_error(_("Mixmaster chains are limited to %d elements"), MAX_MIXES);
803         }
804 
805         break;
806       }
807 
808       case OP_MIX_DELETE:
809       {
810         if (chain->cl)
811         {
812           chain->cl--;
813 
814           for (int i = c_cur; i < chain->cl; i++)
815             chain->ch[i] = chain->ch[i + 1];
816 
817           if ((c_cur == chain->cl) && c_cur)
818             c_cur--;
819 
820           mix_screen_coordinates(menu->win, type2_list, &coords, chain, c_cur);
821           c_redraw = true;
822         }
823         else
824         {
825           mutt_error(_("The remailer chain is already empty"));
826         }
827         break;
828       }
829 
830       case OP_MIX_CHAIN_PREV:
831       {
832         if (c_cur)
833           c_cur--;
834         else
835           mutt_error(_("You already have the first chain element selected"));
836 
837         break;
838       }
839 
840       case OP_MIX_CHAIN_NEXT:
841       {
842         if (chain->cl && (c_cur < chain->cl - 1))
843           c_cur++;
844         else
845           mutt_error(_("You already have the last chain element selected"));
846 
847         break;
848       }
849     }
850   }
851 
852   dialog_pop();
853   mutt_window_free(&dlg);
854 
855   /* construct the remailer list */
856 
857   if (chain->cl)
858   {
859     for (int i = 0; i < chain->cl; i++)
860     {
861       const int j = chain->ch[i];
862       if (j != 0)
863         t = type2_list[j]->shortname;
864       else
865         t = "*";
866 
867       mutt_list_insert_tail(chainhead, mutt_str_dup(t));
868     }
869   }
870 
871   mix_type2_list_free(&type2_list);
872   FREE(&coords);
873   FREE(&chain);
874 }
875 
876 /**
877  * mix_check_message - Safety-check the message before passing it to mixmaster
878  * @param e Email
879  * @retval  0 Success
880  * @retval -1 Error
881  */
mix_check_message(struct Email * e)882 int mix_check_message(struct Email *e)
883 {
884   bool need_hostname = false;
885 
886   if (!TAILQ_EMPTY(&e->env->cc) || !TAILQ_EMPTY(&e->env->bcc))
887   {
888     mutt_error(_("Mixmaster doesn't accept Cc or Bcc headers"));
889     return -1;
890   }
891 
892   /* When using mixmaster, we MUST qualify any addresses since
893    * the message will be delivered through remote systems.
894    *
895    * use_domain won't be respected at this point, hidden_host will.
896    */
897 
898   struct Address *a = NULL;
899   TAILQ_FOREACH(a, &e->env->to, entries)
900   {
901     if (!a->group && !strchr(a->mailbox, '@'))
902     {
903       need_hostname = true;
904       break;
905     }
906   }
907 
908   if (need_hostname)
909   {
910     const char *fqdn = mutt_fqdn(true, NeoMutt->sub);
911     if (!fqdn)
912     {
913       mutt_error(_("Please set the hostname variable to a proper value when "
914                    "using mixmaster"));
915       return -1;
916     }
917 
918     /* Cc and Bcc are empty at this point. */
919     mutt_addrlist_qualify(&e->env->to, fqdn);
920     mutt_addrlist_qualify(&e->env->reply_to, fqdn);
921     mutt_addrlist_qualify(&e->env->mail_followup_to, fqdn);
922   }
923 
924   return 0;
925 }
926 
927 /**
928  * mix_send_message - Send an email via Mixmaster
929  * @param chain    String list of hosts
930  * @param tempfile Temporary file containing email
931  * @retval -1  Error
932  * @retval >=0 Success (Mixmaster's return code)
933  */
mix_send_message(struct ListHead * chain,const char * tempfile)934 int mix_send_message(struct ListHead *chain, const char *tempfile)
935 {
936   int i = 0;
937   struct Buffer *cmd = mutt_buffer_pool_get();
938   struct Buffer *cd_quoted = mutt_buffer_pool_get();
939 
940   const char *const c_mixmaster = cs_subset_string(NeoMutt->sub, "mixmaster");
941   mutt_buffer_printf(cmd, "cat %s | %s -m ", tempfile, c_mixmaster);
942 
943   struct ListNode *np = NULL;
944   STAILQ_FOREACH(np, chain, entries)
945   {
946     mutt_buffer_addstr(cmd, (i != 0) ? "," : " -l ");
947     mutt_buffer_quote_filename(cd_quoted, (char *) np->data, true);
948     mutt_buffer_addstr(cmd, mutt_buffer_string(cd_quoted));
949     i = 1;
950   }
951 
952   mutt_endwin();
953 
954   i = mutt_system(cmd->data);
955   if (i != 0)
956   {
957     fprintf(stderr, _("Error sending message, child exited %d\n"), i);
958     if (!OptNoCurses)
959     {
960       mutt_any_key_to_continue(NULL);
961       mutt_error(_("Error sending message"));
962     }
963   }
964 
965   mutt_buffer_pool_release(&cmd);
966   mutt_buffer_pool_release(&cd_quoted);
967   unlink(tempfile);
968   return i;
969 }
970