3 * Miscellaneous functions for sending an email
6 * Copyright (C) 1996-2002,2009-2012 Michael R. Elkins <me@mutt.org>
7 * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
10 * This program is free software: you can redistribute it and/or modify it under
11 * the terms of the GNU General Public License as published by the Free Software
12 * Foundation, either version 2 of the License, or (at your option) any later
15 * This program is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
20 * You should have received a copy of the GNU General Public License along with
21 * this program. If not, see <http://www.gnu.org/licenses/>.
25 * @page sendlib Miscellaneous functions for sending an email
27 * Miscellaneous functions for sending an email
34 #include <inttypes.h> // IWYU pragma: keep
45 #include "mutt/mutt.h"
46 #include "address/lib.h"
47 #include "email/lib.h"
55 #include "format_flags.h"
58 #include "mutt_mailbox.h"
59 #include "mutt_parse.h"
60 #include "mutt_window.h"
63 #include "ncrypt/ncrypt.h"
70 #include "nntp/nntp.h"
72 #ifdef HAVE_SYSEXITS_H
78 #include "autocrypt/autocrypt.h"
81 /* These Config Variables are only used in sendlib.c */
82 bool C_Allow8bit; ///< Config: Allow 8-bit messages, don't use quoted-printable or base64
83 char *C_AttachCharset; ///< Config: When attaching files, use one of these character sets
84 bool C_BounceDelivered; ///< Config: Add 'Delivered-To' to bounced messages
85 bool C_EncodeFrom; ///< Config: Encode 'From ' as 'quote-printable' at the beginning of lines
86 bool C_ForwardDecrypt; ///< Config: Decrypt the message when forwarding it
87 bool C_HiddenHost; ///< Config: Don't use the hostname, just the domain, when generating the message id
88 char *C_Inews; ///< Config: (nntp) External command to post news articles
89 bool C_MimeForwardDecode; ///< Config: Decode the forwarded message before attaching it
90 bool C_MimeSubject; ///< Config: (nntp) Encode the article subject in base64
91 char *C_MimeTypeQueryCommand; ///< Config: External command to determine the MIME type of an attachment
92 bool C_MimeTypeQueryFirst; ///< Config: Run the #C_MimeTypeQueryCommand before the mime.types lookup
93 char *C_Sendmail; ///< Config: External command to send email
94 short C_SendmailWait; ///< Config: Time to wait for sendmail to finish
95 bool C_Use8bitmime; ///< Config: Use 8-bit messages and ESMTP to send messages
96 bool C_UseEnvelopeFrom; ///< Config: Set the envelope sender of the message
97 bool C_UserAgent; ///< Config: Add a 'User-Agent' head to outgoing mail
98 short C_WrapHeaders; ///< Config: Width to wrap headers in outgoing messages
100 #define MUTT_RANDTAG_LEN 16
103 * struct B64Context - Cursor for the Base64 conversion
113 * struct ContentState - Info about the body of an email
125 * The next array/enum pair is used to to keep track of user headers that
126 * override pre-defined headers NeoMutt would emit. Keep the array sorted and
127 * in sync with the enum.
129 static const char *const userhdrs_override_headers[] = {
134 enum UserHdrsOverrideIdx
136 USERHDRS_OVERRIDE_CONTENT_TYPE,
137 USERHDRS_OVERRIDE_USER_AGENT,
140 struct UserHdrsOverride
142 bool is_overridden[mutt_array_size(userhdrs_override_headers)];
146 * encode_quoted - Encode text as quoted printable
147 * @param fc Cursor for converting a file's encoding
148 * @param fp_out File to store the result
149 * @param istext Is the input text?
151 static void encode_quoted(struct FgetConv *fc, FILE *fp_out, bool istext)
154 char line[77], savechar;
156 while ((c = mutt_ch_fgetconv(fc)) != EOF)
158 /* Wrap the line if needed. */
159 if ((linelen == 76) && ((istext && (c != '\n')) || !istext))
161 /* If the last character is "quoted", then be sure to move all three
162 * characters to the next line. Otherwise, just move the last
164 if (line[linelen - 3] == '=')
166 line[linelen - 3] = 0;
168 fputs("=\n", fp_out);
171 line[1] = line[linelen - 2];
172 line[2] = line[linelen - 1];
177 savechar = line[linelen - 1];
178 line[linelen - 1] = '=';
187 /* Escape lines that begin with/only contain "the message separator". */
188 if ((linelen == 4) && mutt_str_startswith(line, "From", CASE_MATCH))
190 mutt_str_strfcpy(line, "=46rom", sizeof(line));
193 else if ((linelen == 4) && mutt_str_startswith(line, "from", CASE_MATCH))
195 mutt_str_strfcpy(line, "=66rom", sizeof(line));
198 else if ((linelen == 1) && (line[0] == '.'))
200 mutt_str_strfcpy(line, "=2E", sizeof(line));
204 if ((c == '\n') && istext)
206 /* Check to make sure there is no trailing space on this line. */
207 if ((linelen > 0) && ((line[linelen - 1] == ' ') || (line[linelen - 1] == '\t')))
211 sprintf(line + linelen - 1, "=%2.2X", (unsigned char) line[linelen - 1]);
216 int savechar2 = line[linelen - 1];
218 line[linelen - 1] = '=';
221 fprintf(fp_out, "\n=%2.2X", (unsigned char) savechar2);
232 else if ((c != 9) && ((c < 32) || (c > 126) || (c == '=')))
234 /* Check to make sure there is enough room for the quoted character.
235 * If not, wrap to the next line. */
238 line[linelen++] = '=';
244 sprintf(line + linelen, "=%2.2X", (unsigned char) c);
249 /* Don't worry about wrapping the line here. That will happen during
250 * the next iteration when I'll also know what the next character is. */
255 /* Take care of anything left in the buffer */
258 if ((line[linelen - 1] == ' ') || (line[linelen - 1] == '\t'))
260 /* take care of trailing whitespace */
262 sprintf(line + linelen - 1, "=%2.2X", (unsigned char) line[linelen - 1]);
265 savechar = line[linelen - 1];
266 line[linelen - 1] = '=';
270 sprintf(line, "=%2.2X", (unsigned char) savechar);
280 * b64_init - Set up the base64 conversion
281 * @param bctx Cursor for the base64 conversion
284 static int b64_init(struct B64Context *bctx)
286 memset(bctx->buffer, '\0', sizeof(bctx->buffer));
294 * b64_flush - Save the bytes to the file
295 * @param bctx Cursor for the base64 conversion
296 * @param fp_out File to save the output
298 static void b64_flush(struct B64Context *bctx, FILE *fp_out)
300 /* for some reasons, mutt_b64_encode expects the
301 * output buffer to be larger than 10B */
308 if (bctx->linelen >= 72)
314 /* ret should always be equal to 4 here, because bctx->size
315 * is a value between 1 and 3 (included), but let's not hardcode it
316 * and prefer the return value of the function */
317 ret = mutt_b64_encode(bctx->buffer, bctx->size, encoded, sizeof(encoded));
318 for (size_t i = 0; i < ret; i++)
320 fputc(encoded[i], fp_out);
328 * b64_putc - Base64-encode one character
329 * @param bctx Cursor for the base64 conversion
330 * @param c Character to encode
331 * @param fp_out File to save the output
333 static void b64_putc(struct B64Context *bctx, char c, FILE *fp_out)
336 b64_flush(bctx, fp_out);
338 bctx->buffer[bctx->size++] = c;
342 * encode_base64 - Base64-encode some data
343 * @param fc Cursor for converting a file's encoding
344 * @param fp_out File to store the result
345 * @param istext Is the input text?
347 static void encode_base64(struct FgetConv *fc, FILE *fp_out, int istext)
349 struct B64Context bctx;
354 while ((ch = mutt_ch_fgetconv(fc)) != EOF)
361 if (istext && (ch == '\n') && (ch1 != '\r'))
362 b64_putc(&bctx, '\r', fp_out);
363 b64_putc(&bctx, ch, fp_out);
366 b64_flush(&bctx, fp_out);
371 * encode_8bit - Write the data as raw 8-bit data
372 * @param fc Cursor for converting a file's encoding
373 * @param fp_out File to store the result
375 static void encode_8bit(struct FgetConv *fc, FILE *fp_out)
379 while ((ch = mutt_ch_fgetconv(fc)) != EOF)
391 * mutt_write_mime_header - Create a MIME header
393 * @param fp File to write to
397 int mutt_write_mime_header(struct Body *a, FILE *fp)
404 char buf[256] = { 0 };
406 fprintf(fp, "Content-Type: %s/%s", TYPE(a), a->subtype);
408 if (!TAILQ_EMPTY(&a->parameter))
410 len = 25 + mutt_str_strlen(a->subtype); /* approximate len. of content-type */
412 struct Parameter *np = NULL;
413 TAILQ_FOREACH(np, &a->parameter, entries)
415 if (!np->attribute || !np->value)
418 struct ParameterList pl_conts = rfc2231_encode_string(np->attribute, np->value);
419 struct Parameter *cont = NULL;
420 TAILQ_FOREACH(cont, &pl_conts, entries)
425 mutt_addr_cat(buf, sizeof(buf), cont->value, MimeSpecials);
427 /* Dirty hack to make messages readable by Outlook Express
428 * for the Mac: force quotes around the boundary parameter
429 * even when they aren't needed.
431 if (!mutt_str_strcasecmp(cont->attribute, "boundary") &&
432 !mutt_str_strcmp(buf, cont->value))
433 snprintf(buf, sizeof(buf), "\"%s\"", cont->value);
435 tmplen = mutt_str_strlen(buf) + mutt_str_strlen(cont->attribute) + 1;
436 if (len + tmplen + 2 > 76)
447 fprintf(fp, "%s=%s", cont->attribute, buf);
450 mutt_param_free(&pl_conts);
457 fprintf(fp, "Content-Language: %s\n", a->language);
460 fprintf(fp, "Content-Description: %s\n", a->description);
462 if (a->disposition != DISP_NONE)
464 const char *dispstr[] = { "inline", "attachment", "form-data" };
466 if (a->disposition < sizeof(dispstr) / sizeof(char *))
468 fprintf(fp, "Content-Disposition: %s", dispstr[a->disposition]);
469 len = 21 + mutt_str_strlen(dispstr[a->disposition]);
471 if (a->use_disp && (a->disposition != DISP_INLINE))
473 char *fn = a->d_filename;
479 /* Strip off the leading path... */
480 char *t = strrchr(fn, '/');
486 struct ParameterList pl_conts = rfc2231_encode_string("filename", t);
487 struct Parameter *cont = NULL;
488 TAILQ_FOREACH(cont, &pl_conts, entries)
492 mutt_addr_cat(buf, sizeof(buf), cont->value, MimeSpecials);
494 tmplen = mutt_str_strlen(buf) + mutt_str_strlen(cont->attribute) + 1;
495 if (len + tmplen + 2 > 76)
506 fprintf(fp, "%s=%s", cont->attribute, buf);
509 mutt_param_free(&pl_conts);
517 mutt_debug(LL_DEBUG1, "ERROR: invalid content-disposition %d\n", a->disposition);
521 if (a->encoding != ENC_7BIT)
522 fprintf(fp, "Content-Transfer-Encoding: %s\n", ENCODING(a->encoding));
524 if ((C_CryptProtectedHeadersWrite
531 mutt_rfc822_write_header(fp, a->mime_headers, NULL, MUTT_WRITE_HEADER_MIME, false, false);
534 /* Do NOT add the terminator here!!! */
535 return ferror(fp) ? -1 : 0;
539 * write_as_text_part - Should the Body be written as a text MIME part
540 * @param b Email to examine
541 * @retval true If the Body should be written as text
543 static bool write_as_text_part(struct Body *b)
545 return mutt_is_text_part(b) ||
546 (((WithCrypto & APPLICATION_PGP) != 0) && mutt_is_application_pgp(b));
550 * mutt_write_mime_body - Write a MIME part
551 * @param a Body to use
552 * @param fp File to write to
556 int mutt_write_mime_body(struct Body *a, FILE *fp)
559 struct FgetConv *fc = NULL;
561 if (a->type == TYPE_MULTIPART)
563 /* First, find the boundary to use */
564 const char *p = mutt_param_get(&a->parameter, "boundary");
567 mutt_debug(LL_DEBUG1, "no boundary parameter found\n");
568 mutt_error(_("No boundary parameter found [report this error]"));
572 mutt_str_strfcpy(boundary, p, sizeof(boundary));
574 for (struct Body *t = a->parts; t; t = t->next)
576 fprintf(fp, "\n--%s\n", boundary);
577 if (mutt_write_mime_header(t, fp) == -1)
580 if (mutt_write_mime_body(t, fp) == -1)
583 fprintf(fp, "\n--%s--\n", boundary);
584 return ferror(fp) ? -1 : 0;
587 /* This is pretty gross, but it's the best solution for now... */
588 if (((WithCrypto & APPLICATION_PGP) != 0) && (a->type == TYPE_APPLICATION) &&
589 (mutt_str_strcmp(a->subtype, "pgp-encrypted") == 0) && !a->filename)
591 fputs("Version: 1\n", fp);
595 fp_in = fopen(a->filename, "r");
598 mutt_debug(LL_DEBUG1, "%s no longer exists\n", a->filename);
599 mutt_error(_("%s no longer exists"), a->filename);
603 if ((a->type == TYPE_TEXT) && (!a->noconv))
605 char send_charset[128];
606 fc = mutt_ch_fgetconv_open(
608 mutt_body_get_charset(a, send_charset, sizeof(send_charset)), 0);
611 fc = mutt_ch_fgetconv_open(fp_in, 0, 0, 0);
613 mutt_sig_allow_interrupt(1);
614 if (a->encoding == ENC_QUOTED_PRINTABLE)
615 encode_quoted(fc, fp, write_as_text_part(a));
616 else if (a->encoding == ENC_BASE64)
617 encode_base64(fc, fp, write_as_text_part(a));
618 else if ((a->type == TYPE_TEXT) && (!a->noconv))
621 mutt_file_copy_stream(fp_in, fp);
622 mutt_sig_allow_interrupt(0);
624 mutt_ch_fgetconv_close(&fc);
625 mutt_file_fclose(&fp_in);
632 return ferror(fp) ? -1 : 0;
636 * mutt_generate_boundary - Create a unique boundary id for a MIME part
637 * @param pl MIME part
639 void mutt_generate_boundary(struct ParameterList *pl)
641 char rs[MUTT_RANDTAG_LEN + 1];
643 mutt_rand_base32(rs, sizeof(rs) - 1);
644 rs[MUTT_RANDTAG_LEN] = 0;
645 mutt_param_set(pl, "boundary", rs);
649 * update_content_info - Cache some info about an email
650 * @param info Info about an Attachment
651 * @param s Info about the Body of an email
652 * @param buf Buffer for the result
653 * @param buflen Length of the buffer
655 static void update_content_info(struct Content *info, struct ContentState *s,
656 char *buf, size_t buflen)
659 int whitespace = s->whitespace;
661 int linelen = s->linelen;
662 bool was_cr = s->was_cr;
664 if (!buf) /* This signals EOF */
668 if (linelen > info->linemax)
669 info->linemax = linelen;
674 for (; buflen; buf++, buflen--)
691 if (linelen > info->linemax)
692 info->linemax = linelen;
708 if (linelen > info->linemax)
709 info->linemax = linelen;
723 else if ((ch == '\t') || (ch == '\f'))
733 else if ((ch < 32) || (ch == 127))
739 if ((ch == 'F') || (ch == 'f'))
750 if ((linelen == 2) && (ch != 'r'))
752 else if ((linelen == 3) && (ch != 'o'))
754 else if (linelen == 4)
768 if ((ch != ' ') && (ch != '\t'))
773 s->whitespace = whitespace;
775 s->linelen = linelen;
780 * convert_file_to - Change the encoding of a file
781 * @param[in] fp File to convert
782 * @param[in] fromcode Original encoding
783 * @param[in] ncodes Number of target encodings
784 * @param[in] tocodes List of target encodings
785 * @param[out] tocode Chosen encoding
786 * @param[in] info Encoding information
787 * @retval -1 Error, no conversion was possible
788 * @retval >0 Success, number of bytes converted
790 * Find the best charset conversion of the file from fromcode into one
791 * of the tocodes. If successful, set *tocode and Content *info and
792 * return the number of characters converted inexactly.
794 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
795 * which would otherwise prevent us from knowing the number of inexact
796 * conversions. Where the candidate target charset is UTF-8 we avoid
797 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
798 * fails with some libraries.
800 * We assume that the output from iconv is never more than 4 times as
801 * long as the input for any pair of charsets we might be interested
804 static size_t convert_file_to(FILE *fp, const char *fromcode, int ncodes,
805 char const *const *tocodes, int *tocode, struct Content *info)
807 char bufi[256], bufu[512], bufo[4 * sizeof(bufi)];
810 const iconv_t cd1 = mutt_ch_iconv_open("utf-8", fromcode, 0);
811 if (cd1 == (iconv_t)(-1))
814 iconv_t *cd = mutt_mem_calloc(ncodes, sizeof(iconv_t));
815 size_t *score = mutt_mem_calloc(ncodes, sizeof(size_t));
816 struct ContentState *states = mutt_mem_calloc(ncodes, sizeof(struct ContentState));
817 struct Content *infos = mutt_mem_calloc(ncodes, sizeof(struct Content));
819 for (int i = 0; i < ncodes; i++)
821 if (mutt_str_strcasecmp(tocodes[i], "utf-8") != 0)
822 cd[i] = mutt_ch_iconv_open(tocodes[i], "utf-8", 0);
825 /* Special case for conversion to UTF-8 */
826 cd[i] = (iconv_t)(-1);
827 score[i] = (size_t)(-1);
835 /* Try to fill input buffer */
836 size_t n = fread(bufi + ibl, 1, sizeof(bufi) - ibl, fp);
839 /* Convert to UTF-8 */
840 const char *ib = bufi;
842 size_t obl = sizeof(bufu);
843 n = iconv(cd1, (ICONV_CONST char **) ((ibl != 0) ? &ib : 0), &ibl, &ob, &obl);
844 /* assert(n == (size_t)(-1) || !n); */
845 if ((n == (size_t)(-1)) && (((errno != EINVAL) && (errno != E2BIG)) || (ib == bufi)))
847 /* assert(errno == EILSEQ || (errno == EINVAL && ib == bufi && ibl < sizeof(bufi))); */
851 const size_t ubl1 = ob - bufu;
853 /* Convert from UTF-8 */
854 for (int i = 0; i < ncodes; i++)
856 if ((cd[i] != (iconv_t)(-1)) && (score[i] != (size_t)(-1)))
858 const char *ub = bufu;
862 n = iconv(cd[i], (ICONV_CONST char **) ((ibl || ubl) ? &ub : 0), &ubl, &ob, &obl);
863 if (n == (size_t)(-1))
865 /* assert(errno == E2BIG || (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT))); */
866 score[i] = (size_t)(-1);
871 update_content_info(&infos[i], &states[i], bufo, ob - bufo);
874 else if ((cd[i] == (iconv_t)(-1)) && (score[i] == (size_t)(-1)))
876 /* Special case for conversion to UTF-8 */
877 update_content_info(&infos[i], &states[i], bufu, ubl1);
883 /* Save unused input */
884 memmove(bufi, ib, ibl);
886 else if (!ubl1 && (ib < bufi + sizeof(bufi)))
895 /* Find best score */
897 for (int i = 0; i < ncodes; i++)
899 if ((cd[i] == (iconv_t)(-1)) && (score[i] == (size_t)(-1)))
901 /* Special case for conversion to UTF-8 */
906 else if ((cd[i] == (iconv_t)(-1)) || (score[i] == (size_t)(-1)))
908 else if ((ret == (size_t)(-1)) || (score[i] < ret))
916 if (ret != (size_t)(-1))
918 memcpy(info, &infos[*tocode], sizeof(struct Content));
919 update_content_info(info, &states[*tocode], 0, 0); /* EOF */
923 for (int i = 0; i < ncodes; i++)
924 if (cd[i] != (iconv_t)(-1))
937 * convert_file_from_to - Convert a file between encodings
938 * @param[in] fp File to read from
939 * @param[in] fromcodes Charsets to try converting FROM
940 * @param[in] tocodes Charsets to try converting TO
941 * @param[out] fromcode From charset selected
942 * @param[out] tocode To charset selected
943 * @param[out] info Info about the file
944 * @retval num Characters converted
945 * @retval -1 Error (as a size_t)
947 * Find the first of the fromcodes that gives a valid conversion and the best
948 * charset conversion of the file into one of the tocodes. If successful, set
949 * *fromcode and *tocode to dynamically allocated strings, set Content *info,
950 * and return the number of characters converted inexactly. If no conversion
951 * was possible, return -1.
953 * Both fromcodes and tocodes may be colon-separated lists of charsets.
954 * However, if fromcode is zero then fromcodes is assumed to be the name of a
955 * single charset even if it contains a colon.
957 static size_t convert_file_from_to(FILE *fp, const char *fromcodes, const char *tocodes,
958 char **fromcode, char **tocode, struct Content *info)
962 const char *c = NULL, *c1 = NULL;
966 /* Count the tocodes */
968 for (c = tocodes; c; c = c1 ? c1 + 1 : 0)
977 tcode = mutt_mem_malloc(ncodes * sizeof(char *));
978 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++)
983 tcode[i] = mutt_str_substr_dup(c, c1);
989 /* Try each fromcode in turn */
990 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0)
995 fcode = mutt_str_substr_dup(c, c1);
997 ret = convert_file_to(fp, fcode, ncodes, (char const *const *) tcode, &cn, info);
998 if (ret != (size_t)(-1))
1001 *tocode = tcode[cn];
1010 /* There is only one fromcode */
1011 ret = convert_file_to(fp, fromcodes, ncodes, (char const *const *) tcode, &cn, info);
1012 if (ret != (size_t)(-1))
1014 *tocode = tcode[cn];
1020 for (i = 0; i < ncodes; i++)
1029 * mutt_get_content_info - Analyze file to determine MIME encoding to use
1030 * @param fname File to examine
1031 * @param b Body to update
1032 * @retval ptr Newly allocated Content
1034 * Also set the body charset, sometimes, or not.
1036 struct Content *mutt_get_content_info(const char *fname, struct Body *b)
1038 struct Content *info = NULL;
1039 struct ContentState state = { 0 };
1041 char *fromcode = NULL;
1042 char *tocode = NULL;
1049 fname = b->filename;
1051 if (stat(fname, &sb) == -1)
1053 mutt_error(_("Can't stat %s: %s"), fname, strerror(errno));
1057 if (!S_ISREG(sb.st_mode))
1059 mutt_error(_("%s isn't a regular file"), fname);
1063 fp = fopen(fname, "r");
1066 mutt_debug(LL_DEBUG1, "%s: %s (errno %d)\n", fname, strerror(errno), errno);
1070 info = mutt_mem_calloc(1, sizeof(struct Content));
1072 if (b && (b->type == TYPE_TEXT) && (!b->noconv && !b->force_charset))
1074 char *chs = mutt_param_get(&b->parameter, "charset");
1075 char *fchs = b->use_disp ? (C_AttachCharset ? C_AttachCharset : C_Charset) : C_Charset;
1076 if (C_Charset && (chs || C_SendCharset) &&
1077 (convert_file_from_to(fp, fchs, chs ? chs : C_SendCharset, &fromcode,
1078 &tocode, info) != (size_t)(-1)))
1083 mutt_ch_canonical_charset(chsbuf, sizeof(chsbuf), tocode);
1084 mutt_param_set(&b->parameter, "charset", chsbuf);
1087 b->charset = fromcode;
1089 mutt_file_fclose(&fp);
1095 while ((r = fread(buf, 1, sizeof(buf), fp)))
1096 update_content_info(info, &state, buf, r);
1097 update_content_info(info, &state, 0, 0);
1099 mutt_file_fclose(&fp);
1101 if (b && (b->type == TYPE_TEXT) && (!b->noconv && !b->force_charset))
1103 mutt_param_set(&b->parameter, "charset",
1106 C_Charset && !mutt_ch_is_us_ascii(C_Charset) ? C_Charset : "unknown-8bit"));
1113 * mutt_lookup_mime_type - Find the MIME type for an attachment
1114 * @param att Email with attachment
1115 * @param path Path to attachment
1116 * @retval num MIME type, e.g. #TYPE_IMAGE
1118 * Given a file at 'path', see if there is a registered MIME type.
1119 * Returns the major MIME type, and copies the subtype to "d". First look
1120 * in a system mime.types if we can find one, then look for ~/.mime.types.
1121 * The longest match is used so that we can match 'ps.gz' when 'gz' also
1124 enum ContentType mutt_lookup_mime_type(struct Body *att, const char *path)
1127 char *p = NULL, *q = NULL, *ct = NULL;
1129 char subtype[256] = { 0 };
1130 char xtype[256] = { 0 };
1131 int sze, cur_sze = 0;
1132 bool found_mimetypes = false;
1133 enum ContentType type = TYPE_OTHER;
1135 int szf = mutt_str_strlen(path);
1137 for (int count = 0; count < 4; count++)
1139 /* can't use strtok() because we use it in an inner loop below, so use
1140 * a switch statement here instead. */
1143 /* last file with last entry to match wins type/xtype */
1145 /* check default unix mimetypes location first */
1146 mutt_str_strfcpy(buf, "/etc/mime.types", sizeof(buf));
1149 mutt_str_strfcpy(buf, SYSCONFDIR "/mime.types", sizeof(buf));
1152 mutt_str_strfcpy(buf, PKGDATADIR "/mime.types", sizeof(buf));
1155 snprintf(buf, sizeof(buf), "%s/.mime.types", NONULL(HomeDir));
1158 mutt_debug(LL_DEBUG1, "Internal error, count = %d\n", count);
1159 goto bye; /* shouldn't happen */
1162 fp = fopen(buf, "r");
1165 found_mimetypes = true;
1167 while (fgets(buf, sizeof(buf) - 1, fp))
1169 /* weed out any comments */
1170 p = strchr(buf, '#');
1174 /* remove any leading space. */
1178 /* position on the next field in this line */
1179 p = strpbrk(ct, " \t");
1185 /* cycle through the file extensions */
1186 while ((p = strtok(p, " \t\n")))
1188 sze = mutt_str_strlen(p);
1189 if ((sze > cur_sze) && (szf >= sze) &&
1190 (mutt_str_strcasecmp(path + szf - sze, p) == 0) &&
1191 ((szf == sze) || (path[szf - sze - 1] == '.')))
1193 /* get the content-type */
1195 p = strchr(ct, '/');
1198 /* malformed line, just skip it. */
1203 for (q = p; *q && !IS_SPACE(*q); q++)
1206 mutt_str_substr_copy(p, q, subtype, sizeof(subtype));
1208 type = mutt_check_mime_type(ct);
1209 if (type == TYPE_OTHER)
1210 mutt_str_strfcpy(xtype, ct, sizeof(xtype));
1217 mutt_file_fclose(&fp);
1223 /* no mime.types file found */
1224 if (!found_mimetypes)
1226 mutt_error(_("Could not find any mime.types file."));
1229 if ((type != TYPE_OTHER) || (*xtype != '\0'))
1232 mutt_str_replace(&att->subtype, subtype);
1233 mutt_str_replace(&att->xtype, xtype);
1240 * transform_to_7bit - Convert MIME parts to 7-bit
1241 * @param a Body of the email
1242 * @param fp_in File to read
1244 static void transform_to_7bit(struct Body *a, FILE *fp_in)
1247 struct State s = { 0 };
1250 for (; a; a = a->next)
1252 if (a->type == TYPE_MULTIPART)
1254 a->encoding = ENC_7BIT;
1255 transform_to_7bit(a->parts, fp_in);
1257 else if (mutt_is_message_type(a->type, a->subtype))
1259 mutt_message_to_7bit(a, fp_in);
1264 a->force_charset = true;
1266 mutt_mktemp(buf, sizeof(buf));
1267 s.fp_out = mutt_file_fopen(buf, "w");
1270 mutt_perror("fopen");
1274 mutt_decode_attachment(a, &s);
1275 mutt_file_fclose(&s.fp_out);
1276 FREE(&a->d_filename);
1277 a->d_filename = a->filename;
1278 a->filename = mutt_str_strdup(buf);
1280 if (stat(a->filename, &sb) == -1)
1282 mutt_perror("stat");
1285 a->length = sb.st_size;
1287 mutt_update_encoding(a);
1288 if (a->encoding == ENC_8BIT)
1289 a->encoding = ENC_QUOTED_PRINTABLE;
1290 else if (a->encoding == ENC_BINARY)
1291 a->encoding = ENC_BASE64;
1297 * mutt_message_to_7bit - Convert an email's MIME parts to 7-bit
1298 * @param a Body of the email
1299 * @param fp File to read (OPTIONAL)
1301 void mutt_message_to_7bit(struct Body *a, FILE *fp)
1303 char temp[PATH_MAX];
1306 FILE *fp_out = NULL;
1309 if (!a->filename && fp)
1311 else if (!a->filename || !(fp_in = fopen(a->filename, "r")))
1313 mutt_error(_("Could not open %s"), a->filename ? a->filename : "(null)");
1319 if (stat(a->filename, &sb) == -1)
1321 mutt_perror("stat");
1322 mutt_file_fclose(&fp_in);
1324 a->length = sb.st_size;
1327 mutt_mktemp(temp, sizeof(temp));
1328 fp_out = mutt_file_fopen(temp, "w+");
1331 mutt_perror("fopen");
1338 fseeko(fp_in, a->offset, SEEK_SET);
1339 a->parts = mutt_rfc822_parse_message(fp_in, a);
1341 transform_to_7bit(a->parts, fp_in);
1343 mutt_copy_hdr(fp_in, fp_out, a->offset, a->offset + a->length,
1344 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL, 0);
1346 fputs("MIME-Version: 1.0\n", fp_out);
1347 mutt_write_mime_header(a->parts, fp_out);
1348 fputc('\n', fp_out);
1349 mutt_write_mime_body(a->parts, fp_out);
1354 if (fp_in && (fp_in != fp))
1355 mutt_file_fclose(&fp_in);
1357 mutt_file_fclose(&fp_out);
1361 a->encoding = ENC_7BIT;
1362 FREE(&a->d_filename);
1363 a->d_filename = a->filename;
1364 if (a->filename && a->unlink)
1365 unlink(a->filename);
1366 a->filename = mutt_str_strdup(temp);
1368 if (stat(a->filename, &sb) == -1)
1370 mutt_perror("stat");
1373 a->length = sb.st_size;
1374 mutt_body_free(&a->parts);
1375 a->email->content = NULL;
1379 * set_encoding - determine which Content-Transfer-Encoding to use
1380 * @param[in] b Body of email
1381 * @param[out] info Info about the email
1383 static void set_encoding(struct Body *b, struct Content *info)
1385 if (b->type == TYPE_TEXT)
1387 char send_charset[128];
1388 char *chsname = mutt_body_get_charset(b, send_charset, sizeof(send_charset));
1389 if ((info->lobin && !mutt_str_startswith(chsname, "iso-2022", CASE_IGNORE)) ||
1390 (info->linemax > 990) || (info->from && C_EncodeFrom))
1392 b->encoding = ENC_QUOTED_PRINTABLE;
1394 else if (info->hibin)
1396 b->encoding = C_Allow8bit ? ENC_8BIT : ENC_QUOTED_PRINTABLE;
1400 b->encoding = ENC_7BIT;
1403 else if ((b->type == TYPE_MESSAGE) || (b->type == TYPE_MULTIPART))
1405 if (info->lobin || info->hibin)
1407 if (C_Allow8bit && !info->lobin)
1408 b->encoding = ENC_8BIT;
1410 mutt_message_to_7bit(b, NULL);
1413 b->encoding = ENC_7BIT;
1415 else if ((b->type == TYPE_APPLICATION) &&
1416 (mutt_str_strcasecmp(b->subtype, "pgp-keys") == 0))
1418 b->encoding = ENC_7BIT;
1422 /* Determine which encoding is smaller */
1423 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1424 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1426 b->encoding = ENC_BASE64;
1430 b->encoding = ENC_QUOTED_PRINTABLE;
1436 * mutt_stamp_attachment - Timestamp an Attachment
1437 * @param a Attachment
1439 void mutt_stamp_attachment(struct Body *a)
1441 a->stamp = mutt_date_epoch();
1445 * mutt_body_get_charset - Get a body's character set
1446 * @param b Body to examine
1447 * @param buf Buffer for the result
1448 * @param buflen Length of the buffer
1449 * @retval ptr Buffer containing character set
1450 * @retval NULL On error, or if not a text type
1452 char *mutt_body_get_charset(struct Body *b, char *buf, size_t buflen)
1456 if (b && (b->type != TYPE_TEXT))
1460 p = mutt_param_get(&b->parameter, "charset");
1463 mutt_ch_canonical_charset(buf, buflen, p);
1465 mutt_str_strfcpy(buf, "us-ascii", buflen);
1471 * mutt_update_encoding - Update the encoding type
1472 * @param a Body to update
1474 * Assumes called from send mode where Body->filename points to actual file
1476 void mutt_update_encoding(struct Body *a)
1478 struct Content *info = NULL;
1481 /* override noconv when it's us-ascii */
1482 if (mutt_ch_is_us_ascii(mutt_body_get_charset(a, chsbuf, sizeof(chsbuf))))
1485 if (!a->force_charset && !a->noconv)
1486 mutt_param_delete(&a->parameter, "charset");
1488 info = mutt_get_content_info(a->filename, a);
1492 set_encoding(a, info);
1493 mutt_stamp_attachment(a);
1500 * mutt_make_message_attach - Create a message attachment
1503 * @param attach_msg true if attaching a message
1504 * @retval ptr Newly allocated Body
1505 * @retval NULL Error
1507 struct Body *mutt_make_message_attach(struct Mailbox *m, struct Email *e, bool attach_msg)
1510 struct Body *body = NULL;
1512 CopyMessageFlags cmflags;
1513 SecurityFlags pgp = WithCrypto ? e->security : SEC_NO_FLAGS;
1517 if ((C_MimeForwardDecode || C_ForwardDecrypt) && (e->security & SEC_ENCRYPT))
1519 if (!crypt_valid_passphrase(e->security))
1524 mutt_mktemp(buf, sizeof(buf));
1525 fp = mutt_file_fopen(buf, "w+");
1529 body = mutt_body_new();
1530 body->type = TYPE_MESSAGE;
1531 body->subtype = mutt_str_strdup("rfc822");
1532 body->filename = mutt_str_strdup(buf);
1533 body->unlink = true;
1534 body->use_disp = false;
1535 body->disposition = DISP_INLINE;
1536 body->noconv = true;
1538 mutt_parse_mime_message(m, e);
1540 CopyHeaderFlags chflags = CH_XMIT;
1541 cmflags = MUTT_CM_NO_FLAGS;
1543 /* If we are attaching a message, ignore C_MimeForwardDecode */
1544 if (!attach_msg && C_MimeForwardDecode)
1546 chflags |= CH_MIME | CH_TXTPLAIN;
1547 cmflags = MUTT_CM_DECODE | MUTT_CM_CHARCONV;
1548 if (WithCrypto & APPLICATION_PGP)
1549 pgp &= ~PGP_ENCRYPT;
1550 if (WithCrypto & APPLICATION_SMIME)
1551 pgp &= ~SMIME_ENCRYPT;
1553 else if ((WithCrypto != 0) && C_ForwardDecrypt && (e->security & SEC_ENCRYPT))
1555 if (((WithCrypto & APPLICATION_PGP) != 0) && mutt_is_multipart_encrypted(e->content))
1557 chflags |= CH_MIME | CH_NONEWLINE;
1558 cmflags = MUTT_CM_DECODE_PGP;
1559 pgp &= ~PGP_ENCRYPT;
1561 else if (((WithCrypto & APPLICATION_PGP) != 0) &&
1562 ((mutt_is_application_pgp(e->content) & PGP_ENCRYPT) == PGP_ENCRYPT))
1564 chflags |= CH_MIME | CH_TXTPLAIN;
1565 cmflags = MUTT_CM_DECODE | MUTT_CM_CHARCONV;
1566 pgp &= ~PGP_ENCRYPT;
1568 else if (((WithCrypto & APPLICATION_SMIME) != 0) &&
1569 ((mutt_is_application_smime(e->content) & SMIME_ENCRYPT) == SMIME_ENCRYPT))
1571 chflags |= CH_MIME | CH_TXTPLAIN;
1572 cmflags = MUTT_CM_DECODE | MUTT_CM_CHARCONV;
1573 pgp &= ~SMIME_ENCRYPT;
1577 mutt_copy_message(fp, m, e, cmflags, chflags, 0);
1582 body->email = email_new();
1583 body->email->offset = 0;
1584 /* we don't need the user headers here */
1585 body->email->env = mutt_rfc822_read_header(fp, body->email, false, false);
1587 body->email->security = pgp;
1588 mutt_update_encoding(body);
1589 body->parts = body->email->content;
1591 mutt_file_fclose(&fp);
1597 * run_mime_type_query - Run an external command to determine the MIME type
1598 * @param att Attachment
1600 * The command in $mime_type_query_command is run.
1602 static void run_mime_type_query(struct Body *att)
1604 FILE *fp = NULL, *fp_err = NULL;
1609 struct Buffer *cmd = mutt_buffer_pool_get();
1611 mutt_buffer_file_expand_fmt_quote(cmd, C_MimeTypeQueryCommand, att->filename);
1613 pid = mutt_create_filter(mutt_b2s(cmd), NULL, &fp, &fp_err);
1616 mutt_error(_("Error running \"%s\""), mutt_b2s(cmd));
1617 mutt_buffer_pool_release(&cmd);
1620 mutt_buffer_pool_release(&cmd);
1622 buf = mutt_file_read_line(buf, &buflen, fp, &dummy, 0);
1625 if (strchr(buf, '/'))
1626 mutt_parse_content_type(buf, att);
1630 mutt_file_fclose(&fp);
1631 mutt_file_fclose(&fp_err);
1632 mutt_wait_filter(pid);
1636 * mutt_make_file_attach - Create a file attachment
1637 * @param path File to attach
1638 * @retval ptr Newly allocated Body
1639 * @retval NULL Error
1641 struct Body *mutt_make_file_attach(const char *path)
1643 struct Body *att = mutt_body_new();
1644 att->filename = mutt_str_strdup(path);
1646 if (C_MimeTypeQueryCommand && C_MimeTypeQueryFirst)
1647 run_mime_type_query(att);
1649 /* Attempt to determine the appropriate content-type based on the filename
1652 mutt_lookup_mime_type(att, path);
1654 if (!att->subtype && C_MimeTypeQueryCommand && !C_MimeTypeQueryFirst)
1656 run_mime_type_query(att);
1659 struct Content *info = mutt_get_content_info(path, att);
1662 mutt_body_free(&att);
1668 if ((info->nulbin == 0) &&
1669 ((info->lobin == 0) || ((info->lobin + info->hibin + info->ascii) / info->lobin >= 10)))
1671 /* Statistically speaking, there should be more than 10% "lobin"
1672 * chars if this is really a binary file... */
1673 att->type = TYPE_TEXT;
1674 att->subtype = mutt_str_strdup("plain");
1678 att->type = TYPE_APPLICATION;
1679 att->subtype = mutt_str_strdup("octet-stream");
1684 mutt_update_encoding(att);
1689 * get_toplevel_encoding - Find the most restrictive encoding type
1690 * @param a Body to examine
1691 * @retval num Encoding type, e.g. #ENC_7BIT
1693 static int get_toplevel_encoding(struct Body *a)
1697 for (; a; a = a->next)
1699 if (a->encoding == ENC_BINARY)
1701 else if (a->encoding == ENC_8BIT)
1709 * check_boundary - check for duplicate boundary
1710 * @param boundary Boundary to look for
1711 * @param b Body parts to check
1712 * @retval true if duplicate found
1714 static bool check_boundary(const char *boundary, struct Body *b)
1718 if (b->parts && check_boundary(boundary, b->parts))
1721 if (b->next && check_boundary(boundary, b->next))
1724 p = mutt_param_get(&b->parameter, "boundary");
1725 if (p && (mutt_str_strcmp(p, boundary) == 0))
1733 * mutt_make_multipart - Create a multipart email
1734 * @param b Body of email to start
1735 * @retval ptr Newly allocated Body
1737 struct Body *mutt_make_multipart(struct Body *b)
1739 struct Body *new_body = mutt_body_new();
1740 new_body->type = TYPE_MULTIPART;
1741 new_body->subtype = mutt_str_strdup("mixed");
1742 new_body->encoding = get_toplevel_encoding(b);
1745 mutt_generate_boundary(&new_body->parameter);
1746 if (check_boundary(mutt_param_get(&new_body->parameter, "boundary"), b))
1747 mutt_param_delete(&new_body->parameter, "boundary");
1748 } while (!mutt_param_get(&new_body->parameter, "boundary"));
1749 new_body->use_disp = false;
1750 new_body->disposition = DISP_INLINE;
1751 new_body->parts = b;
1757 * mutt_remove_multipart - Extract the multipart body if it exists
1758 * @param b Body to alter
1759 * @retval ptr The parts of the Body
1761 * @note The original Body is freed
1763 struct Body *mutt_remove_multipart(struct Body *b)
1765 struct Body *t = NULL;
1778 * mutt_write_addrlist - wrapper around mutt_write_address()
1779 * @param al Address list
1780 * @param fp File to write to
1781 * @param linelen Line length to use
1782 * @param display True if these addresses will be displayed to the user
1784 * So we can handle very large recipient lists without needing a huge temporary
1787 void mutt_write_addrlist(struct AddressList *al, FILE *fp, int linelen, bool display)
1792 struct Address *a = NULL;
1793 TAILQ_FOREACH(a, al, entries)
1796 mutt_addr_write(buf, sizeof(buf), a, display);
1797 size_t len = mutt_str_strlen(buf);
1798 if (count && (linelen + len > 74))
1801 linelen = len + 8; /* tab is usually about 8 spaces... */
1805 if (count && a->mailbox)
1813 struct Address *next = TAILQ_NEXT(a, entries);
1814 if (!a->group && next && next->mailbox)
1825 * mutt_write_references - Add the message references to a list
1826 * @param r String List of references
1827 * @param fp File to write to
1828 * @param trim Trim the list to at most this many items
1830 * Write the list in reverse because they are stored in reverse order when
1831 * parsed to speed up threading.
1833 void mutt_write_references(const struct ListHead *r, FILE *fp, size_t trim)
1835 struct ListNode *np = NULL;
1838 STAILQ_FOREACH(np, r, entries)
1840 if (++length == trim)
1844 struct ListNode **ref = mutt_mem_calloc(length, sizeof(struct ListNode *));
1846 // store in reverse order
1847 size_t tmp = length;
1848 STAILQ_FOREACH(np, r, entries)
1855 for (size_t i = 0; i < length; i++)
1858 fputs(ref[i]->data, fp);
1859 if (i != length - 1)
1867 * print_val - Add pieces to an email header, wrapping where necessary
1868 * @param fp File to write to
1869 * @param pfx Prefix for headers
1870 * @param value Text to be added
1871 * @param chflags Flags, see #CopyHeaderFlags
1872 * @param col Column that this text starts at
1874 * @retval -1 Failure
1876 static int print_val(FILE *fp, const char *pfx, const char *value,
1877 CopyHeaderFlags chflags, size_t col)
1879 while (value && (value[0] != '\0'))
1881 if (fputc(*value, fp) == EOF)
1883 /* corner-case: break words longer than 998 chars by force,
1884 * mandated by RFC5322 */
1885 if (!(chflags & CH_DISPLAY) && (++col >= 998))
1887 if (fputs("\n ", fp) < 0)
1893 if ((value[1] != '\0') && pfx && (pfx[0] != '\0') && (fputs(pfx, fp) == EOF))
1895 /* for display, turn folding spaces into folding tabs */
1896 if ((chflags & CH_DISPLAY) && ((value[1] == ' ') || (value[1] == '\t')))
1899 while ((value[0] != '\0') && ((value[0] == ' ') || (value[0] == '\t')))
1901 if (fputc('\t', fp) == EOF)
1912 * fold_one_header - Fold one header line
1913 * @param fp File to write to
1914 * @param tag Header key, e.g. "From"
1915 * @param value Header value
1916 * @param pfx Prefix for header
1917 * @param wraplen Column to wrap at
1918 * @param chflags Flags, see #CopyHeaderFlags
1920 * @retval -1 Failure
1922 static int fold_one_header(FILE *fp, const char *tag, const char *value,
1923 const char *pfx, int wraplen, CopyHeaderFlags chflags)
1925 const char *p = value;
1926 char buf[8192] = { 0 };
1927 int first = 1, col = 0, l = 0;
1928 const bool display = (chflags & CH_DISPLAY);
1930 mutt_debug(LL_DEBUG5, "pfx=[%s], tag=[%s], flags=%d value=[%s]\n", pfx, tag,
1931 chflags, NONULL(value));
1933 if (tag && *tag && (fprintf(fp, "%s%s: ", NONULL(pfx), tag) < 0))
1935 col = mutt_str_strlen(tag) + ((tag && (tag[0] != '\0')) ? 2 : 0) + mutt_str_strlen(pfx);
1937 while (p && (p[0] != '\0'))
1941 /* find the next word and place it in 'buf'. it may start with
1942 * whitespace we can fold before */
1943 const char *next = mutt_str_find_word(p);
1944 l = MIN(sizeof(buf) - 1, next - p);
1948 /* determine width: character cells for display, bytes for sending
1949 * (we get pure ascii only) */
1950 const int w = mutt_mb_width(buf, col, display);
1951 const int enc = mutt_str_startswith(buf, "=?", CASE_MATCH);
1953 mutt_debug(LL_DEBUG5, "word=[%s], col=%d, w=%d, next=[0x0%x]\n", buf, col, w, *next);
1955 /* insert a folding \n before the current word's lwsp except for
1956 * header name, first word on a line (word longer than wrap width)
1957 * and encoded words */
1958 if (!first && !enc && col && ((col + w) >= wraplen))
1960 col = mutt_str_strlen(pfx);
1962 if (fprintf(fp, "\n%s", NONULL(pfx)) <= 0)
1966 /* print the actual word; for display, ignore leading ws for word
1967 * and fold with tab for readability */
1968 if (display && fold)
1971 while ((pc[0] != '\0') && ((pc[0] == ' ') || (pc[0] == '\t')))
1976 if (fputc('\t', fp) == EOF)
1978 if (print_val(fp, pfx, pc, chflags, col) < 0)
1982 else if (print_val(fp, pfx, buf, chflags, col) < 0)
1986 /* if the current word ends in \n, ignore all its trailing spaces
1987 * and reset column; this prevents us from putting only spaces (or
1988 * even none) on a line if the trailing spaces are located at our
1989 * current line width
1990 * XXX this covers ASCII space only, for display we probably
1991 * want something like iswspace() here */
1992 const char *sp = next;
1993 while ((sp[0] != '\0') && ((sp[0] == ' ') || (sp[0] == '\t')))
2005 /* if we have printed something but didn't \n-terminate it, do it
2006 * except the last word we printed ended in \n already */
2007 if (col && ((l == 0) || (buf[l - 1] != '\n')))
2008 if (putc('\n', fp) == EOF)
2015 * unfold_header - Unfold a wrapped email header
2016 * @param s String to process
2017 * @retval ptr Unfolded string
2019 * @note The string is altered in-place
2021 static char *unfold_header(char *s)
2026 while (p && (p[0] != '\0'))
2028 /* remove CRLF prior to FWSP, turn \t into ' ' */
2029 if ((p[0] == '\r') && (p[1] == '\n') && ((p[2] == ' ') || (p[2] == '\t')))
2035 /* remove LF prior to FWSP, turn \t into ' ' */
2036 else if ((p[0] == '\n') && ((p[1] == ' ') || (p[1] == '\t')))
2051 * write_one_header - Write out one header line
2052 * @param fp File to write to
2053 * @param pfxw Width of prefix string
2054 * @param max Max width
2055 * @param wraplen Column to wrap at
2056 * @param pfx Prefix for header
2057 * @param start Start of header line
2058 * @param end End of header line
2059 * @param chflags Flags, see #CopyHeaderFlags
2061 * @retval -1 Failure
2063 static int write_one_header(FILE *fp, int pfxw, int max, int wraplen, const char *pfx,
2064 const char *start, const char *end, CopyHeaderFlags chflags)
2066 char *tagbuf = NULL, *valbuf = NULL, *t = NULL;
2067 bool is_from = ((end - start) > 5) && mutt_str_startswith(start, "from ", CASE_IGNORE);
2069 /* only pass through folding machinery if necessary for sending,
2070 * never wrap From_ headers on sending */
2071 if (!(chflags & CH_DISPLAY) && ((pfxw + max <= wraplen) || is_from))
2073 valbuf = mutt_str_substr_dup(start, end);
2074 mutt_debug(LL_DEBUG5, "buf[%s%s] short enough, max width = %d <= %d\n",
2075 NONULL(pfx), valbuf, max, wraplen);
2078 if (fputs(pfx, fp) == EOF)
2085 t = strchr(valbuf, ':');
2088 mutt_debug(LL_DEBUG1, "#1 warning: header not in 'key: value' format!\n");
2092 if (print_val(fp, pfx, valbuf, chflags, mutt_str_strlen(pfx)) < 0)
2101 t = strchr(start, ':');
2102 if (!t || (t > end))
2104 mutt_debug(LL_DEBUG1, "#2 warning: header not in 'key: value' format!\n");
2110 valbuf = mutt_str_substr_dup(start, end);
2114 tagbuf = mutt_str_substr_dup(start, t);
2115 /* skip over the colon separating the header field name and value */
2118 /* skip over any leading whitespace (WSP, as defined in RFC5322)
2119 * NOTE: mutt_str_skip_email_wsp() does the wrong thing here.
2120 * See tickets 3609 and 3716. */
2121 while ((*t == ' ') || (*t == '\t'))
2124 valbuf = mutt_str_substr_dup(t, end);
2126 mutt_debug(LL_DEBUG2, "buf[%s%s] too long, max width = %d > %d\n",
2127 NONULL(pfx), NONULL(valbuf), max, wraplen);
2128 if (fold_one_header(fp, tagbuf, valbuf, pfx, wraplen, chflags) < 0)
2141 * mutt_write_one_header - Write one header line to a file
2142 * @param fp File to write to
2143 * @param tag Header key, e.g. "From"
2144 * @param value Header value
2145 * @param pfx Prefix for header
2146 * @param wraplen Column to wrap at
2147 * @param chflags Flags, see #CopyHeaderFlags
2149 * @retval -1 Failure
2151 * split several headers into individual ones and call write_one_header
2154 int mutt_write_one_header(FILE *fp, const char *tag, const char *value,
2155 const char *pfx, int wraplen, CopyHeaderFlags chflags)
2157 char *last = NULL, *line = NULL;
2158 int max = 0, w, rc = -1;
2159 int pfxw = mutt_strwidth(pfx);
2160 char *v = mutt_str_strdup(value);
2161 bool display = (chflags & CH_DISPLAY);
2163 if (!display || C_Weed)
2164 v = unfold_header(v);
2166 /* when not displaying, use sane wrap value */
2169 if ((C_WrapHeaders < 78) || (C_WrapHeaders > 998))
2172 wraplen = C_WrapHeaders;
2174 else if (wraplen <= 0)
2179 /* if header is short enough, simply print it */
2180 if (!display && (mutt_strwidth(tag) + 2 + pfxw + mutt_strwidth(v) <= wraplen))
2182 mutt_debug(LL_DEBUG5, "buf[%s%s: %s] is short enough\n", NONULL(pfx), tag, v);
2183 if (fprintf(fp, "%s%s: %s\n", NONULL(pfx), tag, v) <= 0)
2190 rc = fold_one_header(fp, tag, v, pfx, wraplen, chflags);
2200 p = strchr(p, '\n');
2202 /* find maximum line width in current header */
2205 w = mutt_mb_width(line, 0, display);
2215 if ((*p != ' ') && (*p != '\t'))
2217 if (write_one_header(fp, pfxw, max, wraplen, pfx, last, p, chflags) < 0)
2225 if (write_one_header(fp, pfxw, max, wraplen, pfx, last, p, chflags) < 0)
2236 * userhdrs_override_cmp - Compare a user-defined header with an element of the userhdrs_override_headers list
2237 * @param a Pointer to the string containing the user-defined header
2238 * @param b Pointer to an element of the userhdrs_override_headers list
2239 * @retval -1 a precedes b
2240 * @retval 0 a and b are identical
2241 * @retval 1 b precedes a
2243 static int userhdrs_override_cmp(const void *a, const void *b)
2246 const char *cb = *(const char **) b;
2247 return mutt_str_strncasecmp(ca, cb, strlen(cb));
2251 * write_userhdrs - Write user-defined headers and keep track of the interesting ones
2252 * @param fp FILE pointer where to write the headers
2253 * @param userhdrs List of headers to write
2254 * @param privacy Omit headers that could identify the user
2255 * @retval obj UserHdrsOverride struct containing a bitmask of which unique headers were written
2257 static struct UserHdrsOverride write_userhdrs(FILE *fp, const struct ListHead *userhdrs, bool privacy)
2259 struct UserHdrsOverride overrides = { { 0 } };
2261 struct ListNode *tmp = NULL;
2262 STAILQ_FOREACH(tmp, userhdrs, entries)
2264 char *const colon = strchr(tmp->data, ':');
2270 const char *const value = mutt_str_skip_email_wsp(colon + 1);
2273 continue; /* don't emit empty fields. */
2276 /* check whether the current user-header is an override */
2277 size_t curr_override = (size_t) -1;
2278 const char *const *idx = bsearch(tmp->data, userhdrs_override_headers,
2279 mutt_array_size(userhdrs_override_headers),
2280 sizeof(char *), userhdrs_override_cmp);
2283 curr_override = idx - userhdrs_override_headers;
2284 overrides.is_overridden[curr_override] = true;
2287 if (privacy && (curr_override == USERHDRS_OVERRIDE_USER_AGENT))
2293 mutt_write_one_header(fp, tmp->data, value, NULL, 0, CH_NO_FLAGS);
2301 * mutt_rfc822_write_header - Write out one RFC822 header line
2302 * @param fp File to write to
2303 * @param env Envelope of email
2304 * @param attach Attachment
2305 * @param mode Mode, see #MuttWriteHeaderMode
2306 * @param privacy If true, remove headers that might identify the user
2307 * @param hide_protected_subject If true, replace subject header
2309 * @retval -1 Failure
2311 * Note: all RFC2047 encoding should be done outside of this routine, except
2312 * for the "real name." This will allow this routine to be used more than
2313 * once, if necessary.
2315 * Likewise, all IDN processing should happen outside of this routine.
2317 * privacy true => will omit any headers which may identify the user.
2318 * Output generated is suitable for being sent through
2319 * anonymous remailer chains.
2321 * hide_protected_subject: replaces the Subject header with
2322 * $crypt_protected_headers_subject in NORMAL or POSTPONE mode.
2324 int mutt_rfc822_write_header(FILE *fp, struct Envelope *env,
2325 struct Body *attach, enum MuttWriteHeaderMode mode,
2326 bool privacy, bool hide_protected_subject)
2330 if ((mode == MUTT_WRITE_HEADER_NORMAL) && !privacy)
2331 fputs(mutt_date_make_date(buf, sizeof(buf)), fp);
2333 /* UseFrom is not consulted here so that we can still write a From:
2334 * field if the user sets it with the 'my_hdr' command */
2335 if (!TAILQ_EMPTY(&env->from) && !privacy)
2338 mutt_addrlist_write(buf, sizeof(buf), &env->from, false);
2339 fprintf(fp, "From: %s\n", buf);
2342 if (!TAILQ_EMPTY(&env->sender) && !privacy)
2345 mutt_addrlist_write(buf, sizeof(buf), &env->sender, false);
2346 fprintf(fp, "Sender: %s\n", buf);
2349 if (!TAILQ_EMPTY(&env->to))
2352 mutt_write_addrlist(&env->to, fp, 4, 0);
2354 else if (mode == MUTT_WRITE_HEADER_EDITHDRS)
2360 if (!TAILQ_EMPTY(&env->cc))
2363 mutt_write_addrlist(&env->cc, fp, 4, 0);
2365 else if (mode == MUTT_WRITE_HEADER_EDITHDRS)
2371 if (!TAILQ_EMPTY(&env->bcc))
2373 if ((mode == MUTT_WRITE_HEADER_POSTPONE) || (mode == MUTT_WRITE_HEADER_EDITHDRS) ||
2374 ((mode == MUTT_WRITE_HEADER_NORMAL) && C_WriteBcc))
2377 mutt_write_addrlist(&env->bcc, fp, 5, 0);
2380 else if (mode == MUTT_WRITE_HEADER_EDITHDRS)
2384 fputs("Bcc:\n", fp);
2387 if (env->newsgroups)
2388 fprintf(fp, "Newsgroups: %s\n", env->newsgroups);
2389 else if ((mode == MUTT_WRITE_HEADER_EDITHDRS) && OptNewsSend)
2390 fputs("Newsgroups:\n", fp);
2392 if (env->followup_to)
2393 fprintf(fp, "Followup-To: %s\n", env->followup_to);
2394 else if ((mode == MUTT_WRITE_HEADER_EDITHDRS) && OptNewsSend)
2395 fputs("Followup-To:\n", fp);
2397 if (env->x_comment_to)
2398 fprintf(fp, "X-Comment-To: %s\n", env->x_comment_to);
2399 else if ((mode == MUTT_WRITE_HEADER_EDITHDRS) && OptNewsSend && C_XCommentTo)
2400 fputs("X-Comment-To:\n", fp);
2405 if (hide_protected_subject &&
2406 ((mode == MUTT_WRITE_HEADER_NORMAL) || (mode == MUTT_WRITE_HEADER_POSTPONE)))
2407 mutt_write_one_header(fp, "Subject", C_CryptProtectedHeadersSubject, NULL, 0, CH_NO_FLAGS);
2409 mutt_write_one_header(fp, "Subject", env->subject, NULL, 0, CH_NO_FLAGS);
2411 else if (mode == MUTT_WRITE_HEADER_EDITHDRS)
2412 fputs("Subject:\n", fp);
2414 /* save message id if the user has set it */
2415 if (env->message_id && !privacy)
2416 fprintf(fp, "Message-ID: %s\n", env->message_id);
2418 if (!TAILQ_EMPTY(&env->reply_to))
2420 fputs("Reply-To: ", fp);
2421 mutt_write_addrlist(&env->reply_to, fp, 10, 0);
2423 else if (mode == MUTT_WRITE_HEADER_EDITHDRS)
2424 fputs("Reply-To:\n", fp);
2426 if (!TAILQ_EMPTY(&env->mail_followup_to))
2432 fputs("Mail-Followup-To: ", fp);
2433 mutt_write_addrlist(&env->mail_followup_to, fp, 18, 0);
2437 /* Add any user defined headers */
2438 struct UserHdrsOverride userhdrs_overrides = write_userhdrs(fp, &env->userhdrs, privacy);
2440 if ((mode == MUTT_WRITE_HEADER_NORMAL) || (mode == MUTT_WRITE_HEADER_POSTPONE))
2442 if (!STAILQ_EMPTY(&env->references))
2444 fputs("References:", fp);
2445 mutt_write_references(&env->references, fp, 10);
2449 /* Add the MIME headers */
2450 if (!userhdrs_overrides.is_overridden[USERHDRS_OVERRIDE_CONTENT_TYPE])
2452 fputs("MIME-Version: 1.0\n", fp);
2453 mutt_write_mime_header(attach, fp);
2457 if (!STAILQ_EMPTY(&env->in_reply_to))
2459 fputs("In-Reply-To:", fp);
2460 mutt_write_references(&env->in_reply_to, fp, 0);
2464 #ifdef USE_AUTOCRYPT
2467 if (mode == MUTT_WRITE_HEADER_NORMAL)
2468 mutt_autocrypt_write_autocrypt_header(env, fp);
2469 if (mode == MUTT_WRITE_HEADER_MIME)
2470 mutt_autocrypt_write_gossip_headers(env, fp);
2474 if ((mode == MUTT_WRITE_HEADER_NORMAL) && !privacy && C_UserAgent &&
2475 !userhdrs_overrides.is_overridden[USERHDRS_OVERRIDE_USER_AGENT])
2477 /* Add a vanity header */
2478 fprintf(fp, "User-Agent: NeoMutt/%s%s\n", PACKAGE_VERSION, GitVer);
2481 return (ferror(fp) == 0) ? 0 : -1;
2485 * encode_headers - RFC2047-encode a list of headers
2486 * @param h String List of headers
2488 * The strings are encoded in-place.
2490 static void encode_headers(struct ListHead *h)
2496 struct ListNode *np = NULL;
2497 STAILQ_FOREACH(np, h, entries)
2499 p = strchr(np->data, ':');
2504 p = mutt_str_skip_email_wsp(p + 1);
2505 tmp = mutt_str_strdup(p);
2510 rfc2047_encode(&tmp, NULL, i + 2, C_SendCharset);
2511 mutt_mem_realloc(&np->data, i + 2 + mutt_str_strlen(tmp) + 1);
2513 sprintf(np->data + i + 2, "%s", tmp);
2520 * mutt_fqdn - Get the Fully-Qualified Domain Name
2521 * @param may_hide_host If true, hide the hostname (leaving just the domain)
2522 * @retval ptr string pointer into Hostname
2523 * @retval NULL Error, e.g no Hostname
2525 * @warning Do not free the returned pointer
2527 const char *mutt_fqdn(bool may_hide_host)
2529 if (!C_Hostname || (C_Hostname[0] == '@'))
2532 char *p = C_Hostname;
2534 if (may_hide_host && C_HiddenHost)
2536 p = strchr(C_Hostname, '.');
2540 // sanity check: don't hide the host if the fqdn is something like example.com
2541 if (!p || !strchr(p, '.'))
2549 * gen_msgid - Generate a unique Message ID
2550 * @retval ptr Message ID
2552 * @note The caller should free the string
2554 static char *gen_msgid(void)
2557 unsigned char rndid[MUTT_RANDTAG_LEN + 1];
2559 mutt_rand_base32(rndid, sizeof(rndid) - 1);
2560 rndid[MUTT_RANDTAG_LEN] = 0;
2561 const char *fqdn = mutt_fqdn(false);
2563 fqdn = NONULL(ShortHostname);
2565 struct tm tm = mutt_date_gmtime(MUTT_DATE_NOW);
2566 snprintf(buf, sizeof(buf), "<%d%02d%02d%02d%02d%02d.%s@%s>", tm.tm_year + 1900,
2567 tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, rndid, fqdn);
2568 return mutt_str_strdup(buf);
2572 * alarm_handler - Async notification of an alarm signal
2573 * @param sig Signal, (SIGALRM)
2575 static void alarm_handler(int sig)
2581 * send_msg - invoke sendmail in a subshell
2582 * @param[in] path Path to program to execute
2583 * @param[in] args Arguments to pass to program
2584 * @param[in] msg Temp file containing message to send
2585 * @param[out] tempfile If sendmail is put in the background, this points
2586 * to the temporary file containing the stdout of the
2587 * child process. If it is NULL, stderr and stdout
2588 * are not redirected.
2590 * @retval >0 Failure, return code from sendmail
2592 static int send_msg(const char *path, char **args, const char *msg, char **tempfile)
2597 mutt_sig_block_system();
2600 /* we also don't want to be stopped right now */
2601 sigaddset(&set, SIGTSTP);
2602 sigprocmask(SIG_BLOCK, &set, NULL);
2604 if ((C_SendmailWait >= 0) && tempfile)
2608 mutt_mktemp(tmp, sizeof(tmp));
2609 *tempfile = mutt_str_strdup(tmp);
2615 struct sigaction act, oldalrm;
2617 /* save parent's ID before setsid() */
2618 pid_t ppid = getppid();
2620 /* we want the delivery to continue even after the main process dies,
2621 * so we put ourselves into another session right away */
2624 /* next we close all open files */
2627 for (int fd = tempfile ? 1 : 3; fd < OPEN_MAX; fd++)
2629 #elif defined(_POSIX_OPEN_MAX)
2630 for (int fd = tempfile ? 1 : 3; fd < _POSIX_OPEN_MAX; fd++)
2640 /* now the second fork() */
2644 /* "msg" will be opened as stdin */
2645 if (open(msg, O_RDONLY, 0) < 0)
2652 if ((C_SendmailWait >= 0) && tempfile && *tempfile)
2654 /* *tempfile will be opened as stdout */
2655 if (open(*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) < 0)
2657 /* redirect stderr to *tempfile too */
2663 if (open("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
2665 if (open("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
2669 /* execvpe is a glibc extension */
2670 /* execvpe (path, args, mutt_envlist_getlist()); */
2681 /* C_SendmailWait > 0: interrupt waitpid() after C_SendmailWait seconds
2682 * C_SendmailWait = 0: wait forever
2683 * C_SendmailWait < 0: don't wait */
2684 if (C_SendmailWait > 0)
2687 act.sa_handler = alarm_handler;
2689 /* need to make sure waitpid() is interrupted on SIGALRM */
2690 act.sa_flags = SA_INTERRUPT;
2694 sigemptyset(&act.sa_mask);
2695 sigaction(SIGALRM, &act, &oldalrm);
2696 alarm(C_SendmailWait);
2698 else if (C_SendmailWait < 0)
2699 _exit(0xff & EX_OK);
2701 if (waitpid(pid, &st, 0) > 0)
2703 st = WIFEXITED(st) ? WEXITSTATUS(st) : S_ERR;
2704 if (C_SendmailWait && (st == (0xff & EX_OK)) && tempfile && *tempfile)
2706 unlink(*tempfile); /* no longer needed */
2712 st = ((C_SendmailWait > 0) && (errno == EINTR) && SigAlrm) ? S_BKG : S_ERR;
2713 if ((C_SendmailWait > 0) && tempfile && *tempfile)
2720 if (C_SendmailWait > 0)
2722 /* reset alarm; not really needed, but... */
2724 sigaction(SIGALRM, &oldalrm, NULL);
2727 if ((kill(ppid, 0) == -1) && (errno == ESRCH) && tempfile && *tempfile)
2729 /* the parent is already dead */
2737 sigprocmask(SIG_UNBLOCK, &set, NULL);
2739 if ((pid != -1) && (waitpid(pid, &st, 0) > 0))
2740 st = WIFEXITED(st) ? WEXITSTATUS(st) : S_ERR; /* return child status */
2742 st = S_ERR; /* error */
2744 mutt_sig_unblock_system(true);
2750 * add_args_one - Add an Address to a dynamic array
2751 * @param[out] args Array to add to
2752 * @param[out] argslen Number of entries in array
2753 * @param[out] argsmax Allocated size of the array
2754 * @param[in] addr Address to add
2755 * @retval ptr Updated array
2757 static char **add_args_one(char **args, size_t *argslen, size_t *argsmax, struct Address *addr)
2759 /* weed out group mailboxes, since those are for display only */
2760 if (addr->mailbox && !addr->group)
2762 if (*argslen == *argsmax)
2763 mutt_mem_realloc(&args, (*argsmax += 5) * sizeof(char *));
2764 args[(*argslen)++] = addr->mailbox;
2770 * add_args - Add a list of Addresses to a dynamic array
2771 * @param[out] args Array to add to
2772 * @param[out] argslen Number of entries in array
2773 * @param[out] argsmax Allocated size of the array
2774 * @param[in] al Addresses to add
2775 * @retval ptr Updated array
2777 static char **add_args(char **args, size_t *argslen, size_t *argsmax, struct AddressList *al)
2782 struct Address *a = NULL;
2783 TAILQ_FOREACH(a, al, entries)
2785 args = add_args_one(args, argslen, argsmax, a);
2791 * add_option - Add a string to a dynamic array
2792 * @param[out] args Array to add to
2793 * @param[out] argslen Number of entries in array
2794 * @param[out] argsmax Allocated size of the array
2795 * @param[in] s string to add
2796 * @retval ptr Updated array
2798 * @note The array may be realloc()'d
2800 static char **add_option(char **args, size_t *argslen, size_t *argsmax, char *s)
2802 if (*argslen == *argsmax)
2803 mutt_mem_realloc(&args, (*argsmax += 5) * sizeof(char *));
2804 args[(*argslen)++] = s;
2809 * mutt_invoke_sendmail - Run sendmail
2810 * @param from The sender
2811 * @param to Recipients
2812 * @param cc Recipients
2813 * @param bcc Recipients
2814 * @param msg File containing message
2815 * @param eightbit Message contains 8bit chars
2817 * @retval -1 Failure
2819 int mutt_invoke_sendmail(struct AddressList *from, struct AddressList *to,
2820 struct AddressList *cc, struct AddressList *bcc,
2821 const char *msg, int eightbit)
2823 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
2825 size_t argslen = 0, argsmax = 0;
2826 char **extra_args = NULL;
2834 mutt_expando_format(cmd, sizeof(cmd), 0, sizeof(cmd), NONULL(C_Inews),
2835 nntp_format_str, 0, MUTT_FORMAT_NO_FLAGS);
2838 i = nntp_post(Context->mailbox, msg);
2843 s = mutt_str_strdup(cmd);
2847 s = mutt_str_strdup(C_Sendmail);
2849 /* ensure that $sendmail is set to avoid a crash. http://dev.mutt.org/trac/ticket/3548 */
2852 mutt_error(_("$sendmail must be set in order to send mail"));
2858 while ((ps = strtok(ps, " ")))
2860 if (argslen == argsmax)
2861 mutt_mem_realloc(&args, sizeof(char *) * (argsmax += 5));
2865 if (mutt_str_strcmp(ps, "--") == 0)
2867 args[argslen++] = ps;
2871 path = mutt_str_strdup(ps);
2872 ps = strrchr(ps, '/');
2877 args[argslen++] = ps;
2887 size_t extra_argslen = 0;
2888 /* If C_Sendmail contained a "--", we save the recipients to append to
2889 * args after other possible options added below. */
2893 size_t extra_argsmax = 0;
2894 while ((ps = strtok(ps, " ")))
2896 if (extra_argslen == extra_argsmax)
2897 mutt_mem_realloc(&extra_args, sizeof(char *) * (extra_argsmax += 5));
2899 extra_args[extra_argslen++] = ps;
2904 if (eightbit && C_Use8bitmime)
2905 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
2907 if (C_UseEnvelopeFrom)
2909 if (C_EnvelopeFromAddress)
2911 args = add_option(args, &argslen, &argsmax, "-f");
2912 args = add_args_one(args, &argslen, &argsmax, C_EnvelopeFromAddress);
2914 else if (!TAILQ_EMPTY(from) && !TAILQ_NEXT(TAILQ_FIRST(from), entries))
2916 args = add_option(args, &argslen, &argsmax, "-f");
2917 args = add_args(args, &argslen, &argsmax, from);
2923 args = add_option(args, &argslen, &argsmax, "-N");
2924 args = add_option(args, &argslen, &argsmax, C_DsnNotify);
2928 args = add_option(args, &argslen, &argsmax, "-R");
2929 args = add_option(args, &argslen, &argsmax, C_DsnReturn);
2931 args = add_option(args, &argslen, &argsmax, "--");
2932 for (i = 0; i < extra_argslen; i++)
2933 args = add_option(args, &argslen, &argsmax, extra_args[i]);
2934 args = add_args(args, &argslen, &argsmax, to);
2935 args = add_args(args, &argslen, &argsmax, cc);
2936 args = add_args(args, &argslen, &argsmax, bcc);
2941 if (argslen == argsmax)
2942 mutt_mem_realloc(&args, sizeof(char *) * (++argsmax));
2944 args[argslen++] = NULL;
2946 /* Some user's $sendmail command uses gpg for password decryption,
2947 * and is set up to prompt using ncurses pinentry. If we
2948 * mutt_endwin() it leaves other users staring at a blank screen.
2949 * So instead, just force a hard redraw on the next refresh. */
2951 mutt_need_hard_redraw();
2953 i = send_msg(path, args, msg, OptNoCurses ? NULL : &childout);
2954 if (i != (EX_OK & 0xff))
2958 const char *e = mutt_str_sysexit(i);
2959 mutt_error(_("Error sending message, child exited %d (%s)"), i, NONULL(e));
2964 if ((stat(childout, &st) == 0) && (st.st_size > 0))
2965 mutt_do_pager(_("Output of the delivery process"), childout,
2966 MUTT_PAGER_NO_FLAGS, NULL);
2979 if (i == (EX_OK & 0xff))
2981 else if (i == S_BKG)
2989 * mutt_prepare_envelope - Prepare an email header
2990 * @param env Envelope to prepare
2991 * @param final true if this email is going to be sent (not postponed)
2993 * Encode all the headers prior to sending the email.
2995 * For postponing (!final) do the necessary encodings only
2997 void mutt_prepare_envelope(struct Envelope *env, bool final)
3001 if (!TAILQ_EMPTY(&env->bcc) && TAILQ_EMPTY(&env->to) && TAILQ_EMPTY(&env->cc))
3003 /* some MTA's will put an Apparently-To: header field showing the Bcc:
3004 * recipients if there is no To: or Cc: field, so attempt to suppress
3005 * it by using an empty To: field. */
3006 struct Address *to = mutt_addr_new();
3008 mutt_addrlist_append(&env->to, to);
3009 mutt_addrlist_append(&env->to, mutt_addr_new());
3013 mutt_addr_cat(buf, sizeof(buf), "undisclosed-recipients", AddressSpecials);
3015 to->mailbox = mutt_str_strdup(buf);
3018 mutt_set_followup_to(env);
3020 if (!env->message_id)
3021 env->message_id = gen_msgid();
3024 /* Take care of 8-bit => 7-bit conversion. */
3025 rfc2047_encode_envelope(env);
3026 encode_headers(&env->userhdrs);
3030 * mutt_unprepare_envelope - Undo the encodings of mutt_prepare_envelope()
3031 * @param env Envelope to unprepare
3033 * Decode all the headers of an email, e.g. when the sending failed or was
3036 void mutt_unprepare_envelope(struct Envelope *env)
3038 struct ListNode *item = NULL;
3039 STAILQ_FOREACH(item, &env->userhdrs, entries)
3041 rfc2047_decode(&item->data);
3044 mutt_addrlist_clear(&env->mail_followup_to);
3046 /* back conversions */
3047 rfc2047_decode_envelope(env);
3051 * bounce_message - Bounce an email message
3052 * @param fp Handle of message
3054 * @param to Address to bounce to
3055 * @param resent_from Address of new sender
3056 * @param env_from Envelope of original sender
3058 * @retval -1 Failure
3060 static int bounce_message(FILE *fp, struct Email *e, struct AddressList *to,
3061 const char *resent_from, struct AddressList *env_from)
3067 char tempfile[PATH_MAX];
3069 mutt_mktemp(tempfile, sizeof(tempfile));
3070 FILE *fp_tmp = mutt_file_fopen(tempfile, "w");
3074 CopyHeaderFlags chflags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
3076 if (!C_BounceDelivered)
3077 chflags |= CH_WEED_DELIVERED;
3079 fseeko(fp, e->offset, SEEK_SET);
3080 fprintf(fp_tmp, "Resent-From: %s", resent_from);
3081 fprintf(fp_tmp, "\nResent-%s", mutt_date_make_date(date, sizeof(date)));
3082 char *msgid_str = gen_msgid();
3083 fprintf(fp_tmp, "Resent-Message-ID: %s\n", msgid_str);
3085 fputs("Resent-To: ", fp_tmp);
3086 mutt_write_addrlist(to, fp_tmp, 11, 0);
3087 mutt_copy_header(fp, e, fp_tmp, chflags, NULL, 0);
3088 fputc('\n', fp_tmp);
3089 mutt_file_copy_bytes(fp, fp_tmp, e->content->length);
3090 if (mutt_file_fclose(&fp_tmp) != 0)
3092 mutt_perror(tempfile);
3098 rc = mutt_smtp_send(env_from, to, NULL, NULL, tempfile, e->content->encoding == ENC_8BIT);
3101 rc = mutt_invoke_sendmail(env_from, to, NULL, NULL, tempfile,
3102 e->content->encoding == ENC_8BIT);
3109 * mutt_bounce_message - Bounce an email message
3110 * @param fp Handle of message
3112 * @param to AddressList to bounce to
3114 * @retval -1 Failure
3116 int mutt_bounce_message(FILE *fp, struct Email *e, struct AddressList *to)
3118 if (!fp || !e || !to || TAILQ_EMPTY(to))
3121 const char *fqdn = mutt_fqdn(true);
3122 char resent_from[256];
3125 resent_from[0] = '\0';
3126 struct Address *from = mutt_default_from();
3127 struct AddressList from_list = TAILQ_HEAD_INITIALIZER(from_list);
3128 mutt_addrlist_append(&from_list, from);
3130 /* mutt_default_from() does not use $realname if the real name is not set
3131 * in $from, so we add it here. The reason it is not added in
3132 * mutt_default_from() is that during normal sending, we execute
3133 * send-hooks and set the realname last so that it can be changed based
3134 * upon message criteria. */
3135 if (!from->personal)
3136 from->personal = mutt_str_strdup(C_Realname);
3138 mutt_addrlist_qualify(&from_list, fqdn);
3140 rfc2047_encode_addrlist(&from_list, "Resent-From");
3141 if (mutt_addrlist_to_intl(&from_list, &err))
3143 mutt_error(_("Bad IDN %s while preparing resent-from"), err);
3145 mutt_addrlist_clear(&from_list);
3148 mutt_addrlist_write(resent_from, sizeof(resent_from), &from_list, false);
3151 OptNewsSend = false;
3154 /* prepare recipient list. idna conversion appears to happen before this
3155 * function is called, since the user receives confirmation of the address
3156 * list being bounced to. */
3157 struct AddressList resent_to = TAILQ_HEAD_INITIALIZER(resent_to);
3158 mutt_addrlist_copy(&resent_to, to, false);
3159 rfc2047_encode_addrlist(&resent_to, "Resent-To");
3160 int rc = bounce_message(fp, e, &resent_to, resent_from, &from_list);
3161 mutt_addrlist_clear(&resent_to);
3162 mutt_addrlist_clear(&from_list);
3168 * set_noconv_flags - Set/reset the "x-mutt-noconv" flag
3169 * @param b Body of email
3170 * @param flag If true, set the flag, otherwise remove it
3172 static void set_noconv_flags(struct Body *b, bool flag)
3174 for (; b; b = b->next)
3176 if ((b->type == TYPE_MESSAGE) || (b->type == TYPE_MULTIPART))
3177 set_noconv_flags(b->parts, flag);
3178 else if ((b->type == TYPE_TEXT) && b->noconv)
3181 mutt_param_set(&b->parameter, "x-mutt-noconv", "yes");
3183 mutt_param_delete(&b->parameter, "x-mutt-noconv");
3189 * mutt_write_multiple_fcc - Handle FCC with multiple, comma separated entries
3190 * @param[in] path Path to mailboxes (comma separated)
3191 * @param[in] e Email
3192 * @param[in] msgid Message id
3193 * @param[in] post If true, postpone message
3194 * @param[in] fcc fcc setting to save (postpone only)
3195 * @param[out] finalpath Final path of email
3197 * @retval -1 Failure
3199 int mutt_write_multiple_fcc(const char *path, struct Email *e, const char *msgid,
3200 bool post, char *fcc, char **finalpath)
3202 char fcc_tok[PATH_MAX];
3203 char fcc_expanded[PATH_MAX];
3205 mutt_str_strfcpy(fcc_tok, path, sizeof(fcc_tok));
3207 char *tok = strtok(fcc_tok, ",");
3211 mutt_debug(LL_DEBUG1, "Fcc: initial mailbox = '%s'\n", tok);
3212 /* mutt_expand_path already called above for the first token */
3213 int status = mutt_write_fcc(tok, e, msgid, post, fcc, finalpath);
3217 while ((tok = strtok(NULL, ",")))
3222 /* Only call mutt_expand_path if tok has some data */
3223 mutt_debug(LL_DEBUG1, "Fcc: additional mailbox token = '%s'\n", tok);
3224 mutt_str_strfcpy(fcc_expanded, tok, sizeof(fcc_expanded));
3225 mutt_expand_path(fcc_expanded, sizeof(fcc_expanded));
3226 mutt_debug(LL_DEBUG1, " Additional mailbox expanded = '%s'\n", fcc_expanded);
3227 status = mutt_write_fcc(fcc_expanded, e, msgid, post, fcc, finalpath);
3236 * mutt_write_fcc - Write email to FCC mailbox
3237 * @param[in] path Path to mailbox
3238 * @param[in] e Email
3239 * @param[in] msgid Message id
3240 * @param[in] post If true, postpone message
3241 * @param[in] fcc fcc setting to save (postpone only)
3242 * @param[out] finalpath Final path of email
3244 * @retval -1 Failure
3246 int mutt_write_fcc(const char *path, struct Email *e, const char *msgid,
3247 bool post, const char *fcc, char **finalpath)
3249 struct Message *msg = NULL;
3250 char tempfile[PATH_MAX];
3251 FILE *fp_tmp = NULL;
3253 bool need_mailbox_cleanup = false;
3256 MsgOpenFlags onm_flags;
3259 set_noconv_flags(e->content, true);
3261 #ifdef RECORD_FOLDER_HOOK
3262 mutt_folder_hook(path, NULL);
3264 struct Mailbox *m_fcc = mx_path_resolve(path);
3265 bool old_append = m_fcc->append;
3266 struct Context *ctx_fcc = mx_mbox_open(m_fcc, MUTT_APPEND | MUTT_QUIET);
3269 mutt_debug(LL_DEBUG1, "unable to open mailbox %s in append-mode, aborting\n", path);
3270 mailbox_free(&m_fcc);
3274 /* We need to add a Content-Length field to avoid problems where a line in
3275 * the message body begins with "From " */
3276 if ((ctx_fcc->mailbox->magic == MUTT_MMDF) || (ctx_fcc->mailbox->magic == MUTT_MBOX))
3278 mutt_mktemp(tempfile, sizeof(tempfile));
3279 fp_tmp = mutt_file_fopen(tempfile, "w+");
3282 mutt_perror(tempfile);
3283 mx_mbox_close(&ctx_fcc);
3286 /* remember new mail status before appending message */
3287 need_mailbox_cleanup = true;
3291 e->read = !post; /* make sure to put it in the 'cur' directory (maildir) */
3292 onm_flags = MUTT_ADD_FROM;
3294 onm_flags |= MUTT_SET_DRAFT;
3295 msg = mx_msg_open_new(ctx_fcc->mailbox, e, onm_flags);
3298 mutt_file_fclose(&fp_tmp);
3299 mx_mbox_close(&ctx_fcc);
3303 /* post == 1 => postpone message.
3304 * post == 0 => Normal mode. */
3305 mutt_rfc822_write_header(
3306 msg->fp, e->env, e->content, post ? MUTT_WRITE_HEADER_POSTPONE : MUTT_WRITE_HEADER_NORMAL,
3307 false, C_CryptProtectedHeadersRead && mutt_should_hide_protected_subject(e));
3309 /* (postponement) if this was a reply of some sort, <msgid> contains the
3310 * Message-ID: of message replied to. Save it using a special X-Mutt-
3311 * header so it can be picked up if the message is recalled at a later
3312 * point in time. This will allow the message to be marked as replied if
3313 * the same mailbox is still open. */
3315 fprintf(msg->fp, "X-Mutt-References: %s\n", msgid);
3317 /* (postponement) save the Fcc: using a special X-Mutt- header so that
3318 * it can be picked up when the message is recalled */
3320 fprintf(msg->fp, "X-Mutt-Fcc: %s\n", fcc);
3322 if ((ctx_fcc->mailbox->magic == MUTT_MMDF) || (ctx_fcc->mailbox->magic == MUTT_MBOX))
3323 fprintf(msg->fp, "Status: RO\n");
3325 /* mutt_rfc822_write_header() only writes out a Date: header with
3326 * mode == 0, i.e. _not_ postponement; so write out one ourself */
3328 fprintf(msg->fp, "%s", mutt_date_make_date(buf, sizeof(buf)));
3330 /* (postponement) if the mail is to be signed or encrypted, save this info */
3331 if (((WithCrypto & APPLICATION_PGP) != 0) && post && (e->security & APPLICATION_PGP))
3333 fputs("X-Mutt-PGP: ", msg->fp);
3334 if (e->security & SEC_ENCRYPT)
3335 fputc('E', msg->fp);
3336 if (e->security & SEC_OPPENCRYPT)
3337 fputc('O', msg->fp);
3338 if (e->security & SEC_SIGN)
3340 fputc('S', msg->fp);
3342 fprintf(msg->fp, "<%s>", C_PgpSignAs);
3344 if (e->security & SEC_INLINE)
3345 fputc('I', msg->fp);
3346 #ifdef USE_AUTOCRYPT
3347 if (e->security & SEC_AUTOCRYPT)
3348 fputc('A', msg->fp);
3349 if (e->security & SEC_AUTOCRYPT_OVERRIDE)
3350 fputc('Z', msg->fp);
3352 fputc('\n', msg->fp);
3355 /* (postponement) if the mail is to be signed or encrypted, save this info */
3356 if (((WithCrypto & APPLICATION_SMIME) != 0) && post && (e->security & APPLICATION_SMIME))
3358 fputs("X-Mutt-SMIME: ", msg->fp);
3359 if (e->security & SEC_ENCRYPT)
3361 fputc('E', msg->fp);
3362 if (C_SmimeEncryptWith)
3363 fprintf(msg->fp, "C<%s>", C_SmimeEncryptWith);
3365 if (e->security & SEC_OPPENCRYPT)
3366 fputc('O', msg->fp);
3367 if (e->security & SEC_SIGN)
3369 fputc('S', msg->fp);
3371 fprintf(msg->fp, "<%s>", C_SmimeSignAs);
3373 if (e->security & SEC_INLINE)
3374 fputc('I', msg->fp);
3375 fputc('\n', msg->fp);
3379 /* (postponement) if the mail is to be sent through a mixmaster
3380 * chain, save that information */
3382 if (post && !STAILQ_EMPTY(&e->chain))
3384 fputs("X-Mutt-Mix:", msg->fp);
3385 struct ListNode *p = NULL;
3386 STAILQ_FOREACH(p, &e->chain, entries)
3388 fprintf(msg->fp, " %s", (char *) p->data);
3391 fputc('\n', msg->fp);
3397 mutt_write_mime_body(e->content, fp_tmp);
3399 /* make sure the last line ends with a newline. Emacs doesn't ensure this
3400 * will happen, and it can cause problems parsing the mailbox later. */
3401 fseek(fp_tmp, -1, SEEK_END);
3402 if (fgetc(fp_tmp) != '\n')
3404 fseek(fp_tmp, 0, SEEK_END);
3405 fputc('\n', fp_tmp);
3411 mutt_debug(LL_DEBUG1, "%s: write failed\n", tempfile);
3412 mutt_file_fclose(&fp_tmp);
3414 mx_msg_commit(ctx_fcc->mailbox, msg); /* XXX really? */
3415 mx_msg_close(ctx_fcc->mailbox, &msg);
3416 mx_mbox_close(&ctx_fcc);
3420 /* count the number of lines */
3422 char line_buf[1024];
3424 while (fgets(line_buf, sizeof(line_buf), fp_tmp))
3426 fprintf(msg->fp, "Content-Length: " OFF_T_FMT "\n", (LOFF_T) ftello(fp_tmp));
3427 fprintf(msg->fp, "Lines: %d\n\n", lines);
3429 /* copy the body and clean up */
3431 rc = mutt_file_copy_stream(fp_tmp, msg->fp);
3432 if (fclose(fp_tmp) != 0)
3434 /* if there was an error, leave the temp version */
3440 fputc('\n', msg->fp); /* finish off the header */
3441 rc = mutt_write_mime_body(e->content, msg->fp);
3444 if (mx_msg_commit(ctx_fcc->mailbox, msg) != 0)
3447 *finalpath = mutt_str_strdup(msg->committed_path);
3448 mx_msg_close(ctx_fcc->mailbox, &msg);
3449 mx_mbox_close(&ctx_fcc);
3451 if (!post && need_mailbox_cleanup)
3452 mutt_mailbox_cleanup(path, &st);
3455 set_noconv_flags(e->content, false);
3458 m_fcc->append = old_append;
3459 #ifdef RECORD_FOLDER_HOOK
3460 /* We ran a folder hook for the destination mailbox,
3461 * now we run it for the user's current mailbox */
3462 if (Context && Context->mailbox->path)
3463 mutt_folder_hook(Context->mailbox->path, Context->mailbox->desc);