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