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 "mailcap.h"
19 
20 /*
21   FIXME:
22  decode, this is temporary, until the API on how to present
23  mime/attachements etc is less confusing.
24  */
25 
26 struct decode_closure
27 {
28   int select_hdr;
29 };
30 
31 static int print_stream (mu_stream_t, mu_stream_t);
32 static int display_message (mu_message_t, msgset_t *msgset, void *closure);
33 static int display_submessage (struct mime_descend_closure *closure,
34 			       void *data);
35 static void get_content_encoding (mu_header_t hdr, char **value);
36 static void run_metamail (const char *mailcap, mu_message_t mesg);
37 
38 int
mail_decode(int argc,char ** argv)39 mail_decode (int argc, char **argv)
40 {
41   msgset_t *msgset;
42   struct decode_closure decode_closure;
43 
44   if (msgset_parse (argc, argv, MSG_NODELETED|MSG_SILENT, &msgset))
45     return 1;
46 
47   decode_closure.select_hdr = mu_islower (argv[0][0]);
48 
49   util_msgset_iterate (msgset, display_message, (void *)&decode_closure);
50 
51   msgset_free (msgset);
52   return 0;
53 }
54 
55 int
display_message(mu_message_t mesg,msgset_t * msgset,void * arg)56 display_message (mu_message_t mesg, msgset_t *msgset, void *arg)
57 {
58   struct decode_closure *closure = arg;
59   mu_attribute_t attr = NULL;
60   struct mime_descend_closure mclos;
61 
62   mu_message_get_attribute (mesg, &attr);
63   if (mu_attribute_is_deleted (attr))
64     return 1;
65 
66   mclos.hints = closure->select_hdr ? MDHINT_SELECTED_HEADERS : 0;
67   mclos.msgset = msgset;
68   mclos.message = mesg;
69   mclos.type = NULL;
70   mclos.encoding = NULL;
71   mclos.parent = NULL;
72 
73   mime_descend (&mclos, display_submessage, NULL);
74 
75   /* Mark enclosing message as read */
76   if (mu_mailbox_get_message (mbox, msgset_msgno (msgset), &mesg) == 0)
77     util_mark_read (mesg);
78 
79   return 0;
80 }
81 
82 static void
display_headers(mu_stream_t out,mu_message_t mesg,const msgset_t * msgset MU_ARG_UNUSED,int select_hdr)83 display_headers (mu_stream_t out, mu_message_t mesg,
84                  const msgset_t *msgset MU_ARG_UNUSED,
85 		 int select_hdr)
86 {
87   mu_header_t hdr = NULL;
88 
89   /* Print the selected headers only.  */
90   if (select_hdr)
91     {
92       size_t num = 0;
93       size_t i = 0;
94       const char *sptr;
95 
96       mu_message_get_header (mesg, &hdr);
97       mu_header_get_field_count (hdr, &num);
98       for (i = 1; i <= num; i++)
99 	{
100 	  if (mu_header_sget_field_name (hdr, i, &sptr))
101 	    continue;
102 	  if (mail_header_is_visible (sptr))
103 	    {
104 	      mu_stream_printf (out, "%s: ", sptr);
105 	      if (mu_header_sget_field_value (hdr, i, &sptr))
106 		sptr = "";
107 	      mu_stream_printf (out, "%s\n", sptr);
108 	    }
109 	}
110       mu_stream_printf (out, "\n");
111     }
112   else /* Print displays all headers.  */
113     {
114       mu_stream_t stream = NULL;
115       if (mu_message_get_header (mesg, &hdr) == 0
116 	  && mu_header_get_streamref (hdr, &stream) == 0)
117 	{
118 	  print_stream (stream, out);
119 	  mu_stream_destroy (&stream);
120 	}
121     }
122 }
123 
124 static void
display_part_header(mu_stream_t str,const msgset_t * msgset,const char * type,const char * encoding)125 display_part_header (mu_stream_t str, const msgset_t *msgset,
126 		     const char *type, const char *encoding)
127 {
128   int size = util_screen_columns () - 3;
129   unsigned int i;
130   char *msp;
131 
132   mu_stream_printf (str, "+");
133   for (i = 0; (int)i <= size; i++)
134     mu_stream_printf (str, "-");
135   mu_stream_printf (str, "+");
136   mu_stream_printf (str, "\n");
137   msp = msgset_str (msgset);
138   mu_stream_printf (str, _("| Message=%s"), msp);
139   free (msp);
140   mu_stream_printf (str, "\n");
141 
142   mu_stream_printf (str, _("| Type=%s\n"), type);
143   mu_stream_printf (str, _("| Encoding=%s\n"), encoding);
144   mu_stream_printf (str, "+");
145   for (i = 0; i <= size; i++)
146     mu_stream_printf (str, "-");
147   mu_stream_printf (str, "+");
148   mu_stream_printf (str, "\n");
149 }
150 
151 int
mime_descend(struct mime_descend_closure * closure,mime_descend_fn fun,void * data)152 mime_descend (struct mime_descend_closure *closure,
153 	      mime_descend_fn fun, void *data)
154 {
155   int status = 0;
156   mu_header_t hdr = NULL;
157   char *type;
158   char *encoding;
159   int ismime = 0;
160   struct mime_descend_closure subclosure;
161 
162   mu_message_get_header (closure->message, &hdr);
163   util_get_hdr_value (hdr, MU_HEADER_CONTENT_TYPE, &type);
164   if (!type)
165     type = mu_strdup ("text/plain");
166   get_content_encoding (hdr, &encoding);
167 
168   closure->type = type;
169   closure->encoding = encoding;
170 
171   subclosure.hints = 0;
172   subclosure.parent = closure;
173 
174   mu_message_is_multipart (closure->message, &ismime);
175   if (ismime)
176     {
177       unsigned int j;
178       size_t nparts;
179 
180       status = mu_message_get_num_parts (closure->message, &nparts);
181       if (status)
182 	{
183 	  mu_diag_funcall (MU_DIAG_ERR, "mu_message_get_num_parts", NULL,
184 			   status);
185 	}
186       else
187 	{
188 	  for (j = 1; j <= nparts; j++)
189 	    {
190 	      mu_message_t message = NULL;
191 
192 	      if (mu_message_get_part (closure->message, j, &message) == 0)
193 		{
194 		  msgset_t *set = msgset_expand (msgset_dup (closure->msgset),
195 						 msgset_make_1 (j));
196 		  subclosure.msgset = set;
197 		  subclosure.message = message;
198 		  status = mime_descend (&subclosure, fun, data);
199 		  msgset_free (set);
200 		  if (status)
201 		    break;
202 		}
203 	    }
204 	}
205     }
206   else if (mu_c_strncasecmp (type, "message/rfc822", 14) == 0)
207     {
208       mu_message_t submsg = NULL;
209 
210       switch (mu_message_unencapsulate (closure->message, &submsg, NULL))
211 	{
212 	case 0:
213 	  subclosure.hints = MDHINT_SELECTED_HEADERS;
214 	  subclosure.msgset = closure->msgset;
215 	  subclosure.message = submsg;
216 	  status = mime_descend (&subclosure, fun, data);
217 	  break;
218 
219 	case MU_ERR_INVALID_EMAIL:
220 	  /*
221 	   * The mu_message_unencapsulate function returns this code
222 	   * if it was unable to parse the message body as a RFC822
223 	   * message (this code is propagated from mu_stream_to_message,
224 	   * which does the actual work).
225 	   *
226 	   * If the enclosing messgae(part) is of message/digest type, it
227 	   * is possible that messages are packed into it without MIME
228 	   * headers.  That violates RFC 1341, but such digests are
229 	   * reported to exist (re. email conversation with Karl on
230 	   * 2020-08-07, <202008070138.0771cfHn003390@freefriends.org>).
231 	   *
232 	   * Try to see whether the part has one of the normal RFC822 headers
233 	   * and if so treat it as the message.  Otherwise, treat it as
234 	   * text/plain.
235 	   *
236 	   * FIXME: Do all this only if the upper-level message is of
237 	   * message/digest type.
238 	   */
239 	  {
240 	    char *names[] = { "From", "To", "Subject" };
241 	    mu_header_t hdr;
242 
243 	    if (mu_message_get_header (closure->message, &hdr) == 0 &&
244 		mu_header_sget_firstof (hdr, names, NULL, NULL) == 0)
245 	      {
246 		mu_stream_t str;
247 		if (mu_message_get_streamref (closure->message, &str) == 0)
248 		  {
249 		    status = mu_stream_to_message (str, &submsg);
250 		    mu_stream_unref (str);
251 		    if (status == 0)
252 		      {
253 			mu_header_t subhdr;
254 			if (mu_message_get_header (submsg, &subhdr) == 0)
255 			  {
256 			    mu_header_remove (subhdr,
257 					      MU_HEADER_CONTENT_TYPE, 1);
258 			    subclosure.hints = MDHINT_SELECTED_HEADERS;
259 			    subclosure.msgset = closure->msgset;
260 			    subclosure.message = submsg;
261 			    status = mime_descend (&subclosure, fun, data);
262 			    break;
263 			  }
264 		      }
265 		  }
266 	      }
267 	  }
268 	  /* FALLTHROUGH */
269 
270 	default:
271 	  /* Treat as text/plain */
272 	  status = fun (closure, data);
273 	}
274     }
275   else
276     status = fun (closure, data);
277 
278   closure->type = NULL;
279   closure->encoding = NULL;
280 
281   free (type);
282   free (encoding);
283 
284   return status;
285 }
286 
287 static int
display_submessage(struct mime_descend_closure * closure,void * data)288 display_submessage (struct mime_descend_closure *closure, void *data)
289 {
290   char *tmp;
291 
292   if (mailvar_get (&tmp, mailvar_name_metamail, mailvar_type_string, 0) == 0)
293     {
294       /* If `metamail' is set to a string, treat it as command line
295 	 of external metamail program. */
296       run_metamail (tmp, closure->message);
297     }
298   else
299     {
300       int builtin_display = 1;
301       mu_body_t body = NULL;
302       mu_stream_t b_stream = NULL;
303       mu_stream_t d_stream = NULL;
304       mu_stream_t stream = NULL;
305       mu_header_t hdr = NULL;
306 
307       mu_message_get_body (closure->message, &body);
308       mu_message_get_header (closure->message, &hdr);
309       mu_body_get_streamref (body, &b_stream);
310 
311       /* Can we decode.  */
312       if (mu_filter_create (&d_stream, b_stream, closure->encoding,
313 			    MU_FILTER_DECODE,
314 			    MU_STREAM_READ) == 0)
315         {
316           mu_stream_unref (b_stream);
317 	  stream = d_stream;
318         }
319       else
320 	stream = b_stream;
321 
322       display_part_header (mu_strout,
323 			   closure->msgset,
324 			   closure->type, closure->encoding);
325 
326       /* If `metamail' is set to true, enable internal mailcap
327 	 support */
328       if (mailvar_is_true (mailvar_name_metamail))
329 	{
330 	  char *no_ask = NULL;
331 
332 	  mailvar_get (&no_ask, mailvar_name_mimenoask, mailvar_type_string, 0);
333 	  builtin_display = display_stream_mailcap (NULL, stream, hdr, no_ask,
334 						    interactive, 0,
335 						    MU_DEBCAT_APP);
336 	}
337 
338       if (builtin_display)
339 	{
340 	  size_t lines = 0;
341 	  mu_stream_t str;
342 
343 	  mu_message_lines (closure->message, &lines);
344 	  str = open_pager (lines);
345 
346 	  display_headers (str, closure->message, closure->msgset,
347 			   closure->hints & MDHINT_SELECTED_HEADERS);
348 
349 	  print_stream (stream, str);
350 
351 	  mu_stream_unref (str);
352 	}
353       mu_stream_destroy (&stream);
354     }
355 
356   return 0;
357 }
358 
359 /* FIXME: Try to use mu_stream_copy instead */
360 static int
print_stream(mu_stream_t stream,mu_stream_t out)361 print_stream (mu_stream_t stream, mu_stream_t out)
362 {
363   char buffer[512];
364   size_t n = 0;
365 
366   while (mu_stream_read (stream, buffer, sizeof (buffer) - 1, &n) == 0
367 	 && n != 0)
368     {
369       if (ml_got_interrupt())
370 	{
371 	  mu_error(_("\nInterrupt"));
372 	  break;
373 	}
374       buffer[n] = '\0';
375       mu_stream_printf (out, "%s", buffer);
376     }
377   return 0;
378 }
379 
380 static void
get_content_encoding(mu_header_t hdr,char ** value)381 get_content_encoding (mu_header_t hdr, char **value)
382 {
383   char *encoding;
384   util_get_hdr_value (hdr, MU_HEADER_CONTENT_TRANSFER_ENCODING, &encoding);
385   if (encoding == NULL || *encoding == '\0')
386     {
387       if (encoding)
388 	free (encoding);
389       encoding = mu_strdup ("7bit"); /* Default.  */
390     }
391   *value = encoding;
392 }
393 
394 /* Run `metamail' program MAILCAP_CMD on the message MESG */
395 static void
run_metamail(const char * mailcap_cmd,mu_message_t mesg)396 run_metamail (const char *mailcap_cmd, mu_message_t mesg)
397 {
398   pid_t pid;
399   struct sigaction ignore;
400   struct sigaction saveintr;
401   struct sigaction savequit;
402   sigset_t chldmask;
403   sigset_t savemask;
404 
405   ignore.sa_handler = SIG_IGN;	/* ignore SIGINT and SIGQUIT */
406   ignore.sa_flags = 0;
407   sigemptyset (&ignore.sa_mask);
408   if (sigaction (SIGINT, &ignore, &saveintr) < 0)
409     {
410       mu_error ("sigaction: %s", strerror (errno));
411       return;
412     }
413   if (sigaction (SIGQUIT, &ignore, &savequit) < 0)
414     {
415       mu_error ("sigaction: %s", strerror (errno));
416       sigaction (SIGINT, &saveintr, NULL);
417       return;
418     }
419 
420   sigemptyset (&chldmask);	/* now block SIGCHLD */
421   sigaddset (&chldmask, SIGCHLD);
422 
423   if (sigprocmask (SIG_BLOCK, &chldmask, &savemask) < 0)
424     {
425       sigaction (SIGINT, &saveintr, NULL);
426       sigaction (SIGQUIT, &savequit, NULL);
427       return;
428     }
429 
430   pid = fork ();
431   if (pid < 0)
432     {
433       mu_error ("fork: %s", strerror (errno));
434     }
435   else if (pid == 0)
436     {
437       /* Child process */
438       int status;
439       mu_stream_t stream = NULL;
440 
441       do /* Fake loop to avoid gotos */
442 	{
443 	  mu_stream_t pstr;
444 	  char *no_ask;
445 
446 	  setenv ("METAMAIL_PAGER", getenv ("PAGER"), 0);
447 	  if (mailvar_get (&no_ask, mailvar_name_mimenoask,
448 			   mailvar_type_string, 0))
449 	    setenv ("MM_NOASK", no_ask, 1);
450 
451 	  status = mu_message_get_streamref (mesg, &stream);
452 	  if (status)
453 	    {
454 	      mu_error ("mu_message_get_streamref: %s", mu_strerror (status));
455 	      break;
456 	    }
457 
458 	  status = mu_command_stream_create (&pstr, mailcap_cmd,
459 					     MU_STREAM_WRITE);
460 	  if (status)
461 	    {
462 	      mu_error ("mu_command_stream_create: %s", mu_strerror (status));
463 	      break;
464 	    }
465 
466 	  mu_stream_copy (pstr, stream, 0, NULL);
467 	  mu_stream_close (pstr);
468 	  mu_stream_destroy (&pstr);
469 	  exit (0);
470 	}
471       while (0);
472 
473       abort ();
474     }
475   else
476     {
477       int status;
478       /* Master process */
479 
480       while (waitpid (pid, &status, 0) < 0)
481 	if (errno != EINTR)
482 	  break;
483     }
484 
485   /* Restore the signal handlers */
486   sigaction (SIGINT, &saveintr, NULL);
487   sigaction (SIGQUIT, &savequit, NULL);
488   sigprocmask (SIG_SETMASK, &savemask, NULL);
489 }
490 
491 /* print_message_body(msg, out, stat)
492  *
493  * Prints the body of the message MSG to the output stream OUT.  If
494  * the Content-Transfer-Encoding header is set, the body is decoded.
495  * If stat is not NULL, it must point to an array of two size_t.
496  * Upon return stat[0] will contain the number of bytes and stat[1]
497  * the number of newline characters written to the output.
498  */
499 int
print_message_body(mu_message_t msg,mu_stream_t out,size_t * stat)500 print_message_body (mu_message_t msg, mu_stream_t out, size_t *stat)
501 {
502   int rc;
503   mu_header_t hdr;
504   mu_body_t body;
505   mu_stream_t d_stream;
506   mu_stream_t stream;
507   char *encoding = NULL;
508   mu_stream_stat_buffer sb;
509 
510   rc = mu_message_get_header (msg, &hdr);
511   if (rc)
512     {
513       mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_header",
514 		       NULL, rc);
515       return rc;
516     }
517 
518   rc = mu_message_get_body (msg, &body);
519   if (rc)
520     {
521       mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_body",
522 		       NULL, rc);
523       return rc;
524     }
525 
526   rc = mu_body_get_streamref (body, &stream);
527   if (rc)
528     {
529       mu_diag_funcall (MU_DIAG_ERROR, "mu_body_get_streamref",
530 		       NULL, rc);
531       return rc;
532     }
533 
534   util_get_hdr_value (hdr, MU_HEADER_CONTENT_TRANSFER_ENCODING, &encoding);
535   if (encoding == NULL || *encoding == '\0')
536     /* No need to filter */;
537   else if ((rc = mu_filter_create (&d_stream, stream, encoding,
538 				   MU_FILTER_DECODE, MU_STREAM_READ)) == 0)
539     {
540       mu_stream_unref (stream);
541       stream = d_stream;
542     }
543   else
544     {
545       mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_body",
546 		       encoding, rc);
547       /* FIXME: continue anyway? */
548     }
549 
550   if (stat)
551     {
552       mu_stream_set_stat (stream,
553 			  MU_STREAM_STAT_MASK (MU_STREAM_STAT_IN) |
554 			  MU_STREAM_STAT_MASK (MU_STREAM_STAT_INLN),
555 			  sb);
556     }
557   rc = mu_stream_copy (out, stream, 0, NULL);
558 
559   mu_stream_destroy (&stream);
560   free (encoding);
561 
562   if (rc)
563     {
564       mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_copy",
565 		       encoding, rc);
566     }
567   else if (stat)
568     {
569       stat[0] = sb[MU_STREAM_STAT_IN];
570       stat[1] = sb[MU_STREAM_STAT_INLN];
571     }
572 
573   return rc;
574 }
575