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