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 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
20
21 #include "readmsg.h"
22 #include <fnmatch.h>
23 #include <regex.h>
24 #include "mailutils/cli.h"
25 #include "mailutils/mu_auth.h"
26 #include "mailutils/alloc.h"
27 #include "mu_umaxtostr.h"
28 #include "muaux.h"
29
30 #define WEEDLIST_SEPARATOR " :,"
31
32 static void print_unix_header (mu_message_t);
33 static void print_header (mu_message_t, int, int, char **);
34 static void print_body (mu_message_t, int);
35 static int string_starts_with (const char * s1, const char *s2);
36
37 int dbug = 0;
38 const char *mailbox_name = NULL;
39 const char *weedlist = NULL;
40 int no_header = 0;
41 int all_header = 0;
42 int form_feed = 0;
43 int show_all = 0;
44 int mime_decode = 0;
45 char *charset;
46
47 enum
48 {
49 PAT_EXACT,
50 PAT_GLOB,
51 PAT_REGEX
52 };
53
54 int pattern_type = PAT_EXACT;
55 int pattern_ci = 0;
56
57 static void *
generic_init(char const * pattern)58 generic_init (char const *pattern)
59 {
60 char *res;
61 if (pattern_ci)
62 unistr_downcase (pattern, &res);
63 else
64 res = mu_strdup (pattern);
65 return res;
66 }
67
68 static void
generic_free(void * p)69 generic_free (void *p)
70 {
71 free (p);
72 }
73
74 static int
pat_exact_match(void * pat,char const * text)75 pat_exact_match (void *pat, char const *text)
76 {
77 return (pattern_ci ? unistr_is_substring_dn : unistr_is_substring)
78 (text, pat);
79 }
80
81 static int
pat_glob_match(void * pat,char const * text)82 pat_glob_match (void *pat, char const *text)
83 {
84 return fnmatch (pat, text, 0) == 0;
85 }
86
87 static void *
pat_regex_init(char const * pattern)88 pat_regex_init (char const *pattern)
89 {
90 regex_t *rx = mu_alloc (sizeof (*rx));
91 int rc = regcomp (rx, pattern, REG_EXTENDED|REG_NOSUB
92 |(pattern_ci ? REG_ICASE : 0));
93 if (rc)
94 {
95 char errbuf[512];
96 regerror (rc, rx, errbuf, sizeof errbuf);
97 mu_error ("%s: %s", pattern, errbuf);
98 return NULL;
99 }
100 return rx;
101 }
102
103 static void
pat_regex_free(void * p)104 pat_regex_free (void *p)
105 {
106 regex_t *rx = p;
107 regfree (rx);
108 free (rx);
109 }
110
111 static int
pat_regex_match(void * pat,char const * str)112 pat_regex_match (void *pat, char const *str)
113 {
114 regex_t *rx = pat;
115 return regexec (rx, str, 0, NULL, 0) == 0;
116 }
117
118 static struct pattern_match_fun
119 {
120 void *(*pat_init) (char const *pattern);
121 int (*pat_match) (void *pat, char const *str);
122 void (*pat_free) (void *);
123 } pattern_match_tab[] = {
124 { generic_init, pat_exact_match, generic_free },
125 { generic_init, pat_glob_match, generic_free },
126 { pat_regex_init, pat_regex_match, pat_regex_free }
127 };
128
129 void *
pattern_init(char const * pattern)130 pattern_init (char const *pattern)
131 {
132 return pattern_match_tab[pattern_type].pat_init (pattern);
133 }
134
135 int
pattern_match(void * pat,char const * str)136 pattern_match (void *pat, char const *str)
137 {
138 return pattern_match_tab[pattern_type].pat_match (pat, str);
139 }
140
141 void
pattern_free(void * pat)142 pattern_free (void *pat)
143 {
144 return pattern_match_tab[pattern_type].pat_free (pat);
145 }
146
147 static void
cli_pattern_match(struct mu_parseopt * po,struct mu_option * opt,char const * arg)148 cli_pattern_match (struct mu_parseopt *po, struct mu_option *opt,
149 char const *arg)
150 {
151 switch (opt->opt_short)
152 {
153 case 'e':
154 pattern_type = PAT_EXACT;
155 break;
156
157 case 'g':
158 pattern_type = PAT_GLOB;
159 break;
160
161 case 'r':
162 pattern_type = PAT_REGEX;
163 break;
164
165 case 'i':
166 pattern_ci = 1;
167 }
168 }
169
170 static struct mu_option readmsg_options[] =
171 {
172 { "debug", 'd', NULL, MU_OPTION_DEFAULT,
173 N_("display debugging information"),
174 mu_c_incr, &dbug },
175 { "header", 'h', NULL, MU_OPTION_DEFAULT,
176 N_("display entire headers"),
177 mu_c_bool, &all_header },
178 { "weedlist", 'w', N_("LIST"), MU_OPTION_DEFAULT,
179 N_("list of header names separated by whitespace or commas"),
180 mu_c_string, &weedlist },
181 { "folder", 'f', N_("FOLDER"), MU_OPTION_DEFAULT,
182 N_("folder to use"), mu_c_string, &mailbox_name },
183 { "no-header", 'n', NULL, MU_OPTION_DEFAULT,
184 N_("exclude all headers"),
185 mu_c_bool, &no_header },
186 { "form-feeds", 'p', NULL, MU_OPTION_DEFAULT,
187 N_("output formfeeds between messages"),
188 mu_c_bool, &form_feed },
189 { "show-all-match", 'a', NULL, MU_OPTION_DEFAULT,
190 N_("print all messages matching pattern, not only the first"),
191 mu_c_bool, &show_all },
192 { "exact", 'e', NULL, MU_OPTION_DEFAULT,
193 N_("match exact string (default)"),
194 mu_c_int, NULL, cli_pattern_match },
195 { "glob", 'g', NULL, MU_OPTION_DEFAULT,
196 N_("match using globbing pattern"),
197 mu_c_int, NULL, cli_pattern_match },
198 { "regex", 'r', NULL, MU_OPTION_DEFAULT,
199 N_("match using POSIX regular expressions"),
200 mu_c_int, NULL, cli_pattern_match },
201 { "ignorecase", 'i', NULL, MU_OPTION_DEFAULT,
202 N_("case-insensitive matching"),
203 mu_c_int, NULL, cli_pattern_match },
204 { "mime", 'm', NULL, MU_OPTION_DEFAULT,
205 N_("decode MIME messages on output"),
206 mu_c_bool, &mime_decode },
207 MU_OPTION_END
208 }, *options[] = { readmsg_options, NULL };
209
210 struct mu_cfg_param readmsg_cfg_param[] = {
211 { "debug", mu_c_int, &dbug, 0, NULL,
212 N_("Set debug verbosity level.") },
213 { "header", mu_c_bool, &all_header, 0, NULL,
214 N_("Display entire headers.") },
215 { "weedlist", mu_c_string, &weedlist, 0, NULL,
216 N_("Display only headers from this list. Argument is a list of header "
217 "names separated by whitespace or commas."),
218 N_("list") },
219 { "folder", mu_c_string, &mailbox_name, 0, NULL,
220 N_("Read messages from this folder.") },
221 { "no-header", mu_c_bool, &no_header, 0, NULL,
222 N_("Exclude all headers.") },
223 { "form-feeds", mu_c_bool, &form_feed, 0, NULL,
224 N_("Output formfeed character between messages.") },
225 { "show-all-match", mu_c_bool, &show_all, 0, NULL,
226 N_("Print all messages matching pattern, not only the first.") },
227 { NULL }
228 };
229
230 struct mu_cli_setup cli = {
231 options,
232 readmsg_cfg_param,
233 N_("GNU readmsg -- print messages."),
234 NULL
235 };
236
237 static char *readmsg_capa[] = {
238 "debug",
239 "mailbox",
240 "locking",
241 NULL
242 };
243
244 static int
string_starts_with(const char * s1,const char * s2)245 string_starts_with (const char * s1, const char *s2)
246 {
247 const unsigned char * p1 = (const unsigned char *) s1;
248 const unsigned char * p2 = (const unsigned char *) s2;
249 int n = 0;
250
251 /* Sanity. */
252 if (s1 == NULL || s2 == NULL)
253 return n;
254
255 while (*p1 && *p2)
256 {
257 if ((n = mu_toupper (*p1++) - mu_toupper (*p2++)) != 0)
258 break;
259 }
260 return (n == 0);
261 }
262
263 static void
print_unix_header(mu_message_t message)264 print_unix_header (mu_message_t message)
265 {
266 const char *buf;
267 size_t size;
268 mu_envelope_t envelope = NULL;
269
270 mu_message_get_envelope (message, &envelope);
271
272 if (mu_envelope_sget_sender (envelope, &buf))
273 buf = "UNKNOWN";
274 mu_printf ("From %s ", buf);
275
276 if (mu_envelope_sget_date (envelope, &buf))
277 {
278 char datebuf[MU_DATETIME_FROM_LENGTH+1];
279 time_t t;
280 struct tm *tm;
281
282 t = time (NULL);
283 tm = gmtime (&t);
284 mu_strftime (datebuf, sizeof datebuf, MU_DATETIME_FROM, tm);
285 buf = datebuf;
286 }
287
288 mu_printf ("%s", buf);
289 size = strlen (buf);
290 if (size > 1 && buf[size-1] != '\n')
291 mu_printf ("\n");
292 }
293
294 static void
print_header_field(const char * name,const char * value)295 print_header_field (const char *name, const char *value)
296 {
297 if (mime_decode)
298 {
299 int rc;
300 char *s;
301
302 rc = mu_rfc2047_decode (charset, value, &s);
303 if (rc == 0)
304 {
305 mu_printf ("%s: %s\n", name, s);
306 free (s);
307 }
308 }
309 else
310 mu_printf ("%s: %s\n", name, value);
311 }
312
313
314 static void
print_header(mu_message_t message,int unix_header,int weedc,char ** weedv)315 print_header (mu_message_t message, int unix_header, int weedc, char **weedv)
316 {
317 mu_header_t header = NULL;
318
319 mu_message_get_header (message, &header);
320
321 if (weedc == 0)
322 {
323 mu_stream_t stream = NULL;
324
325 mu_header_get_streamref (header, &stream);
326 mu_stream_copy (mu_strout, stream, 0, NULL);
327 mu_stream_destroy (&stream);
328 }
329 else
330 {
331 int status;
332 size_t count;
333 size_t i;
334
335 status = mu_header_get_field_count (header, &count);
336 if (status)
337 {
338 mu_error (_("cannot get number of headers: %s"),
339 mu_strerror (status));
340 return;
341 }
342
343 for (i = 1; i <= count; i++)
344 {
345 int j;
346 const char *name = NULL;
347 const char *value = NULL;
348
349 mu_header_sget_field_name (header, i, &name);
350 mu_header_sget_field_value (header, i, &value);
351 for (j = 0; j < weedc; j++)
352 {
353 if (weedv[j][0] == '!')
354 {
355 if (string_starts_with (name, weedv[j]+1))
356 break;
357 }
358 else if (strcmp (weedv[j], "*") == 0 ||
359 string_starts_with (name, weedv[j]))
360 {
361 print_header_field (name, value);
362 }
363 }
364 }
365 mu_printf ("\n");
366 }
367 }
368
369 static void
print_body_simple(mu_message_t message,int unix_header)370 print_body_simple (mu_message_t message, int unix_header)
371 {
372 int status;
373 mu_body_t body = NULL;
374 mu_stream_t stream = NULL;
375
376 mu_message_get_body (message, &body);
377
378 status = mu_body_get_streamref (body, &stream);
379 if (status)
380 {
381 mu_error (_("cannot get body stream: %s"), mu_strerror (status));
382 return;
383 }
384 mu_stream_copy (mu_strout, stream, 0, NULL);
385 mu_stream_destroy (&stream);
386 }
387
388 static void
print_body_decode(mu_message_t message,int unix_header)389 print_body_decode (mu_message_t message, int unix_header)
390 {
391 int rc;
392 mu_iterator_t itr;
393
394 rc = mu_message_get_iterator (message, &itr);
395 if (rc)
396 {
397 mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_iterator", NULL, rc);
398 exit (2);
399 }
400
401 for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
402 mu_iterator_next (itr))
403 {
404 mu_message_t partmsg;
405 mu_stream_t str;
406 mu_coord_t crd;
407
408 rc = mu_iterator_current_kv (itr, (const void**)&crd, (void**)&partmsg);
409 if (rc)
410 {
411 mu_diag_funcall (MU_DIAG_ERROR, "mu_iterator_current", NULL, rc);
412 continue;
413 }
414
415 rc = message_body_stream (partmsg, unix_header, charset, &str);
416 if (rc == 0)
417 {
418 mu_stream_copy (mu_strout, str, 0, NULL);
419 mu_stream_destroy (&str);
420 }
421 else if (rc == MU_ERR_USER0)
422 {
423 char *s = mu_coord_string (crd);
424 mu_stream_printf (mu_strout,
425 "[part %s is a binary attachment: not shown]\n",
426 s);
427 free (s);
428 }
429 }
430 mu_iterator_destroy (&itr);
431 }
432
433 static void
print_body(mu_message_t message,int unix_header)434 print_body (mu_message_t message, int unix_header)
435 {
436 if (mime_decode)
437 print_body_decode (message, unix_header);
438 else
439 print_body_simple (message, unix_header);
440 }
441
442 static void
define_charset(void)443 define_charset (void)
444 {
445 struct mu_lc_all lc_all = { .flags = 0 };
446 char *ep = getenv ("LC_ALL");
447 if (!ep)
448 ep = getenv ("LANG");
449
450 if (ep && mu_parse_lc_all (ep, &lc_all, MU_LC_CSET) == 0)
451 {
452 charset = mu_strdup (lc_all.charset);
453 mu_lc_all_free (&lc_all);
454 }
455 else
456 charset = mu_strdup ("us-ascii");
457 }
458
459 int
main(int argc,char ** argv)460 main (int argc, char **argv)
461 {
462 int status;
463 int *set = NULL;
464 int n = 0;
465 int i;
466 mu_mailbox_t mbox = NULL;
467 struct mu_wordsplit ws;
468 char **weedv;
469 int weedc = 0;
470 int unix_header = 0;
471
472 /* Native Language Support */
473 MU_APP_INIT_NLS ();
474
475 /* register the formats. */
476 mu_register_all_mbox_formats ();
477 mu_register_extra_formats ();
478
479 mu_auth_register_module (&mu_auth_tls_module);
480
481 mu_cli (argc, argv, &cli, readmsg_capa, NULL, &argc, &argv);
482
483 if (argc == 0)
484 {
485 mu_error (_("not enough arguments"));
486 exit (1);
487 }
488
489 define_charset ();
490
491 status = mu_mailbox_create_default (&mbox, mailbox_name);
492 if (status != 0)
493 {
494 if (mailbox_name)
495 mu_error (_("could not create mailbox `%s': %s"),
496 mailbox_name,
497 mu_strerror(status));
498 else
499 mu_error (_("could not create default mailbox: %s"),
500 mu_strerror(status));
501 exit (2);
502 }
503
504 /* Debuging Trace. */
505 if (dbug)
506 {
507 mu_debug_set_category_level (MU_DEBCAT_MAILBOX,
508 MU_DEBUG_LEVEL_UPTO (MU_DEBUG_PROT));
509 }
510
511 status = mu_mailbox_open (mbox, MU_STREAM_READ);
512 if (status != 0)
513 {
514 mu_url_t url = NULL;
515
516 mu_mailbox_get_url (mbox, &url);
517 mu_error (_("could not open mailbox `%s': %s"),
518 mu_url_to_string (url), mu_strerror (status));
519 exit (2);
520 }
521
522 if (all_header)
523 {
524 unix_header = 1;
525 if (mime_decode)
526 weedlist = "!MIME-Version !Content- *";
527 else
528 weedlist = "";
529 }
530 else if (weedlist == NULL)
531 weedlist = "Date To Cc Subject From Apparently-";
532
533 ws.ws_delim = WEEDLIST_SEPARATOR;
534 status = mu_wordsplit (weedlist, &ws, MU_WRDSF_DEFFLAGS | MU_WRDSF_DELIM);
535 if (status)
536 {
537 mu_error (_("cannot parse weedlist: %s"), mu_wordsplit_strerror (&ws));
538 exit (2);
539 }
540
541 if (ws.ws_wordc)
542 {
543 for (i = 0; i < ws.ws_wordc; i++)
544 {
545 if (mu_c_strcasecmp (ws.ws_wordv[i], "From_") == 0)
546 {
547 int j;
548 unix_header = 1;
549 free (ws.ws_wordv[i]);
550 for (j = i; j < ws.ws_wordc; j++)
551 ws.ws_wordv[j] = ws.ws_wordv[j+1];
552 ws.ws_wordc--;
553 if (ws.ws_wordc == 0 && !all_header)
554 no_header = 1;
555 }
556 }
557 weedc = ws.ws_wordc;
558 weedv = ws.ws_wordv;
559 }
560
561 /* Build an array containing the message number. */
562 msglist (mbox, show_all, argc, argv, &set, &n);
563
564 for (i = 0; i < n; ++i)
565 {
566 mu_message_t msg = NULL;
567
568 status = mu_mailbox_get_message (mbox, set[i], &msg);
569 if (status != 0)
570 {
571 mu_error ("mu_mailbox_get_message: %s",
572 mu_strerror (status));
573 exit (2);
574 }
575
576 if (unix_header)
577 print_unix_header (msg);
578
579 if (!no_header)
580 print_header (msg, unix_header, weedc, weedv);
581
582 print_body (msg, unix_header);
583 mu_printf (form_feed ? "\f" : "\n");
584 }
585
586 mu_printf ("\n");
587
588 mu_mailbox_close (mbox);
589 mu_mailbox_destroy (&mbox);
590 return 0;
591 }
592