1 /* GNU Mailutils -- a suite of utilities for electronic mail
2 Copyright (C) 2003-2021 Free Software Foundation, Inc.
3
4 GNU Mailutils is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 3, or (at your option)
7 any later version.
8
9 GNU Mailutils is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <mh.h>
18
19 typedef int (*handler_fp) (struct mh_whatnow_env *wh,
20 int argc, char **argv,
21 int *status);
22
23 /* ********************* Auxiliary functions *********************** */
24 /* Auxiliary function for option comparisons */
25 static char
strnulcmp(const char * str,const char * pattern)26 strnulcmp (const char *str, const char *pattern)
27 {
28 return strncmp (str, pattern, strlen (str));
29 }
30
31 /* Dispatcher functions */
32
33 struct action_tab {
34 char *name;
35 handler_fp fp;
36 };
37
38 static handler_fp
func(struct action_tab * p,const char * name)39 func (struct action_tab *p, const char *name)
40 {
41 int len;
42
43 if (!name)
44 return func (p, "help");
45
46 len = strlen (name);
47 for (; p->name; p++)
48 {
49 int min = strlen (p->name);
50 if (min > len)
51 min = len;
52 if (strncmp (p->name, name, min) == 0)
53 return p->fp;
54 }
55
56 mu_error (_("%s is unknown. Hit <CR> for help"), name);
57 return NULL;
58 }
59
60 struct helpdata {
61 char *name;
62 char *descr;
63 };
64
65 /* Functions for printing help information */
66
67 #define OPT_DOC_COL 29 /* column in which option text starts */
68 #define RMARGIN 79 /* right margin used for wrapping */
69
70 static int
print_short(const char * str)71 print_short (const char *str)
72 {
73 int n;
74 const char *s;
75
76 for (n = 0; *str; str++, n++)
77 {
78 switch (*str)
79 {
80 case '+':
81 putchar ('+');
82 s = _("FOLDER");
83 n += printf ("%s", s);
84 break;
85
86 case '<':
87 switch (str[1])
88 {
89 case '>':
90 s = _("SWITCHES");
91 n += printf ("%s", s) - 1;
92 str++;
93 break;
94
95 case 'e':
96 s = _("EDITOR");
97 n += printf ("%s", s) - 1;
98 str++;
99 break;
100
101 default:
102 putchar (*str);
103 }
104 break;
105
106 default:
107 putchar (*str);
108 }
109 }
110 return n;
111 }
112
113 static void
print_descr(int n,const char * s)114 print_descr (int n, const char *s)
115 {
116 do
117 {
118 const char *p;
119 const char *space = NULL;
120
121 for (; n < OPT_DOC_COL; n++)
122 putchar (' ');
123
124 for (p = s; *p && p < s + (RMARGIN - OPT_DOC_COL); p++)
125 if (mu_isspace (*p))
126 space = p;
127
128 if (!space || p < s + (RMARGIN - OPT_DOC_COL))
129 {
130 printf ("%s", s);
131 s += strlen (s);
132 }
133 else
134 {
135 for (; s < space; s++)
136 putchar (*s);
137 for (; *s && mu_isspace (*s); s++)
138 ;
139 }
140 putchar ('\n');
141 n = 1;
142 }
143 while (*s);
144 }
145
146 static int
_help(struct helpdata * helpdata,char * argname)147 _help (struct helpdata *helpdata, char *argname)
148 {
149 struct helpdata *p;
150
151 printf ("%s\n", _("Options are:"));
152 if (argname == NULL || argname[0] == '?')
153 {
154 /* Short version */
155 for (p = helpdata; p->name; p++)
156 {
157 printf (" ");
158 print_short (p->name);
159 putchar ('\n');
160 }
161 }
162 else
163 {
164 for (p = helpdata; p->name; p++)
165 {
166 int n;
167
168 n = printf (" ");
169 n += print_short (p->name);
170
171 print_descr (n+1, _(p->descr));
172 }
173 }
174 return 0;
175 }
176
177 /* Display the contents of the given file on the terminal */
178 static void
display_file(const char * name)179 display_file (const char *name)
180 {
181 const char *pager = mh_global_profile_get ("moreproc", getenv ("PAGER"));
182
183 if (pager)
184 mh_spawnp (pager, name);
185 else
186 {
187 mu_stream_t stream;
188 int rc;
189 size_t n;
190 char buffer[512];
191
192 rc = mu_file_stream_create (&stream, name, MU_STREAM_READ);
193 if (rc)
194 {
195 mu_error ("mu_file_stream_create: %s", mu_strerror (rc));
196 return;
197 }
198
199 mu_stream_seek (stream, 0, MU_SEEK_SET, NULL);
200 while (mu_stream_read (stream, buffer, sizeof buffer - 1, &n) == 0
201 && n != 0)
202 {
203 buffer[n] = '\0';
204 printf ("%s", buffer);
205 }
206 mu_stream_destroy (&stream);
207 }
208 }
209
210 static int
check_exit_status(const char * progname,int status)211 check_exit_status (const char *progname, int status)
212 {
213 if (WIFEXITED (status))
214 {
215 if (WEXITSTATUS (status))
216 {
217 mu_error (_("command `%s' exited with status %d"),
218 progname, WEXITSTATUS(status));
219 return 1;
220 }
221 return 0;
222 }
223 else if (WIFSIGNALED (status))
224 mu_error (_("command `%s' terminated on signal %d"),
225 progname, WTERMSIG (status));
226 else
227 mu_error (_("command `%s' terminated abnormally"), progname);
228 return 1;
229 }
230
231 static int
invoke(const char * compname,const char * defval,int argc,char ** argv,const char * extra0,const char * extra1)232 invoke (const char *compname, const char *defval, int argc, char **argv,
233 const char *extra0, const char *extra1)
234 {
235 int i, rc;
236 char **xargv;
237 const char *progname;
238 int status;
239
240 progname = mh_global_profile_get (compname, defval);
241 if (!progname)
242 return -1;
243
244 xargv = calloc (argc+3, sizeof (*xargv));
245 if (!xargv)
246 {
247 mh_err_memory (0);
248 return -1;
249 }
250
251 xargv[0] = (char*) progname;
252 for (i = 1; i < argc; i++)
253 xargv[i] = argv[i];
254 if (extra0)
255 xargv[i++] = (char*) extra0;
256 if (extra1)
257 xargv[i++] = (char*) extra1;
258 xargv[i++] = NULL;
259 rc = mu_spawnvp (xargv[0], xargv, &status);
260 free (xargv);
261 return rc ? rc : check_exit_status (progname, status);
262 }
263
264 struct anno_data
265 {
266 const char *field;
267 const char *value;
268 int date;
269 };
270
271 static int
anno(void * item,void * data)272 anno (void *item, void *data)
273 {
274 struct anno_data *d = item;
275 mh_annotate (item, d->field, d->value, d->date);
276 return 0;
277 }
278
279 static void
annotate(struct mh_whatnow_env * wh)280 annotate (struct mh_whatnow_env *wh)
281 {
282 mu_message_t msg;
283 mu_address_t addr = NULL;
284 size_t i, count;
285
286 if (!wh->anno_field || !wh->anno_list)
287 return;
288
289 msg = mh_file_to_message (NULL, wh->file);
290 if (!msg)
291 return;
292
293 mh_expand_aliases (msg, &addr, NULL, NULL);
294 mu_address_get_count (addr, &count);
295 for (i = 1; i <= count; i++)
296 {
297 mu_address_t subaddr;
298
299 if (mu_address_get_nth (addr, i, &subaddr) == 0)
300 {
301 struct anno_data d;
302
303 d.field = wh->anno_field;
304 d.date = i == 1;
305 if (mu_address_sget_printable (subaddr, &d.value) == 0)
306 mu_list_foreach (wh->anno_list, anno, &d);
307 mu_address_destroy (&subaddr);
308 }
309 }
310 mu_address_destroy (&addr);
311 mu_message_destroy (&msg, NULL);
312 }
313
314
315 /* ************************ Shell Function ************************* */
316
317 static int
_whatnow(struct mh_whatnow_env * wh,struct action_tab * tab)318 _whatnow (struct mh_whatnow_env *wh, struct action_tab *tab)
319 {
320 int rc, status = 0;
321 char *line = NULL;
322 size_t size = 0;
323 struct mu_wordsplit ws;
324 int wsflags = MU_WRDSF_DEFFLAGS|MU_WRDSF_COMMENT;
325
326 wh->reedit = 0;
327 wh->last_ed = NULL;
328
329 do
330 {
331 size_t n;
332 handler_fp fun;
333
334 mu_printf ("%s ", wh->prompt);
335 mu_stream_flush (mu_strout);
336 rc = mu_stream_getline (mu_strin, &line, &size, &n);
337 if (rc)
338 {
339 mu_error (_("cannot read input stream: %s"), mu_strerror (rc));
340 status = 1;
341 break;
342 }
343 if (n == 0)
344 break;
345
346 ws.ws_comment = "#";
347 rc = mu_wordsplit (line, &ws, wsflags);
348 if (rc)
349 {
350 mu_error (_("cannot split line `%s': %s"), line,
351 mu_wordsplit_strerror (&ws));
352 status = 1;
353 break;
354 }
355 wsflags |= MU_WRDSF_REUSE;
356 fun = func (tab, ws.ws_wordv[0]);
357 if (fun)
358 rc = fun (wh, ws.ws_wordc, ws.ws_wordv, &status);
359 else
360 rc = 0;
361 }
362 while (rc == 0);
363 if (wsflags & MU_WRDSF_REUSE)
364 mu_wordsplit_free (&ws);
365 free (wh->last_ed);
366 wh->last_ed = NULL;
367 free (line);
368 return status;
369 }
370
371
372 /* ************************** Actions ****************************** */
373
374 /* Display action */
375 static int
display(struct mh_whatnow_env * wh,int argc,char ** argv,int * status)376 display (struct mh_whatnow_env *wh, int argc, char **argv, int *status)
377 {
378 if (!wh->msg)
379 mu_error (_("no alternate message to display"));
380 else
381 display_file (wh->msg);
382 return 0;
383 }
384
385 /* Edit action */
386 static int
edit(struct mh_whatnow_env * wh,int argc,char ** argv,int * whs)387 edit (struct mh_whatnow_env *wh, int argc, char **argv, int *whs)
388 {
389 char const *ed = wh->last_ed ? wh->last_ed : wh->editor;
390 int i, rc, status;
391 char *p;
392
393 if (wh->reedit)
394 {
395 if (argc > 1)
396 ed = argv[1];
397 else
398 {
399 char *name;
400 char const *newed;
401 mu_asprintf (&name, "%s-next", wh->editor);
402 newed = mh_global_profile_get (name, NULL);
403 free (name);
404 if (newed)
405 ed = newed;
406 }
407 }
408 else if (argc > 1)
409 ed = argv[1];
410
411 if (argc > 1)
412 {
413 char **xargv;
414
415 xargv = mu_calloc (argc+2, sizeof (*xargv));
416 xargv[0] = (char *)ed;
417 for (i = 1; i + 1 < argc; i++)
418 xargv[i] = argv[i+1];
419 xargv[i++] = wh->file;
420 xargv[i] = NULL;
421 rc = mu_spawnvp (xargv[0], xargv, &status);
422 free (xargv);
423 }
424 else
425 {
426 struct mu_wordsplit ws;
427 extern char **environ;
428 ws.ws_env = (char const **)environ;
429 if (mu_wordsplit (ed, &ws,
430 MU_WRDSF_QUOTE
431 | MU_WRDSF_SQUEEZE_DELIMS
432 | MU_WRDSF_ENV
433 | MU_WRDSF_NOCMD))
434 {
435 mu_error (_("cannot split line `%s': %s"), ed,
436 mu_wordsplit_strerror (&ws));
437 rc = MU_ERR_FAILURE;
438 }
439 else
440 {
441 char *xargv[2];
442 xargv[0] = wh->file;
443 xargv[1] = NULL;
444 if (mu_wordsplit_append (&ws, 1, xargv))
445 {
446 mu_error (_("cannot append arguments: %s"),
447 mu_wordsplit_strerror (&ws));
448 rc = ENOMEM;
449 }
450 else
451 rc = mu_spawnvp (ws.ws_wordv[0], ws.ws_wordv, &status);
452 mu_wordsplit_free (&ws);
453 }
454 }
455
456 if (rc || check_exit_status (ed, status))
457 {
458 if (wh->file)
459 mu_error (_("problems with edit--%s preserved"), wh->file);
460 else
461 mu_error (_("problems with edit"));
462 }
463
464 p = mu_strdup (ed);
465 free (wh->last_ed);
466 wh->last_ed = p;
467 wh->reedit = 1;
468
469 return 0;
470 }
471
472 /* List action */
473 static int
list(struct mh_whatnow_env * wh,int argc,char ** argv,int * status)474 list (struct mh_whatnow_env *wh, int argc, char **argv, int *status)
475 {
476 if (!wh->file)
477 mu_error (_("no draft file to display"));
478 else
479 display_file (wh->file);
480 return 0;
481 }
482
483 /* Push action */
484 static int
push(struct mh_whatnow_env * wh,int argc,char ** argv,int * status)485 push (struct mh_whatnow_env *wh, int argc, char **argv, int *status)
486 {
487 if (invoke ("sendproc", MHBINDIR "/send", argc, argv, "-push", wh->file) == 0)
488 annotate (wh);
489 return 0;
490 }
491
492 /* Quit action */
493 static int
quit(struct mh_whatnow_env * wh,int argc,char ** argv,int * status)494 quit (struct mh_whatnow_env *wh, int argc, char **argv, int *status)
495 {
496 *status = 0;
497
498 if (wh->draftfile)
499 {
500 if (argc == 2 && strnulcmp (argv[1], "-delete") == 0)
501 unlink (wh->draftfile);
502 else
503 {
504 mu_printf (_("draft left on \"%s\"."), wh->draftfile);
505 if (strcmp (wh->file, wh->draftfile))
506 {
507 int rc;
508 rc = mu_rename_file (wh->file, wh->draftfile, MU_COPY_OVERWRITE);
509 if (rc)
510 mu_error (_("can't rename %s to %s: %s"),
511 wh->file, wh->draftfile, mu_strerror (rc));
512 }
513 }
514 }
515 mu_printf ("\n");
516 return 1;
517 }
518
519 /* Refile action */
520 static int
refile(struct mh_whatnow_env * wh,int argc,char ** argv,int * status)521 refile (struct mh_whatnow_env *wh, int argc, char **argv, int *status)
522 {
523 invoke ("fileproc", MHBINDIR "/refile", argc, argv, "-file", wh->file);
524 return 0;
525 }
526
527 /* Send action */
528 static int
call_send(struct mh_whatnow_env * wh,int argc,char ** argv,int * status)529 call_send (struct mh_whatnow_env *wh, int argc, char **argv, int *status)
530 {
531 if (invoke ("sendproc", MHBINDIR "/send", argc, argv, wh->file, NULL) == 0)
532 {
533 annotate (wh);
534 return 1;
535 }
536 return 0;
537 }
538
539 /* Whom action */
540 static int
whom(struct mh_whatnow_env * wh,int argc,char ** argv,int * status)541 whom (struct mh_whatnow_env *wh, int argc, char **argv, int *status)
542 {
543 if (!wh->file)
544 mu_error (_("no draft file to display"));
545 else
546 mh_whom_file (wh->file,
547 (argc == 2
548 && (strcmp (argv[1], "-check") == 0
549 || strcmp (argv[1], "--check") == 0)));
550 return 0;
551 }
552
553 /* Help table for whatnow */
554 static struct helpdata whatnow_helptab[] = {
555 { "display [<>]",
556 N_("List the message being distributed/replied-to on the terminal.") },
557 { "edit [<e <>]",
558 N_("Edit the message. If EDITOR is omitted use the one that was used on"
559 " the preceding round unless the profile entry \"LASTEDITOR-next\""
560 " names an alternate editor.") },
561 { "list [<>]",
562 N_("List the draft on the terminal.") },
563 { "push [<>]",
564 N_("Send the message in the background.") },
565 { "quit [-delete]",
566 N_("Terminate the session. Preserve the draft, unless -delete flag is given.") },
567 { "refile [<>] +",
568 N_("Refile the draft into the given FOLDER.") },
569 { "send [-watch] [<>]",
570 N_("Send the message. The -watch flag causes the delivery process to be "
571 "monitored. SWITCHES are passed to send program verbatim.") },
572 { "whom [-check] [<>]",
573 N_("List the addresses and verify that they are acceptable to the "
574 "transport service.") },
575 { NULL },
576 };
577
578 /* Help action for whatnow shell */
579 static int
whatnow_help(struct mh_whatnow_env * wh,int argc,char ** argv,int * status)580 whatnow_help (struct mh_whatnow_env *wh, int argc, char **argv, int *status)
581 {
582 return _help (whatnow_helptab, argv[0]);
583 }
584
585 /* Actions specific for the ``disposition'' shell */
586
587 /* Help table for ``disposition'' shell */
588 static struct helpdata disposition_helptab[] = {
589 { "quit", N_("Terminate the session. Preserve the draft.") },
590 { "replace", N_("Replace the draft with the newly created one") },
591 { "use", N_("Use this draft") },
592 { "list", N_("List the draft on the terminal.") },
593 { "refile [<>] +", N_("Refile the draft into the given FOLDER.") },
594 { NULL },
595 };
596
597 /* Help action */
598 static int
disp_help(struct mh_whatnow_env * wh,int argc,char ** argv,int * status)599 disp_help (struct mh_whatnow_env *wh, int argc, char **argv, int *status)
600 {
601 return _help (disposition_helptab, argv[0]);
602 }
603
604 /* Use action */
605 static int
use(struct mh_whatnow_env * wh,int argc,char ** argv,int * status)606 use (struct mh_whatnow_env *wh, int argc, char **argv, int *status)
607 {
608 *status = DISP_USE;
609 return 1;
610 }
611
612 /* Replace action */
613 static int
replace(struct mh_whatnow_env * wh,int argc,char ** argv,int * status)614 replace (struct mh_whatnow_env *wh, int argc, char **argv, int *status)
615 {
616 *status = DISP_REPLACE;
617 return 1;
618 }
619
620
621 /* *********************** Interfaces ****************************** */
622
623 /* Whatnow shell */
624 static struct action_tab whatnow_tab[] = {
625 { "help", whatnow_help },
626 { "?", whatnow_help },
627 { "display", display },
628 { "edit", edit },
629 { "list", list },
630 { "push", push },
631 { "quit", quit },
632 { "refile", refile },
633 { "send", call_send },
634 { "whom", whom },
635 { NULL }
636 };
637
638 static void
set_default_editor(struct mh_whatnow_env * wh)639 set_default_editor (struct mh_whatnow_env *wh)
640 {
641 if (!wh->editor)
642 {
643 char *p;
644 wh->editor = mh_global_profile_get ("Editor",
645 (p = getenv ("VISUAL")) ?
646 p : (p = (getenv ("EDITOR"))) ?
647 p : "prompter");
648 }
649 }
650
651 int
mh_whatnow(struct mh_whatnow_env * wh,int initial_edit)652 mh_whatnow (struct mh_whatnow_env *wh, int initial_edit)
653 {
654 set_default_editor (wh);
655
656 if (initial_edit && wh->file)
657 {
658 char *argv[2] = { "edit", NULL };
659 int status;
660 edit (wh, 1, argv, &status);
661 }
662
663 if (!wh->prompt)
664 wh->prompt = (char*) _("What now?");
665
666 return _whatnow (wh, whatnow_tab);
667 }
668
669 int
mh_whatnowproc(struct mh_whatnow_env * wh,int initial_edit,const char * prog)670 mh_whatnowproc (struct mh_whatnow_env *wh, int initial_edit, const char *prog)
671 {
672 int rc;
673 pid_t pid;
674
675 if (wh->nowhatnowproc)
676 return 0;
677
678 if (!prog)
679 return mh_whatnow (wh, initial_edit);
680
681 pid = fork ();
682 if (pid == -1)
683 {
684 mu_diag_funcall (MU_DIAG_ERROR, "fork", NULL, errno);
685 return 1;
686 }
687
688 if (pid == 0)
689 {
690 struct mu_wordsplit ws;
691
692 if (mu_wordsplit (prog, &ws,
693 MU_WRDSF_DEFFLAGS & ~MU_WRDSF_CESCAPES))
694 {
695 mu_error (_("cannot parse command line (%s): %s"), prog,
696 mu_wordsplit_strerror (&ws));
697 _exit (127);
698 }
699
700 set_default_editor (wh);
701 mh_whatnow_env_to_environ (wh);
702 mu_close_fds (3);
703 execvp (ws.ws_wordv[0], ws.ws_wordv);
704 mu_diag_funcall (MU_DIAG_ERROR, "execvp", prog, errno);
705 _exit (127);
706 }
707
708 /* Master */
709 rc = 0;
710 while (1)
711 {
712 int status;
713
714 if (waitpid (pid, &status, 0) == (pid_t)-1)
715 {
716 if (errno == EINTR)
717 continue;
718 mu_diag_funcall (MU_DIAG_ERROR, "waitpid", prog, errno);
719 rc = 1;
720 }
721 break;
722 }
723 return rc;
724 }
725
726 /* Disposition shell */
727 static struct action_tab disp_tab[] = {
728 { "help", disp_help },
729 { "?", disp_help },
730 { "quit", quit },
731 { "replace", replace },
732 { "use", use },
733 { "list", list },
734 { "refile", refile },
735 { NULL }
736 };
737
738 int
mh_disposition(const char * filename)739 mh_disposition (const char *filename)
740 {
741 struct mh_whatnow_env wh;
742 int rc;
743 memset (&wh, 0, sizeof (wh));
744 wh.file = mu_strdup (filename);
745 wh.prompt = (char*) _("Disposition?");
746 rc = _whatnow (&wh, disp_tab);
747 free (wh.file);
748 return rc;
749 }
750
751 /* Use draft shell */
752
753 /* Help table for ``use draft'' shell */
754 static struct helpdata usedraft_helptab[] = {
755 { "no", N_("Don't use the draft.") },
756 { "yes", N_("Use the draft.") },
757 { "list", N_("List the draft on the terminal.") },
758 { NULL },
759 };
760
761 static int
usedraft_help(struct mh_whatnow_env * wh,int argc,char ** argv,int * status)762 usedraft_help (struct mh_whatnow_env *wh, int argc, char **argv, int *status)
763 {
764 return _help (usedraft_helptab, argv[0]);
765 }
766
767 static int
yes(struct mh_whatnow_env * wh,int argc,char ** argv,int * status)768 yes (struct mh_whatnow_env *wh, int argc, char **argv, int *status)
769 {
770 *status = 1;
771 return 1;
772 }
773
774 static int
no(struct mh_whatnow_env * wh,int argc,char ** argv,int * status)775 no (struct mh_whatnow_env *wh, int argc, char **argv, int *status)
776 {
777 *status = 0;
778 return 1;
779 }
780
781 static struct action_tab usedraft_tab[] = {
782 { "help", usedraft_help },
783 { "?", usedraft_help },
784 { "yes", yes },
785 { "no", no },
786 { "list", list },
787 { NULL }
788 };
789
790 int
mh_usedraft(const char * filename)791 mh_usedraft (const char *filename)
792 {
793 struct mh_whatnow_env wh;
794 int rc;
795
796 memset (&wh, 0, sizeof (wh));
797 wh.file = mu_strdup (filename);
798 mu_asprintf (&wh.prompt, _("Use \"%s\"?"), filename);
799 rc = _whatnow (&wh, usedraft_tab);
800 free (wh.prompt);
801 free (wh.file);
802 return rc;
803 }
804
805