/* 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 "mail.h"
#include
#include
#include
#include
static int isfilename (const char *);
static int msg_to_pipe (const char *cmd, mu_message_t msg);
int multipart_alternative;
/* Additional message headers */
struct add_header
{
int mode;
char *name;
char *value;
};
static mu_list_t add_header_list;
static int
seed_headers (void *item, void *data)
{
struct add_header *hp = item;
compose_env_t *env = data;
compose_header_set (env, hp->name, hp->value, hp->mode);
return 0;
}
static int
list_headers (void *item, void *data)
{
struct add_header *hp = item;
char *name = data;
if (!name || strcmp (name, hp->name) == 0)
{
mu_printf ("%s: %s\n", hp->name, hp->value);
}
return 0;
}
static void
add_header (char *name, char *value, int mode)
{
struct add_header *hp;
if (!add_header_list)
{
int rc = mu_list_create (&add_header_list);
if (rc)
{
mu_error (_("Cannot create header list: %s"), mu_strerror (rc));
exit (1);
}
}
hp = mu_alloc (sizeof (*hp));
hp->mode = mode;
hp->name = name;
hp->value = value;
mu_list_append (add_header_list, hp);
}
void
send_append_header (char const *text)
{
char *p;
size_t len;
char *name;
p = strchr (text, ':');
if (!p)
{
mu_error (_("Invalid header: %s"), text);
return;
}
len = p - text;
name = mu_alloc (len + 1);
memcpy (name, text, len);
name[len] = 0;
for (p++; *p && mu_isspace (*p); p++)
;
add_header (name, mu_strdup (p), COMPOSE_APPEND);
}
void
send_append_header2 (char const *name, char const *value, int mode)
{
add_header (mu_strdup (name), mu_strdup (value), mode);
}
int
mail_sendheader (int argc, char **argv)
{
if (argc == 1)
mu_list_foreach (add_header_list, list_headers, NULL);
else if (argc == 2)
{
if (strchr (argv[1], ':'))
send_append_header (argv[1]);
else
mu_list_foreach (add_header_list, list_headers, argv[1]);
}
else
{
size_t len = strlen (argv[1]);
if (len > 0 && argv[1][len - 1] == ':')
argv[1][len - 1] = 0;
add_header (mu_strdup (argv[1]), mu_strdup (argv[2]), COMPOSE_APPEND);
}
return 0;
}
/* Attachments */
struct atchinfo
{
char *id; /* Attachment id (for listing) */
char *encoding; /* Encoding the attachment uses */
char *content_type; /* Full content type (type/subtype[; attrs]) */
char *name; /* Attachment name */
char *filename; /* Attachment file name */
mu_stream_t source; /* Attachment source stream */
int skip_empty; /* Skip this attachment if it is empty */
int disp_inline; /* Inline content disposition */
};
static void
atchinfo_free (void *p)
{
struct atchinfo *ap = p;
free (ap->id);
free (ap->encoding);
free (ap->content_type);
free (ap->name);
free (ap->filename);
mu_stream_destroy (&ap->source);
free (ap);
}
static mu_list_t
attlist_new (void)
{
mu_list_t lst;
int rc = mu_list_create (&lst);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_list_create", NULL, rc);
exit (1);
}
mu_list_set_destroy_item (lst, atchinfo_free);
return lst;
}
static void
attach_set_content_type (struct atchinfo *aptr, char const *content_type)
{
char *charset;
if (!content_type)
content_type = "text/plain";
if (strncmp (content_type, "text/", 5) == 0
&& !strstr (content_type, "charset=")
&& (charset = util_get_charset ()))
{
mu_asprintf (&aptr->content_type, "%s; charset=%s",
content_type, charset);
free (charset);
}
else
aptr->content_type = mu_strdup (content_type);
}
static void
attlist_add (mu_list_t attlist, char *id, char const *encoding,
char const *content_type, char const *content_name,
char const *content_filename,
mu_stream_t stream, int skip_empty, int disp_inline)
{
struct atchinfo *aptr;
int rc;
aptr = mu_alloc (sizeof (*aptr));
aptr->id = id ? mu_strdup (id) : id;
aptr->encoding = mu_strdup (encoding);
attach_set_content_type (aptr,
content_type
? content_type : "application/octet-stream");
aptr->name = content_name ? mu_strdup (content_name) : NULL;
aptr->filename = content_filename ? mu_strdup (content_filename) : NULL;
aptr->source = stream;
if (stream)
mu_stream_ref (stream);
aptr->skip_empty = skip_empty;
aptr->disp_inline = disp_inline;
rc = mu_list_append (attlist, aptr);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_list_append", NULL, rc);
exit (1);
}
}
int
attlist_attach_file (mu_list_t *attlist_ptr,
int fd,
const char *realname,
const char *content_filename, const char *content_name,
const char *content_type, const char *encoding)
{
int rc;
struct stat st;
mu_list_t list;
mu_stream_t stream = NULL;
char *id = NULL;
mu_list_t attlist;
if (fd >= 0)
{
rc = mu_fd_stream_create (&stream, NULL, fd, MU_STREAM_READ);
if (rc)
{
mu_error (_("can't open descriptor %d: %s"), fd, mu_strerror (rc));
return 1;
}
mu_asprintf (&id, "fd %d", fd);
if (fd == 0)
{
mu_stream_destroy (&mu_strin);
mu_nullstream_create (&mu_strin, MU_STREAM_READ);
mu_stream_ioctl (mu_strin, MU_IOCTL_NULLSTREAM,
MU_IOCTL_NULLSTREAM_SET_PATTERN, NULL);
util_do_command ("set nullbody nullbodymsg");
}
}
else if (realname)
{
if (!content_filename)
content_filename = realname;
if (stat (realname, &st))
{
if (errno == ENOENT)
{
mu_error (_("%s: file does not exist"), realname);
return 1;
}
else
{
mu_error (_("%s: cannot stat: %s"), realname,
mu_strerror (errno));
return 1;
}
}
if (S_ISREG (st.st_mode))
rc = mu_mapfile_stream_create (&stream, realname, MU_STREAM_READ);
else if (S_ISFIFO (st.st_mode))
rc = mu_file_stream_create (&stream, realname, MU_STREAM_READ);
else
{
mu_error (_("%s: not a regular file or FIFO"), realname);
return 1;
}
if (rc)
{
mu_error (_("can't open file %s: %s"),
realname, mu_strerror (rc));
return 1;
}
mu_asprintf (&id, "\"%s\"", realname);
}
else
abort ();
if (!encoding)
encoding = "base64";
mu_filter_get_list (&list);
rc = mu_list_locate (list, (void*) encoding, NULL);
if (rc)
{
mu_error (_("unsupported encoding: %s"), encoding);
free (id);
mu_stream_destroy (&stream);
return 1;
}
if (!*attlist_ptr)
{
attlist = attlist_new ();
*attlist_ptr = attlist;
}
else
attlist = *attlist_ptr;
attlist_add (attlist, id, encoding, content_type,
content_name, content_filename,
stream, skip_empty_attachments, 0);
if (stream)
mu_stream_unref (stream);
free (id);
return 0;
}
static int
attlist_helper (void *item, void *data)
{
struct atchinfo *aptr = item;
mu_list_t list = data;
attlist_add (list, aptr->id, aptr->encoding, aptr->content_type,
aptr->name, aptr->filename, aptr->source, aptr->skip_empty,
aptr->disp_inline);
return 0;
}
static mu_list_t
attlist_copy (mu_list_t src)
{
mu_list_t dst;
if (!src)
return NULL;
dst = attlist_new ();
mu_list_foreach (src, attlist_helper, dst);
return dst;
}
static mu_list_t attachment_list;
int
send_attach_file (int fd,
const char *realname,
const char *content_filename, const char *content_name,
const char *content_type, const char *encoding)
{
return attlist_attach_file (&attachment_list,
fd,
realname,
content_filename,
content_name,
content_type,
encoding);
}
static void
report_multipart_type (compose_env_t *env)
{
mu_printf ("multipart/%s\n", env->alt ? "alternative" : "mixed");
}
/* ~/ - toggle between multipart/mixed and multipart/alternative */
int
escape_toggle_multipart_type (int argc, char **argv, compose_env_t *env)
{
env->alt = !env->alt;
report_multipart_type (env);
return 0;
}
int
escape_list_attachments (int argc, char **argv, compose_env_t *env)
{
mu_iterator_t itr;
int i;
report_multipart_type (env);
if (mu_list_is_empty (env->attlist) ||
mu_list_get_iterator (env->attlist, &itr))
{
mu_printf ("%s\n", _("No attachments"));
return 0;
}
for (mu_iterator_first (itr), i = 1; !mu_iterator_is_done (itr);
mu_iterator_next (itr), i++)
{
struct atchinfo *aptr;
if (mu_iterator_current (itr, (void**)&aptr))
continue;
mu_printf ("%3d %-12s %-30s %-s\n",
i, aptr->id, aptr->content_type, aptr->encoding);
}
mu_iterator_destroy (&itr);
return 0;
}
int
escape_attach (int argc, char **argv, compose_env_t *env)
{
const char *encoding = default_encoding;
const char *content_type = default_content_type;
switch (argc)
{
case 4:
encoding = argv[3];
case 3:
content_type = argv[2];
case 2:
return attlist_attach_file (&env->attlist,
-1, argv[1], argv[1], argv[1],
content_type, encoding);
default:
return escape_check_args (argc, argv, 2, 4);
}
return 1;
}
int
escape_remove_attachment (int argc, char **argv, compose_env_t *env)
{
size_t count;
unsigned long n;
char *p;
if (escape_check_args (argc, argv, 2, 2))
return 1;
n = strtoul (argv[1], &p, 10);
if (*p)
{
mu_error (_("not a valid number: %s"), argv[1]);
return 1;
}
mu_list_count (env->attlist, &count);
if (n == 0 || n > count)
{
mu_error (_("index out of range"));
return 1;
}
return mu_list_remove_nth (env->attlist, n - 1);
}
static int
save_attachment (struct atchinfo *aptr, compose_env_t *env, mu_message_t part)
{
mu_header_t hdr;
int rc;
size_t nparts;
char *p;
rc = mu_attachment_copy_from_stream (part, aptr->source);
if (rc)
{
mu_error (_("cannot attach %s: %s"), aptr->id, mu_strerror (rc));
return 1;
}
if (aptr->skip_empty)
{
mu_body_t body;
size_t size;
rc = mu_message_get_body (part, &body);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_body", aptr->id, rc);
return 1;
}
rc = mu_body_size (body, &size);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_body_size", aptr->id, rc);
return 1;
}
if (size == 0)
return 0;
}
mu_mime_get_num_parts (env->mime, &nparts);
mu_message_get_header (part, &hdr);
mu_rfc2822_msg_id (nparts, &p);
mu_header_set_value (hdr, MU_HEADER_CONTENT_ID, p, 1);
free (p);
if (aptr->disp_inline)
{
rc = mu_header_set_value (hdr, MU_HEADER_CONTENT_DISPOSITION,
"inline",
1);
if (rc)
mu_diag_funcall (MU_DIAG_ERROR, "mu_header_set_value",
MU_HEADER_CONTENT_DISPOSITION, rc);
}
rc = mu_mime_add_part (env->mime, part);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_mime_add_part", aptr->filename, rc);
return 1;
}
return 0;
}
static int
saveatt (void *item, void *data)
{
struct atchinfo *aptr = item;
compose_env_t *env = data;
mu_message_t part;
int rc;
rc = mu_attachment_create (&part, aptr->content_type, aptr->encoding,
aptr->name,
aptr->filename);
if (rc)
{
mu_error (_("can't create attachment %s: %s"),
aptr->id, mu_strerror (rc));
return 1;
}
rc = save_attachment (aptr, env, part);
mu_message_unref (part);
return rc;
}
static int
add_body (mu_message_t inmsg, compose_env_t *env)
{
int rc;
mu_body_t body;
mu_stream_t str;
struct atchinfo *aptr;
mu_message_get_body (inmsg, &body);
mu_body_get_streamref (body, &str);
aptr = mu_alloc (sizeof (*aptr));
mu_asprintf (&aptr->id, "(body)");
aptr->encoding = default_encoding ? mu_strdup (default_encoding) : NULL;
attach_set_content_type (aptr, default_content_type);
aptr->name = NULL;
aptr->filename = NULL;
aptr->source = str;
aptr->skip_empty = skip_empty_attachments || multipart_alternative;
aptr->disp_inline = 1;
if (!env->attlist)
env->attlist = attlist_new ();
rc = mu_list_prepend (env->attlist, aptr);
if (rc)
mu_diag_funcall (MU_DIAG_ERROR, "mu_list_prepend", NULL, rc);
return rc;
}
static int
add_attachments (compose_env_t *env, mu_message_t *pmsg)
{
mu_message_t inmsg, outmsg;
mu_header_t inhdr, outhdr;
mu_iterator_t itr;
int rc;
inmsg = *pmsg;
if (mailvar_is_true (mailvar_name_mime) && add_body (inmsg, env))
return 1;
if (mu_list_is_empty (env->attlist))
return 0;
/* Create a mime object */
rc = mu_mime_create (&env->mime, NULL,
env->alt ?
MU_MIME_MULTIPART_ALT : MU_MIME_MULTIPART_MIXED);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_mime_create", NULL, rc);
return 1;
}
mu_message_get_header (inmsg, &inhdr);
/* Add the respective attachments */
rc = mu_list_foreach (env->attlist, saveatt, env);
if (rc)
return 1;
/* Get the resulting message */
rc = mu_mime_get_message (env->mime, &outmsg);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_mime_get_message", NULL, rc);
return 1;
}
/* Copy rest of headers from the original message */
rc = mu_message_get_header (outmsg, &outhdr);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_header", NULL, rc);
return 1;
}
rc = mu_header_get_iterator (inhdr, &itr);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_header_get_iterator", NULL, rc);
return 1;
}
for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
mu_iterator_next (itr))
{
const char *name, *value;
if (mu_iterator_current_kv (itr, (const void **)&name,
(void**)&value) == 0)
{
if (mu_c_strcasecmp (name, MU_HEADER_RECEIVED) == 0
|| mu_c_strncasecmp (name, "X-", 2) == 0)
mu_header_append (outhdr, name, value);
else if (mu_c_strcasecmp (name, MU_HEADER_MIME_VERSION) == 0 ||
mu_c_strncasecmp (name, "Content-", 8) == 0)
{
mu_error (_("%s: not setting header"), name);
continue;
}
else
mu_header_set_value (outhdr, name, value, 1);
}
}
mu_iterator_destroy (&itr);
mu_message_unref (outmsg);
mu_message_unref (inmsg);
*pmsg = outmsg;
return 0;
}
/* Send-related commands */
static void
read_cc_bcc (compose_env_t *env)
{
if (mailvar_is_true (mailvar_name_askcc))
compose_header_set (env, MU_HEADER_CC,
ml_readline_with_intr ("Cc: "), COMPOSE_REPLACE);
if (mailvar_is_true (mailvar_name_askbcc))
compose_header_set (env, MU_HEADER_BCC,
ml_readline_with_intr ("Bcc: "), COMPOSE_REPLACE);
}
/*
* m[ail] address...
if address is starting with
'/' it is considered a file and the message is saveed to a file;
'.' it is considered to be a relative path;
'|' it is considered to be a pipe, and the message is written to
there;
example:
mail joe '| cat >/tmp/save'
mail will be sent to joe and the message saved in /tmp/save. */
int
mail_send (int argc, char **argv)
{
compose_env_t env;
int status;
int save_to = mu_isupper (argv[0][0]);
compose_init (&env);
if (argc < 2)
{
if (interactive)
compose_header_set (&env, MU_HEADER_TO, ml_readline_with_intr ("To: "),
COMPOSE_REPLACE);
else if (mailvar_is_true (mailvar_name_editheaders))
{
if (parse_headers (mu_strin, &env) != parse_headers_ok)
{
mu_error ("%s", _("Errors parsing message"));
exit (EXIT_FAILURE);
}
if (add_header_list)
{
mu_iterator_t itr;
mu_list_get_iterator (add_header_list, &itr);
for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
mu_iterator_next (itr))
{
struct add_header *hp;
int mode;
if (mu_iterator_current (itr, (void**) &hp))
break;
mode = hp->mode;
if (mu_header_sget_value (env.header, hp->name, NULL) == 0)
mode = COMPOSE_REPLACE;
compose_header_set (&env, hp->name, hp->value, mode);
}
mu_iterator_destroy (&itr);
}
}
else
{
mu_error ("%s", _("No recipients specified"));
exit (EXIT_FAILURE);
}
}
else
{
while (--argc)
{
char *p = *++argv;
if (isfilename (p))
{
env.outfiles = realloc (env.outfiles,
(env.nfiles + 1) *
sizeof (*(env.outfiles)));
if (env.outfiles)
{
env.outfiles[env.nfiles] = p;
env.nfiles++;
}
}
else
compose_header_set (&env, MU_HEADER_TO, p, COMPOSE_SINGLE_LINE);
}
}
if (interactive)
{
if (!mailvar_is_true (mailvar_name_mailx))
read_cc_bcc (&env);
if (mailvar_is_true (mailvar_name_asksub))
compose_header_set (&env, MU_HEADER_SUBJECT,
ml_readline_with_intr ("Subject: "),
COMPOSE_REPLACE);
}
status = mail_compose_send (&env, save_to);
compose_destroy (&env);
return status;
}
int
parse_headers (mu_stream_t input, compose_env_t *env)
{
int status;
mu_header_t header;
char *name = NULL;
char *value = NULL;
enum { STATE_INIT, STATE_READ, STATE_BODY } state = STATE_INIT;
char *buf = NULL;
size_t size = 0, n;
int errcnt = 0, line = 0;
if ((status = mu_header_create (&header, NULL, 0)) != 0)
{
mu_error (_("Cannot create header: %s"), mu_strerror (status));
return parse_headers_fatal;
}
while (state != STATE_BODY &&
errcnt == 0 &&
mu_stream_getline (input, &buf, &size, &n) == 0 && n > 0)
{
mu_rtrim_class (buf, MU_CTYPE_SPACE);
line++;
switch (state)
{
case STATE_INIT:
if (!buf[0] || mu_isspace (buf[0]))
continue;
else
state = STATE_READ;
/*FALLTHRU*/
case STATE_READ:
if (buf[0] == 0)
state = STATE_BODY;
else if (mu_isspace (buf[0]))
{
/* A continuation line */
if (name)
{
char *p = NULL;
mu_asprintf (&p, "%s\n%s", value, buf);
free (value);
value = p;
}
else
{
mu_error (_("%d: not a header line"), line);
errcnt++;
}
}
else
{
char *p;
if (name)
{
mu_header_append (header, name, value[0] ? value : NULL);
free (name);
free (value);
name = value = NULL;
}
p = strchr (buf, ':');
if (p)
{
*p++ = 0;
while (*p && mu_isspace (*p))
p++;
value = mu_strdup (p);
name = mu_strdup (buf);
}
else
{
mu_error (_("%d: not a header line"), line);
errcnt++;
}
}
break;
default:
abort ();
}
}
free (buf);
if (name)
{
mu_header_append (header, name, value);
free (name);
free (value);
}
if (errcnt)
{
mu_header_destroy (&header);
return parse_headers_error;
}
mu_header_destroy (&env->header);
env->header = header;
return parse_headers_ok;
}
void
compose_init (compose_env_t *env)
{
memset (env, 0, sizeof (*env));
env->alt = multipart_alternative;
env->attlist = attlist_copy (attachment_list);
mu_list_foreach (add_header_list, seed_headers, env);
}
int
compose_header_set (compose_env_t *env, const char *name,
const char *value, int mode)
{
int status;
char *expansion = NULL;
if (!value || value[0] == 0)
return EINVAL;
if (is_address_field (name)
&& mailvar_is_true (mailvar_name_inplacealiases))
{
struct mu_address hint = MU_ADDRESS_HINT_INITIALIZER;
mu_address_t a;
status = mu_address_create_hint (&a, value, &hint, 0);
if (status)
{
mu_error (_("Cannot parse address `%s': %s"),
value, mu_strerror (status));
return status;
}
util_address_expand_aliases (&a);
status = mu_address_aget_printable (a, &expansion);
mu_address_destroy (&a);
if (status)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_address_aget_printable",
NULL, status);
return status;
}
value = expansion;
}
if (!env->header
&& (status = mu_header_create (&env->header, NULL, 0)) != 0)
{
mu_error (_("Cannot create header: %s"), mu_strerror (status));
}
else
{
switch (mode)
{
case COMPOSE_REPLACE:
status = mu_header_set_value (env->header, name, value, 1);
break;
case COMPOSE_APPEND:
status = mu_header_append (env->header, name, value);
break;
case COMPOSE_SINGLE_LINE:
{
char *old_value;
if (mu_header_aget_value (env->header, name, &old_value) == 0
&& old_value[0])
{
if (is_address_field (name))
{
status = util_merge_addresses (&old_value, value);
if (status == 0)
status = mu_header_set_value (env->header, name,
old_value, 1);
}
else
{
size_t size = strlen (old_value) + strlen (value) + 2;
char *p = realloc (old_value, size);
if (!p)
status = ENOMEM;
else
{
old_value = p;
strcat (old_value, ",");
strcat (old_value, value);
status = mu_header_set_value (env->header,
name, old_value,
1);
}
}
free (old_value);
}
else
status = mu_header_set_value (env->header, name, value, 1);
}
}
}
free (expansion);
return status;
}
char const *
compose_header_get (compose_env_t *env, char *name, char *defval)
{
char const *p;
if (mu_header_sget_value (env->header, name, &p))
p = defval;
return p;
}
void
compose_destroy (compose_env_t *env)
{
mu_header_destroy (&env->header);
free (env->outfiles);
mu_mime_destroy (&env->mime);
mu_list_destroy (&env->attlist);
mu_stream_destroy (&env->compstr);
}
static int
fill_body (mu_message_t msg, mu_stream_t instr)
{
int rc;
mu_body_t body = NULL;
mu_stream_t stream = NULL;
mu_off_t n;
rc = mu_message_get_body (msg, &body);
if (rc)
{
mu_error (_("cannot get message body: %s"), mu_strerror (rc));
return 1;
}
rc = mu_body_get_streamref (body, &stream);
if (rc)
{
mu_error (_("cannot get body: %s"), mu_strerror (rc));
return 1;
}
rc = mu_stream_copy (stream, instr, 0, &n);
mu_stream_destroy (&stream);
if (rc)
{
mu_error (_("cannot copy temporary stream: %s"), mu_strerror (rc));
return 1;
}
if (n == 0)
{
if (mailvar_is_true (mailvar_name_nullbody))
{
char *str;
if (mailvar_get (&str, mailvar_name_nullbodymsg,
mailvar_type_string, 0) == 0)
mu_error ("%s", _(str));
}
else
return 1;
}
return 0;
}
static int
save_dead_message_env (compose_env_t *env)
{
if (mailvar_is_true (mailvar_name_save))
{
mu_stream_t dead_letter, str;
int rc;
time_t t;
struct tm *tm;
const char *name = getenv ("DEAD");
char *sender;
/* FIXME: Use MU_STREAM_APPEND if appenddeadletter, instead of the
stream manipulations below */
rc = mu_file_stream_create (&dead_letter, name,
MU_STREAM_CREAT|MU_STREAM_WRITE);
if (rc)
{
mu_error (_("Cannot open file %s: %s"), name, strerror (rc));
return 1;
}
if (mailvar_is_true (mailvar_name_appenddeadletter))
mu_stream_seek (dead_letter, 0, MU_SEEK_END, NULL);
else
mu_stream_truncate (dead_letter, 0);
time (&t);
tm = gmtime (&t);
sender = mu_get_user_email (NULL);
if (!sender)
sender = mu_strdup ("UNKNOWN");
mu_stream_printf (dead_letter, "From %s ", sender);
free (sender);
mu_c_streamftime (dead_letter, "%c%n", tm, NULL);
if (mu_header_get_streamref (env->header, &str) == 0)
{
mu_stream_copy (dead_letter, str, 0, NULL);
mu_stream_unref (str);
}
else
mu_stream_write (dead_letter, "\n", 1, NULL);
mu_stream_seek (env->compstr, 0, MU_SEEK_SET, NULL);
mu_stream_copy (dead_letter, env->compstr, 0, NULL);
mu_stream_write (dead_letter, "\n", 1, NULL);
mu_stream_destroy (&dead_letter);
}
return 0;
}
static int
save_dead_message (mu_message_t msg)
{
if (mailvar_is_true (mailvar_name_save))
{
mu_stream_t dead_letter, str;
int rc;
time_t t;
struct tm *tm;
const char *name = getenv ("DEAD");
char *sender;
/* FIXME: Use MU_STREAM_APPEND if appenddeadletter, instead of the
stream manipulations below */
rc = mu_file_stream_create (&dead_letter, name,
MU_STREAM_CREAT|MU_STREAM_WRITE);
if (rc)
{
mu_error (_("Cannot open file %s: %s"), name, strerror (rc));
return 1;
}
if (mailvar_is_true (mailvar_name_appenddeadletter))
mu_stream_seek (dead_letter, 0, MU_SEEK_END, NULL);
else
mu_stream_truncate (dead_letter, 0);
time (&t);
tm = gmtime (&t);
sender = mu_get_user_email (NULL);
if (!sender)
sender = mu_strdup ("UNKNOWN");
mu_stream_printf (dead_letter, "From %s ", sender);
free (sender);
mu_c_streamftime (dead_letter, "%c%n", tm, NULL);
if (mu_message_get_streamref (msg, &str) == 0)
{
mu_stream_copy (dead_letter, str, 0, NULL);
mu_stream_unref (str);
}
mu_stream_write (dead_letter, "\n", 1, NULL);
mu_stream_destroy (&dead_letter);
}
return 0;
}
static int
send_message (mu_message_t msg)
{
char *mailer_url = NULL;
char *sendmail;
int status;
if (mailvar_get (&sendmail, mailvar_name_sendmail,
mailvar_type_string, 0) == 0)
{
if (mailvar_is_true (mailvar_name_mailx))
{
/*
* Mailx compatibility: assume sendmail:// scheme.
*/
if (!mu_is_proto (sendmail))
{
status = mu_asprintf (&mailer_url, "sendmail://%s", sendmail);
if (status)
return status;
sendmail = mailer_url;
}
}
if (sendmail[0] == '/')
status = msg_to_pipe (sendmail, msg);
else
{
mu_mailer_t mailer;
status = mu_mailer_create (&mailer, sendmail);
if (status == 0)
{
const char *return_address_str;
mu_address_t return_address = NULL;
if (mailvar_get (&return_address_str, mailvar_name_return_address,
mailvar_type_string, 0) == 0)
{
struct mu_address hint = MU_ADDRESS_HINT_INITIALIZER;
status = mu_address_create_hint (&return_address,
return_address_str,
&hint, 0);
if (status)
{
mu_error (_("invalid return address: %s"),
mu_strerror (status));
mu_mailer_destroy (&mailer);
return status;
}
}
if (mailvar_is_true (mailvar_name_verbose))
{
mu_debug_set_category_level (MU_DEBCAT_MAILER,
MU_DEBUG_LEVEL_UPTO (MU_DEBUG_PROT));
}
status = mu_mailer_open (mailer, MU_STREAM_RDWR);
if (status == 0)
{
status = mu_mailer_send_message (mailer, msg,
return_address, NULL);
mu_mailer_close (mailer);
}
else
mu_error (_("Cannot open mailer: %s"),
mu_strerror (status));
mu_mailer_destroy (&mailer);
mu_address_destroy (&return_address);
}
else
mu_error (_("Cannot create mailer: %s"), mu_strerror (status));
}
}
else
{
mu_error (_("Variable sendmail not set: no mailer"));
status = ENOSYS;
}
free (mailer_url);
return status;
}
#define MU_DATETIME_RFC822_LENGTH 31
static void
message_add_date (mu_message_t msg)
{
mu_header_t hdr;
char buf[MU_DATETIME_RFC822_LENGTH+1];
struct tm ltm;
time_t t;
int rc;
rc = mu_message_get_header (msg, &hdr);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_message_get_header", NULL, rc);
return;
}
t = time (NULL);
localtime_r (&t, <m);
mu_strftime (buf, sizeof (buf), MU_DATETIME_FORM_RFC822, <m);
rc = mu_header_set_value (hdr, MU_HEADER_DATE, buf, 1);
if (rc)
mu_diag_funcall (MU_DIAG_ERROR, "mu_header_set_value", MU_HEADER_DATE, rc);
}
/* mail_compose_send(): shared between mail_send() and mail_reply();
If the variable "record" is set, the outgoing message is
saved after being sent. If "save_to" argument is non-zero,
the name of the save file is derived from "to" argument. Otherwise,
it is taken from the value of the "record" variable.
sendmail
contains a command, possibly with options, that mailx invokes to send
mail. You must manually set the default for this environment variable
by editing ROOTDIR/etc/mailx.rc to specify the mail agent of your
choice. The default is sendmail, but it can be any command that takes
addresses on the command line and message contents on standard input. */
int
mail_compose_send (compose_env_t *env, int save_to)
{
int done = 0;
int rc;
char *savefile = NULL;
int int_cnt;
char *escape;
/* Prepare environment */
rc = mu_temp_stream_create (&env->compstr, 0);
if (rc)
{
mu_error (_("Cannot open temporary file: %s"), mu_strerror (rc));
return 1;
}
ml_clear_interrupt ();
int_cnt = 0;
while (!done)
{
char *buf;
buf = ml_readline (" \b");
if (ml_got_interrupt ())
{
if (buf)
free (buf);
if (mailvar_is_true (mailvar_name_ignore))
{
mu_printf ("@\n");
}
else
{
if (++int_cnt == 2)
break;
mu_error (_("\n(Interrupt -- one more to kill letter)"));
}
continue;
}
if (!buf)
{
if (interactive && mailvar_is_true (mailvar_name_ignoreeof))
{
mu_error (mailvar_is_true (mailvar_name_dot)
? _("Use \".\" to terminate letter.")
: _("Use \"~.\" to terminate letter."));
continue;
}
else
break;
}
int_cnt = 0;
if (strcmp (buf, ".") == 0 && mailvar_is_true (mailvar_name_dot))
done = 1;
else if (interactive
&& mailvar_get (&escape, mailvar_name_escape,
mailvar_type_string, 0) == 0
&& buf[0] == escape[0])
{
if (buf[1] == buf[0])
mu_stream_printf (env->compstr, "%s\n", buf + 1);
else if (buf[1] == '.')
done = 1;
else if (buf[1] == 'x')
{
int_cnt = 2;
done = 1;
}
else
{
struct mu_wordsplit ws;
if (mu_wordsplit (buf + 1, &ws, MU_WRDSF_DEFFLAGS) == 0)
{
if (ws.ws_wordc > 0)
{
const struct mail_escape_entry *entry =
mail_find_escape (ws.ws_wordv[0]);
if (entry)
(*entry->escfunc) (ws.ws_wordc, ws.ws_wordv, env);
else
mu_error (_("Unknown escape %s"), ws.ws_wordv[0]);
}
else
mu_error (_("Unfinished escape"));
mu_wordsplit_free (&ws);
}
else
{
mu_error (_("Cannot parse escape sequence: %s"),
mu_wordsplit_strerror (&ws));
}
}
}
else
mu_stream_printf (env->compstr, "%s\n", buf);
mu_stream_flush (env->compstr);
free (buf);
}
/* If interrupted, dump the file to dead.letter. */
if (int_cnt)
{
save_dead_message_env (env);
return 1;
}
/* In mailx compatibility mode, ask for Cc and Bcc after editing
the body of the message */
if (mailvar_is_true (mailvar_name_mailx))
read_cc_bcc (env);
/* Prepare the header */
if (mailvar_is_true (mailvar_name_useragent))
mu_header_set_value (env->header, MU_HEADER_USER_AGENT, program_version, 1);
if (util_header_expand_aliases (&env->header) == 0)
{
mu_message_t msg = NULL;
int status = 0;
int sendit = (compose_header_get (env, MU_HEADER_TO, NULL) ||
compose_header_get (env, MU_HEADER_CC, NULL) ||
compose_header_get (env, MU_HEADER_BCC, NULL));
do
{
status = mu_message_create (&msg, NULL);
if (status)
break;
/* Fill the body. */
mu_stream_seek (env->compstr, 0, MU_SEEK_SET, NULL);
status = fill_body (msg, env->compstr);
if (status)
break;
mu_message_set_header (msg, env->header, NULL);
env->header = NULL;
status = add_attachments (env, &msg);
if (status)
break;
message_add_date (msg);
/* Save outgoing message */
if (save_to)
{
mu_header_t hdr;
char const *rcpt;
mu_message_get_header (msg, &hdr);
if (mu_header_sget_value (hdr, MU_HEADER_TO, &rcpt) == 0)
{
mu_address_t addr = NULL;
struct mu_address hint = MU_ADDRESS_HINT_INITIALIZER;
mu_address_create_hint (&addr, rcpt, &hint, 0);
savefile = util_outfilename (addr);
mu_address_destroy (&addr);
}
}
util_save_outgoing (msg, savefile);
if (savefile)
free (savefile);
/* Check if we need to save the message to files or pipes. */
if (env->outfiles)
{
int i;
for (i = 0; i < env->nfiles; i++)
{
/* Pipe to a cmd. */
if (env->outfiles[i][0] == '|')
status = msg_to_pipe (env->outfiles[i] + 1, msg);
/* Save to a file. */
else
{
mu_mailbox_t mbx = NULL;
status = mu_mailbox_create_default (&mbx,
env->outfiles[i]);
if (status == 0)
{
status = mu_mailbox_open (mbx, MU_STREAM_WRITE
| MU_STREAM_CREAT);
if (status == 0)
{
status = mu_mailbox_append_message (mbx, msg);
if (status)
mu_error (_("Cannot append message: %s"),
mu_strerror (status));
mu_mailbox_close (mbx);
}
mu_mailbox_destroy (&mbx);
}
if (status)
mu_error (_("Cannot create mailbox %s: %s"),
env->outfiles[i],
mu_strerror (status));
}
}
}
/* Do we need to Send the message on the wire? */
if (status == 0 && sendit)
{
status = send_message (msg);
if (status)
{
mu_error (_("cannot send message: %s"),
mu_strerror (status));
save_dead_message (msg);
}
}
}
while (0);
mu_stream_destroy (&env->compstr);
mu_message_destroy (&msg, NULL);
return status;
}
else
save_dead_message_env (env);
return 1;
}
/* Starting with '|' '/' or not consider addresses and we cheat
by adding '.' in the mix for none absolute path. */
static int
isfilename (const char *p)
{
if (p)
if (*p == '/' || *p == '.' || *p == '|')
return 1;
return 0;
}
/* FIXME: Should probably be in util.c. */
/* Call popen(cmd) and write the message to it. */
static int
msg_to_pipe (const char *cmd, mu_message_t msg)
{
mu_stream_t progstream, msgstream;
int status, rc;
char *argv[4];
argv[0] = getenv ("SHELL");
if (!argv[0])
argv[0] = "/bin/sh";
argv[1] = "-c";
argv[2] = (char*) cmd;
argv[3] = NULL;
status = mu_prog_stream_create (&progstream,
argv[0],
3, argv,
0, NULL, MU_STREAM_WRITE);
if (status)
{
mu_error (_("Cannot pipe to %s: %s"), cmd, mu_strerror (status));
return status;
}
mu_message_get_streamref (msg, &msgstream);
status = mu_stream_copy (progstream, msgstream, 0, NULL);
rc = mu_stream_close (progstream);
if (status == 0 && rc)
status = rc;
mu_stream_destroy (&progstream);
mu_stream_destroy (&msgstream);
if (status)
{
mu_error (_("Sending data to %s failed: %s"), cmd,
mu_strerror (status));
}
return status;
}