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, &ltm);
1247 
1248   mu_strftime (buf, sizeof (buf), MU_DATETIME_FORM_RFC822, &ltm);
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