2 * Copyright notice from original mutt:
3 * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
5 * This file is part of mutt-ng, see http://www.muttng.org/.
6 * It's licensed under the GNU General Public License,
7 * please see the file GPL in the top level source directory.
25 #include <sys/utsname.h>
27 #include <lib-lib/mem.h>
28 #include <lib-lib/ascii.h>
29 #include <lib-lib/str.h>
30 #include <lib-lib/macros.h>
31 #include <lib-lib/file.h>
33 #include <lib-sys/exit.h>
34 #include <lib-sys/mutt_signal.h>
36 #include <lib-mime/mime.h>
38 #include <lib-ui/curses.h>
42 #include "recvattach.h"
47 #include <lib-crypt/crypt.h>
48 #include "mutt_idna.h"
51 # include "mutt_libesmtp.h"
52 #endif /* USE_LIBESMTP */
58 #ifdef HAVE_SYSEXITS_H
60 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
64 /* If you are debugging this file, comment out the following line. */
73 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
75 static void transform_to_7bit (BODY * a, FILE * fpin);
77 static void encode_quoted (fgetconv_t * fc, FILE * fout, int istext)
80 char line[77], savechar;
82 while ((c = fgetconv (fc)) != EOF) {
83 /* Wrap the line if needed. */
84 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
85 /* If the last character is "quoted", then be sure to move all three
86 * characters to the next line. Otherwise, just move the last
89 if (line[linelen - 3] == '=') {
90 line[linelen - 3] = 0;
95 line[1] = line[linelen - 2];
96 line[2] = line[linelen - 1];
100 savechar = line[linelen - 1];
101 line[linelen - 1] = '=';
110 /* Escape lines that begin with/only contain "the message separator". */
111 if (linelen == 4 && !m_strncmp("From", line, 4)) {
112 m_strcpy(line, sizeof(line), "=46rom");
115 else if (linelen == 4 && !m_strncmp("from", line, 4)) {
116 m_strcpy(line, sizeof(line), "=66rom");
119 else if (linelen == 1 && line[0] == '.') {
120 m_strcpy(line, sizeof(line), "=2E");
125 if (c == '\n' && istext) {
126 /* Check to make sure there is no trailing space on this line. */
128 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
130 sprintf (line + linelen - 1, "=%2.2X",
131 (unsigned char) line[linelen - 1]);
135 savechar = line[linelen - 1];
137 line[linelen - 1] = '=';
140 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
150 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
151 /* Check to make sure there is enough room for the quoted character.
152 * If not, wrap to the next line.
155 line[linelen++] = '=';
161 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
165 /* Don't worry about wrapping the line here. That will happen during
166 * the next iteration when I'll also know what the next character is.
172 /* Take care of anything left in the buffer */
174 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
175 /* take care of trailing whitespace */
177 sprintf (line + linelen - 1, "=%2.2X",
178 (unsigned char) line[linelen - 1]);
180 savechar = line[linelen - 1];
181 line[linelen - 1] = '=';
185 sprintf (line, "=%2.2X", (unsigned char) savechar);
194 static char b64_buffer[3];
195 static short b64_num;
196 static short b64_linelen;
198 static void b64_flush (FILE * fout)
205 if (b64_linelen >= 72) {
210 for (i = b64_num; i < 3; i++)
211 b64_buffer[i] = '\0';
213 fputc(__m_b64chars[(b64_buffer[0] >> 2) & 0x3f], fout);
216 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
221 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
225 fputc (__m_b64chars[b64_buffer[2] & 0x3f], fout);
230 while (b64_linelen % 4) {
239 static void b64_putc (char c, FILE * fout)
244 b64_buffer[b64_num++] = c;
248 static void encode_base64 (fgetconv_t * fc, FILE * fout, int istext)
252 b64_num = b64_linelen = 0;
254 while ((ch = fgetconv (fc)) != EOF) {
255 if (istext && ch == '\n' && ch1 != '\r')
256 b64_putc ('\r', fout);
264 static void encode_8bit (fgetconv_t * fc, FILE * fout, int istext)
268 while ((ch = fgetconv (fc)) != EOF)
273 int mutt_write_mime_header (BODY * a, FILE * f)
282 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
287 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
289 for (p = a->parameter; p; p = p->next) {
298 tmp = m_strdup(p->value);
299 encode = rfc2231_encode_string (&tmp);
300 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
302 /* Dirty hack to make messages readable by Outlook Express
303 * for the Mac: force quotes around the boundary parameter
304 * even when they aren't needed.
307 if (!ascii_strcasecmp (p->attribute, "boundary")
308 && !strcmp (buffer, tmp))
309 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
313 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
315 if (len + tmplen + 2 > 76) {
324 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
332 fprintf (f, "Content-Description: %s\n", a->description);
334 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
337 if (!(fn = a->d_filename))
343 /* Strip off the leading path... */
344 if ((t = strrchr (fn, '/')))
351 encode = rfc2231_encode_string (&tmp);
352 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
354 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
360 if (a->encoding != ENC7BIT)
361 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
363 /* Do NOT add the terminator here!!! */
364 return (ferror (f) ? -1 : 0);
367 int mutt_write_mime_body (BODY * a, FILE * f)
370 char boundary[SHORT_STRING];
371 char send_charset[SHORT_STRING];
376 if (a->type == TYPEMULTIPART) {
377 /* First, find the boundary to use */
378 if (!(p = parameter_getval(a->parameter, "boundary"))) {
379 mutt_error _("No boundary parameter found! [report this error]");
383 m_strcpy(boundary, sizeof(boundary), p);
385 for (t = a->parts; t; t = t->next) {
386 fprintf (f, "\n--%s\n", boundary);
387 if (mutt_write_mime_header (t, f) == -1)
390 if (mutt_write_mime_body (t, f) == -1)
393 fprintf (f, "\n--%s--\n", boundary);
394 return (ferror (f) ? -1 : 0);
397 /* This is pretty gross, but it's the best solution for now... */
398 if (a->type == TYPEAPPLICATION && !m_strcmp(a->subtype, "pgp-encrypted")) {
399 fputs ("Version: 1\n", f);
403 if ((fpin = fopen (a->filename, "r")) == NULL) {
404 mutt_error (_("%s no longer exists!"), a->filename);
408 if (a->type == TYPETEXT && (!a->noconv))
409 fc = fgetconv_open (fpin, a->file_charset,
410 mutt_get_body_charset (send_charset,
411 sizeof (send_charset), a), 0);
413 fc = fgetconv_open (fpin, 0, 0, 0);
415 #define write_as_text_part(a) (mutt_is_text_part(a) || mutt_is_application_pgp(a))
416 if (a->encoding == ENCQUOTEDPRINTABLE)
417 encode_quoted (fc, f, write_as_text_part (a));
418 else if (a->encoding == ENCBASE64)
419 encode_base64 (fc, f, write_as_text_part (a));
420 else if (a->type == TYPETEXT && (!a->noconv))
421 encode_8bit (fc, f, write_as_text_part (a));
423 mutt_copy_stream (fpin, f);
424 #undef write_as_text_part
426 fgetconv_close (&fc);
429 return (ferror (f) ? -1 : 0);
441 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
445 int whitespace = s->whitespace;
447 int linelen = s->linelen;
448 int was_cr = s->was_cr;
450 if (!d) { /* This signals EOF */
453 if (linelen > info->linemax)
454 info->linemax = linelen;
459 for (; dlen; d++, dlen--) {
472 if (linelen > info->linemax)
473 info->linemax = linelen;
488 if (linelen > info->linemax)
489 info->linemax = linelen;
494 else if (ch == '\r') {
502 else if (ch == '\t' || ch == '\f') {
506 else if (ch < 32 || ch == 127)
510 if ((ch == 'F') || (ch == 'f'))
520 if (linelen == 2 && ch != 'r')
522 else if (linelen == 3 && ch != 'o')
524 else if (linelen == 4) {
537 if (ch != ' ' && ch != '\t')
542 s->whitespace = whitespace;
544 s->linelen = linelen;
550 * Find the best charset conversion of the file from fromcode into one
551 * of the tocodes. If successful, set *tocode and CONTENT *info and
552 * return the number of characters converted inexactly. If no
553 * conversion was possible, return -1.
555 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
556 * which would otherwise prevent us from knowing the number of inexact
557 * conversions. Where the candidate target charset is UTF-8 we avoid
558 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
559 * fails with some libraries.
561 * We assume that the output from iconv is never more than 4 times as
562 * long as the input for any pair of charsets we might be interested
565 static ssize_t convert_file_to (FILE * file, const char *fromcode,
566 int ncodes, const char **tocodes,
567 int *tocode, CONTENT * info)
571 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
574 ssize_t ibl, obl, ubl, ubl1, n, ret;
577 CONTENT_STATE *states;
580 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
581 if (cd1 == MUTT_ICONV_ERROR)
584 cd = p_new(iconv_t, ncodes);
585 score = p_new(ssize_t, ncodes);
586 states = p_new(CONTENT_STATE, ncodes);
587 infos = p_new(CONTENT, ncodes);
589 for (i = 0; i < ncodes; i++)
590 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
591 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
593 /* Special case for conversion to UTF-8 */
594 cd[i] = MUTT_ICONV_ERROR, score[i] = -1;
600 /* Try to fill input buffer */
601 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
604 /* Convert to UTF-8 */
606 ob = bufu, obl = sizeof (bufu);
607 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
608 assert (n == -1 || !n);
609 if (n == -1 && ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
610 assert (errno == EILSEQ ||
611 (errno == EINVAL && ib == bufi && ibl < ssizeof (bufi)));
617 /* Convert from UTF-8 */
618 for (i = 0; i < ncodes; i++)
619 if (cd[i] != MUTT_ICONV_ERROR && score[i] != -1) {
620 ub = bufu, ubl = ubl1;
621 ob = bufo, obl = sizeof (bufo);
622 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
628 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
631 else if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1)
632 /* Special case for conversion to UTF-8 */
633 update_content_info (&infos[i], &states[i], bufu, ubl1);
636 /* Save unused input */
637 memmove (bufi, ib, ibl);
638 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
645 /* Find best score */
647 for (i = 0; i < ncodes; i++) {
648 if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1) {
649 /* Special case for conversion to UTF-8 */
654 else if (cd[i] == MUTT_ICONV_ERROR || score[i] == -1)
656 else if (ret == -1 || score[i] < ret) {
664 memcpy (info, &infos[*tocode], sizeof (CONTENT));
665 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
669 for (i = 0; i < ncodes; i++)
670 if (cd[i] != MUTT_ICONV_ERROR)
682 #endif /* !HAVE_ICONV */
686 * Find the first of the fromcodes that gives a valid conversion and
687 * the best charset conversion of the file into one of the tocodes. If
688 * successful, set *fromcode and *tocode to dynamically allocated
689 * strings, set CONTENT *info, and return the number of characters
690 * converted inexactly. If no conversion was possible, return -1.
692 * Both fromcodes and tocodes may be colon-separated lists of charsets.
693 * However, if fromcode is zero then fromcodes is assumed to be the
694 * name of a single charset even if it contains a colon.
696 static ssize_t convert_file_from_to (FILE * file,
697 const char *fromcodes,
698 const char *tocodes, char **fromcode,
699 char **tocode, CONTENT * info)
707 /* Count the tocodes */
709 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
710 if ((c1 = strchr (c, ':')) == c)
716 tcode = p_new(char *, ncodes);
717 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
718 if ((c1 = strchr (c, ':')) == c)
720 tcode[i] = m_substrdup(c, c1);
725 /* Try each fromcode in turn */
726 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
727 if ((c1 = strchr (c, ':')) == c)
729 fcode = m_substrdup(c, c1);
731 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
743 /* There is only one fromcode */
744 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
753 for (i = 0; i < ncodes; i++)
762 * Analyze the contents of a file to determine which MIME encoding to use.
763 * Also set the body charset, sometimes, or not.
765 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
770 char *fromcode = NULL;
781 if (stat (fname, &sb) == -1) {
782 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
786 if (!S_ISREG (sb.st_mode)) {
787 mutt_error (_("%s isn't a regular file."), fname);
791 if ((fp = fopen (fname, "r")) == NULL) {
795 info = p_new(CONTENT, 1);
798 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
799 const char *chs = parameter_getval(b->parameter, "charset");
800 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
801 FileCharset : Charset) : Charset;
802 if (Charset && (chs || SendCharset) &&
803 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
804 &fromcode, &tocode, info) != -1) {
806 charset_canonicalize (chsbuf, sizeof (chsbuf), tocode);
807 parameter_setval(&b->parameter, "charset", chsbuf);
809 b->file_charset = fromcode;
817 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
818 update_content_info (info, &state, buffer, r);
819 update_content_info (info, &state, 0, 0);
823 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
824 parameter_setval(&b->parameter, "charset",
825 (!info->hibin ? "us-ascii"
826 : Charset && !charset_is_us_ascii(Charset) ? Charset : "unknown-8bit"));
831 /* Given a file with path ``s'', see if there is a registered MIME type.
832 * returns the major MIME type, and copies the subtype to ``d''. First look
833 * for ~/.mime.types, then look in a system mime.types if we can find one.
834 * The longest match is used so that we can match `ps.gz' when `gz' also
838 int mutt_lookup_mime_type (BODY * att, const char *path)
842 char buf[LONG_STRING];
843 char subtype[STRING], xtype[STRING];
845 int szf, sze, cur_sze;
853 szf = m_strlen(path);
855 for (count = 0; count < 4; count++) {
857 * can't use strtok() because we use it in an inner loop below, so use
858 * a switch statement here instead.
862 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
865 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
868 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
871 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
874 goto bye; /* shouldn't happen */
877 if ((f = fopen (buf, "r")) != NULL) {
878 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
879 /* weed out any comments */
880 if ((p = strchr (buf, '#')))
883 /* remove any leading space. */
884 ct = vskipspaces(buf);
886 /* position on the next field in this line */
887 if ((p = strpbrk (ct, " \t")) == NULL)
892 /* cycle through the file extensions */
893 while ((p = strtok (p, " \t\n"))) {
895 if ((sze > cur_sze) && (szf >= sze) &&
896 (m_strcasecmp(path + szf - sze, p) == 0
897 || ascii_strcasecmp (path + szf - sze, p) == 0)
898 && (szf == sze || path[szf - sze - 1] == '.'))
900 /* get the content-type */
902 if ((p = strchr (ct, '/')) == NULL) {
903 /* malformed line, just skip it. */
908 for (q = p; *q && !ISSPACE (*q); q++);
910 m_strncpy(subtype, sizeof(subtype), p, q - p);
912 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
913 m_strcpy(xtype, sizeof(xtype), ct);
926 if (type != TYPEOTHER || *xtype != '\0') {
928 m_strreplace(&att->subtype, subtype);
929 m_strreplace(&att->xtype, xtype);
935 void mutt_message_to_7bit (BODY * a, FILE * fp)
937 char temp[_POSIX_PATH_MAX];
943 if (!a->filename && fp)
945 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
946 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
951 if (stat (a->filename, &sb) == -1) {
952 mutt_perror ("stat");
955 a->length = sb.st_size;
959 if (!(fpout = safe_fopen (temp, "w+"))) {
960 mutt_perror ("fopen");
964 fseeko (fpin, a->offset, 0);
965 a->parts = mutt_parse_messageRFC822 (fpin, a);
967 transform_to_7bit (a->parts, fpin);
969 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
970 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
972 fputs ("MIME-Version: 1.0\n", fpout);
973 mutt_write_mime_header (a->parts, fpout);
975 mutt_write_mime_body (a->parts, fpout);
987 a->encoding = ENC7BIT;
988 a->d_filename = a->filename;
989 if (a->filename && a->unlink)
990 unlink (a->filename);
991 a->filename = m_strdup(temp);
993 if (stat (a->filename, &sb) == -1) {
994 mutt_perror ("stat");
997 a->length = sb.st_size;
998 body_list_wipe(&a->parts);
999 a->hdr->content = NULL;
1002 static void transform_to_7bit (BODY * a, FILE * fpin)
1004 char buff[_POSIX_PATH_MAX];
1009 for (; a; a = a->next) {
1010 if (a->type == TYPEMULTIPART) {
1011 if (a->encoding != ENC7BIT)
1012 a->encoding = ENC7BIT;
1014 transform_to_7bit (a->parts, fpin);
1016 else if (mutt_is_message_type (a->type, a->subtype)) {
1017 mutt_message_to_7bit (a, fpin);
1021 a->force_charset = 1;
1024 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1025 mutt_perror ("fopen");
1029 mutt_decode_attachment (a, &s);
1031 a->d_filename = a->filename;
1032 a->filename = m_strdup(buff);
1034 if (stat (a->filename, &sb) == -1) {
1035 mutt_perror ("stat");
1038 a->length = sb.st_size;
1040 mutt_update_encoding (a);
1041 if (a->encoding == ENC8BIT)
1042 a->encoding = ENCQUOTEDPRINTABLE;
1043 else if (a->encoding == ENCBINARY)
1044 a->encoding = ENCBASE64;
1049 /* determine which Content-Transfer-Encoding to use */
1050 static void mutt_set_encoding (BODY * b, CONTENT * info)
1052 char send_charset[SHORT_STRING];
1054 if (b->type == TYPETEXT) {
1056 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1057 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1058 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1059 b->encoding = ENCQUOTEDPRINTABLE;
1060 else if (info->hibin)
1061 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1063 b->encoding = ENC7BIT;
1065 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1066 if (info->lobin || info->hibin) {
1067 if (option (OPTALLOW8BIT) && !info->lobin)
1068 b->encoding = ENC8BIT;
1070 mutt_message_to_7bit (b, NULL);
1073 b->encoding = ENC7BIT;
1075 else if (b->type == TYPEAPPLICATION
1076 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1077 b->encoding = ENC7BIT;
1080 /* Determine which encoding is smaller */
1081 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1082 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1083 b->encoding = ENCBASE64;
1085 b->encoding = ENCQUOTEDPRINTABLE;
1089 void mutt_stamp_attachment (BODY * a)
1091 a->stamp = time (NULL);
1094 /* Get a body's character set */
1096 char *mutt_get_body_charset(char *d, ssize_t dlen, BODY * b)
1100 if (b && b->type != TYPETEXT)
1103 p = b ? parameter_getval(b->parameter, "charset") : NULL;
1104 charset_canonicalize(d, dlen, p);
1109 /* Assumes called from send mode where BODY->filename points to actual file */
1110 void mutt_update_encoding (BODY * a)
1113 char chsbuff[STRING];
1115 /* override noconv when it's us-ascii */
1116 if (charset_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1119 if (!a->force_charset && !a->noconv)
1120 parameter_delval(&a->parameter, "charset");
1122 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1125 mutt_set_encoding (a, info);
1126 mutt_stamp_attachment (a);
1128 p_delete(&a->content);
1133 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1135 char buffer[LONG_STRING];
1138 int cmflags, chflags;
1139 int pgp = hdr->security;
1141 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1142 (hdr->security & ENCRYPT)) {
1143 if (!crypt_valid_passphrase (hdr->security))
1147 mutt_mktemp (buffer);
1148 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1152 body->type = TYPEMESSAGE;
1153 body->subtype = m_strdup("rfc822");
1154 body->filename = m_strdup(buffer);
1157 body->disposition = DISPINLINE;
1160 mutt_parse_mime_message (ctx, hdr);
1165 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1166 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1167 chflags |= CH_MIME | CH_TXTPLAIN;
1168 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1169 pgp &= ~(PGPENCRYPT|SMIMEENCRYPT);
1171 else if (option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1172 if (mutt_is_multipart_encrypted (hdr->content)) {
1173 chflags |= CH_MIME | CH_NONEWLINE;
1174 cmflags = M_CM_DECODE_PGP;
1177 else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1178 chflags |= CH_MIME | CH_TXTPLAIN;
1179 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1182 else if (mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1183 chflags |= CH_MIME | CH_TXTPLAIN;
1184 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1185 pgp &= ~SMIMEENCRYPT;
1189 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1194 body->hdr = header_new();
1195 body->hdr->offset = 0;
1196 /* we don't need the user headers here */
1197 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1198 body->hdr->security = pgp;
1199 mutt_update_encoding (body);
1200 body->parts = body->hdr->content;
1207 BODY *mutt_make_file_attach (const char *path)
1213 att->filename = m_strdup(path);
1215 /* Attempt to determine the appropriate content-type based on the filename
1218 mutt_lookup_mime_type (att, path);
1220 if ((info = mutt_get_content_info (path, att)) == NULL) {
1221 body_list_wipe(&att);
1225 if (!att->subtype) {
1226 if (info->lobin == 0
1227 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1229 * Statistically speaking, there should be more than 10% "lobin"
1230 * chars if this is really a binary file...
1232 att->type = TYPETEXT;
1233 att->subtype = m_strdup("plain");
1235 att->type = TYPEAPPLICATION;
1236 att->subtype = m_strdup("octet-stream");
1240 mutt_update_encoding (att);
1244 static int get_toplevel_encoding (BODY * a)
1248 for (; a; a = a->next) {
1249 if (a->encoding == ENCBINARY)
1251 else if (a->encoding == ENC8BIT)
1258 BODY *mutt_make_multipart (BODY * b)
1263 new->type = TYPEMULTIPART;
1264 new->subtype = m_strdup("mixed");
1265 new->encoding = get_toplevel_encoding (b);
1266 parameter_set_boundary(&new->parameter);
1268 new->disposition = DISPINLINE;
1274 /* remove the multipart body if it exists */
1275 BODY *mutt_remove_multipart (BODY * b)
1288 char *mutt_make_date (char *s, ssize_t len)
1290 time_t t = time (NULL);
1291 struct tm *l = localtime (&t);
1292 time_t tz = mutt_local_tz (t);
1296 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1297 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1298 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1299 (int) tz / 60, (int) abs (tz) % 60);
1303 /* wrapper around mutt_write_address() so we can handle very large
1304 recipient lists without needing a huge temporary buffer in memory */
1305 void mutt_write_address_list (address_t * adr, FILE * fp, int linelen,
1309 char buf[LONG_STRING];
1317 rfc822_write_address (buf, sizeof (buf), adr, display);
1318 len = m_strlen(buf);
1319 if (count && linelen + len > 74) {
1321 linelen = len + 8; /* tab is usually about 8 spaces... */
1324 if (count && adr->mailbox) {
1332 if (!adr->group && adr->next && adr->next->mailbox) {
1342 /* arbitrary number of elements to grow the array by */
1347 /* need to write the list in reverse because they are stored in reverse order
1348 * when parsed to speed up threading
1350 void mutt_write_references (string_list_t * r, FILE * f)
1352 string_list_t **ref = NULL;
1353 int refcnt = 0, refmax = 0;
1355 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1356 if (refcnt == refmax)
1357 p_realloc(&ref, refmax += REF_INC);
1361 while (refcnt-- > 0) {
1363 fputs (ref[refcnt]->data, f);
1369 /* Note: all RFC2047 encoding should be done outside of this routine, except
1370 * for the "real name." This will allow this routine to be used more than
1371 * once, if necessary.
1373 * Likewise, all IDN processing should happen outside of this routine.
1375 * mode == 1 => "lite" mode (used for edit_hdrs)
1376 * mode == 0 => normal mode. write full header + MIME headers
1377 * mode == -1 => write just the envelope info (used for postponing messages)
1379 * privacy != 0 => will omit any headers which may identify the user.
1380 * Output generated is suitable for being sent through
1381 * anonymous remailer chains.
1385 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1386 int mode, int privacy)
1388 char buffer[LONG_STRING];
1390 string_list_t *tmp = env->userhdrs;
1391 int has_agent = 0; /* user defined user-agent header field exists */
1392 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1395 if (!option (OPTNEWSSEND))
1397 if (mode == 0 && !privacy)
1398 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1400 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1402 /* OPTUSEFROM is not consulted here so that we can still write a From:
1403 * field if the user sets it with the `my_hdr' command
1405 if (env->from && !privacy) {
1407 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1408 fprintf (fp, "From: %s\n", buffer);
1413 mutt_write_address_list (env->to, fp, 4, 0);
1417 if (!option (OPTNEWSSEND))
1419 if (EDIT_HEADER("To:"))
1420 fputs ("To:\n", fp);
1424 mutt_write_address_list (env->cc, fp, 4, 0);
1428 if (!option (OPTNEWSSEND))
1430 if (EDIT_HEADER("Cc:"))
1431 fputs ("Cc:\n", fp);
1434 if (mode != 0 || option (OPTWRITEBCC)) {
1435 fputs ("Bcc: ", fp);
1436 mutt_write_address_list (env->bcc, fp, 5, 0);
1441 if (!option (OPTNEWSSEND))
1443 if (EDIT_HEADER("Bcc:"))
1444 fputs ("Bcc:\n", fp);
1447 if (env->newsgroups)
1448 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1449 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1450 fputs ("Newsgroups:\n", fp);
1452 if (env->followup_to)
1453 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1454 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1455 fputs ("Followup-To:\n", fp);
1457 if (env->x_comment_to)
1458 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1459 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1460 EDIT_HEADER("X-Comment-To:"))
1461 fputs ("X-Comment-To:\n", fp);
1465 fprintf (fp, "Subject: %s\n", env->subject);
1466 else if (mode == 1 && EDIT_HEADER("Subject:"))
1467 fputs ("Subject:\n", fp);
1469 /* save message id if the user has set it */
1470 if (env->message_id && !privacy)
1471 fprintf (fp, "Message-ID: %s\n", env->message_id);
1473 if (env->reply_to) {
1474 fputs ("Reply-To: ", fp);
1475 mutt_write_address_list (env->reply_to, fp, 10, 0);
1477 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1478 fputs ("Reply-To:\n", fp);
1480 if (env->mail_followup_to)
1482 if (!option (OPTNEWSSEND))
1485 fputs ("Mail-Followup-To: ", fp);
1486 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1490 if (env->references) {
1491 fputs ("References:", fp);
1492 mutt_write_references (env->references, fp);
1496 /* Add the MIME headers */
1497 fputs ("MIME-Version: 1.0\n", fp);
1498 mutt_write_mime_header (attach, fp);
1501 if (env->in_reply_to) {
1502 fputs ("In-Reply-To:", fp);
1503 mutt_write_references (env->in_reply_to, fp);
1509 /* Add any user defined headers */
1510 for (; tmp; tmp = tmp->next) {
1511 if ((p = strchr (tmp->data, ':'))) {
1512 p = vskipspaces(p + 1);
1514 continue; /* don't emit empty fields. */
1516 /* check to see if the user has overridden the user-agent field */
1517 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1523 fputs (tmp->data, fp);
1528 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1531 if (OperatingSystem != NULL) {
1532 os = OperatingSystem;
1535 os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1537 /* Add a vanity header */
1538 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1541 list_del (&hdrs, (list_del_t*)xmemfree);
1543 return (ferror (fp) == 0 ? 0 : -1);
1546 static void encode_headers (string_list_t * h)
1552 for (; h; h = h->next) {
1553 if (!(p = strchr (h->data, ':')))
1557 p = vskipspaces(p + 1);
1563 rfc2047_encode_string (&tmp);
1564 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1566 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1572 const char *mutt_fqdn (short may_hide_host)
1576 if (Fqdn && Fqdn[0] != '@') {
1579 if (may_hide_host && option (OPTHIDDENHOST)) {
1580 if ((p = strchr (Fqdn, '.')))
1583 /* sanity check: don't hide the host if
1584 * the fqdn is something like detebe.org.
1587 if (!p || !(q = strchr (p, '.')))
1595 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1596 static char mutt_normalized_char(char c)
1598 return (isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.';
1601 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1603 #define APPEND_FMT(fmt, arg) \
1605 int snlen = snprintf(buf, len, fmt, arg); \
1610 #define APPEND_BYTE(c) \
1624 static char MsgIdPfx = 'A';
1628 APPEND_BYTE(mutt_normalized_char(c));
1636 APPEND_FMT("%02d", tm->tm_mday);
1639 APPEND_FMT("%02d", tm->tm_hour);
1642 APPEND_FMT("%02d", tm->tm_mon + 1);
1645 APPEND_FMT("%02d", tm->tm_min);
1648 APPEND_FMT("%lo", (unsigned long)now);
1651 APPEND_FMT("%u", (unsigned int)getpid());
1654 APPEND_FMT("%c", MsgIdPfx);
1655 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1658 APPEND_FMT("%u", (unsigned int)rand());
1661 APPEND_FMT("%x", (unsigned int)rand());
1664 APPEND_FMT("%02d", tm->tm_sec);
1667 APPEND_FMT("%u", (unsigned int) now);
1670 APPEND_FMT("%x", (unsigned int) now);
1672 case 'Y': /* this will break in the year 10000 ;-) */
1673 APPEND_FMT("%04d", tm->tm_year + 1900);
1678 default: /* invalid formats are replaced by '.' */
1680 m_strncat(buf, len, ".", 1);
1690 char *mutt_gen_msgid (void)
1692 char buf[SHORT_STRING];
1693 char localpart[SHORT_STRING];
1694 unsigned int localpart_length;
1697 if (!(fqdn = mutt_fqdn (0)))
1698 fqdn = NONULL (Hostname);
1700 localpart_length = sizeof (buf) - m_strlen(fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1702 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1704 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1705 return (m_strdup(buf));
1708 static RETSIGTYPE alarm_handler (int sig)
1713 /* invoke sendmail in a subshell
1714 path (in) path to program to execute
1715 args (in) arguments to pass to program
1716 msg (in) temp file containing message to send
1717 tempfile (out) if sendmail is put in the background, this points
1718 to the temporary file containing the stdout of the
1721 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1727 mutt_block_signals_system ();
1730 /* we also don't want to be stopped right now */
1731 sigaddset (&set, SIGTSTP);
1732 sigprocmask (SIG_BLOCK, &set, NULL);
1734 if (SendmailWait >= 0) {
1735 char tmp[_POSIX_PATH_MAX];
1738 *tempfile = m_strdup(tmp);
1741 if ((pid = fork ()) == 0) {
1742 struct sigaction act, oldalrm;
1744 /* save parent's ID before setsid() */
1747 /* we want the delivery to continue even after the main process dies,
1748 * so we put ourselves into another session right away
1752 /* next we close all open files */
1753 #if defined(OPEN_MAX)
1754 for (fd = 0; fd < OPEN_MAX; fd++)
1756 #elif defined(_POSIX_OPEN_MAX)
1757 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1765 /* now the second fork() */
1766 if ((pid = fork ()) == 0) {
1767 /* "msg" will be opened as stdin */
1768 if (open (msg, O_RDONLY, 0) < 0) {
1774 if (SendmailWait >= 0) {
1775 /* *tempfile will be opened as stdout */
1776 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1779 /* redirect stderr to *tempfile too */
1784 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1786 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1790 execv (path, (char**)args);
1793 else if (pid == -1) {
1799 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1800 * SendmailWait = 0: wait forever
1801 * SendmailWait < 0: don't wait
1803 if (SendmailWait > 0) {
1805 act.sa_handler = alarm_handler;
1807 /* need to make sure waitpid() is interrupted on SIGALRM */
1808 act.sa_flags = SA_INTERRUPT;
1812 sigemptyset (&act.sa_mask);
1813 sigaction (SIGALRM, &act, &oldalrm);
1814 alarm (SendmailWait);
1816 else if (SendmailWait < 0)
1817 _exit (0xff & EX_OK);
1819 if (waitpid (pid, &st, 0) > 0) {
1820 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1821 if (SendmailWait && st == (0xff & EX_OK)) {
1822 unlink (*tempfile); /* no longer needed */
1827 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1828 if (SendmailWait > 0) {
1834 /* reset alarm; not really needed, but... */
1836 sigaction (SIGALRM, &oldalrm, NULL);
1838 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1839 /* the parent is already dead */
1847 sigprocmask (SIG_UNBLOCK, &set, NULL);
1849 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1850 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1852 st = S_ERR; /* error */
1854 mutt_unblock_signals_system (1);
1859 static const char **
1860 add_args(const char **args, ssize_t *argslen, ssize_t *argsmax, address_t * addr)
1862 for (; addr; addr = addr->next) {
1863 /* weed out group mailboxes, since those are for display only */
1864 if (addr->mailbox && !addr->group) {
1865 if (*argslen == *argsmax)
1866 p_realloc(&args, *argsmax += 5);
1867 args[(*argslen)++] = addr->mailbox;
1873 static const char **
1874 add_option(const char **args, ssize_t *argslen, ssize_t *argsmax, const char *s)
1876 if (*argslen == *argsmax) {
1877 p_realloc(&args, *argsmax += 5);
1879 args[(*argslen)++] = s;
1883 static int mutt_invoke_sendmail (address_t * from, /* the sender */
1884 address_t * to, address_t * cc, address_t * bcc, /* recips */
1885 const char *msg, /* file containing message */
1887 { /* message contains 8bit chars */
1888 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1889 const char **args = NULL;
1890 ssize_t argslen = 0, argsmax = 0;
1894 if (option (OPTNEWSSEND)) {
1895 char cmd[LONG_STRING];
1897 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1900 i = nntp_post (msg);
1909 s = m_strdup(Sendmail);
1913 while ((ps = strtok (ps, " "))) {
1914 if (argslen == argsmax)
1915 p_realloc(&args, argsmax += 5);
1918 args[argslen++] = ps;
1920 path = m_strdup(ps);
1921 ps = strrchr (ps, '/');
1926 args[argslen++] = ps;
1933 if (!option (OPTNEWSSEND)) {
1935 if (eightbit && option (OPTUSE8BITMIME))
1936 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
1938 if (option (OPTENVFROM)) {
1939 address_t *f = NULL;
1942 else if (from && !from->next)
1945 args = add_option (args, &argslen, &argsmax, "-f");
1946 args = add_args (args, &argslen, &argsmax, f);
1950 args = add_option (args, &argslen, &argsmax, "-N");
1951 args = add_option (args, &argslen, &argsmax, DsnNotify);
1954 args = add_option (args, &argslen, &argsmax, "-R");
1955 args = add_option (args, &argslen, &argsmax, DsnReturn);
1957 args = add_option (args, &argslen, &argsmax, "--");
1958 args = add_args (args, &argslen, &argsmax, to);
1959 args = add_args (args, &argslen, &argsmax, cc);
1960 args = add_args (args, &argslen, &argsmax, bcc);
1965 if (argslen == argsmax)
1966 p_realloc(&args, ++argsmax);
1968 args[argslen++] = NULL;
1970 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
1972 mutt_error (_("Error sending message, child exited %d (%s)."), i,
1977 if (stat (childout, &st) == 0 && st.st_size > 0)
1978 mutt_do_pager (_("Output of the delivery process"), childout, 0,
1986 p_delete(&childout);
1991 if (i == (EX_OK & 0xff))
1993 else if (i == S_BKG)
2000 int mutt_invoke_mta (address_t * from, /* the sender */
2001 address_t * to, address_t * cc, address_t * bcc, /* recips */
2002 const char *msg, /* file containing message */
2004 { /* message contains 8bit chars */
2007 if (!option (OPTNEWSSEND))
2010 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2013 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2016 /* For postponing (!final) do the necessary encodings only */
2017 void mutt_prepare_envelope (ENVELOPE * env, int final)
2019 char buffer[LONG_STRING];
2022 if (env->bcc && !(env->to || env->cc)) {
2023 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2024 * recipients if there is no To: or Cc: field, so attempt to suppress
2025 * it by using an empty To: field.
2027 env->to = address_new ();
2029 env->to->next = address_new ();
2032 rfc822_strcpy(buffer, sizeof(buffer), "undisclosed-recipients",
2035 env->to->mailbox = m_strdup(buffer);
2038 mutt_set_followup_to (env);
2040 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2041 env->message_id = mutt_gen_msgid ();
2044 /* Take care of 8-bit => 7-bit conversion. */
2045 rfc2047_encode_adrlist (env->to, "To");
2046 rfc2047_encode_adrlist (env->cc, "Cc");
2047 rfc2047_encode_adrlist (env->bcc, "Bcc");
2048 rfc2047_encode_adrlist (env->from, "From");
2049 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2050 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2054 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2057 rfc2047_encode_string (&env->subject);
2059 encode_headers (env->userhdrs);
2062 void mutt_unprepare_envelope (ENVELOPE * env)
2064 string_list_t *item;
2066 for (item = env->userhdrs; item; item = item->next)
2067 rfc2047_decode (&item->data);
2069 address_list_wipe(&env->mail_followup_to);
2071 /* back conversions */
2072 rfc2047_decode_adrlist (env->to);
2073 rfc2047_decode_adrlist (env->cc);
2074 rfc2047_decode_adrlist (env->bcc);
2075 rfc2047_decode_adrlist (env->from);
2076 rfc2047_decode_adrlist (env->reply_to);
2077 rfc2047_decode (&env->subject);
2080 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2081 const char *resent_from, address_t * env_from)
2085 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2086 MESSAGE *msg = NULL;
2089 /* Try to bounce each message out, aborting if we get any failures. */
2090 for (i = 0; i < Context->msgcount; i++)
2091 if (Context->hdrs[i]->tagged)
2093 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2098 /* If we failed to open a message, return with error */
2099 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2105 mutt_mktemp (tempfile);
2106 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2107 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2109 if (!option (OPTBOUNCEDELIVERED))
2110 ch_flags |= CH_WEED_DELIVERED;
2112 fseeko (fp, h->offset, 0);
2113 fprintf (f, "Resent-From: %s", resent_from);
2114 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2115 if (MsgIdFormat && *MsgIdFormat)
2116 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2117 fputs ("Resent-To: ", f);
2118 mutt_write_address_list (to, f, 11, 0);
2119 mutt_copy_header (fp, h, f, ch_flags, NULL);
2121 mutt_copy_bytes (fp, f, h->content->length);
2124 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2125 h->content->encoding == ENC8BIT);
2129 mx_close_message (&msg);
2134 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2137 const char *fqdn = mutt_fqdn (1);
2138 char resent_from[STRING];
2142 resent_from[0] = '\0';
2143 from = mutt_default_from ();
2146 rfc822_qualify (from, fqdn);
2148 rfc2047_encode_adrlist (from, "Resent-From");
2149 if (mutt_addrlist_to_idna (from, &err)) {
2150 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2153 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2156 unset_option (OPTNEWSSEND);
2159 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2161 address_list_wipe(&from);
2167 /* given a list of addresses, return a list of unique addresses */
2168 address_t *mutt_remove_duplicates (address_t * addr)
2170 address_t *top = addr;
2171 address_t **last = ⊤
2176 for (tmp = top; tmp && tmp != addr; tmp = tmp->next) {
2177 if (tmp->mailbox && addr->mailbox &&
2178 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2188 address_list_wipe(&addr);
2201 static void set_noconv_flags (BODY * b, short flag)
2203 for (; b; b = b->next) {
2204 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2205 set_noconv_flags (b->parts, flag);
2206 else if (b->type == TYPETEXT && b->noconv) {
2207 parameter_setval(&b->parameter, "x-mutt-noconv", flag ? "yes" : NULL);
2212 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2213 int post, char *fcc)
2217 char tempfile[_POSIX_PATH_MAX];
2218 FILE *tempfp = NULL;
2222 set_noconv_flags (hdr->content, 1);
2224 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2228 /* We need to add a Content-Length field to avoid problems where a line in
2229 * the message body begins with "From "
2231 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2232 mutt_mktemp (tempfile);
2233 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2234 mutt_perror (tempfile);
2235 mx_close_mailbox (&f, NULL);
2240 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2241 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2242 mx_close_mailbox (&f, NULL);
2246 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2247 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2249 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2252 /* (postponment) if this was a reply of some sort, <msgid> contians the
2253 * Message-ID: of message replied to. Save it using a special X-Mutt-
2254 * header so it can be picked up if the message is recalled at a later
2255 * point in time. This will allow the message to be marked as replied if
2256 * the same mailbox is still open.
2259 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2261 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2262 * it can be picked up when the message is recalled
2265 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2266 fprintf (msg->fp, "Status: RO\n");
2270 /* (postponment) if the mail is to be signed or encrypted, save this info */
2271 if (post && (hdr->security & APPLICATION_PGP)) {
2272 fputs ("X-Mutt-PGP: ", msg->fp);
2273 if (hdr->security & ENCRYPT)
2274 fputc ('E', msg->fp);
2275 if (hdr->security & SIGN) {
2276 fputc ('S', msg->fp);
2277 if (PgpSignAs && *PgpSignAs)
2278 fprintf (msg->fp, "<%s>", PgpSignAs);
2280 if (hdr->security & INLINE)
2281 fputc ('I', msg->fp);
2282 fputc ('\n', msg->fp);
2285 /* (postponment) if the mail is to be signed or encrypted, save this info */
2286 if (post && (hdr->security & APPLICATION_SMIME)) {
2287 fputs ("X-Mutt-SMIME: ", msg->fp);
2288 if (hdr->security & ENCRYPT) {
2289 fputc ('E', msg->fp);
2290 if (SmimeCryptAlg && *SmimeCryptAlg)
2291 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2293 if (hdr->security & SIGN) {
2294 fputc ('S', msg->fp);
2295 if (SmimeDefaultKey && *SmimeDefaultKey)
2296 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2298 if (hdr->security & INLINE)
2299 fputc ('I', msg->fp);
2300 fputc ('\n', msg->fp);
2304 /* (postponement) if the mail is to be sent through a mixmaster
2305 * chain, save that information
2308 if (post && hdr->chain && hdr->chain) {
2311 fputs ("X-Mutt-Mix:", msg->fp);
2312 for (p = hdr->chain; p; p = p->next)
2313 fprintf (msg->fp, " %s", (char *) p->data);
2315 fputc ('\n', msg->fp);
2320 char sasha[LONG_STRING];
2323 mutt_write_mime_body (hdr->content, tempfp);
2325 /* make sure the last line ends with a newline. Emacs doesn't ensure
2326 * this will happen, and it can cause problems parsing the mailbox
2329 fseeko (tempfp, -1, 2);
2330 if (fgetc (tempfp) != '\n') {
2331 fseeko (tempfp, 0, 2);
2332 fputc ('\n', tempfp);
2336 if (ferror (tempfp)) {
2339 mx_commit_message (msg, &f); /* XXX - really? */
2340 mx_close_message (&msg);
2341 mx_close_mailbox (&f, NULL);
2345 /* count the number of lines */
2347 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2349 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2350 fprintf (msg->fp, "Lines: %d\n\n", lines);
2352 /* copy the body and clean up */
2354 r = mutt_copy_stream (tempfp, msg->fp);
2355 if (fclose (tempfp) != 0)
2357 /* if there was an error, leave the temp version */
2362 fputc ('\n', msg->fp); /* finish off the header */
2363 r = mutt_write_mime_body (hdr->content, msg->fp);
2366 if (mx_commit_message (msg, &f) != 0)
2368 mx_close_message (&msg);
2369 mx_close_mailbox (&f, NULL);
2372 set_noconv_flags (hdr->content, 0);