1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 1999-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 /* Functions for handling escape variables */
18 
19 #include "mail.h"
20 #include <sys/stat.h>
21 
22 static void
dump_headers(mu_stream_t out,compose_env_t * env)23 dump_headers (mu_stream_t out, compose_env_t *env)
24 {
25   mu_stream_t stream = NULL;
26   int rc;
27 
28   rc = mu_header_get_streamref (env->header, &stream);
29   if (rc)
30     {
31       mu_error ("mu_header_get_streamref: %s",
32 		  mu_stream_strerror (stream, rc));
33       return;
34     }
35   mu_stream_copy (out, stream, 0, NULL);
36   mu_stream_destroy (&stream);
37 }
38 
39 static int
check_headers(mu_stream_t input,compose_env_t * env)40 check_headers (mu_stream_t input, compose_env_t *env)
41 {
42   char *p;
43 
44   mu_stream_seek (input, 0, MU_SEEK_SET, NULL);
45   switch (parse_headers (input, env))
46     {
47     case parse_headers_ok:
48       return 0;
49     case parse_headers_fatal:
50       return -1;
51     case parse_headers_error:
52       break;
53     }
54 
55   p = ml_readline (_("Edit again?"));
56   return mu_true_answer_p (p);
57 }
58 
59 static void
escape_continue(void)60 escape_continue (void)
61 {
62   mu_printf (_("(continue)\n"));
63 }
64 
65 int
escape_check_args(int argc,char ** argv,int minargs,int maxargs)66 escape_check_args (int argc, char **argv, int minargs, int maxargs)
67 {
68   char *escape = "~";
69   if (argc < minargs)
70     {
71       minargs--;
72       mailvar_get (&escape, mailvar_name_escape, mailvar_type_string, 0);
73       mu_error (ngettext ("%c%s requires at least %d argument",
74 			  "%c%s requires at least %d arguments",
75 			  minargs), escape[0], argv[0], minargs);
76       return 1;
77     }
78   if (maxargs > 1 && argc > maxargs)
79     {
80       maxargs--;
81       mailvar_get (&escape, mailvar_name_escape, mailvar_type_string, 0);
82       mu_error (ngettext ("%c%s accepts at most %d argument",
83 			  "%c%s accepts at most %d arguments",
84 			  maxargs), escape[0], argv[0], maxargs);
85       return 1;
86     }
87 
88   return 0;
89 }
90 
91 /* ~![shell-command] */
92 int
escape_shell(int argc,char ** argv,compose_env_t * env)93 escape_shell (int argc, char **argv, compose_env_t *env)
94 {
95   return mail_execute (1, argv[1], argc - 1, argv + 1);
96 }
97 
98 /* ~:[mail-command] */
99 /* ~-[mail-command] */
100 int
escape_command(int argc,char ** argv,compose_env_t * env)101 escape_command (int argc, char **argv, compose_env_t *env)
102 {
103   const struct mail_command_entry *entry;
104   int status;
105 
106   if (escape_check_args (argc, argv, 2, 2))
107     return 1;
108   if (argv[1][0] == '#')
109     return 0;
110   entry = mail_find_command (argv[1]);
111   if (!entry)
112     {
113       mu_error (_("Unknown command: %s"), argv[1]);
114       return 1;
115     }
116   if (entry->flags & (EF_FLOW | EF_SEND))
117     {
118       mu_error (_("Command not allowed in an escape sequence\n"));
119       return 1;
120     }
121 
122   status = (*entry->func) (argc - 1, argv + 1);
123   return status;
124 }
125 
126 /* ~? */
127 int
escape_help(int argc,char ** argv,compose_env_t * env MU_ARG_UNUSED)128 escape_help (int argc, char **argv, compose_env_t *env MU_ARG_UNUSED)
129 {
130   int status;
131   if (argc < 2)
132     status = mail_escape_help (NULL);
133   else
134     while (--argc)
135       status |= mail_escape_help (*++argv);
136   escape_continue ();
137   return status;
138 }
139 
140 /* ~A */
141 /* ~a */
142 int
escape_sign(int argc MU_ARG_UNUSED,char ** argv,compose_env_t * env)143 escape_sign (int argc MU_ARG_UNUSED, char **argv, compose_env_t *env)
144 {
145   char *p;
146 
147   if (mailvar_get (&p,
148 		   mu_isupper (argv[0][0])
149 		      ? mailvar_name_Sign : mailvar_name_sign,
150 		   mailvar_type_string, 1) == 0)
151     {
152       mu_stream_printf (env->compstr, "-- \n");
153       if (mu_isupper (argv[0][0]))
154 	{
155 	  char *name = util_fullpath (p);
156 	  int rc;
157 	  mu_stream_t signstr;
158 
159 	  rc = mu_file_stream_create (&signstr, name, MU_STREAM_READ);
160 	  if (rc)
161 	    mu_error (_("Cannot open %s: %s"), name, mu_strerror (rc));
162 	  else
163 	    {
164 	      mu_printf (_("Reading %s\n"), name);
165 	      mu_stream_copy (env->compstr, signstr, 0, NULL);
166 	      mu_stream_destroy (&signstr);
167 	    }
168 	}
169       else
170 	mu_stream_printf (env->compstr, "%s\n", p);
171       escape_continue ();
172     }
173   return 0;
174 }
175 
176 /* ~b[bcc-list] */
177 int
escape_bcc(int argc,char ** argv,compose_env_t * env)178 escape_bcc (int argc, char **argv, compose_env_t *env)
179 {
180   while (--argc)
181     compose_header_set (env, MU_HEADER_BCC, *++argv, COMPOSE_SINGLE_LINE);
182   return 0;
183 }
184 
185 /* ~c[cc-list] */
186 int
escape_cc(int argc,char ** argv,compose_env_t * env)187 escape_cc (int argc, char **argv, compose_env_t *env)
188 {
189   while (--argc)
190     compose_header_set (env, MU_HEADER_CC, *++argv, COMPOSE_SINGLE_LINE);
191   return 0;
192 }
193 
194 static int
verbose_copy(mu_stream_t dest,mu_stream_t src,const char * filename,mu_off_t * psize)195 verbose_copy (mu_stream_t dest, mu_stream_t src, const char *filename,
196 	      mu_off_t *psize)
197 {
198   int rc;
199   char *buf = NULL;
200   size_t size = 0, n;
201   size_t lines;
202   mu_off_t total;
203 
204   total = lines = 0;
205 
206   while ((rc = mu_stream_getline (src, &buf, &size, &n)) == 0 && n > 0)
207     {
208       lines++;
209       rc = mu_stream_write (dest, buf, n, NULL);
210       if (rc)
211 	break;
212       total += n;
213     }
214   if (rc)
215     mu_error (_("error copying data: %s"), mu_strerror (rc));
216   mu_printf ("\"%s\" %lu/%lu\n", filename,
217 		    (unsigned long) lines, (unsigned long) total);
218   if (psize)
219     *psize = total;
220   return rc;
221 }
222 
223 /* ~d */
224 int
escape_deadletter(int argc MU_ARG_UNUSED,char ** argv MU_ARG_UNUSED,compose_env_t * env)225 escape_deadletter (int argc MU_ARG_UNUSED, char **argv MU_ARG_UNUSED,
226 		   compose_env_t *env)
227 {
228   const char *name = getenv ("DEAD");
229   mu_stream_t str;
230   int rc = mu_file_stream_create (&str, name, MU_STREAM_READ);
231   if (rc)
232     {
233       mu_error (_("Cannot open file %s: %s"), name, strerror (rc));
234       return 1;
235     }
236   verbose_copy (env->compstr, str, name, NULL);
237   mu_stream_destroy (&str);
238   return 0;
239 }
240 
241 static int
run_editor(char * ed,char * arg)242 run_editor (char *ed, char *arg)
243 {
244   char *argv[3];
245 
246   argv[0] = ed;
247   argv[1] = arg;
248   argv[2] = NULL;
249   return mail_execute (1, ed, 2, argv);
250 }
251 
252 static int
escape_run_editor(char * ed,int argc,char ** argv,compose_env_t * env)253 escape_run_editor (char *ed, int argc, char **argv, compose_env_t *env)
254 {
255   char *filename;
256   int fd;
257   mu_stream_t tempstream;
258   int rc;
259 
260   rc = mu_tempfile (NULL, 0, &fd, &filename);
261   if (rc)
262     {
263       mu_diag_funcall (MU_DIAG_ERROR, "mu_tempfile", NULL, rc);
264       return rc;
265     }
266   rc = mu_fd_stream_create (&tempstream, filename, fd, MU_STREAM_RDWR);
267   if (rc)
268     {
269       mu_diag_funcall (MU_DIAG_ERROR, "mu_fd_stream_create", filename, rc);
270       unlink (filename);
271       free (filename);
272       close (fd);
273       return rc;
274     }
275 
276   mu_stream_seek (env->compstr, 0, MU_SEEK_SET, NULL);
277   if (mailvar_is_true (mailvar_name_editheaders))
278     {
279       dump_headers (tempstream, env);
280 
281       mu_stream_copy (tempstream, env->compstr, 0, NULL);
282 
283       do
284 	{
285 	  mu_stream_destroy (&tempstream);
286 	  run_editor (ed, filename);
287 	  rc = mu_file_stream_create (&tempstream, filename, MU_STREAM_RDWR);
288 	  if (rc)
289 	    {
290 	      mu_diag_funcall (MU_DIAG_ERROR, "mu_file_stream_create",
291 			       filename, rc);
292 	      unlink (filename);
293 	      free (filename);
294 	      return rc;
295 	    }
296 	}
297       while (check_headers (tempstream, env));
298     }
299   else
300     {
301       mu_stream_copy (tempstream, env->compstr, 0, NULL);
302       mu_stream_destroy (&tempstream);
303       run_editor (ed, filename);
304       rc = mu_file_stream_create (&tempstream, filename, MU_STREAM_RDWR);
305       if (rc)
306 	{
307 	  mu_diag_funcall (MU_DIAG_ERROR, "mu_file_stream_create",
308 			   filename, rc);
309 	  unlink (filename);
310 	  free (filename);
311 	  return rc;
312 	}
313     }
314 
315   if (rc == 0)
316     {
317       mu_off_t size;
318 
319       mu_stream_seek (env->compstr, 0, MU_SEEK_SET, NULL);
320       mu_stream_copy (env->compstr, tempstream, 0, &size);
321       mu_stream_truncate (env->compstr, size);
322     }
323   mu_stream_destroy (&tempstream);
324   unlink (filename);
325   free (filename);
326 
327   mu_stream_seek (env->compstr, 0, MU_SEEK_END, NULL);
328 
329   escape_continue ();
330   return 0;
331 }
332 
333 /* ~e */
334 int
escape_editor(int argc,char ** argv,compose_env_t * env)335 escape_editor (int argc, char **argv, compose_env_t *env)
336 {
337   return escape_run_editor (getenv ("EDITOR"), argc, argv, env);
338 }
339 
340 /* ~l -- escape_list_attachments (send.c) */
341 
342 /* ~v */
343 int
escape_visual(int argc,char ** argv,compose_env_t * env)344 escape_visual (int argc, char **argv, compose_env_t *env)
345 {
346   return escape_run_editor (getenv ("VISUAL"), argc, argv, env);
347 }
348 
349 /* ~f[mesg-list] */
350 /* ~F[mesg-list] */
351 int
escape_print(int argc,char ** argv,compose_env_t * env MU_ARG_UNUSED)352 escape_print (int argc, char **argv, compose_env_t *env MU_ARG_UNUSED)
353 {
354   return mail_print (argc, argv);
355 }
356 
357 void
reread_header(compose_env_t * env,char * hdr,char * prompt)358 reread_header (compose_env_t *env, char *hdr, char *prompt)
359 {
360   char *p;
361   p = mu_strdup (compose_header_get (env, hdr, ""));
362   ml_reread (prompt, &p);
363   compose_header_set (env, hdr, p, COMPOSE_REPLACE);
364   free (p);
365 }
366 
367 /* ~h */
368 int
escape_headers(int argc,char ** argv,compose_env_t * env)369 escape_headers (int argc, char **argv, compose_env_t *env)
370 {
371   reread_header (env, MU_HEADER_TO, "To: ");
372   reread_header (env, MU_HEADER_CC, "Cc: ");
373   reread_header (env, MU_HEADER_BCC, "Bcc: ");
374   reread_header (env, MU_HEADER_SUBJECT, "Subject: ");
375   escape_continue ();
376   return 0;
377 }
378 
379 /* ~i[var-name] */
380 int
escape_insert(int argc,char ** argv,compose_env_t * env)381 escape_insert (int argc, char **argv, compose_env_t *env)
382 {
383   if (escape_check_args (argc, argv, 2, 2))
384     return 1;
385   mailvar_variable_format (env->compstr, mailvar_find_variable (argv[1], 0),
386 			   NULL);
387   return 0;
388 }
389 
390 /* ~m[mesg-list] */
391 /* ~M[mesg-list] */
392 
393 struct quote_closure
394 {
395   mu_stream_t str;
396   int islower;
397 };
398 
399 int
quote0(msgset_t * mspec,mu_message_t mesg,void * data)400 quote0 (msgset_t *mspec, mu_message_t mesg, void *data)
401 {
402   struct quote_closure *clos = data;
403   int rc;
404   mu_stream_t stream;
405   char *prefix = "\t";
406   mu_stream_t flt;
407   char *argv[3];
408 
409   mu_printf (_("Interpolating: %lu\n"), (unsigned long) msgset_msgno (mspec));
410 
411   mailvar_get (&prefix, mailvar_name_indentprefix, mailvar_type_string, 0);
412 
413   argv[0] = "INLINE-COMMENT";
414   argv[1] = prefix;
415   argv[2] = NULL;
416   rc = mu_filter_create_args (&flt, clos->str, "INLINE-COMMENT",
417 			      2, (const char**) argv,
418 			      MU_FILTER_ENCODE,
419 			      MU_STREAM_WRITE);
420   if (rc)
421     {
422       mu_diag_funcall (MU_DIAG_ERROR, "mu_filter_create_args", NULL, rc);
423       return rc;
424     }
425 
426   if (clos->islower)
427     {
428       mu_header_t hdr;
429       mu_body_t body;
430       mu_iterator_t itr;
431 
432       mu_message_get_header (mesg, &hdr);
433       mu_header_get_iterator (hdr, &itr);
434       for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
435 	   mu_iterator_next (itr))
436 	{
437 	  const char *name, *value;
438 
439 	  if (mu_iterator_current_kv (itr, (const void **)&name,
440 				      (void**)&value) == 0 &&
441 	      mail_header_is_visible (name))
442 	    mu_stream_printf (flt, "%s: %s\n", name, value);
443 	}
444       mu_iterator_destroy (&itr);
445       mu_stream_write (flt, "\n", 1, NULL);
446       mu_message_get_body (mesg, &body);
447       rc = mu_body_get_streamref (body, &stream);
448     }
449   else
450     rc = mu_message_get_streamref (mesg, &stream);
451 
452   if (rc)
453     {
454       mu_error (_("get_streamref error: %s"), mu_strerror (rc));
455       return rc;
456     }
457 
458   mu_stream_copy (flt, stream, 0, NULL);
459 
460   mu_stream_destroy (&stream);
461   mu_stream_destroy (&flt);
462 
463   return 0;
464 }
465 
466 int
escape_quote(int argc,char ** argv,compose_env_t * env)467 escape_quote (int argc, char **argv, compose_env_t *env)
468 {
469   struct quote_closure clos;
470 
471   clos.str = env->compstr;
472   clos.islower = mu_islower (argv[0][0]);
473   util_foreach_msg (argc, argv, MSG_NODELETED|MSG_SILENT, quote0, &clos);
474   escape_continue ();
475   return 0;
476 }
477 
478 /* ~p */
479 int
escape_type_input(int argc,char ** argv,compose_env_t * env)480 escape_type_input (int argc, char **argv, compose_env_t *env)
481 {
482   /* FIXME: Enable paging */
483   mu_printf (_("Message contains:\n"));
484   dump_headers (mu_strout, env);
485   mu_stream_seek (env->compstr, 0, MU_SEEK_SET, NULL);
486   mu_stream_copy (mu_strout, env->compstr, 0, NULL);
487   escape_continue ();
488   return 0;
489 }
490 
491 /* ~r[filename] */
492 int
escape_read(int argc,char ** argv,compose_env_t * env MU_ARG_UNUSED)493 escape_read (int argc, char **argv, compose_env_t *env MU_ARG_UNUSED)
494 {
495   char *filename;
496   mu_stream_t instr;
497   int rc;
498 
499   if (escape_check_args (argc, argv, 2, 2))
500     return 1;
501   filename = util_fullpath (argv[1]);
502 
503   rc = mu_file_stream_create (&instr, filename, MU_STREAM_READ);
504   if (rc)
505     {
506       mu_error (_("Cannot open %s: %s"), filename, mu_strerror (rc));
507       free (filename);
508       return 1;
509     }
510 
511   verbose_copy (env->compstr, instr, filename, NULL);
512   mu_stream_destroy (&instr);
513   free (filename);
514   return 0;
515 }
516 
517 /* ~s[string] */
518 int
escape_subj(int argc,char ** argv,compose_env_t * env)519 escape_subj (int argc, char **argv, compose_env_t *env)
520 {
521   char *buf;
522   if (escape_check_args (argc, argv, 2, 2))
523     return 1;
524   mu_argcv_string (argc - 1, argv + 1, &buf);
525   compose_header_set (env, MU_HEADER_SUBJECT, buf, COMPOSE_REPLACE);
526   free (buf);
527   return 0;
528 }
529 
530 /* ~t[name-list] */
531 int
escape_to(int argc,char ** argv,compose_env_t * env)532 escape_to (int argc, char **argv, compose_env_t *env)
533 {
534   while (--argc)
535     compose_header_set (env, MU_HEADER_TO, *++argv, COMPOSE_SINGLE_LINE);
536   return 0;
537 }
538 
539 /* ~w[filename] */
540 int
escape_write(int argc,char ** argv,compose_env_t * env)541 escape_write (int argc, char **argv, compose_env_t *env)
542 {
543   char *filename;
544   mu_stream_t wstr;
545   int rc;
546   mu_off_t size;
547 
548   if (escape_check_args (argc, argv, 2, 2))
549     return 1;
550   filename = util_fullpath (argv[1]);
551   /* FIXME: check for existence first */
552   rc = mu_file_stream_create (&wstr, filename,
553 			      MU_STREAM_WRITE|MU_STREAM_CREAT);
554   if (rc)
555     {
556       mu_error (_("Cannot open %s for writing: %s"),
557 		  filename, mu_strerror (rc));
558       free (filename);
559       return 1;
560     }
561 
562   mu_stream_seek (env->compstr, 0, MU_SEEK_SET, NULL);
563   verbose_copy (wstr, env->compstr, filename, &size);
564   mu_stream_truncate (wstr, size);
565   mu_stream_destroy (&wstr);
566   free (filename);
567   return 0;
568 }
569 
570 /* ~|[shell-command] */
571 int
escape_pipe(int argc,char ** argv,compose_env_t * env)572 escape_pipe (int argc, char **argv, compose_env_t *env)
573 {
574   int rc, status;
575   int p[2];
576   pid_t pid;
577   int fd;
578   mu_off_t isize, osize = 0;
579   mu_stream_t tstr;
580 
581   if (pipe (p))
582     {
583       mu_error ("pipe: %s", mu_strerror (errno));
584       return 1;
585     }
586 
587   if (mu_tempfile (NULL, 0, &fd, NULL))
588     return 1;
589 
590   if ((pid = fork ()) < 0)
591     {
592       close (p[0]);
593       close (p[1]);
594       close (fd);
595       mu_error ("fork: %s", mu_strerror (errno));
596       return 1;
597     }
598   else if (pid == 0)
599     {
600       /* Child */
601 
602       /* Attach the pipes */
603       close (0);
604       dup (p[0]);
605       close (p[0]);
606       close (p[1]);
607 
608       close (1);
609       dup (fd);
610       close (fd);
611 
612       execvp (argv[1], argv + 1);
613       mu_error (_("Cannot execute `%s': %s"), argv[1], mu_strerror (errno));
614       _exit (127);
615     }
616 
617   close (p[0]);
618 
619   rc = mu_stdio_stream_create (&tstr, p[1], MU_STREAM_WRITE);
620   if (rc)
621     {
622       mu_diag_funcall (MU_DIAG_ERROR, "mu_stdio_stream_create", NULL, rc);
623       kill (pid, SIGKILL);
624       close (fd);
625       return rc;
626     }
627 
628   mu_stream_seek (env->compstr, 0, MU_SEEK_SET, NULL);
629   mu_stream_copy (tstr, env->compstr, 0, &isize);
630   mu_stream_destroy (&tstr);
631 
632   waitpid (pid, &status, 0);
633   if (!WIFEXITED (status))
634     mu_error (_("Child terminated abnormally: %d"),
635 		WEXITSTATUS (status));
636   else
637     {
638       struct stat st;
639       if (fstat (fd, &st))
640 	mu_error (_("Cannot stat output file: %s"), mu_strerror (errno));
641       else
642 	osize = st.st_size;
643     }
644 
645   mu_stream_printf (mu_strout, "\"|%s\" in: %lu ", argv[1],
646 		    (unsigned long) isize);
647   if (osize == 0)
648     mu_stream_printf (mu_strout, _("no lines out\n"));
649   else
650     {
651       mu_stream_t str;
652 
653       mu_stream_printf (mu_strout, "out: %lu\n", (unsigned long) osize);
654       rc = mu_fd_stream_create (&str, NULL, fd,
655 				MU_STREAM_RDWR | MU_STREAM_SEEK);
656       if (rc)
657 	{
658 	  mu_error (_("Cannot open composition stream: %s"),
659 		      mu_strerror (rc));
660 	  close (fd);
661 	}
662       else
663 	{
664 	  mu_stream_destroy (&env->compstr);
665 	  env->compstr = str;
666 	}
667     }
668   return 0;
669 }
670