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