/* 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;
}