/* GNU Mailutils -- a suite of utilities for electronic mail Copyright (C) 1999-2021 Free Software Foundation, Inc. GNU Mailutils is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Mailutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Mailutils. If not, see . */ #include #include #include #include #include #include #include #include int truncate_opt; int from_filter; int recode_charset; char *charset; int fd_err; static struct mu_option decodemail_options[] = { { "truncate", 't', NULL, MU_OPTION_DEFAULT, N_("truncate the output mailbox, if it exists"), mu_c_bool, &truncate_opt }, { "charset", 'c', N_("CHARSET"), MU_OPTION_DEFAULT, N_("recode output to this charset"), mu_c_string, &charset }, { "recode", 'R', NULL, MU_OPTION_DEFAULT, N_("recode text parts to the current charset"), mu_c_bool, &recode_charset }, MU_OPTION_END }, *options[] = { decodemail_options, NULL }; struct mu_cli_setup cli = { .optv = options, .prog_doc = N_("GNU decodemail -- decode messages."), .prog_args = N_("[INBOX] [OUTBOX]") }; static char *decodemail_capa[] = { "debug", "mailbox", "locking", "mime", NULL }; char *charset; static void define_charset (void) { struct mu_lc_all lc_all = { .flags = 0 }; char *ep = getenv ("LC_ALL"); if (!ep) ep = getenv ("LANG"); if (ep && mu_parse_lc_all (ep, &lc_all, MU_LC_CSET) == 0) { charset = mu_strdup (lc_all.charset); mu_lc_all_free (&lc_all); } else charset = mu_strdup ("us-ascii"); } static mu_message_t message_decode (mu_message_t, mu_coord_t *, size_t); static void message_store_mbox (mu_message_t, mu_mailbox_t); static void message_store_stdout (mu_message_t, mu_mailbox_t); static void enable_log_prefix (int on) { int mode; mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_GET_MODE, &mode); if (on) mode |= MU_LOGMODE_PREFIX; else mode &= ~MU_LOGMODE_PREFIX; mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_SET_MODE, &mode); } static void set_log_prefix (mu_coord_t crd, size_t dim) { char *prefix = mu_coord_part_string (crd, dim); mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_SET_PREFIX, prefix); free (prefix); } void abend (int code) { if (fd_err) { struct rlimit rlim; getrlimit (RLIMIT_NOFILE, &rlim); rlim.rlim_cur += fd_err; mu_error (_("at least %lu file descriptors are needed to process this message"), (unsigned long) rlim.rlim_cur); } exit (code); } static void mailbox_truncate (mu_mailbox_t mbox) { int rc; mu_iterator_t itr; mu_mailbox_get_iterator (mbox, &itr); for (mu_iterator_first (itr); !mu_iterator_is_done (itr); mu_iterator_next (itr)) { mu_message_t m; mu_attribute_t a; if ((rc = mu_iterator_current (itr, (void **)&m)) != 0) { mu_error (_("can't get current message while truncating the mailbox: %s"), mu_strerror (rc)); exit (EX_OSERR); } mu_message_get_attribute (m, &a); mu_attribute_set_deleted (a); } mu_iterator_destroy (&itr); if ((rc = mu_mailbox_expunge (mbox)) != 0) { mu_error (_("error expunging destination mailbox: %s"), mu_strerror (rc)); exit (EX_OSERR); } } static void output_mbox_cleanup (void *arg) { if (arg) { mu_mailbox_t mbox = arg; mu_mailbox_unlock (mbox); } } int main (int argc, char **argv) { int rc; mu_mailbox_t imbox, ombox = NULL; char *imbox_name = NULL, *ombox_name = NULL; void (*message_store) (mu_message_t, mu_mailbox_t); mu_iterator_t itr; unsigned long i; int err = 0; mu_coord_t crd; /* Native Language Support */ MU_APP_INIT_NLS (); /* register the formats. */ mu_register_all_mbox_formats (); mu_register_extra_formats (); mu_auth_register_module (&mu_auth_tls_module); mu_cli_capa_register (&mu_cli_capa_mime); mu_cli (argc, argv, &cli, decodemail_capa, NULL, &argc, &argv); switch (argc) { case 2: ombox_name = argv[1]; case 1: imbox_name = argv[0]; break; case 0: break; default: mu_error (_("too many arguments; try %s --help for help"), mu_program_name); exit (EX_USAGE); } if (!charset && recode_charset) define_charset (); /* Open input mailbox */ rc = mu_mailbox_create_default (&imbox, imbox_name); if (rc != 0) { if (imbox_name) mu_error (_("could not create mailbox `%s': %s"), imbox_name, mu_strerror (rc)); else mu_error (_("could not create default mailbox: %s"), mu_strerror (rc)); abend (EX_OSERR); } rc = mu_mailbox_open (imbox, MU_STREAM_READ); if (rc) { mu_url_t url = NULL; mu_mailbox_get_url (imbox, &url); mu_error (_("could not open input mailbox `%s': %s"), mu_url_to_string (url), mu_strerror (rc)); abend (EX_NOINPUT); } /* Create output mailbox */ if (ombox_name) { mu_property_t prop; const char *type; rc = mu_mailbox_create_default (&ombox, ombox_name); if (rc != 0) { mu_error (_("could not create output mailbox `%s': %s"), ombox_name, mu_strerror (rc)); abend (EX_OSERR); } mu_onexit (output_mbox_cleanup, ombox); if (truncate_opt) { rc = mu_mailbox_open (ombox, MU_STREAM_RDWR); switch (rc) { case 0: mailbox_truncate (ombox); mu_mailbox_close (ombox); break; case ENOENT: case MU_ERR_NOENT: break; default: mu_error (_("could not open mailbox `%s': %s"), ombox_name, mu_strerror (rc)); abend (EX_OSERR); } } rc = mu_mailbox_open (ombox, MU_STREAM_APPEND|MU_STREAM_CREAT); if (rc) { mu_error (_("could not open mailbox `%s': %s"), ombox_name, mu_strerror (rc)); abend (EX_CANTCREAT); } if (mu_mailbox_get_property (ombox, &prop) == 0 && mu_property_sget_value (prop, "TYPE", &type) == 0 && strcmp (type, "MBOX") == 0) from_filter = 1; message_store = message_store_mbox; } else { message_store = message_store_stdout; from_filter = 1; } rc = mu_mailbox_get_iterator (imbox, &itr); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_mailbox_get_iterator", NULL, rc); abend (EX_SOFTWARE); } rc = mu_coord_alloc (&crd, 1); if (rc) mu_alloc_die (); enable_log_prefix (1); for (mu_iterator_first (itr), i = 1; !mu_iterator_is_done (itr); mu_iterator_next (itr), i++) { mu_message_t msg, newmsg; rc = mu_iterator_current (itr, (void **)&msg); if (rc) { mu_error (_("cannot read message %lu: %s"), i, mu_strerror (rc)); err = 1; continue; } crd[1] = i; fd_err = 0; newmsg = message_decode (msg, &crd, 1); message_store (newmsg, ombox); mu_message_unref (newmsg); mu_message_unref (msg); } enable_log_prefix (0); mu_mailbox_destroy (&imbox); mu_mailbox_sync (ombox); mu_mailbox_destroy (&ombox); if (err) abend (EX_UNAVAILABLE); exit (EX_OK); } static void message_store_mbox (mu_message_t msg, mu_mailbox_t mbx) { int rc = mu_mailbox_append_message (mbx, msg); if (rc) { mu_error (_("cannot store message: %s"), mu_strerror (rc)); switch (rc) { case MU_ERR_INVALID_EMAIL: case MU_ERR_EMPTY_ADDRESS: break; case EMFILE: fd_err++; /* FALLTHROUGH */ default: abend (EX_IOERR); } } } static void env_print (mu_message_t msg) { mu_envelope_t env; char const *buf; size_t len; mu_message_get_envelope (msg, &env); if (mu_envelope_sget_sender (env, &buf)) buf = "UNKNOWN"; mu_printf ("From %s ", buf); if (mu_envelope_sget_date (env, &buf)) { char datebuf[MU_DATETIME_FROM_LENGTH+1]; time_t t; struct tm *tm; t = time (NULL); tm = gmtime (&t); mu_strftime (datebuf, sizeof datebuf, MU_DATETIME_FROM, tm); buf = datebuf; } mu_printf ("%s", buf); len = strlen (buf); if (len > 1 && buf[len-1] != '\n') mu_printf ("\n"); } static void message_store_stdout (mu_message_t msg, mu_mailbox_t mbx) { mu_stream_t str; env_print (msg); mu_message_get_streamref (msg, &str); mu_stream_copy_nl (mu_strout, str, 0, NULL); mu_stream_destroy (&str); mu_printf ("\n"); } static inline int is_address_header (char const *name) { return !mu_c_strcasecmp (name, MU_HEADER_FROM) || !mu_c_strcasecmp (name, MU_HEADER_TO) || !mu_c_strcasecmp (name, MU_HEADER_CC) || !mu_c_strcasecmp (name, MU_HEADER_BCC); } static int qstring_needed (char const *s) { for (; *s; s++) { if (mu_isascii (*s) && !mu_istspec (*s)) continue; return 1; } return 0; } static void qstring_format (mu_stream_t stream, char const *s) { if (!s) return; if (qstring_needed (s)) { char const *cp; mu_stream_write (stream, "\"", 1, NULL); while (*(cp = mu_str_skip_cset_comp (s, "\\\""))) { mu_stream_write (stream, s, cp - s, NULL); mu_stream_write (stream, "\\", 1, NULL); mu_stream_write (stream, cp, 1, NULL); s = cp + 1; } if (*s) mu_stream_write (stream, s, strlen (s), NULL); mu_stream_write (stream, "\"", 1, NULL); } else mu_stream_write (stream, s, strlen (s), NULL); } static int address_decode (char const *name, char const *value, char const *charset, mu_header_t newhdr) { int rc; mu_address_t addr; mu_stream_t mstr; mu_transport_t trans[2]; rc = mu_memory_stream_create (&mstr, MU_STREAM_RDWR); if (rc) return rc; rc = mu_address_create (&addr, value); if (rc == 0) { mu_address_t cur; for (cur = addr; cur; cur = cur->next) { char *s; rc = mu_rfc2047_decode (charset, cur->personal, &s); if (rc == 0) { qstring_format (mstr, s); free (s); } else qstring_format (mstr, cur->personal); mu_stream_printf (mstr, " <%s>", cur->email); if (cur->next) mu_stream_write (mstr, ", ", 2, NULL); } mu_stream_write (mstr, "", 1, NULL); rc = mu_stream_err (mstr); if (rc == 0) { mu_stream_ioctl (mstr, MU_IOCTL_TRANSPORT, MU_IOCTL_OP_GET, trans); mu_header_append (newhdr, name, (char*)trans[0]); } mu_stream_destroy (&mstr); mu_address_destroy (&addr); } return rc; } /* * Decode a single message or message part. * * Arguments: * msg - Message or message part. * crd - Pointer to mu_coord_t object that keeps its location. * dim - Number of significant positions in crd. If it is 1, * msg is the message. If it is greater than 1, msg is * part of a MIME message. * * The function can reallocate crd to increase its actual dimension. * It can modify the coordinate positions starting from dim+1 (inclusive). */ static mu_message_t message_decode_nomime (mu_message_t msg) { mu_message_t newmsg; int rc; mu_stream_t str; mu_body_t body; mu_stream_t bstr; mu_header_t hdr, newhdr; mu_iterator_t itr; size_t i; char *content_type = NULL; mu_stream_stat_buffer stat; rc = message_body_stream (msg, from_filter, charset, &str); if (rc) return NULL; rc = mu_message_create (&newmsg, NULL); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_message_create", NULL, rc); abend (EX_OSERR); } rc = mu_message_get_body (newmsg, &body); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_body", NULL, rc); goto end; } rc = mu_body_get_streamref (body, &bstr); if (rc) { if (rc == EMFILE) fd_err++; mu_diag_funcall (MU_DIAG_ERROR, "mu_body_get_streamref", NULL, rc); goto end; } mu_stream_set_stat (bstr, MU_STREAM_STAT_MASK (MU_STREAM_STAT_IN8BIT), stat); rc = mu_stream_copy (bstr, str, 0, NULL); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_copy", NULL, rc); if (mu_stream_err (bstr)) { abend (EX_IOERR); } else { mu_stream_printf (bstr, "\n[decodemail: content decoding failed: %s]\n", mu_strerror (rc)); } } mu_stream_unref (bstr); mu_stream_unref (str); rc = mu_message_get_header (msg, &hdr); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_header", "msg", rc); goto end; } rc = mu_message_get_header (newmsg, &newhdr); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_header", "newmsg", rc); goto end; } rc = mu_header_get_iterator (hdr, &itr); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_header_get_iterator", NULL, rc); goto end; } for (mu_iterator_first (itr), i = 1; !mu_iterator_is_done (itr); mu_iterator_next (itr), i++) { const char *name; const char *value; char *s; rc = mu_iterator_current_kv (itr, (void const **) &name, (void**)&value); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_iterator_current_kv", NULL, rc); continue; } if (!mu_c_strcasecmp (name, MU_HEADER_CONTENT_TYPE)) { if (charset) { mu_content_type_t ct; struct mu_mime_param **pparam; char *vc = mu_strdup (value); size_t len; mu_string_unfold (vc, &len); rc = mu_content_type_parse_ext (vc, NULL, MU_CONTENT_TYPE_RELAXED | MU_CONTENT_TYPE_PARAM, &ct); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_content_type_parse_ext", vc, rc); free (vc); continue; } free (vc); rc = mu_assoc_install_ref (ct->param, "charset", &pparam); switch (rc) { case 0: *pparam = mu_zalloc (sizeof **pparam); break; case MU_ERR_EXISTS: free ((*pparam)->value); break; default: mu_diag_funcall (MU_DIAG_ERROR, "mu_assoc_install_ref", NULL, rc); abend (EX_IOERR); } (*pparam)->value = mu_strdup (charset); mu_content_type_format (ct, &content_type); mu_content_type_destroy (&ct); continue; } } else if (!mu_c_strcasecmp (name, MU_HEADER_CONTENT_TRANSFER_ENCODING)) continue; else if (is_address_header (name)) { if (address_decode (name, value, charset, newhdr)) mu_header_append (newhdr, name, value); continue; } rc = mu_rfc2047_decode (charset, value, &s); if (rc == 0) { mu_header_append (newhdr, name, s); free (s); } else mu_header_append (newhdr, name, value); } mu_iterator_destroy (&itr); rc = 0; mu_header_set_value (newhdr, MU_HEADER_CONTENT_TRANSFER_ENCODING, stat[MU_STREAM_STAT_IN8BIT] ? "8bit" : "7bit", 1); if (charset) { if (!content_type) mu_asprintf (&content_type, "text/plain; charset=%s", charset); mu_header_set_value (newhdr, MU_HEADER_CONTENT_TYPE, content_type, 1); free (content_type); } end: if (rc) { mu_message_unref (newmsg); newmsg = NULL; } return newmsg; } static mu_message_t message_decode_mime (mu_message_t msg, mu_coord_t *crd, size_t dim) { int rc; mu_message_t newmsg; size_t nparts, i; mu_mime_t mime; mu_header_t hdr, newhdr; mu_iterator_t itr; char *s; mu_content_type_t ct; /* FIXME: The following could be simplified if we could obtain a mime object from the message */ rc = mu_message_get_header (msg, &hdr); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_header", "msg", rc); return NULL; } rc = mu_header_aget_value_unfold (hdr, MU_HEADER_CONTENT_TYPE, &s); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_header_aget_value_unfold", MU_HEADER_CONTENT_TYPE, rc); return NULL; } rc = mu_content_type_parse_ext (s, NULL, MU_CONTENT_TYPE_RELAXED | MU_CONTENT_TYPE_PARAM, &ct); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_content_type_parse_ext", s, rc); free (s); return NULL; } free (s); if (!ct->subtype) { mu_content_type_destroy (&ct); return NULL; } rc = mu_mime_create_multipart (&mime, ct->subtype, ct->param); mu_content_type_destroy (&ct); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_mime_create_multipart", NULL, rc); return NULL; } rc = mu_message_get_num_parts (msg, &nparts); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_num_parts", NULL, rc); return NULL; } ++dim; if (dim > mu_coord_length (*crd)) { rc = mu_coord_realloc (crd, dim); if (rc) mu_alloc_die (); } for (i = 1; i <= nparts; i++) { mu_message_t msgpart, msgdec; (*crd)[dim] = i; rc = mu_message_get_part (msg, i, &msgpart); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_part", NULL, rc); mu_mime_unref (mime); return NULL; } msgdec = message_decode (msgpart, crd, dim); rc = mu_mime_add_part (mime, msgdec); mu_message_unref (msgdec); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_mime_add_part", NULL, rc); mu_mime_unref (mime); return NULL; } } --dim; rc = mu_mime_to_message (mime, &newmsg); mu_mime_unref (mime); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_mime_to_message", NULL, rc); return NULL; } /* Copy headers */ rc = mu_message_get_header (newmsg, &newhdr); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_header", "newmsg", rc); goto end; } rc = mu_header_get_iterator (hdr, &itr); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_header_get_iterator", NULL, rc); goto end; } for (mu_iterator_first (itr), i = 1; !mu_iterator_is_done (itr); mu_iterator_next (itr), i++) { const char *name; const char *value; char *s; rc = mu_iterator_current_kv (itr, (void const **) &name, (void**)&value); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_iterator_current_kv", NULL, rc); continue; } if (mu_c_strcasecmp (name, MU_HEADER_MIME_VERSION) == 0 || mu_c_strcasecmp (name, MU_HEADER_CONTENT_TYPE) == 0) continue; else if (is_address_header (name)) { if (address_decode (name, value, charset, newhdr)) mu_header_append (newhdr, name, value); continue; } rc = mu_rfc2047_decode (charset, value, &s); if (rc == 0) { mu_header_append (newhdr, name, s); free (s); } else mu_header_append (newhdr, name, value); } mu_iterator_destroy (&itr); rc = 0; end: if (rc) { mu_message_unref (newmsg); newmsg = NULL; } return newmsg; } static mu_message_t message_decode (mu_message_t msg, mu_coord_t *crd, size_t dim) { mu_message_t newmsg; int ismime = 0; int rc; set_log_prefix (*crd, dim); rc = mu_message_is_multipart (msg, &ismime); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_message_is_multipart", NULL, rc); newmsg = NULL; } else if (!ismime) { newmsg = message_decode_nomime (msg); } else { newmsg = message_decode_mime (msg, crd, dim); } if (!newmsg) { mu_message_ref (msg); return msg; } set_log_prefix (*crd, dim); if (dim == 1) { /* Copy envelope */ mu_envelope_t env, newenv; rc = mu_message_get_envelope (msg, &env); if (rc == 0) { char *sender = NULL, *date = NULL; if ((rc = mu_envelope_aget_sender (env, &sender)) != 0) { mu_diag_funcall (MU_DIAG_ERROR, "mu_envelope_aget_sender", NULL, rc); } else if ((rc = mu_envelope_aget_date (env, &date)) != 0) { free (sender); sender = NULL; mu_diag_funcall (MU_DIAG_ERROR, "mu_envelope_aget_date", NULL, rc); } if (sender) { if ((rc = mu_envelope_create (&newenv, newmsg)) == 0) { newenv->sender = sender; newenv->date = date; mu_message_set_envelope (newmsg, newenv, mu_message_get_owner (newmsg)); } else { free (sender); free (date); mu_diag_funcall (MU_DIAG_ERROR, "mu_envelope_create", NULL, rc); } } } else mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_envelope", NULL, rc); } return newmsg; }