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