2 * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
22 #include "mutt_curses.h"
31 #include "mutt_crypt.h"
32 #include "mutt_idna.h"
43 #include <sys/utsname.h>
46 # include "mutt_libesmtp.h"
47 #endif /* USE_LIBESMTP */
53 #ifdef HAVE_SYSEXITS_H
55 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
59 /* If you are debugging this file, comment out the following line. */
68 extern char RFC822Specials[];
70 static struct sysexits
78 { 0xff & EX_USAGE, "Bad usage." },
81 { 0xff & EX_DATAERR, "Data format error." },
84 { 0xff & EX_NOINPUT, "Cannot open input." },
87 { 0xff & EX_NOUSER, "User unknown." },
90 { 0xff & EX_NOHOST, "Host unknown." },
93 { 0xff & EX_UNAVAILABLE, "Service unavailable." },
96 { 0xff & EX_SOFTWARE, "Internal error." },
99 { 0xff & EX_OSERR, "Operating system error." },
102 { 0xff & EX_OSFILE, "System file missing." },
105 { 0xff & EX_CANTCREAT, "Can't create output." },
108 { 0xff & EX_IOERR, "I/O error." },
111 { 0xff & EX_TEMPFAIL, "Deferred." },
114 { 0xff & EX_PROTOCOL, "Remote protocol error." },
117 { 0xff & EX_NOPERM, "Insufficient permission." },
120 { 0xff & EX_NOPERM, "Local configuration error." },
122 { S_ERR, "Exec error." },
128 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
130 const char MimeSpecials[] = "@.,;:<>[]\\\"()?/= \t";
132 char B64Chars[64] = {
133 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
134 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
135 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
136 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
140 static char MsgIdPfx = 'A';
142 static void transform_to_7bit (BODY *a, FILE *fpin);
144 static void encode_quoted (FGETCONV * fc, FILE *fout, int istext)
147 char line[77], savechar;
149 while ((c = fgetconv (fc)) != EOF)
151 /* Wrap the line if needed. */
152 if (linelen == 76 && ((istext && c != '\n') || !istext))
154 /* If the last character is "quoted", then be sure to move all three
155 * characters to the next line. Otherwise, just move the last
158 if (line[linelen-3] == '=')
165 line[1] = line[linelen-2];
166 line[2] = line[linelen-1];
171 savechar = line[linelen-1];
172 line[linelen-1] = '=';
181 /* Escape lines that begin with/only contain "the message separator". */
182 if (linelen == 4 && !mutt_strncmp ("From", line, 4))
184 strfcpy (line, "=46rom", sizeof (line));
187 else if (linelen == 4 && !mutt_strncmp ("from", line, 4))
189 strfcpy (line, "=66rom", sizeof (line));
192 else if (linelen == 1 && line[0] == '.')
194 strfcpy (line, "=2E", sizeof (line));
199 if (c == '\n' && istext)
201 /* Check to make sure there is no trailing space on this line. */
202 if (linelen > 0 && (line[linelen-1] == ' ' || line[linelen-1] == '\t'))
206 sprintf (line+linelen-1, "=%2.2X", (unsigned char) line[linelen-1]);
211 int savechar = line[linelen-1];
213 line[linelen-1] = '=';
216 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
227 else if (c != 9 && (c < 32 || c > 126 || c == '='))
229 /* Check to make sure there is enough room for the quoted character.
230 * If not, wrap to the next line.
234 line[linelen++] = '=';
240 sprintf (line+linelen,"=%2.2X", (unsigned char) c);
245 /* Don't worry about wrapping the line here. That will happen during
246 * the next iteration when I'll also know what the next character is.
252 /* Take care of anything left in the buffer */
255 if (line[linelen-1] == ' ' || line[linelen-1] == '\t')
257 /* take care of trailing whitespace */
259 sprintf (line+linelen-1, "=%2.2X", (unsigned char) line[linelen-1]);
262 savechar = line[linelen-1];
263 line[linelen-1] = '=';
267 sprintf (line, "=%2.2X", (unsigned char) savechar);
276 static char b64_buffer[3];
277 static short b64_num;
278 static short b64_linelen;
280 static void b64_flush(FILE *fout)
287 if(b64_linelen >= 72)
293 for(i = b64_num; i < 3; i++)
294 b64_buffer[i] = '\0';
296 fputc(B64Chars[(b64_buffer[0] >> 2) & 0x3f], fout);
298 fputc(B64Chars[((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf) ], fout);
303 fputc(B64Chars[((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3) ], fout);
307 fputc(B64Chars[b64_buffer[2] & 0x3f], fout);
312 while(b64_linelen % 4)
322 static void b64_putc(char c, FILE *fout)
327 b64_buffer[b64_num++] = c;
331 static void encode_base64 (FGETCONV * fc, FILE *fout, int istext)
335 b64_num = b64_linelen = 0;
337 while ((ch = fgetconv (fc)) != EOF)
339 if (istext && ch == '\n' && ch1 != '\r')
340 b64_putc('\r', fout);
348 static void encode_8bit (FGETCONV *fc, FILE *fout, int istext)
352 while ((ch = fgetconv (fc)) != EOF)
357 int mutt_write_mime_header (BODY *a, FILE *f)
367 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
371 len = 25 + mutt_strlen (a->subtype); /* approximate len. of content-type */
373 for(p = a->parameter; p; p = p->next)
383 tmp = safe_strdup (p->value);
384 encode = rfc2231_encode_string (&tmp);
385 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
387 /* Dirty hack to make messages readable by Outlook Express
388 * for the Mac: force quotes around the boundary parameter
389 * even when they aren't needed.
392 if (!ascii_strcasecmp (p->attribute, "boundary") && !strcmp (buffer, tmp))
393 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
397 tmplen = mutt_strlen (buffer) + mutt_strlen (p->attribute) + 1;
399 if (len + tmplen + 2 > 76)
410 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
418 fprintf(f, "Content-Description: %s\n", a->description);
420 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
424 if(!(fn = a->d_filename))
431 /* Strip off the leading path... */
432 if ((t = strrchr (fn, '/')))
438 tmp = safe_strdup (t);
439 encode = rfc2231_encode_string (&tmp);
440 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
442 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
448 if (a->encoding != ENC7BIT)
449 fprintf(f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
451 /* Do NOT add the terminator here!!! */
452 return (ferror (f) ? -1 : 0);
455 # define write_as_text_part(a) (mutt_is_text_part(a) \
456 || ((WithCrypto & APPLICATION_PGP)\
457 && mutt_is_application_pgp(a)))
459 int mutt_write_mime_body (BODY *a, FILE *f)
461 char *p, boundary[SHORT_STRING];
462 char send_charset[SHORT_STRING];
467 if (a->type == TYPEMULTIPART)
469 /* First, find the boundary to use */
470 if (!(p = mutt_get_parameter ("boundary", a->parameter)))
472 dprint (1, (debugfile, "mutt_write_mime_body(): no boundary parameter found!\n"));
473 mutt_error _("No boundary parameter found! [report this error]");
476 strfcpy (boundary, p, sizeof (boundary));
478 for (t = a->parts; t ; t = t->next)
480 fprintf (f, "\n--%s\n", boundary);
481 if (mutt_write_mime_header (t, f) == -1)
484 if (mutt_write_mime_body (t, f) == -1)
487 fprintf (f, "\n--%s--\n", boundary);
488 return (ferror (f) ? -1 : 0);
491 /* This is pretty gross, but it's the best solution for now... */
492 if ((WithCrypto & APPLICATION_PGP)
493 && a->type == TYPEAPPLICATION
494 && mutt_strcmp (a->subtype, "pgp-encrypted") == 0)
496 fputs ("Version: 1\n", f);
500 if ((fpin = fopen (a->filename, "r")) == NULL)
502 dprint(1,(debugfile, "write_mime_body: %s no longer exists!\n",a->filename));
503 mutt_error (_("%s no longer exists!"), a->filename);
507 if (a->type == TYPETEXT && (!a->noconv))
508 fc = fgetconv_open (fpin, Charset,
509 mutt_get_body_charset (send_charset, sizeof (send_charset), a),
512 fc = fgetconv_open (fpin, 0, 0, 0);
514 if (a->encoding == ENCQUOTEDPRINTABLE)
515 encode_quoted (fc, f, write_as_text_part (a));
516 else if (a->encoding == ENCBASE64)
517 encode_base64 (fc, f, write_as_text_part (a));
518 else if (a->type == TYPETEXT && (!a->noconv))
519 encode_8bit (fc, f, write_as_text_part (a));
521 mutt_copy_stream (fpin, f);
523 fgetconv_close (&fc);
526 return (ferror (f) ? -1 : 0);
529 #undef write_as_text_part
531 #define BOUNDARYLEN 16
532 void mutt_generate_boundary (PARAMETER **parm)
534 char rs[BOUNDARYLEN + 1];
539 for (i=0;i<BOUNDARYLEN;i++)
540 *p++ = B64Chars[LRAND() % sizeof (B64Chars)];
543 mutt_set_parameter ("boundary", rs, parm);
557 static void update_content_info (CONTENT *info, CONTENT_STATE *s, char *d, size_t dlen)
560 int whitespace = s->whitespace;
562 int linelen = s->linelen;
563 int was_cr = s->was_cr;
565 if (!d) /* This signals EOF */
569 if (linelen > info->linemax)
570 info->linemax = linelen;
575 for (; dlen; d++, dlen--)
588 if (whitespace) info->space = 1;
589 if (dot) info->dot = 1;
590 if (linelen > info->linemax) info->linemax = linelen;
602 if (whitespace) info->space = 1;
603 if (dot) info->dot = 1;
604 if (linelen > info->linemax) info->linemax = linelen;
618 else if (ch == '\t' || ch == '\f')
623 else if (ch < 32 || ch == 127)
629 if ((ch == 'F') || (ch == 'f'))
640 if (linelen == 2 && ch != 'r') from = 0;
641 else if (linelen == 3 && ch != 'o') from = 0;
642 else if (linelen == 4)
644 if (ch == 'm') info->from = 1;
648 if (ch == ' ') whitespace++;
652 if (linelen > 1) dot = 0;
653 if (ch != ' ' && ch != '\t') whitespace = 0;
657 s->whitespace = whitespace;
659 s->linelen = linelen;
664 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
665 #define BUGGY_ICONV 1
668 * Find the best charset conversion of the file from fromcode into one
669 * of the tocodes. If successful, set *tocode and CONTENT *info and
670 * return the number of characters converted inexactly. If no
671 * conversion was possible, return -1.
673 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
674 * which would otherwise prevent us from knowing the number of inexact
675 * conversions. Where the candidate target charset is UTF-8 we avoid
676 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
677 * fails with some libraries.
679 * We assume that the output from iconv is never more than 4 times as
680 * long as the input for any pair of charsets we might be interested
683 static size_t convert_file_to (FILE *file, const char *fromcode,
684 int ncodes, const char **tocodes,
685 int *tocode, CONTENT *info)
689 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
690 ICONV_CONST char *ib, *ub;
692 size_t ibl, obl, ubl, ubl1, n, ret;
695 CONTENT_STATE *states;
698 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
699 if (cd1 == (iconv_t)(-1))
702 cd = safe_calloc (ncodes, sizeof (iconv_t));
703 score = safe_calloc (ncodes, sizeof (size_t));
704 states = safe_calloc (ncodes, sizeof (CONTENT_STATE));
705 infos = safe_calloc (ncodes, sizeof (CONTENT));
707 for (i = 0; i < ncodes; i++)
708 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
709 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
711 /* Special case for conversion to UTF-8 */
712 cd[i] = (iconv_t)(-1), score[i] = (size_t)(-1);
719 /* Try to fill input buffer */
720 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
723 /* Convert to UTF-8 */
725 ob = bufu, obl = sizeof (bufu);
726 n = iconv (cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
727 assert (n == (size_t)(-1) || !n || ICONV_NONTRANS);
728 if (n == (size_t)(-1) &&
729 ((errno != EINVAL && errno != E2BIG) || ib == bufi))
731 assert (errno == EILSEQ ||
732 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
738 /* Convert from UTF-8 */
739 for (i = 0; i < ncodes; i++)
740 if (cd[i] != (iconv_t)(-1) && score[i] != (size_t)(-1))
742 ub = bufu, ubl = ubl1;
743 ob = bufo, obl = sizeof (bufo);
744 n = iconv (cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
745 if (n == (size_t)(-1))
747 assert (errno == E2BIG ||
748 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
749 score[i] = (size_t)(-1);
754 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
757 else if (cd[i] == (iconv_t)(-1) && score[i] == (size_t)(-1))
758 /* Special case for conversion to UTF-8 */
759 update_content_info (&infos[i], &states[i], bufu, ubl1);
762 /* Save unused input */
763 memmove (bufi, ib, ibl);
764 else if (!ubl1 && ib < bufi + sizeof (bufi))
773 /* Find best score */
775 for (i = 0; i < ncodes; i++)
777 if (cd[i] == (iconv_t)(-1) && score[i] == (size_t)(-1))
779 /* Special case for conversion to UTF-8 */
784 else if (cd[i] == (iconv_t)(-1) || score[i] == (size_t)(-1))
786 else if (ret == (size_t)(-1) || score[i] < ret)
794 if (ret != (size_t)(-1))
796 memcpy (info, &infos[*tocode], sizeof(CONTENT));
797 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
801 for (i = 0; i < ncodes; i++)
802 if (cd[i] != (iconv_t)(-1))
814 #endif /* !HAVE_ICONV */
818 * Find the first of the fromcodes that gives a valid conversion and
819 * the best charset conversion of the file into one of the tocodes. If
820 * successful, set *fromcode and *tocode to dynamically allocated
821 * strings, set CONTENT *info, and return the number of characters
822 * converted inexactly. If no conversion was possible, return -1.
824 * Both fromcodes and tocodes may be colon-separated lists of charsets.
825 * However, if fromcode is zero then fromcodes is assumed to be the
826 * name of a single charset even if it contains a colon.
828 static size_t convert_file_from_to (FILE *file,
829 const char *fromcodes, const char *tocodes,
830 char **fromcode, char **tocode, CONTENT *info)
838 /* Count the tocodes */
840 for (c = tocodes; c; c = c1 ? c1 + 1 : 0)
842 if ((c1 = strchr (c, ':')) == c)
848 tcode = safe_malloc (ncodes * sizeof (char *));
849 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++)
851 if ((c1 = strchr (c, ':')) == c)
853 tcode[i] = mutt_substrdup (c, c1);
859 /* Try each fromcode in turn */
860 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0)
862 if ((c1 = strchr (c, ':')) == c)
864 fcode = mutt_substrdup (c, c1);
866 ret = convert_file_to (file, fcode, ncodes, (const char **)tcode,
868 if (ret != (size_t)(-1))
880 /* There is only one fromcode */
881 ret = convert_file_to (file, fromcodes, ncodes, (const char **)tcode,
883 if (ret != (size_t)(-1))
891 for (i = 0; i < ncodes; i++)
900 * Analyze the contents of a file to determine which MIME encoding to use.
901 * Also set the body charset, sometimes, or not.
903 CONTENT *mutt_get_content_info (const char *fname, BODY *b)
915 if(b && !fname) fname = b->filename;
917 if (stat (fname, &sb) == -1)
919 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
923 if (!S_ISREG(sb.st_mode))
925 mutt_error (_("%s isn't a regular file."), fname);
929 if ((fp = fopen (fname, "r")) == NULL)
931 dprint (1, (debugfile, "mutt_get_content_info: %s: %s (errno %d).\n",
932 fname, strerror (errno), errno));
936 info = safe_calloc (1, sizeof (CONTENT));
937 memset (&state, 0, sizeof (state));
939 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
941 char *chs = mutt_get_parameter ("charset", b->parameter);
942 if (Charset && (chs || SendCharset) &&
943 convert_file_from_to (fp, Charset, chs ? chs : SendCharset,
944 0, &tocode, info) != (size_t)(-1))
948 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
949 mutt_set_parameter ("charset", chsbuf, &b->parameter);
958 while ((r = fread (buffer, 1, sizeof(buffer), fp)))
959 update_content_info (info, &state, buffer, r);
960 update_content_info (info, &state, 0, 0);
964 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
965 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
966 Charset && !mutt_is_us_ascii (Charset) ? Charset : "unknown-8bit"),
972 /* Given a file with path ``s'', see if there is a registered MIME type.
973 * returns the major MIME type, and copies the subtype to ``d''. First look
974 * for ~/.mime.types, then look in a system mime.types if we can find one.
975 * The longest match is used so that we can match `ps.gz' when `gz' also
979 int mutt_lookup_mime_type (BODY *att, const char *path)
983 char buf[LONG_STRING];
984 char subtype[STRING], xtype[STRING];
986 int szf, sze, cur_sze;
994 szf = mutt_strlen (path);
996 for (count = 0 ; count < 3 ; count++)
999 * can't use strtok() because we use it in an inner loop below, so use
1000 * a switch statement here instead.
1005 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL(Homedir));
1008 strfcpy (buf, SYSCONFDIR"/mime.types", sizeof(buf));
1011 strfcpy (buf, PKGDATADIR"/mime.types", sizeof (buf));
1014 dprint (1, (debugfile, "mutt_lookup_mime_type: Internal error, count = %d.\n", count));
1015 goto bye; /* shouldn't happen */
1018 if ((f = fopen (buf, "r")) != NULL)
1020 while (fgets (buf, sizeof (buf) - 1, f) != NULL)
1022 /* weed out any comments */
1023 if ((p = strchr (buf, '#')))
1026 /* remove any leading space. */
1030 /* position on the next field in this line */
1031 if ((p = strpbrk (ct, " \t")) == NULL)
1036 /* cycle through the file extensions */
1037 while ((p = strtok (p, " \t\n")))
1039 sze = mutt_strlen (p);
1040 if ((sze > cur_sze) && (szf >= sze) &&
1041 (mutt_strcasecmp (path + szf - sze, p) == 0 || ascii_strcasecmp (path + szf - sze, p) == 0) &&
1042 (szf == sze || path[szf - sze - 1] == '.'))
1044 /* get the content-type */
1046 if ((p = strchr (ct, '/')) == NULL)
1048 /* malformed line, just skip it. */
1053 for (q = p; *q && !ISSPACE (*q); q++)
1056 mutt_substrcpy (subtype, p, q, sizeof (subtype));
1058 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
1059 strfcpy (xtype, ct, sizeof (xtype));
1072 if (type != TYPEOTHER || *xtype != '\0')
1075 mutt_str_replace (&att->subtype, subtype);
1076 mutt_str_replace (&att->xtype, xtype);
1082 void mutt_message_to_7bit (BODY *a, FILE *fp)
1084 char temp[_POSIX_PATH_MAX];
1090 if (!a->filename && fp)
1092 else if (!a->filename || !(fpin = fopen (a->filename, "r")))
1094 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
1100 if (stat (a->filename, &sb) == -1)
1102 mutt_perror ("stat");
1105 a->length = sb.st_size;
1109 if (!(fpout = safe_fopen (temp, "w+")))
1111 mutt_perror ("fopen");
1115 fseek (fpin, a->offset, 0);
1116 a->parts = mutt_parse_messageRFC822 (fpin, a);
1118 transform_to_7bit (a->parts, fpin);
1120 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1121 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1123 fputs ("Mime-Version: 1.0\n", fpout);
1124 mutt_write_mime_header (a->parts, fpout);
1125 fputc ('\n', fpout);
1126 mutt_write_mime_body (a->parts, fpout);
1138 a->encoding = ENC7BIT;
1139 a->d_filename = a->filename;
1140 if (a->filename && a->unlink)
1141 unlink (a->filename);
1142 a->filename = safe_strdup (temp);
1144 if(stat (a->filename, &sb) == -1)
1146 mutt_perror ("stat");
1149 a->length = sb.st_size;
1150 mutt_free_body (&a->parts);
1151 a->hdr->content = NULL;
1154 static void transform_to_7bit (BODY *a, FILE *fpin)
1156 char buff[_POSIX_PATH_MAX];
1160 memset (&s, 0, sizeof (s));
1161 for (; a; a = a->next)
1163 if (a->type == TYPEMULTIPART)
1165 if (a->encoding != ENC7BIT)
1166 a->encoding = ENC7BIT;
1168 transform_to_7bit (a->parts, fpin);
1170 else if (mutt_is_message_type(a->type, a->subtype))
1172 mutt_message_to_7bit (a, fpin);
1177 if ((s.fpout = safe_fopen (buff, "w")) == NULL)
1179 mutt_perror ("fopen");
1183 mutt_decode_attachment (a, &s);
1185 a->d_filename = a->filename;
1186 a->filename = safe_strdup (buff);
1188 if (stat (a->filename, &sb) == -1)
1190 mutt_perror ("stat");
1193 a->length = sb.st_size;
1195 mutt_update_encoding (a);
1196 if (a->encoding == ENC8BIT)
1197 a->encoding = ENCQUOTEDPRINTABLE;
1198 else if(a->encoding == ENCBINARY)
1199 a->encoding = ENCBASE64;
1204 /* determine which Content-Transfer-Encoding to use */
1205 static void mutt_set_encoding (BODY *b, CONTENT *info)
1207 char send_charset[SHORT_STRING];
1209 if (b->type == TYPETEXT)
1211 char *chsname = mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1212 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8)) || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1213 b->encoding = ENCQUOTEDPRINTABLE;
1214 else if (info->hibin)
1215 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1217 b->encoding = ENC7BIT;
1219 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
1221 if (info->lobin || info->hibin)
1223 if (option (OPTALLOW8BIT) && !info->lobin)
1224 b->encoding = ENC8BIT;
1226 mutt_message_to_7bit (b, NULL);
1229 b->encoding = ENC7BIT;
1231 else if (b->type == TYPEAPPLICATION && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1232 b->encoding = ENC7BIT;
1235 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1236 || info->cr || (/* option (OPTENCODEFROM) && */ info->from))
1239 /* Determine which encoding is smaller */
1240 if (1.33 * (float)(info->lobin+info->hibin+info->ascii) <
1241 3.0 * (float)(info->lobin + info->hibin) + (float)info->ascii)
1242 b->encoding = ENCBASE64;
1244 b->encoding = ENCQUOTEDPRINTABLE;
1248 b->encoding = ENC7BIT;
1252 void mutt_stamp_attachment(BODY *a)
1254 a->stamp = time(NULL);
1257 /* Get a body's character set */
1259 char *mutt_get_body_charset (char *d, size_t dlen, BODY *b)
1263 if (b && b->type != TYPETEXT)
1267 p = mutt_get_parameter ("charset", b->parameter);
1270 mutt_canonical_charset (d, dlen, NONULL(p));
1272 strfcpy (d, "us-ascii", dlen);
1278 /* Assumes called from send mode where BODY->filename points to actual file */
1279 void mutt_update_encoding (BODY *a)
1282 char chsbuff[STRING];
1284 /* override noconv when it's us-ascii */
1285 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1288 if (!a->force_charset && !a->noconv)
1289 mutt_delete_parameter ("charset", &a->parameter);
1291 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1294 mutt_set_encoding (a, info);
1295 mutt_stamp_attachment(a);
1302 BODY *mutt_make_message_attach (CONTEXT *ctx, HEADER *hdr, int attach_msg)
1304 char buffer[LONG_STRING];
1307 int cmflags, chflags;
1308 int pgp = WithCrypto? hdr->security : 0;
1312 if ((option(OPTMIMEFORWDECODE) || option(OPTFORWDECRYPT)) &&
1313 (hdr->security & ENCRYPT)) {
1314 if (!crypt_valid_passphrase(hdr->security))
1319 mutt_mktemp (buffer);
1320 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1323 body = mutt_new_body ();
1324 body->type = TYPEMESSAGE;
1325 body->subtype = safe_strdup ("rfc822");
1326 body->filename = safe_strdup (buffer);
1329 body->disposition = DISPINLINE;
1331 mutt_parse_mime_message (ctx, hdr);
1336 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1337 if (!attach_msg && option (OPTMIMEFORWDECODE))
1339 chflags |= CH_MIME | CH_TXTPLAIN;
1340 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1341 if ((WithCrypto & APPLICATION_PGP))
1343 if ((WithCrypto & APPLICATION_SMIME))
1344 pgp &= ~SMIMEENCRYPT;
1347 && option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT))
1349 if ((WithCrypto & APPLICATION_PGP)
1350 && mutt_is_multipart_encrypted (hdr->content))
1352 chflags |= CH_MIME | CH_NONEWLINE;
1353 cmflags = M_CM_DECODE_PGP;
1356 else if ((WithCrypto & APPLICATION_PGP)
1357 && (mutt_is_application_pgp (hdr->content) & PGPENCRYPT))
1359 chflags |= CH_MIME | CH_TXTPLAIN;
1360 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1363 else if ((WithCrypto & APPLICATION_SMIME)
1364 && mutt_is_application_smime (hdr->content) & SMIMEENCRYPT)
1366 chflags |= CH_MIME | CH_TXTPLAIN;
1367 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1368 pgp &= ~SMIMEENCRYPT;
1372 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1377 body->hdr = mutt_new_header();
1378 body->hdr->offset = 0;
1379 /* we don't need the user headers here */
1380 body->hdr->env = mutt_read_rfc822_header(fp, body->hdr, 0, 0);
1382 body->hdr->security = pgp;
1383 mutt_update_encoding (body);
1384 body->parts = body->hdr->content;
1391 BODY *mutt_make_file_attach (const char *path)
1396 att = mutt_new_body ();
1397 att->filename = safe_strdup (path);
1399 /* Attempt to determine the appropriate content-type based on the filename
1405 if ((n = mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf), path)) != TYPEOTHER
1409 att->subtype = safe_strdup (buf);
1410 att->xtype = safe_strdup (xbuf);
1415 mutt_lookup_mime_type (att, path);
1419 if ((info = mutt_get_content_info (path, att)) == NULL)
1421 mutt_free_body (&att);
1427 if (info->lobin == 0 || (info->lobin + info->hibin + info->ascii)/ info->lobin >= 10)
1430 * Statistically speaking, there should be more than 10% "lobin"
1431 * chars if this is really a binary file...
1433 att->type = TYPETEXT;
1434 att->subtype = safe_strdup ("plain");
1438 att->type = TYPEAPPLICATION;
1439 att->subtype = safe_strdup ("octet-stream");
1443 mutt_update_encoding (att);
1447 static int get_toplevel_encoding (BODY *a)
1451 for (; a; a = a->next)
1453 if (a->encoding == ENCBINARY)
1455 else if (a->encoding == ENC8BIT)
1462 BODY *mutt_make_multipart (BODY *b)
1466 new = mutt_new_body ();
1467 new->type = TYPEMULTIPART;
1468 new->subtype = safe_strdup ("mixed");
1469 new->encoding = get_toplevel_encoding (b);
1470 mutt_generate_boundary (&new->parameter);
1472 new->disposition = DISPINLINE;
1478 /* remove the multipart body if it exists */
1479 BODY *mutt_remove_multipart (BODY *b)
1488 mutt_free_body (&t);
1493 char *mutt_make_date (char *s, size_t len)
1495 time_t t = time (NULL);
1496 struct tm *l = localtime (&t);
1497 time_t tz = mutt_local_tz (t);
1501 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1502 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1503 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1504 (int) tz / 60, (int) abs (tz) % 60);
1508 /* wrapper around mutt_write_address() so we can handle very large
1509 recipient lists without needing a huge temporary buffer in memory */
1510 void mutt_write_address_list (ADDRESS *adr, FILE *fp, int linelen, int display)
1513 char buf[LONG_STRING];
1522 rfc822_write_address (buf, sizeof (buf), adr, display);
1523 len = mutt_strlen (buf);
1524 if (count && linelen + len > 74)
1527 linelen = len + 8; /* tab is usually about 8 spaces... */
1531 if (count && adr->mailbox)
1540 if (!adr->group && adr->next && adr->next->mailbox)
1551 /* arbitrary number of elements to grow the array by */
1556 /* need to write the list in reverse because they are stored in reverse order
1557 * when parsed to speed up threading
1559 void mutt_write_references (LIST *r, FILE *f)
1562 int refcnt = 0, refmax = 0;
1564 for ( ; (TrimRef == 0 || refcnt < TrimRef) && r ; r = r->next)
1566 if (refcnt == refmax)
1567 safe_realloc (&ref, (refmax += REF_INC) * sizeof (LIST *));
1571 while (refcnt-- > 0)
1574 fputs (ref[refcnt]->data, f);
1580 /* Note: all RFC2047 encoding should be done outside of this routine, except
1581 * for the "real name." This will allow this routine to be used more than
1582 * once, if necessary.
1584 * Likewise, all IDN processing should happen outside of this routine.
1586 * mode == 1 => "lite" mode (used for edit_hdrs)
1587 * mode == 0 => normal mode. write full header + MIME headers
1588 * mode == -1 => write just the envelope info (used for postponing messages)
1590 * privacy != 0 => will omit any headers which may identify the user.
1591 * Output generated is suitable for being sent through
1592 * anonymous remailer chains.
1596 int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach,
1597 int mode, int privacy)
1599 char buffer[LONG_STRING];
1601 LIST *tmp = env->userhdrs;
1602 int has_agent = 0; /* user defined user-agent header field exists */
1605 if (!option (OPTNEWSSEND))
1607 if (mode == 0 && !privacy)
1608 fputs (mutt_make_date (buffer, sizeof(buffer)), fp);
1610 /* OPTUSEFROM is not consulted here so that we can still write a From:
1611 * field if the user sets it with the `my_hdr' command
1613 if (env->from && !privacy)
1616 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1617 fprintf (fp, "From: %s\n", buffer);
1623 mutt_write_address_list (env->to, fp, 4, 0);
1627 if (!option (OPTNEWSSEND))
1629 fputs ("To: \n", fp);
1634 mutt_write_address_list (env->cc, fp, 4, 0);
1638 if (!option (OPTNEWSSEND))
1640 fputs ("Cc: \n", fp);
1644 if(mode != 0 || option(OPTWRITEBCC))
1646 fputs ("Bcc: ", fp);
1647 mutt_write_address_list (env->bcc, fp, 5, 0);
1652 if (!option (OPTNEWSSEND))
1654 fputs ("Bcc: \n", fp);
1657 if (env->newsgroups)
1658 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1659 else if (mode == 1 && option (OPTNEWSSEND))
1660 fputs ("Newsgroups: \n", fp);
1662 if (env->followup_to)
1663 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1664 else if (mode == 1 && option (OPTNEWSSEND))
1665 fputs ("Followup-To: \n", fp);
1667 if (env->x_comment_to)
1668 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1669 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO))
1670 fputs ("X-Comment-To: \n", fp);
1674 fprintf (fp, "Subject: %s\n", env->subject);
1676 fputs ("Subject: \n", fp);
1678 /* save message id if the user has set it */
1679 if (env->message_id && !privacy)
1680 fprintf (fp, "Message-ID: %s\n", env->message_id);
1684 fputs ("Reply-To: ", fp);
1685 mutt_write_address_list (env->reply_to, fp, 10, 0);
1688 fputs ("Reply-To: \n", fp);
1690 if (env->mail_followup_to)
1692 if (!option (OPTNEWSSEND))
1695 fputs ("Mail-Followup-To: ", fp);
1696 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1701 if (env->references)
1703 fputs ("References:", fp);
1704 mutt_write_references (env->references, fp);
1708 /* Add the MIME headers */
1709 fputs ("Mime-Version: 1.0\n", fp);
1710 mutt_write_mime_header (attach, fp);
1713 if (env->in_reply_to)
1715 fputs ("In-Reply-To:", fp);
1716 mutt_write_references (env->in_reply_to, fp);
1720 /* Add any user defined headers */
1721 for (; tmp; tmp = tmp->next)
1723 if ((p = strchr (tmp->data, ':')))
1726 if (!*p) continue; /* don't emit empty fields. */
1728 /* check to see if the user has overridden the user-agent field */
1729 if (!ascii_strncasecmp ("user-agent", tmp->data, 10))
1736 fputs (tmp->data, fp);
1741 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent)
1745 if (OperatingSystem!=NULL) {
1746 os = OperatingSystem;
1748 if (uname(&un)==-1) {
1754 /* Add a vanity header */
1755 fprintf (fp, "User-Agent: mutt-ng %s (%s)\n", MUTT_VERSION,os);
1758 return (ferror (fp) == 0 ? 0 : -1);
1761 static void encode_headers (LIST *h)
1767 for (; h; h = h->next)
1769 if (!(p = strchr (h->data, ':')))
1774 tmp = safe_strdup (p);
1779 rfc2047_encode_string (&tmp);
1780 safe_realloc (&h->data, mutt_strlen (h->data) + 2 + mutt_strlen (tmp) + 1);
1782 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1788 const char *mutt_fqdn(short may_hide_host)
1792 if(Fqdn && Fqdn[0] != '@')
1796 if(may_hide_host && option(OPTHIDDENHOST))
1798 if((p = strchr(Fqdn, '.')))
1801 /* sanity check: don't hide the host if
1802 * the fqdn is something like detebe.org.
1805 if(!p || !(q = strchr(p, '.')))
1813 char *mutt_gen_msgid (void)
1815 char buf[SHORT_STRING];
1822 if(!(fqdn = mutt_fqdn(0)))
1823 fqdn = NONULL(Hostname);
1825 snprintf (buf, sizeof (buf), "<%d%02d%02d%02d%02d%02d.G%c%d@%s>",
1826 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour,
1827 tm->tm_min, tm->tm_sec, MsgIdPfx, getpid (), fqdn);
1828 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1829 return (safe_strdup (buf));
1832 static RETSIGTYPE alarm_handler (int sig)
1837 /* invoke sendmail in a subshell
1838 path (in) path to program to execute
1839 args (in) arguments to pass to program
1840 msg (in) temp file containing message to send
1841 tempfile (out) if sendmail is put in the background, this points
1842 to the temporary file containing the stdout of the
1845 send_msg (const char *path, char **args, const char *msg, char **tempfile)
1851 mutt_block_signals_system ();
1854 /* we also don't want to be stopped right now */
1855 sigaddset (&set, SIGTSTP);
1856 sigprocmask (SIG_BLOCK, &set, NULL);
1858 if (SendmailWait >= 0)
1860 char tmp[_POSIX_PATH_MAX];
1863 *tempfile = safe_strdup (tmp);
1866 if ((pid = fork ()) == 0)
1868 struct sigaction act, oldalrm;
1870 /* save parent's ID before setsid() */
1873 /* we want the delivery to continue even after the main process dies,
1874 * so we put ourselves into another session right away
1878 /* next we close all open files */
1879 #if defined(OPEN_MAX)
1880 for (fd = 0; fd < OPEN_MAX; fd++)
1882 #elif defined(_POSIX_OPEN_MAX)
1883 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1891 /* now the second fork() */
1892 if ((pid = fork ()) == 0)
1894 /* "msg" will be opened as stdin */
1895 if (open (msg, O_RDONLY, 0) < 0)
1902 if (SendmailWait >= 0)
1904 /* *tempfile will be opened as stdout */
1905 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) < 0)
1907 /* redirect stderr to *tempfile too */
1913 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1915 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1929 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1930 * SendmailWait = 0: wait forever
1931 * SendmailWait < 0: don't wait
1933 if (SendmailWait > 0)
1936 act.sa_handler = alarm_handler;
1938 /* need to make sure waitpid() is interrupted on SIGALRM */
1939 act.sa_flags = SA_INTERRUPT;
1943 sigemptyset (&act.sa_mask);
1944 sigaction (SIGALRM, &act, &oldalrm);
1945 alarm (SendmailWait);
1947 else if (SendmailWait < 0)
1948 _exit (0xff & EX_OK);
1950 if (waitpid (pid, &st, 0) > 0)
1952 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1953 if (SendmailWait && st == (0xff & EX_OK))
1955 unlink (*tempfile); /* no longer needed */
1961 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ?
1963 if (SendmailWait > 0)
1970 /* reset alarm; not really needed, but... */
1972 sigaction (SIGALRM, &oldalrm, NULL);
1974 if (kill (ppid, 0) == -1 && errno == ESRCH)
1976 /* the parent is already dead */
1984 sigprocmask (SIG_UNBLOCK, &set, NULL);
1986 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1987 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1989 st = S_ERR; /* error */
1991 mutt_unblock_signals_system (1);
1997 add_args (char **args, size_t *argslen, size_t *argsmax, ADDRESS *addr)
1999 for (; addr; addr = addr->next)
2001 /* weed out group mailboxes, since those are for display only */
2002 if (addr->mailbox && !addr->group)
2004 if (*argslen == *argsmax)
2005 safe_realloc (&args, (*argsmax += 5) * sizeof (char *));
2006 args[(*argslen)++] = addr->mailbox;
2013 add_option (char **args, size_t *argslen, size_t *argsmax, char *s)
2015 if (*argslen == *argsmax)
2016 safe_realloc (&args, (*argsmax += 5) * sizeof (char *));
2017 args[(*argslen)++] = s;
2026 for(i = 0; sysexits_h[i].str; i++)
2028 if(e == sysexits_h[i].v)
2032 return sysexits_h[i].str;
2037 mutt_invoke_sendmail (ADDRESS *from, /* the sender */
2038 ADDRESS *to, ADDRESS *cc, ADDRESS *bcc, /* recips */
2039 const char *msg, /* file containing message */
2040 int eightbit) /* message contains 8bit chars */
2042 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
2044 size_t argslen = 0, argsmax = 0;
2048 if (option (OPTNEWSSEND))
2050 char cmd[LONG_STRING];
2052 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0, 0);
2055 i = nntp_post (msg);
2060 s = safe_strdup (cmd);
2064 s = safe_strdup (Sendmail);
2068 while ((ps = strtok (ps, " ")))
2070 if (argslen == argsmax)
2071 safe_realloc (&args, sizeof (char *) * (argsmax += 5));
2074 args[argslen++] = ps;
2077 path = safe_strdup (ps);
2078 ps = strrchr (ps, '/');
2083 args[argslen++] = ps;
2090 if (!option (OPTNEWSSEND))
2093 if (eightbit && option (OPTUSE8BITMIME))
2094 args = add_option (args, &argslen, &argsmax, "-B8BITMIME");
2096 if (option (OPTENVFROM) && from && !from->next)
2098 args = add_option (args, &argslen, &argsmax, "-f");
2099 args = add_args (args, &argslen, &argsmax, from);
2103 args = add_option (args, &argslen, &argsmax, "-N");
2104 args = add_option (args, &argslen, &argsmax, DsnNotify);
2108 args = add_option (args, &argslen, &argsmax, "-R");
2109 args = add_option (args, &argslen, &argsmax, DsnReturn);
2111 args = add_option (args, &argslen, &argsmax, "--");
2112 args = add_args (args, &argslen, &argsmax, to);
2113 args = add_args (args, &argslen, &argsmax, cc);
2114 args = add_args (args, &argslen, &argsmax, bcc);
2119 if (argslen == argsmax)
2120 safe_realloc (&args, sizeof (char *) * (++argsmax));
2122 args[argslen++] = NULL;
2124 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff))
2128 const char *e = strsysexit (i);
2131 mutt_error (_("Error sending message, child exited %d (%s)."), i, NONULL (e));
2136 if (stat (childout, &st) == 0 && st.st_size > 0)
2137 mutt_do_pager (_("Output of the delivery process"), childout, 0, NULL);
2149 if (i == (EX_OK & 0xff))
2151 else if (i == S_BKG)
2159 mutt_invoke_mta (ADDRESS *from, /* the sender */
2160 ADDRESS *to, ADDRESS *cc, ADDRESS *bcc, /* recips */
2161 const char *msg, /* file containing message */
2162 int eightbit) /* message contains 8bit chars */
2166 return mutt_invoke_libesmtp(from, to, cc, bcc, msg, eightbit);
2169 return mutt_invoke_sendmail(from, to, cc, bcc, msg, eightbit);
2172 /* appends string 'b' to string 'a', and returns the pointer to the new
2174 char *mutt_append_string (char *a, const char *b)
2176 size_t la = mutt_strlen (a);
2177 safe_realloc (&a, la + mutt_strlen (b) + 1);
2178 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2182 /* returns 1 if char `c' needs to be quoted to protect from shell
2183 interpretation when executing commands in a subshell */
2184 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2186 /* returns 1 if string `s' contains characters which could cause problems
2187 when used on a command line to execute a command */
2188 int mutt_needs_quote (const char *s)
2192 if (INVALID_CHAR (*s))
2199 /* Quote a string to prevent shell escapes when this string is used on the
2200 command line to send mail. */
2201 char *mutt_quote_string (const char *s)
2206 rlen = mutt_strlen (s) + 3;
2207 pr = r = (char *) safe_malloc (rlen);
2211 if (INVALID_CHAR (*s))
2214 safe_realloc (&r, ++rlen);
2225 /* For postponing (!final) do the necessary encodings only */
2226 void mutt_prepare_envelope (ENVELOPE *env, int final)
2228 char buffer[LONG_STRING];
2232 if (env->bcc && !(env->to || env->cc))
2234 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2235 * recipients if there is no To: or Cc: field, so attempt to suppress
2236 * it by using an empty To: field.
2238 env->to = rfc822_new_address ();
2240 env->to->next = rfc822_new_address ();
2243 rfc822_cat (buffer, sizeof (buffer), "undisclosed-recipients",
2246 env->to->mailbox = safe_strdup (buffer);
2249 mutt_set_followup_to (env);
2251 if (!env->message_id)
2252 env->message_id = mutt_gen_msgid ();
2255 /* Take care of 8-bit => 7-bit conversion. */
2256 rfc2047_encode_adrlist (env->to, "To");
2257 rfc2047_encode_adrlist (env->cc, "Cc");
2258 rfc2047_encode_adrlist (env->from, "From");
2259 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2260 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2264 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2267 rfc2047_encode_string (&env->subject);
2269 encode_headers (env->userhdrs);
2272 void mutt_unprepare_envelope (ENVELOPE *env)
2276 for (item = env->userhdrs; item; item = item->next)
2277 rfc2047_decode (&item->data);
2279 rfc822_free_address (&env->mail_followup_to);
2281 /* back conversions */
2282 rfc2047_decode_adrlist (env->to);
2283 rfc2047_decode_adrlist (env->cc);
2284 rfc2047_decode_adrlist (env->from);
2285 rfc2047_decode_adrlist (env->reply_to);
2286 rfc2047_decode (&env->subject);
2289 static int _mutt_bounce_message (FILE *fp, HEADER *h, ADDRESS *to, const char *resent_from,
2294 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2295 MESSAGE *msg = NULL;
2299 /* Try to bounce each message out, aborting if we get any failures. */
2300 for (i=0; i<Context->msgcount; i++)
2301 if (Context->hdrs[i]->tagged)
2302 ret |= _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from, env_from);
2306 /* If we failed to open a message, return with error */
2307 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2310 if (!fp) fp = msg->fp;
2312 mutt_mktemp (tempfile);
2313 if ((f = safe_fopen (tempfile, "w")) != NULL)
2315 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2317 if (!option (OPTBOUNCEDELIVERED))
2318 ch_flags |= CH_WEED_DELIVERED;
2320 fseek (fp, h->offset, 0);
2321 fprintf (f, "Resent-From: %s", resent_from);
2322 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof(date)));
2323 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid());
2324 fputs ("Resent-To: ", f);
2325 mutt_write_address_list (to, f, 11, 0);
2326 mutt_copy_header (fp, h, f, ch_flags, NULL);
2328 mutt_copy_bytes (fp, f, h->content->length);
2331 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2332 h->content->encoding == ENC8BIT);
2336 mx_close_message (&msg);
2341 int mutt_bounce_message (FILE *fp, HEADER *h, ADDRESS *to)
2344 const char *fqdn = mutt_fqdn (1);
2345 char resent_from[STRING];
2349 resent_from[0] = '\0';
2350 from = mutt_default_from ();
2353 rfc822_qualify (from, fqdn);
2355 rfc2047_encode_adrlist (from, "Resent-From");
2356 if (mutt_addrlist_to_idna (from, &err))
2358 mutt_error (_("Bad IDN %s while preparing resent-from."),
2362 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2365 unset_option (OPTNEWSSEND);
2368 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2370 rfc822_free_address (&from);
2376 /* given a list of addresses, return a list of unique addresses */
2377 ADDRESS *mutt_remove_duplicates (ADDRESS *addr)
2379 ADDRESS *top = NULL;
2382 if ((top = addr) == NULL)
2390 if (addr->mailbox && tmp->mailbox &&
2391 !ascii_strcasecmp (addr->mailbox, tmp->mailbox))
2393 /* duplicate address, just ignore it */
2397 rfc822_free_address (&tmp);
2399 else if (!tmp->next)
2401 /* unique address. add it to the list */
2406 tmp = NULL; /* so we exit the loop */
2416 static void set_noconv_flags (BODY *b, short flag)
2418 for(; b; b = b->next)
2420 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2421 set_noconv_flags (b->parts, flag);
2422 else if (b->type == TYPETEXT && b->noconv)
2425 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2427 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2432 int mutt_write_fcc (const char *path, HEADER *hdr, const char *msgid, int post, char *fcc)
2436 char tempfile[_POSIX_PATH_MAX];
2437 FILE *tempfp = NULL;
2441 set_noconv_flags (hdr->content, 1);
2443 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL)
2445 dprint (1, (debugfile, "mutt_write_fcc(): unable to open mailbox %s in append-mode, aborting.\n",
2450 /* We need to add a Content-Length field to avoid problems where a line in
2451 * the message body begins with "From "
2453 if (f.magic == M_MMDF || f.magic == M_MBOX)
2455 mutt_mktemp (tempfile);
2456 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL)
2458 mutt_perror (tempfile);
2459 mx_close_mailbox (&f, NULL);
2464 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2465 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL)
2467 mx_close_mailbox (&f, NULL);
2471 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2472 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2474 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0, 0);
2476 /* (postponment) if this was a reply of some sort, <msgid> contians the
2477 * Message-ID: of message replied to. Save it using a special X-Mutt-
2478 * header so it can be picked up if the message is recalled at a later
2479 * point in time. This will allow the message to be marked as replied if
2480 * the same mailbox is still open.
2483 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2485 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2486 * it can be picked up when the message is recalled
2489 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2490 fprintf (msg->fp, "Status: RO\n");
2494 /* (postponment) if the mail is to be signed or encrypted, save this info */
2495 if ((WithCrypto & APPLICATION_PGP)
2496 && post && (hdr->security & APPLICATION_PGP))
2498 fputs ("X-Mutt-PGP: ", msg->fp);
2499 if (hdr->security & ENCRYPT)
2500 fputc ('E', msg->fp);
2501 if (hdr->security & SIGN)
2503 fputc ('S', msg->fp);
2504 if (PgpSignAs && *PgpSignAs)
2505 fprintf (msg->fp, "<%s>", PgpSignAs);
2507 fputc ('\n', msg->fp);
2510 /* (postponment) if the mail is to be signed or encrypted, save this info */
2511 if ((WithCrypto & APPLICATION_SMIME)
2512 && post && (hdr->security & APPLICATION_SMIME))
2514 fputs ("X-Mutt-SMIME: ", msg->fp);
2515 if (hdr->security & ENCRYPT) {
2516 fputc ('E', msg->fp);
2517 if (SmimeCryptAlg && *SmimeCryptAlg)
2518 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2520 if (hdr->security & SIGN) {
2521 fputc ('S', msg->fp);
2522 if (SmimeDefaultKey && *SmimeDefaultKey)
2523 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2525 fputc ('\n', msg->fp);
2529 /* (postponement) if the mail is to be sent through a mixmaster
2530 * chain, save that information
2533 if (post && hdr->chain && hdr->chain)
2537 fputs ("X-Mutt-Mix:", msg->fp);
2538 for (p = hdr->chain; p; p = p->next)
2539 fprintf (msg->fp, " %s", (char *) p->data);
2541 fputc ('\n', msg->fp);
2547 char sasha[LONG_STRING];
2550 mutt_write_mime_body (hdr->content, tempfp);
2552 /* make sure the last line ends with a newline. Emacs doesn't ensure
2553 * this will happen, and it can cause problems parsing the mailbox
2556 fseek (tempfp, -1, 2);
2557 if (fgetc (tempfp) != '\n')
2559 fseek (tempfp, 0, 2);
2560 fputc ('\n', tempfp);
2564 if (ferror (tempfp))
2566 dprint (1, (debugfile, "mutt_write_fcc(): %s: write failed.\n", tempfile));
2569 mx_commit_message (msg, &f); /* XXX - really? */
2570 mx_close_message (&msg);
2571 mx_close_mailbox (&f, NULL);
2575 /* count the number of lines */
2577 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2579 fprintf (msg->fp, "Content-Length: %ld\n", (long) ftell (tempfp));
2580 fprintf (msg->fp, "Lines: %d\n\n", lines);
2582 /* copy the body and clean up */
2584 r = mutt_copy_stream (tempfp, msg->fp);
2585 if (fclose (tempfp) != 0)
2587 /* if there was an error, leave the temp version */
2593 fputc ('\n', msg->fp); /* finish off the header */
2594 r = mutt_write_mime_body (hdr->content, msg->fp);
2597 if (mx_commit_message (msg, &f) != 0)
2599 mx_close_message (&msg);
2600 mx_close_mailbox (&f, NULL);
2603 set_noconv_flags (hdr->content, 0);