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 #include "mail.h"
18 #include <mailutils/mime.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <fcntl.h>
22
23 static int isfilename (const char *);
24 static int msg_to_pipe (const char *cmd, mu_message_t msg);
25
26 int multipart_alternative;
27
28
29 /* Additional message headers */
30 struct add_header
31 {
32 int mode;
33 char *name;
34 char *value;
35 };
36
37 static mu_list_t add_header_list;
38
39 static int
seed_headers(void * item,void * data)40 seed_headers (void *item, void *data)
41 {
42 struct add_header *hp = item;
43 compose_env_t *env = data;
44
45 compose_header_set (env, hp->name, hp->value, hp->mode);
46 return 0;
47 }
48
49 static int
list_headers(void * item,void * data)50 list_headers (void *item, void *data)
51 {
52 struct add_header *hp = item;
53 char *name = data;
54
55 if (!name || strcmp (name, hp->name) == 0)
56 {
57 mu_printf ("%s: %s\n", hp->name, hp->value);
58 }
59 return 0;
60 }
61
62 static void
add_header(char * name,char * value,int mode)63 add_header (char *name, char *value, int mode)
64 {
65 struct add_header *hp;
66
67 if (!add_header_list)
68 {
69 int rc = mu_list_create (&add_header_list);
70 if (rc)
71 {
72 mu_error (_("Cannot create header list: %s"), mu_strerror (rc));
73 exit (1);
74 }
75 }
76
77 hp = mu_alloc (sizeof (*hp));
78 hp->mode = mode;
79 hp->name = name;
80 hp->value = value;
81 mu_list_append (add_header_list, hp);
82 }
83
84 void
send_append_header(char const * text)85 send_append_header (char const *text)
86 {
87 char *p;
88 size_t len;
89 char *name;
90
91 p = strchr (text, ':');
92 if (!p)
93 {
94 mu_error (_("Invalid header: %s"), text);
95 return;
96 }
97 len = p - text;
98 name = mu_alloc (len + 1);
99 memcpy (name, text, len);
100 name[len] = 0;
101 for (p++; *p && mu_isspace (*p); p++)
102 ;
103
104 add_header (name, mu_strdup (p), COMPOSE_APPEND);
105 }
106
107 void
send_append_header2(char const * name,char const * value,int mode)108 send_append_header2 (char const *name, char const *value, int mode)
109 {
110 add_header (mu_strdup (name), mu_strdup (value), mode);
111 }
112
113 int
mail_sendheader(int argc,char ** argv)114 mail_sendheader (int argc, char **argv)
115 {
116 if (argc == 1)
117 mu_list_foreach (add_header_list, list_headers, NULL);
118 else if (argc == 2)
119 {
120 if (strchr (argv[1], ':'))
121 send_append_header (argv[1]);
122 else
123 mu_list_foreach (add_header_list, list_headers, argv[1]);
124 }
125 else
126 {
127 size_t len = strlen (argv[1]);
128 if (len > 0 && argv[1][len - 1] == ':')
129 argv[1][len - 1] = 0;
130 add_header (mu_strdup (argv[1]), mu_strdup (argv[2]), COMPOSE_APPEND);
131 }
132 return 0;
133 }
134
135 /* Attachments */
136 struct atchinfo
137 {
138 char *id; /* Attachment id (for listing) */
139 char *encoding; /* Encoding the attachment uses */
140 char *content_type; /* Full content type (type/subtype[; attrs]) */
141 char *name; /* Attachment name */
142 char *filename; /* Attachment file name */
143 mu_stream_t source; /* Attachment source stream */
144 int skip_empty; /* Skip this attachment if it is empty */
145 int disp_inline; /* Inline content disposition */
146 };
147
148 static void
atchinfo_free(void * p)149 atchinfo_free (void *p)
150 {
151 struct atchinfo *ap = p;
152 free (ap->id);
153 free (ap->encoding);
154 free (ap->content_type);
155 free (ap->name);
156 free (ap->filename);
157 mu_stream_destroy (&ap->source);
158 free (ap);
159 }
160
161 static mu_list_t
attlist_new(void)162 attlist_new (void)
163 {
164 mu_list_t lst;
165 int rc = mu_list_create (&lst);
166 if (rc)
167 {
168 mu_diag_funcall (MU_DIAG_ERROR, "mu_list_create", NULL, rc);
169 exit (1);
170 }
171 mu_list_set_destroy_item (lst, atchinfo_free);
172 return lst;
173 }
174
175 static void
attach_set_content_type(struct atchinfo * aptr,char const * content_type)176 attach_set_content_type (struct atchinfo *aptr, char const *content_type)
177 {
178 char *charset;
179
180 if (!content_type)
181 content_type = "text/plain";
182 if (strncmp (content_type, "text/", 5) == 0
183 && !strstr (content_type, "charset=")
184 && (charset = util_get_charset ()))
185 {
186 mu_asprintf (&aptr->content_type, "%s; charset=%s",
187 content_type, charset);
188 free (charset);
189 }
190 else
191 aptr->content_type = mu_strdup (content_type);
192 }
193
194 static void
attlist_add(mu_list_t attlist,char * id,char const * encoding,char const * content_type,char const * content_name,char const * content_filename,mu_stream_t stream,int skip_empty,int disp_inline)195 attlist_add (mu_list_t attlist, char *id, char const *encoding,
196 char const *content_type, char const *content_name,
197 char const *content_filename,
198 mu_stream_t stream, int skip_empty, int disp_inline)
199 {
200 struct atchinfo *aptr;
201 int rc;
202
203 aptr = mu_alloc (sizeof (*aptr));
204
205 aptr->id = id ? mu_strdup (id) : id;
206 aptr->encoding = mu_strdup (encoding);
207 attach_set_content_type (aptr,
208 content_type
209 ? content_type : "application/octet-stream");
210 aptr->name = content_name ? mu_strdup (content_name) : NULL;
211 aptr->filename = content_filename ? mu_strdup (content_filename) : NULL;
212 aptr->source = stream;
213 if (stream)
214 mu_stream_ref (stream);
215 aptr->skip_empty = skip_empty;
216 aptr->disp_inline = disp_inline;
217 rc = mu_list_append (attlist, aptr);
218 if (rc)
219 {
220 mu_diag_funcall (MU_DIAG_ERROR, "mu_list_append", NULL, rc);
221 exit (1);
222 }
223 }
224
225 int
attlist_attach_file(mu_list_t * attlist_ptr,int fd,const char * realname,const char * content_filename,const char * content_name,const char * content_type,const char * encoding)226 attlist_attach_file (mu_list_t *attlist_ptr,
227 int fd,
228 const char *realname,
229 const char *content_filename, const char *content_name,
230 const char *content_type, const char *encoding)
231 {
232 int rc;
233 struct stat st;
234 mu_list_t list;
235 mu_stream_t stream = NULL;
236 char *id = NULL;
237 mu_list_t attlist;
238
239 if (fd >= 0)
240 {
241 rc = mu_fd_stream_create (&stream, NULL, fd, MU_STREAM_READ);
242 if (rc)
243 {
244 mu_error (_("can't open descriptor %d: %s"), fd, mu_strerror (rc));
245 return 1;
246 }
247 mu_asprintf (&id, "fd %d", fd);
248 if (fd == 0)
249 {
250 mu_stream_destroy (&mu_strin);
251 mu_nullstream_create (&mu_strin, MU_STREAM_READ);
252 mu_stream_ioctl (mu_strin, MU_IOCTL_NULLSTREAM,
253 MU_IOCTL_NULLSTREAM_SET_PATTERN, NULL);
254 util_do_command ("set nullbody nullbodymsg");
255 }
256 }
257 else if (realname)
258 {
259 if (!content_filename)
260 content_filename = realname;
261 if (stat (realname, &st))
262 {
263 if (errno == ENOENT)
264 {
265 mu_error (_("%s: file does not exist"), realname);
266 return 1;
267 }
268 else
269 {
270 mu_error (_("%s: cannot stat: %s"), realname,
271 mu_strerror (errno));
272 return 1;
273 }
274 }
275
276 if (S_ISREG (st.st_mode))
277 rc = mu_mapfile_stream_create (&stream, realname, MU_STREAM_READ);
278 else if (S_ISFIFO (st.st_mode))
279 rc = mu_file_stream_create (&stream, realname, MU_STREAM_READ);
280 else
281 {
282 mu_error (_("%s: not a regular file or FIFO"), realname);
283 return 1;
284 }
285
286 if (rc)
287 {
288 mu_error (_("can't open file %s: %s"),
289 realname, mu_strerror (rc));
290 return 1;
291 }
292 mu_asprintf (&id, "\"%s\"", realname);
293 }
294 else
295 abort ();
296
297 if (!encoding)
298 encoding = "base64";
299 mu_filter_get_list (&list);
300 rc = mu_list_locate (list, (void*) encoding, NULL);
301 if (rc)
302 {
303 mu_error (_("unsupported encoding: %s"), encoding);
304 free (id);
305 mu_stream_destroy (&stream);
306 return 1;
307 }
308
309 if (!*attlist_ptr)
310 {
311 attlist = attlist_new ();
312 *attlist_ptr = attlist;
313 }
314 else
315 attlist = *attlist_ptr;
316
317 attlist_add (attlist, id, encoding, content_type,
318 content_name, content_filename,
319 stream, skip_empty_attachments, 0);
320 if (stream)
321 mu_stream_unref (stream);
322 free (id);
323
324 return 0;
325 }
326
327 static int
attlist_helper(void * item,void * data)328 attlist_helper (void *item, void *data)
329 {
330 struct atchinfo *aptr = item;
331 mu_list_t list = data;
332 attlist_add (list, aptr->id, aptr->encoding, aptr->content_type,
333 aptr->name, aptr->filename, aptr->source, aptr->skip_empty,
334 aptr->disp_inline);
335 return 0;
336 }
337
338 static mu_list_t
attlist_copy(mu_list_t src)339 attlist_copy (mu_list_t src)
340 {
341 mu_list_t dst;
342
343 if (!src)
344 return NULL;
345 dst = attlist_new ();
346 mu_list_foreach (src, attlist_helper, dst);
347 return dst;
348 }
349
350 static mu_list_t attachment_list;
351
352 int
send_attach_file(int fd,const char * realname,const char * content_filename,const char * content_name,const char * content_type,const char * encoding)353 send_attach_file (int fd,
354 const char *realname,
355 const char *content_filename, const char *content_name,
356 const char *content_type, const char *encoding)
357 {
358 return attlist_attach_file (&attachment_list,
359 fd,
360 realname,
361 content_filename,
362 content_name,
363 content_type,
364 encoding);
365 }
366
367 static void
report_multipart_type(compose_env_t * env)368 report_multipart_type (compose_env_t *env)
369 {
370 mu_printf ("multipart/%s\n", env->alt ? "alternative" : "mixed");
371 }
372
373 /* ~/ - toggle between multipart/mixed and multipart/alternative */
374 int
escape_toggle_multipart_type(int argc,char ** argv,compose_env_t * env)375 escape_toggle_multipart_type (int argc, char **argv, compose_env_t *env)
376 {
377 env->alt = !env->alt;
378 report_multipart_type (env);
379 return 0;
380 }
381
382 int
escape_list_attachments(int argc,char ** argv,compose_env_t * env)383 escape_list_attachments (int argc, char **argv, compose_env_t *env)
384 {
385 mu_iterator_t itr;
386 int i;
387
388 report_multipart_type (env);
389
390 if (mu_list_is_empty (env->attlist) ||
391 mu_list_get_iterator (env->attlist, &itr))
392 {
393 mu_printf ("%s\n", _("No attachments"));
394 return 0;
395 }
396
397 for (mu_iterator_first (itr), i = 1; !mu_iterator_is_done (itr);
398 mu_iterator_next (itr), i++)
399 {
400 struct atchinfo *aptr;
401 if (mu_iterator_current (itr, (void**)&aptr))
402 continue;
403
404 mu_printf ("%3d %-12s %-30s %-s\n",
405 i, aptr->id, aptr->content_type, aptr->encoding);
406 }
407 mu_iterator_destroy (&itr);
408
409 return 0;
410 }
411
412 int
escape_attach(int argc,char ** argv,compose_env_t * env)413 escape_attach (int argc, char **argv, compose_env_t *env)
414 {
415 const char *encoding = default_encoding;
416 const char *content_type = default_content_type;
417
418 switch (argc)
419 {
420 case 4:
421 encoding = argv[3];
422 case 3:
423 content_type = argv[2];
424 case 2:
425 return attlist_attach_file (&env->attlist,
426 -1, argv[1], argv[1], argv[1],
427 content_type, encoding);
428 default:
429 return escape_check_args (argc, argv, 2, 4);
430 }
431 return 1;
432 }
433
434 int
escape_remove_attachment(int argc,char ** argv,compose_env_t * env)435 escape_remove_attachment (int argc, char **argv, compose_env_t *env)
436 {
437 size_t count;
438 unsigned long n;
439 char *p;
440
441 if (escape_check_args (argc, argv, 2, 2))
442 return 1;
443 n = strtoul (argv[1], &p, 10);
444 if (*p)
445 {
446 mu_error (_("not a valid number: %s"), argv[1]);
447 return 1;
448 }
449
450 mu_list_count (env->attlist, &count);
451 if (n == 0 || n > count)
452 {
453 mu_error (_("index out of range"));
454 return 1;
455 }
456
457 return mu_list_remove_nth (env->attlist, n - 1);
458 }
459
460 static int
save_attachment(struct atchinfo * aptr,compose_env_t * env,mu_message_t part)461 save_attachment (struct atchinfo *aptr, compose_env_t *env, mu_message_t part)
462 {
463 mu_header_t hdr;
464 int rc;
465 size_t nparts;
466 char *p;
467
468 rc = mu_attachment_copy_from_stream (part, aptr->source);
469 if (rc)
470 {
471 mu_error (_("cannot attach %s: %s"), aptr->id, mu_strerror (rc));
472 return 1;
473 }
474
475 if (aptr->skip_empty)
476 {
477 mu_body_t body;
478 size_t size;
479
480 rc = mu_message_get_body (part, &body);
481 if (rc)
482 {
483 mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_body", aptr->id, rc);
484 return 1;
485 }
486 rc = mu_body_size (body, &size);
487 if (rc)
488 {
489 mu_diag_funcall (MU_DIAG_ERROR, "mu_body_size", aptr->id, rc);
490 return 1;
491 }
492 if (size == 0)
493 return 0;
494 }
495
496 mu_mime_get_num_parts (env->mime, &nparts);
497 mu_message_get_header (part, &hdr);
498 mu_rfc2822_msg_id (nparts, &p);
499 mu_header_set_value (hdr, MU_HEADER_CONTENT_ID, p, 1);
500 free (p);
501
502 if (aptr->disp_inline)
503 {
504 rc = mu_header_set_value (hdr, MU_HEADER_CONTENT_DISPOSITION,
505 "inline",
506 1);
507 if (rc)
508 mu_diag_funcall (MU_DIAG_ERROR, "mu_header_set_value",
509 MU_HEADER_CONTENT_DISPOSITION, rc);
510 }
511
512 rc = mu_mime_add_part (env->mime, part);
513 if (rc)
514 {
515 mu_diag_funcall (MU_DIAG_ERROR, "mu_mime_add_part", aptr->filename, rc);
516 return 1;
517 }
518
519 return 0;
520 }
521
522 static int
saveatt(void * item,void * data)523 saveatt (void *item, void *data)
524 {
525 struct atchinfo *aptr = item;
526 compose_env_t *env = data;
527 mu_message_t part;
528 int rc;
529
530 rc = mu_attachment_create (&part, aptr->content_type, aptr->encoding,
531 aptr->name,
532 aptr->filename);
533 if (rc)
534 {
535 mu_error (_("can't create attachment %s: %s"),
536 aptr->id, mu_strerror (rc));
537 return 1;
538 }
539
540 rc = save_attachment (aptr, env, part);
541 mu_message_unref (part);
542 return rc;
543 }
544
545 static int
add_body(mu_message_t inmsg,compose_env_t * env)546 add_body (mu_message_t inmsg, compose_env_t *env)
547 {
548 int rc;
549 mu_body_t body;
550 mu_stream_t str;
551 struct atchinfo *aptr;
552
553 mu_message_get_body (inmsg, &body);
554 mu_body_get_streamref (body, &str);
555
556 aptr = mu_alloc (sizeof (*aptr));
557 mu_asprintf (&aptr->id, "(body)");
558 aptr->encoding = default_encoding ? mu_strdup (default_encoding) : NULL;
559 attach_set_content_type (aptr, default_content_type);
560 aptr->name = NULL;
561 aptr->filename = NULL;
562 aptr->source = str;
563 aptr->skip_empty = skip_empty_attachments || multipart_alternative;
564 aptr->disp_inline = 1;
565 if (!env->attlist)
566 env->attlist = attlist_new ();
567 rc = mu_list_prepend (env->attlist, aptr);
568 if (rc)
569 mu_diag_funcall (MU_DIAG_ERROR, "mu_list_prepend", NULL, rc);
570 return rc;
571 }
572
573 static int
add_attachments(compose_env_t * env,mu_message_t * pmsg)574 add_attachments (compose_env_t *env, mu_message_t *pmsg)
575 {
576 mu_message_t inmsg, outmsg;
577 mu_header_t inhdr, outhdr;
578 mu_iterator_t itr;
579 int rc;
580
581 inmsg = *pmsg;
582
583 if (mailvar_is_true (mailvar_name_mime) && add_body (inmsg, env))
584 return 1;
585
586 if (mu_list_is_empty (env->attlist))
587 return 0;
588
589 /* Create a mime object */
590 rc = mu_mime_create (&env->mime, NULL,
591 env->alt ?
592 MU_MIME_MULTIPART_ALT : MU_MIME_MULTIPART_MIXED);
593 if (rc)
594 {
595 mu_diag_funcall (MU_DIAG_ERROR, "mu_mime_create", NULL, rc);
596 return 1;
597 }
598
599 mu_message_get_header (inmsg, &inhdr);
600
601 /* Add the respective attachments */
602 rc = mu_list_foreach (env->attlist, saveatt, env);
603 if (rc)
604 return 1;
605
606 /* Get the resulting message */
607 rc = mu_mime_get_message (env->mime, &outmsg);
608
609 if (rc)
610 {
611 mu_diag_funcall (MU_DIAG_ERROR, "mu_mime_get_message", NULL, rc);
612 return 1;
613 }
614
615 /* Copy rest of headers from the original message */
616 rc = mu_message_get_header (outmsg, &outhdr);
617 if (rc)
618 {
619 mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_header", NULL, rc);
620 return 1;
621 }
622
623 rc = mu_header_get_iterator (inhdr, &itr);
624 if (rc)
625 {
626 mu_diag_funcall (MU_DIAG_ERROR, "mu_header_get_iterator", NULL, rc);
627 return 1;
628 }
629 for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
630 mu_iterator_next (itr))
631 {
632 const char *name, *value;
633
634 if (mu_iterator_current_kv (itr, (const void **)&name,
635 (void**)&value) == 0)
636 {
637 if (mu_c_strcasecmp (name, MU_HEADER_RECEIVED) == 0
638 || mu_c_strncasecmp (name, "X-", 2) == 0)
639 mu_header_append (outhdr, name, value);
640 else if (mu_c_strcasecmp (name, MU_HEADER_MIME_VERSION) == 0 ||
641 mu_c_strncasecmp (name, "Content-", 8) == 0)
642 {
643 mu_error (_("%s: not setting header"), name);
644 continue;
645 }
646
647 else
648 mu_header_set_value (outhdr, name, value, 1);
649 }
650 }
651 mu_iterator_destroy (&itr);
652
653 mu_message_unref (outmsg);
654 mu_message_unref (inmsg);
655
656 *pmsg = outmsg;
657 return 0;
658 }
659
660
661
662 /* Send-related commands */
663
664 static void
read_cc_bcc(compose_env_t * env)665 read_cc_bcc (compose_env_t *env)
666 {
667 if (mailvar_is_true (mailvar_name_askcc))
668 compose_header_set (env, MU_HEADER_CC,
669 ml_readline_with_intr ("Cc: "), COMPOSE_REPLACE);
670 if (mailvar_is_true (mailvar_name_askbcc))
671 compose_header_set (env, MU_HEADER_BCC,
672 ml_readline_with_intr ("Bcc: "), COMPOSE_REPLACE);
673 }
674
675 /*
676 * m[ail] address...
677 if address is starting with
678
679 '/' it is considered a file and the message is saveed to a file;
680 '.' it is considered to be a relative path;
681 '|' it is considered to be a pipe, and the message is written to
682 there;
683
684 example:
685
686 mail joe '| cat >/tmp/save'
687
688 mail will be sent to joe and the message saved in /tmp/save. */
689
690 int
mail_send(int argc,char ** argv)691 mail_send (int argc, char **argv)
692 {
693 compose_env_t env;
694 int status;
695 int save_to = mu_isupper (argv[0][0]);
696
697 compose_init (&env);
698
699 if (argc < 2)
700 {
701 if (interactive)
702 compose_header_set (&env, MU_HEADER_TO, ml_readline_with_intr ("To: "),
703 COMPOSE_REPLACE);
704 else if (mailvar_is_true (mailvar_name_editheaders))
705 {
706 if (parse_headers (mu_strin, &env) != parse_headers_ok)
707 {
708 mu_error ("%s", _("Errors parsing message"));
709 exit (EXIT_FAILURE);
710 }
711 if (add_header_list)
712 {
713 mu_iterator_t itr;
714 mu_list_get_iterator (add_header_list, &itr);
715 for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
716 mu_iterator_next (itr))
717 {
718 struct add_header *hp;
719 int mode;
720 if (mu_iterator_current (itr, (void**) &hp))
721 break;
722 mode = hp->mode;
723 if (mu_header_sget_value (env.header, hp->name, NULL) == 0)
724 mode = COMPOSE_REPLACE;
725 compose_header_set (&env, hp->name, hp->value, mode);
726 }
727 mu_iterator_destroy (&itr);
728 }
729 }
730 else
731 {
732 mu_error ("%s", _("No recipients specified"));
733 exit (EXIT_FAILURE);
734 }
735 }
736 else
737 {
738 while (--argc)
739 {
740 char *p = *++argv;
741 if (isfilename (p))
742 {
743 env.outfiles = realloc (env.outfiles,
744 (env.nfiles + 1) *
745 sizeof (*(env.outfiles)));
746 if (env.outfiles)
747 {
748 env.outfiles[env.nfiles] = p;
749 env.nfiles++;
750 }
751 }
752 else
753 compose_header_set (&env, MU_HEADER_TO, p, COMPOSE_SINGLE_LINE);
754 }
755 }
756
757 if (interactive)
758 {
759 if (!mailvar_is_true (mailvar_name_mailx))
760 read_cc_bcc (&env);
761
762 if (mailvar_is_true (mailvar_name_asksub))
763 compose_header_set (&env, MU_HEADER_SUBJECT,
764 ml_readline_with_intr ("Subject: "),
765 COMPOSE_REPLACE);
766 }
767
768 status = mail_compose_send (&env, save_to);
769 compose_destroy (&env);
770 return status;
771 }
772
773 int
parse_headers(mu_stream_t input,compose_env_t * env)774 parse_headers (mu_stream_t input, compose_env_t *env)
775 {
776 int status;
777 mu_header_t header;
778 char *name = NULL;
779 char *value = NULL;
780 enum { STATE_INIT, STATE_READ, STATE_BODY } state = STATE_INIT;
781 char *buf = NULL;
782 size_t size = 0, n;
783 int errcnt = 0, line = 0;
784
785 if ((status = mu_header_create (&header, NULL, 0)) != 0)
786 {
787 mu_error (_("Cannot create header: %s"), mu_strerror (status));
788 return parse_headers_fatal;
789 }
790
791 while (state != STATE_BODY &&
792 errcnt == 0 &&
793 mu_stream_getline (input, &buf, &size, &n) == 0 && n > 0)
794 {
795 mu_rtrim_class (buf, MU_CTYPE_SPACE);
796
797 line++;
798 switch (state)
799 {
800 case STATE_INIT:
801 if (!buf[0] || mu_isspace (buf[0]))
802 continue;
803 else
804 state = STATE_READ;
805 /*FALLTHRU*/
806
807 case STATE_READ:
808 if (buf[0] == 0)
809 state = STATE_BODY;
810 else if (mu_isspace (buf[0]))
811 {
812 /* A continuation line */
813 if (name)
814 {
815 char *p = NULL;
816 mu_asprintf (&p, "%s\n%s", value, buf);
817 free (value);
818 value = p;
819 }
820 else
821 {
822 mu_error (_("%d: not a header line"), line);
823 errcnt++;
824 }
825 }
826 else
827 {
828 char *p;
829
830 if (name)
831 {
832 mu_header_append (header, name, value[0] ? value : NULL);
833 free (name);
834 free (value);
835 name = value = NULL;
836 }
837 p = strchr (buf, ':');
838 if (p)
839 {
840 *p++ = 0;
841 while (*p && mu_isspace (*p))
842 p++;
843 value = mu_strdup (p);
844 name = mu_strdup (buf);
845 }
846 else
847 {
848 mu_error (_("%d: not a header line"), line);
849 errcnt++;
850 }
851 }
852 break;
853
854 default:
855 abort ();
856 }
857 }
858
859 free (buf);
860 if (name)
861 {
862 mu_header_append (header, name, value);
863 free (name);
864 free (value);
865 }
866
867 if (errcnt)
868 {
869 mu_header_destroy (&header);
870 return parse_headers_error;
871 }
872
873 mu_header_destroy (&env->header);
874 env->header = header;
875 return parse_headers_ok;
876 }
877
878
879 void
compose_init(compose_env_t * env)880 compose_init (compose_env_t *env)
881 {
882 memset (env, 0, sizeof (*env));
883 env->alt = multipart_alternative;
884 env->attlist = attlist_copy (attachment_list);
885 mu_list_foreach (add_header_list, seed_headers, env);
886 }
887
888 int
compose_header_set(compose_env_t * env,const char * name,const char * value,int mode)889 compose_header_set (compose_env_t *env, const char *name,
890 const char *value, int mode)
891 {
892 int status;
893 char *expansion = NULL;
894
895 if (!value || value[0] == 0)
896 return EINVAL;
897
898 if (is_address_field (name)
899 && mailvar_is_true (mailvar_name_inplacealiases))
900 {
901 struct mu_address hint = MU_ADDRESS_HINT_INITIALIZER;
902 mu_address_t a;
903
904 status = mu_address_create_hint (&a, value, &hint, 0);
905 if (status)
906 {
907 mu_error (_("Cannot parse address `%s': %s"),
908 value, mu_strerror (status));
909 return status;
910 }
911 util_address_expand_aliases (&a);
912 status = mu_address_aget_printable (a, &expansion);
913 mu_address_destroy (&a);
914 if (status)
915 {
916 mu_diag_funcall (MU_DIAG_ERROR, "mu_address_aget_printable",
917 NULL, status);
918 return status;
919 }
920 value = expansion;
921 }
922
923 if (!env->header
924 && (status = mu_header_create (&env->header, NULL, 0)) != 0)
925 {
926 mu_error (_("Cannot create header: %s"), mu_strerror (status));
927 }
928 else
929 {
930 switch (mode)
931 {
932 case COMPOSE_REPLACE:
933 status = mu_header_set_value (env->header, name, value, 1);
934 break;
935
936 case COMPOSE_APPEND:
937 status = mu_header_append (env->header, name, value);
938 break;
939
940 case COMPOSE_SINGLE_LINE:
941 {
942 char *old_value;
943
944 if (mu_header_aget_value (env->header, name, &old_value) == 0
945 && old_value[0])
946 {
947 if (is_address_field (name))
948 {
949 status = util_merge_addresses (&old_value, value);
950 if (status == 0)
951 status = mu_header_set_value (env->header, name,
952 old_value, 1);
953 }
954 else
955 {
956 size_t size = strlen (old_value) + strlen (value) + 2;
957 char *p = realloc (old_value, size);
958 if (!p)
959 status = ENOMEM;
960 else
961 {
962 old_value = p;
963 strcat (old_value, ",");
964 strcat (old_value, value);
965 status = mu_header_set_value (env->header,
966 name, old_value,
967 1);
968 }
969 }
970 free (old_value);
971 }
972 else
973 status = mu_header_set_value (env->header, name, value, 1);
974 }
975 }
976 }
977 free (expansion);
978 return status;
979 }
980
981 char const *
compose_header_get(compose_env_t * env,char * name,char * defval)982 compose_header_get (compose_env_t *env, char *name, char *defval)
983 {
984 char const *p;
985
986 if (mu_header_sget_value (env->header, name, &p))
987 p = defval;
988 return p;
989 }
990
991 void
compose_destroy(compose_env_t * env)992 compose_destroy (compose_env_t *env)
993 {
994 mu_header_destroy (&env->header);
995 free (env->outfiles);
996 mu_mime_destroy (&env->mime);
997 mu_list_destroy (&env->attlist);
998 mu_stream_destroy (&env->compstr);
999 }
1000
1001 static int
fill_body(mu_message_t msg,mu_stream_t instr)1002 fill_body (mu_message_t msg, mu_stream_t instr)
1003 {
1004 int rc;
1005 mu_body_t body = NULL;
1006 mu_stream_t stream = NULL;
1007 mu_off_t n;
1008
1009 rc = mu_message_get_body (msg, &body);
1010 if (rc)
1011 {
1012 mu_error (_("cannot get message body: %s"), mu_strerror (rc));
1013 return 1;
1014 }
1015 rc = mu_body_get_streamref (body, &stream);
1016 if (rc)
1017 {
1018 mu_error (_("cannot get body: %s"), mu_strerror (rc));
1019 return 1;
1020 }
1021
1022 rc = mu_stream_copy (stream, instr, 0, &n);
1023 mu_stream_destroy (&stream);
1024
1025 if (rc)
1026 {
1027 mu_error (_("cannot copy temporary stream: %s"), mu_strerror (rc));
1028 return 1;
1029 }
1030
1031 if (n == 0)
1032 {
1033 if (mailvar_is_true (mailvar_name_nullbody))
1034 {
1035 char *str;
1036 if (mailvar_get (&str, mailvar_name_nullbodymsg,
1037 mailvar_type_string, 0) == 0)
1038 mu_error ("%s", _(str));
1039 }
1040 else
1041 return 1;
1042 }
1043
1044 return 0;
1045 }
1046
1047 static int
save_dead_message_env(compose_env_t * env)1048 save_dead_message_env (compose_env_t *env)
1049 {
1050 if (mailvar_is_true (mailvar_name_save))
1051 {
1052 mu_stream_t dead_letter, str;
1053 int rc;
1054 time_t t;
1055 struct tm *tm;
1056 const char *name = getenv ("DEAD");
1057 char *sender;
1058
1059 /* FIXME: Use MU_STREAM_APPEND if appenddeadletter, instead of the
1060 stream manipulations below */
1061 rc = mu_file_stream_create (&dead_letter, name,
1062 MU_STREAM_CREAT|MU_STREAM_WRITE);
1063 if (rc)
1064 {
1065 mu_error (_("Cannot open file %s: %s"), name, strerror (rc));
1066 return 1;
1067 }
1068 if (mailvar_is_true (mailvar_name_appenddeadletter))
1069 mu_stream_seek (dead_letter, 0, MU_SEEK_END, NULL);
1070 else
1071 mu_stream_truncate (dead_letter, 0);
1072
1073 time (&t);
1074 tm = gmtime (&t);
1075 sender = mu_get_user_email (NULL);
1076 if (!sender)
1077 sender = mu_strdup ("UNKNOWN");
1078 mu_stream_printf (dead_letter, "From %s ", sender);
1079 free (sender);
1080 mu_c_streamftime (dead_letter, "%c%n", tm, NULL);
1081
1082 if (mu_header_get_streamref (env->header, &str) == 0)
1083 {
1084 mu_stream_copy (dead_letter, str, 0, NULL);
1085 mu_stream_unref (str);
1086 }
1087 else
1088 mu_stream_write (dead_letter, "\n", 1, NULL);
1089
1090 mu_stream_seek (env->compstr, 0, MU_SEEK_SET, NULL);
1091 mu_stream_copy (dead_letter, env->compstr, 0, NULL);
1092 mu_stream_write (dead_letter, "\n", 1, NULL);
1093 mu_stream_destroy (&dead_letter);
1094 }
1095 return 0;
1096 }
1097
1098 static int
save_dead_message(mu_message_t msg)1099 save_dead_message (mu_message_t msg)
1100 {
1101 if (mailvar_is_true (mailvar_name_save))
1102 {
1103 mu_stream_t dead_letter, str;
1104 int rc;
1105 time_t t;
1106 struct tm *tm;
1107 const char *name = getenv ("DEAD");
1108 char *sender;
1109
1110 /* FIXME: Use MU_STREAM_APPEND if appenddeadletter, instead of the
1111 stream manipulations below */
1112 rc = mu_file_stream_create (&dead_letter, name,
1113 MU_STREAM_CREAT|MU_STREAM_WRITE);
1114 if (rc)
1115 {
1116 mu_error (_("Cannot open file %s: %s"), name, strerror (rc));
1117 return 1;
1118 }
1119 if (mailvar_is_true (mailvar_name_appenddeadletter))
1120 mu_stream_seek (dead_letter, 0, MU_SEEK_END, NULL);
1121 else
1122 mu_stream_truncate (dead_letter, 0);
1123
1124 time (&t);
1125 tm = gmtime (&t);
1126 sender = mu_get_user_email (NULL);
1127 if (!sender)
1128 sender = mu_strdup ("UNKNOWN");
1129 mu_stream_printf (dead_letter, "From %s ", sender);
1130 free (sender);
1131 mu_c_streamftime (dead_letter, "%c%n", tm, NULL);
1132
1133 if (mu_message_get_streamref (msg, &str) == 0)
1134 {
1135 mu_stream_copy (dead_letter, str, 0, NULL);
1136 mu_stream_unref (str);
1137 }
1138 mu_stream_write (dead_letter, "\n", 1, NULL);
1139 mu_stream_destroy (&dead_letter);
1140 }
1141 return 0;
1142 }
1143
1144 static int
send_message(mu_message_t msg)1145 send_message (mu_message_t msg)
1146 {
1147 char *mailer_url = NULL;
1148 char *sendmail;
1149 int status;
1150
1151 if (mailvar_get (&sendmail, mailvar_name_sendmail,
1152 mailvar_type_string, 0) == 0)
1153 {
1154 if (mailvar_is_true (mailvar_name_mailx))
1155 {
1156 /*
1157 * Mailx compatibility: assume sendmail:// scheme.
1158 */
1159 if (!mu_is_proto (sendmail))
1160 {
1161 status = mu_asprintf (&mailer_url, "sendmail://%s", sendmail);
1162 if (status)
1163 return status;
1164 sendmail = mailer_url;
1165 }
1166 }
1167
1168 if (sendmail[0] == '/')
1169 status = msg_to_pipe (sendmail, msg);
1170 else
1171 {
1172 mu_mailer_t mailer;
1173
1174 status = mu_mailer_create (&mailer, sendmail);
1175 if (status == 0)
1176 {
1177 const char *return_address_str;
1178 mu_address_t return_address = NULL;
1179
1180 if (mailvar_get (&return_address_str, mailvar_name_return_address,
1181 mailvar_type_string, 0) == 0)
1182 {
1183 struct mu_address hint = MU_ADDRESS_HINT_INITIALIZER;
1184 status = mu_address_create_hint (&return_address,
1185 return_address_str,
1186 &hint, 0);
1187 if (status)
1188 {
1189 mu_error (_("invalid return address: %s"),
1190 mu_strerror (status));
1191 mu_mailer_destroy (&mailer);
1192 return status;
1193 }
1194 }
1195
1196 if (mailvar_is_true (mailvar_name_verbose))
1197 {
1198 mu_debug_set_category_level (MU_DEBCAT_MAILER,
1199 MU_DEBUG_LEVEL_UPTO (MU_DEBUG_PROT));
1200 }
1201 status = mu_mailer_open (mailer, MU_STREAM_RDWR);
1202 if (status == 0)
1203 {
1204 status = mu_mailer_send_message (mailer, msg,
1205 return_address, NULL);
1206 mu_mailer_close (mailer);
1207 }
1208 else
1209 mu_error (_("Cannot open mailer: %s"),
1210 mu_strerror (status));
1211 mu_mailer_destroy (&mailer);
1212 mu_address_destroy (&return_address);
1213 }
1214 else
1215 mu_error (_("Cannot create mailer: %s"), mu_strerror (status));
1216 }
1217 }
1218 else
1219 {
1220 mu_error (_("Variable sendmail not set: no mailer"));
1221 status = ENOSYS;
1222 }
1223 free (mailer_url);
1224 return status;
1225 }
1226
1227 #define MU_DATETIME_RFC822_LENGTH 31
1228
1229 static void
message_add_date(mu_message_t msg)1230 message_add_date (mu_message_t msg)
1231 {
1232 mu_header_t hdr;
1233 char buf[MU_DATETIME_RFC822_LENGTH+1];
1234 struct tm ltm;
1235 time_t t;
1236 int rc;
1237
1238 rc = mu_message_get_header (msg, &hdr);
1239 if (rc)
1240 {
1241 mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_header", NULL, rc);
1242 return;
1243 }
1244
1245 t = time (NULL);
1246 localtime_r (&t, <m);
1247
1248 mu_strftime (buf, sizeof (buf), MU_DATETIME_FORM_RFC822, <m);
1249 rc = mu_header_set_value (hdr, MU_HEADER_DATE, buf, 1);
1250 if (rc)
1251 mu_diag_funcall (MU_DIAG_ERROR, "mu_header_set_value", MU_HEADER_DATE, rc);
1252 }
1253
1254 /* mail_compose_send(): shared between mail_send() and mail_reply();
1255
1256 If the variable "record" is set, the outgoing message is
1257 saved after being sent. If "save_to" argument is non-zero,
1258 the name of the save file is derived from "to" argument. Otherwise,
1259 it is taken from the value of the "record" variable.
1260
1261 sendmail
1262
1263 contains a command, possibly with options, that mailx invokes to send
1264 mail. You must manually set the default for this environment variable
1265 by editing ROOTDIR/etc/mailx.rc to specify the mail agent of your
1266 choice. The default is sendmail, but it can be any command that takes
1267 addresses on the command line and message contents on standard input. */
1268
1269 int
mail_compose_send(compose_env_t * env,int save_to)1270 mail_compose_send (compose_env_t *env, int save_to)
1271 {
1272 int done = 0;
1273 int rc;
1274 char *savefile = NULL;
1275 int int_cnt;
1276 char *escape;
1277
1278 /* Prepare environment */
1279 rc = mu_temp_stream_create (&env->compstr, 0);
1280 if (rc)
1281 {
1282 mu_error (_("Cannot open temporary file: %s"), mu_strerror (rc));
1283 return 1;
1284 }
1285
1286 ml_clear_interrupt ();
1287 int_cnt = 0;
1288 while (!done)
1289 {
1290 char *buf;
1291 buf = ml_readline (" \b");
1292
1293 if (ml_got_interrupt ())
1294 {
1295 if (buf)
1296 free (buf);
1297 if (mailvar_is_true (mailvar_name_ignore))
1298 {
1299 mu_printf ("@\n");
1300 }
1301 else
1302 {
1303 if (++int_cnt == 2)
1304 break;
1305 mu_error (_("\n(Interrupt -- one more to kill letter)"));
1306 }
1307 continue;
1308 }
1309
1310 if (!buf)
1311 {
1312 if (interactive && mailvar_is_true (mailvar_name_ignoreeof))
1313 {
1314 mu_error (mailvar_is_true (mailvar_name_dot)
1315 ? _("Use \".\" to terminate letter.")
1316 : _("Use \"~.\" to terminate letter."));
1317 continue;
1318 }
1319 else
1320 break;
1321 }
1322
1323 int_cnt = 0;
1324
1325 if (strcmp (buf, ".") == 0 && mailvar_is_true (mailvar_name_dot))
1326 done = 1;
1327 else if (interactive
1328 && mailvar_get (&escape, mailvar_name_escape,
1329 mailvar_type_string, 0) == 0
1330 && buf[0] == escape[0])
1331 {
1332 if (buf[1] == buf[0])
1333 mu_stream_printf (env->compstr, "%s\n", buf + 1);
1334 else if (buf[1] == '.')
1335 done = 1;
1336 else if (buf[1] == 'x')
1337 {
1338 int_cnt = 2;
1339 done = 1;
1340 }
1341 else
1342 {
1343 struct mu_wordsplit ws;
1344
1345 if (mu_wordsplit (buf + 1, &ws, MU_WRDSF_DEFFLAGS) == 0)
1346 {
1347 if (ws.ws_wordc > 0)
1348 {
1349 const struct mail_escape_entry *entry =
1350 mail_find_escape (ws.ws_wordv[0]);
1351
1352 if (entry)
1353 (*entry->escfunc) (ws.ws_wordc, ws.ws_wordv, env);
1354 else
1355 mu_error (_("Unknown escape %s"), ws.ws_wordv[0]);
1356 }
1357 else
1358 mu_error (_("Unfinished escape"));
1359 mu_wordsplit_free (&ws);
1360 }
1361 else
1362 {
1363 mu_error (_("Cannot parse escape sequence: %s"),
1364 mu_wordsplit_strerror (&ws));
1365 }
1366 }
1367 }
1368 else
1369 mu_stream_printf (env->compstr, "%s\n", buf);
1370 mu_stream_flush (env->compstr);
1371 free (buf);
1372 }
1373
1374 /* If interrupted, dump the file to dead.letter. */
1375 if (int_cnt)
1376 {
1377 save_dead_message_env (env);
1378 return 1;
1379 }
1380
1381 /* In mailx compatibility mode, ask for Cc and Bcc after editing
1382 the body of the message */
1383 if (mailvar_is_true (mailvar_name_mailx))
1384 read_cc_bcc (env);
1385
1386 /* Prepare the header */
1387 if (mailvar_is_true (mailvar_name_useragent))
1388 mu_header_set_value (env->header, MU_HEADER_USER_AGENT, program_version, 1);
1389
1390 if (util_header_expand_aliases (&env->header) == 0)
1391 {
1392 mu_message_t msg = NULL;
1393 int status = 0;
1394 int sendit = (compose_header_get (env, MU_HEADER_TO, NULL) ||
1395 compose_header_get (env, MU_HEADER_CC, NULL) ||
1396 compose_header_get (env, MU_HEADER_BCC, NULL));
1397 do
1398 {
1399 status = mu_message_create (&msg, NULL);
1400 if (status)
1401 break;
1402
1403 /* Fill the body. */
1404 mu_stream_seek (env->compstr, 0, MU_SEEK_SET, NULL);
1405 status = fill_body (msg, env->compstr);
1406 if (status)
1407 break;
1408
1409 mu_message_set_header (msg, env->header, NULL);
1410 env->header = NULL;
1411
1412 status = add_attachments (env, &msg);
1413 if (status)
1414 break;
1415
1416 message_add_date (msg);
1417
1418 /* Save outgoing message */
1419 if (save_to)
1420 {
1421 mu_header_t hdr;
1422 char const *rcpt;
1423
1424 mu_message_get_header (msg, &hdr);
1425 if (mu_header_sget_value (hdr, MU_HEADER_TO, &rcpt) == 0)
1426 {
1427 mu_address_t addr = NULL;
1428 struct mu_address hint = MU_ADDRESS_HINT_INITIALIZER;
1429
1430 mu_address_create_hint (&addr, rcpt, &hint, 0);
1431 savefile = util_outfilename (addr);
1432 mu_address_destroy (&addr);
1433 }
1434 }
1435 util_save_outgoing (msg, savefile);
1436 if (savefile)
1437 free (savefile);
1438
1439 /* Check if we need to save the message to files or pipes. */
1440 if (env->outfiles)
1441 {
1442 int i;
1443 for (i = 0; i < env->nfiles; i++)
1444 {
1445 /* Pipe to a cmd. */
1446 if (env->outfiles[i][0] == '|')
1447 status = msg_to_pipe (env->outfiles[i] + 1, msg);
1448 /* Save to a file. */
1449 else
1450 {
1451 mu_mailbox_t mbx = NULL;
1452 status = mu_mailbox_create_default (&mbx,
1453 env->outfiles[i]);
1454 if (status == 0)
1455 {
1456 status = mu_mailbox_open (mbx, MU_STREAM_WRITE
1457 | MU_STREAM_CREAT);
1458 if (status == 0)
1459 {
1460 status = mu_mailbox_append_message (mbx, msg);
1461 if (status)
1462 mu_error (_("Cannot append message: %s"),
1463 mu_strerror (status));
1464 mu_mailbox_close (mbx);
1465 }
1466 mu_mailbox_destroy (&mbx);
1467 }
1468 if (status)
1469 mu_error (_("Cannot create mailbox %s: %s"),
1470 env->outfiles[i],
1471 mu_strerror (status));
1472 }
1473 }
1474 }
1475
1476 /* Do we need to Send the message on the wire? */
1477 if (status == 0 && sendit)
1478 {
1479 status = send_message (msg);
1480 if (status)
1481 {
1482 mu_error (_("cannot send message: %s"),
1483 mu_strerror (status));
1484 save_dead_message (msg);
1485 }
1486 }
1487 }
1488 while (0);
1489
1490 mu_stream_destroy (&env->compstr);
1491 mu_message_destroy (&msg, NULL);
1492 return status;
1493 }
1494 else
1495 save_dead_message_env (env);
1496 return 1;
1497 }
1498
1499 /* Starting with '|' '/' or not consider addresses and we cheat
1500 by adding '.' in the mix for none absolute path. */
1501 static int
isfilename(const char * p)1502 isfilename (const char *p)
1503 {
1504 if (p)
1505 if (*p == '/' || *p == '.' || *p == '|')
1506 return 1;
1507 return 0;
1508 }
1509
1510 /* FIXME: Should probably be in util.c. */
1511 /* Call popen(cmd) and write the message to it. */
1512 static int
msg_to_pipe(const char * cmd,mu_message_t msg)1513 msg_to_pipe (const char *cmd, mu_message_t msg)
1514 {
1515 mu_stream_t progstream, msgstream;
1516 int status, rc;
1517 char *argv[4];
1518
1519 argv[0] = getenv ("SHELL");
1520 if (!argv[0])
1521 argv[0] = "/bin/sh";
1522 argv[1] = "-c";
1523 argv[2] = (char*) cmd;
1524 argv[3] = NULL;
1525 status = mu_prog_stream_create (&progstream,
1526 argv[0],
1527 3, argv,
1528 0, NULL, MU_STREAM_WRITE);
1529 if (status)
1530 {
1531 mu_error (_("Cannot pipe to %s: %s"), cmd, mu_strerror (status));
1532 return status;
1533 }
1534
1535 mu_message_get_streamref (msg, &msgstream);
1536 status = mu_stream_copy (progstream, msgstream, 0, NULL);
1537 rc = mu_stream_close (progstream);
1538
1539 if (status == 0 && rc)
1540 status = rc;
1541
1542 mu_stream_destroy (&progstream);
1543 mu_stream_destroy (&msgstream);
1544
1545 if (status)
1546 {
1547 mu_error (_("Sending data to %s failed: %s"), cmd,
1548 mu_strerror (status));
1549 }
1550 return status;
1551 }
1552