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.
17 #include "mutt_curses.h"
26 #include "mutt_crypt.h"
27 #include "mutt_idna.h"
42 #include <sys/utsname.h>
45 # include "mutt_libesmtp.h"
46 #endif /* USE_LIBESMTP */
52 #ifdef HAVE_SYSEXITS_H
54 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
58 /* If you are debugging this file, comment out the following line. */
67 extern char RFC822Specials[];
69 static struct sysexits {
75 0xff & EX_USAGE, "Bad usage."},
79 0xff & EX_DATAERR, "Data format error."},
83 0xff & EX_NOINPUT, "Cannot open input."},
87 0xff & EX_NOUSER, "User unknown."},
91 0xff & EX_NOHOST, "Host unknown."},
95 0xff & EX_UNAVAILABLE, "Service unavailable."},
99 0xff & EX_SOFTWARE, "Internal error."},
103 0xff & EX_OSERR, "Operating system error."},
107 0xff & EX_OSFILE, "System file missing."},
111 0xff & EX_CANTCREAT, "Can't create output."},
115 0xff & EX_IOERR, "I/O error."},
119 0xff & EX_TEMPFAIL, "Deferred."},
123 0xff & EX_PROTOCOL, "Remote protocol error."},
127 0xff & EX_NOPERM, "Insufficient permission."},
131 0xff & EX_NOPERM, "Local configuration error."},
134 S_ERR, "Exec error."}, {
140 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
142 const char MimeSpecials[] = "@.,;:<>[]\\\"()?/= \t";
144 char B64Chars[64] = {
145 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
146 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
147 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
148 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
152 static char MsgIdPfx = 'A';
154 static void transform_to_7bit (BODY * a, FILE * fpin);
156 static void encode_quoted (FGETCONV * fc, FILE * fout, int istext)
159 char line[77], savechar;
161 while ((c = fgetconv (fc)) != EOF) {
162 /* Wrap the line if needed. */
163 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
164 /* If the last character is "quoted", then be sure to move all three
165 * characters to the next line. Otherwise, just move the last
168 if (line[linelen - 3] == '=') {
169 line[linelen - 3] = 0;
174 line[1] = line[linelen - 2];
175 line[2] = line[linelen - 1];
179 savechar = line[linelen - 1];
180 line[linelen - 1] = '=';
189 /* Escape lines that begin with/only contain "the message separator". */
190 if (linelen == 4 && !mutt_strncmp ("From", line, 4)) {
191 strfcpy (line, "=46rom", sizeof (line));
194 else if (linelen == 4 && !mutt_strncmp ("from", line, 4)) {
195 strfcpy (line, "=66rom", sizeof (line));
198 else if (linelen == 1 && line[0] == '.') {
199 strfcpy (line, "=2E", sizeof (line));
204 if (c == '\n' && istext) {
205 /* Check to make sure there is no trailing space on this line. */
207 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
209 sprintf (line + linelen - 1, "=%2.2X",
210 (unsigned char) line[linelen - 1]);
214 int savechar = line[linelen - 1];
216 line[linelen - 1] = '=';
219 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
229 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
230 /* Check to make sure there is enough room for the quoted character.
231 * If not, wrap to the next line.
234 line[linelen++] = '=';
240 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
244 /* Don't worry about wrapping the line here. That will happen during
245 * the next iteration when I'll also know what the next character is.
251 /* Take care of anything left in the buffer */
253 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
254 /* take care of trailing whitespace */
256 sprintf (line + linelen - 1, "=%2.2X",
257 (unsigned char) line[linelen - 1]);
259 savechar = line[linelen - 1];
260 line[linelen - 1] = '=';
264 sprintf (line, "=%2.2X", (unsigned char) savechar);
273 static char b64_buffer[3];
274 static short b64_num;
275 static short b64_linelen;
277 static void b64_flush (FILE * fout)
284 if (b64_linelen >= 72) {
289 for (i = b64_num; i < 3; i++)
290 b64_buffer[i] = '\0';
292 fputc (B64Chars[(b64_buffer[0] >> 2) & 0x3f], fout);
295 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
300 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
304 fputc (B64Chars[b64_buffer[2] & 0x3f], fout);
309 while (b64_linelen % 4) {
318 static void b64_putc (char c, FILE * fout)
323 b64_buffer[b64_num++] = c;
327 static void encode_base64 (FGETCONV * fc, FILE * fout, int istext)
331 b64_num = b64_linelen = 0;
333 while ((ch = fgetconv (fc)) != EOF) {
334 if (istext && ch == '\n' && ch1 != '\r')
335 b64_putc ('\r', fout);
343 static void encode_8bit (FGETCONV * fc, FILE * fout, int istext)
347 while ((ch = fgetconv (fc)) != EOF)
352 int mutt_write_mime_header (BODY * a, FILE * f)
362 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
365 len = 25 + mutt_strlen (a->subtype); /* approximate len. of content-type */
367 for (p = a->parameter; p; p = p->next) {
376 tmp = safe_strdup (p->value);
377 encode = rfc2231_encode_string (&tmp);
378 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
380 /* Dirty hack to make messages readable by Outlook Express
381 * for the Mac: force quotes around the boundary parameter
382 * even when they aren't needed.
385 if (!ascii_strcasecmp (p->attribute, "boundary")
386 && !strcmp (buffer, tmp))
387 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
391 tmplen = mutt_strlen (buffer) + mutt_strlen (p->attribute) + 1;
393 if (len + tmplen + 2 > 76) {
402 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
410 fprintf (f, "Content-Description: %s\n", a->description);
412 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
415 if (!(fn = a->d_filename))
421 /* Strip off the leading path... */
422 if ((t = strrchr (fn, '/')))
428 tmp = safe_strdup (t);
429 encode = rfc2231_encode_string (&tmp);
430 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
432 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
438 if (a->encoding != ENC7BIT)
439 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
441 /* Do NOT add the terminator here!!! */
442 return (ferror (f) ? -1 : 0);
445 # define write_as_text_part(a) (mutt_is_text_part(a) \
446 || ((WithCrypto & APPLICATION_PGP)\
447 && mutt_is_application_pgp(a)))
449 int mutt_write_mime_body (BODY * a, FILE * f)
451 char *p, boundary[SHORT_STRING];
452 char send_charset[SHORT_STRING];
457 if (a->type == TYPEMULTIPART) {
458 /* First, find the boundary to use */
459 if (!(p = mutt_get_parameter ("boundary", a->parameter))) {
462 "mutt_write_mime_body(): no boundary parameter found!\n"));
463 mutt_error _("No boundary parameter found! [report this error]");
467 strfcpy (boundary, p, sizeof (boundary));
469 for (t = a->parts; t; t = t->next) {
470 fprintf (f, "\n--%s\n", boundary);
471 if (mutt_write_mime_header (t, f) == -1)
474 if (mutt_write_mime_body (t, f) == -1)
477 fprintf (f, "\n--%s--\n", boundary);
478 return (ferror (f) ? -1 : 0);
481 /* This is pretty gross, but it's the best solution for now... */
482 if ((WithCrypto & APPLICATION_PGP)
483 && a->type == TYPEAPPLICATION
484 && mutt_strcmp (a->subtype, "pgp-encrypted") == 0) {
485 fputs ("Version: 1\n", f);
489 if ((fpin = fopen (a->filename, "r")) == NULL) {
491 (debugfile, "write_mime_body: %s no longer exists!\n",
493 mutt_error (_("%s no longer exists!"), a->filename);
497 if (a->type == TYPETEXT && (!a->noconv))
498 fc = fgetconv_open (fpin, a->file_charset,
499 mutt_get_body_charset (send_charset,
500 sizeof (send_charset), a), 0);
502 fc = fgetconv_open (fpin, 0, 0, 0);
504 if (a->encoding == ENCQUOTEDPRINTABLE)
505 encode_quoted (fc, f, write_as_text_part (a));
506 else if (a->encoding == ENCBASE64)
507 encode_base64 (fc, f, write_as_text_part (a));
508 else if (a->type == TYPETEXT && (!a->noconv))
509 encode_8bit (fc, f, write_as_text_part (a));
511 mutt_copy_stream (fpin, f);
513 fgetconv_close (&fc);
516 return (ferror (f) ? -1 : 0);
519 #undef write_as_text_part
521 #define BOUNDARYLEN 16
522 void mutt_generate_boundary (PARAMETER ** parm)
524 char rs[BOUNDARYLEN + 1];
529 for (i = 0; i < BOUNDARYLEN; i++)
530 *p++ = B64Chars[LRAND () % sizeof (B64Chars)];
533 mutt_set_parameter ("boundary", rs, parm);
545 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
549 int whitespace = s->whitespace;
551 int linelen = s->linelen;
552 int was_cr = s->was_cr;
554 if (!d) { /* This signals EOF */
557 if (linelen > info->linemax)
558 info->linemax = linelen;
563 for (; dlen; d++, dlen--) {
576 if (linelen > info->linemax)
577 info->linemax = linelen;
592 if (linelen > info->linemax)
593 info->linemax = linelen;
598 else if (ch == '\r') {
606 else if (ch == '\t' || ch == '\f') {
610 else if (ch < 32 || ch == 127)
614 if ((ch == 'F') || (ch == 'f'))
624 if (linelen == 2 && ch != 'r')
626 else if (linelen == 3 && ch != 'o')
628 else if (linelen == 4) {
641 if (ch != ' ' && ch != '\t')
646 s->whitespace = whitespace;
648 s->linelen = linelen;
653 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
654 #define BUGGY_ICONV 1
657 * Find the best charset conversion of the file from fromcode into one
658 * of the tocodes. If successful, set *tocode and CONTENT *info and
659 * return the number of characters converted inexactly. If no
660 * conversion was possible, return -1.
662 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
663 * which would otherwise prevent us from knowing the number of inexact
664 * conversions. Where the candidate target charset is UTF-8 we avoid
665 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
666 * fails with some libraries.
668 * We assume that the output from iconv is never more than 4 times as
669 * long as the input for any pair of charsets we might be interested
672 static size_t convert_file_to (FILE * file, const char *fromcode,
673 int ncodes, const char **tocodes,
674 int *tocode, CONTENT * info)
678 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
679 ICONV_CONST char *ib, *ub;
681 size_t ibl, obl, ubl, ubl1, n, ret;
684 CONTENT_STATE *states;
687 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
688 if (cd1 == (iconv_t) (-1))
691 cd = safe_calloc (ncodes, sizeof (iconv_t));
692 score = safe_calloc (ncodes, sizeof (size_t));
693 states = safe_calloc (ncodes, sizeof (CONTENT_STATE));
694 infos = safe_calloc (ncodes, sizeof (CONTENT));
696 for (i = 0; i < ncodes; i++)
697 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
698 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
700 /* Special case for conversion to UTF-8 */
701 cd[i] = (iconv_t) (-1), score[i] = (size_t) (-1);
707 /* Try to fill input buffer */
708 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
711 /* Convert to UTF-8 */
713 ob = bufu, obl = sizeof (bufu);
714 n = iconv (cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
715 assert (n == (size_t) (-1) || !n || ICONV_NONTRANS);
716 if (n == (size_t) (-1) &&
717 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
718 assert (errno == EILSEQ ||
719 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
725 /* Convert from UTF-8 */
726 for (i = 0; i < ncodes; i++)
727 if (cd[i] != (iconv_t) (-1) && score[i] != (size_t) (-1)) {
728 ub = bufu, ubl = ubl1;
729 ob = bufo, obl = sizeof (bufo);
730 n = iconv (cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
731 if (n == (size_t) (-1)) {
732 assert (errno == E2BIG ||
733 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
734 score[i] = (size_t) (-1);
738 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
741 else if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1))
742 /* Special case for conversion to UTF-8 */
743 update_content_info (&infos[i], &states[i], bufu, ubl1);
746 /* Save unused input */
747 memmove (bufi, ib, ibl);
748 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
755 /* Find best score */
757 for (i = 0; i < ncodes; i++) {
758 if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1)) {
759 /* Special case for conversion to UTF-8 */
764 else if (cd[i] == (iconv_t) (-1) || score[i] == (size_t) (-1))
766 else if (ret == (size_t) (-1) || score[i] < ret) {
773 if (ret != (size_t) (-1)) {
774 memcpy (info, &infos[*tocode], sizeof (CONTENT));
775 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
779 for (i = 0; i < ncodes; i++)
780 if (cd[i] != (iconv_t) (-1))
792 #endif /* !HAVE_ICONV */
796 * Find the first of the fromcodes that gives a valid conversion and
797 * the best charset conversion of the file into one of the tocodes. If
798 * successful, set *fromcode and *tocode to dynamically allocated
799 * strings, set CONTENT *info, and return the number of characters
800 * converted inexactly. If no conversion was possible, return -1.
802 * Both fromcodes and tocodes may be colon-separated lists of charsets.
803 * However, if fromcode is zero then fromcodes is assumed to be the
804 * name of a single charset even if it contains a colon.
806 static size_t convert_file_from_to (FILE * file,
807 const char *fromcodes,
808 const char *tocodes, char **fromcode,
809 char **tocode, CONTENT * info)
817 /* Count the tocodes */
819 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
820 if ((c1 = strchr (c, ':')) == c)
826 tcode = safe_malloc (ncodes * sizeof (char *));
827 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
828 if ((c1 = strchr (c, ':')) == c)
830 tcode[i] = mutt_substrdup (c, c1);
835 /* Try each fromcode in turn */
836 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
837 if ((c1 = strchr (c, ':')) == c)
839 fcode = mutt_substrdup (c, c1);
841 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
843 if (ret != (size_t) (-1)) {
853 /* There is only one fromcode */
854 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
856 if (ret != (size_t) (-1)) {
863 for (i = 0; i < ncodes; i++)
872 * Analyze the contents of a file to determine which MIME encoding to use.
873 * Also set the body charset, sometimes, or not.
875 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
891 if (stat (fname, &sb) == -1) {
892 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
896 if (!S_ISREG (sb.st_mode)) {
897 mutt_error (_("%s isn't a regular file."), fname);
901 if ((fp = fopen (fname, "r")) == NULL) {
902 dprint (1, (debugfile, "mutt_get_content_info: %s: %s (errno %d).\n",
903 fname, strerror (errno), errno));
907 info = safe_calloc (1, sizeof (CONTENT));
908 memset (&state, 0, sizeof (state));
910 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
911 char *chs = mutt_get_parameter ("charset", b->parameter);
912 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
913 FileCharset : Charset) : Charset;
914 if (Charset && (chs || SendCharset) &&
915 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
916 &fromcode, &tocode, info) != (size_t) (-1)) {
918 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
919 mutt_set_parameter ("charset", chsbuf, &b->parameter);
921 b->file_charset = fromcode;
929 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
930 update_content_info (info, &state, buffer, r);
931 update_content_info (info, &state, 0, 0);
935 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
936 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
938 && !mutt_is_us_ascii (Charset) ? Charset :
939 "unknown-8bit"), &b->parameter);
944 /* Given a file with path ``s'', see if there is a registered MIME type.
945 * returns the major MIME type, and copies the subtype to ``d''. First look
946 * for ~/.mime.types, then look in a system mime.types if we can find one.
947 * The longest match is used so that we can match `ps.gz' when `gz' also
951 int mutt_lookup_mime_type (BODY * att, const char *path)
955 char buf[LONG_STRING];
956 char subtype[STRING], xtype[STRING];
958 int szf, sze, cur_sze;
966 szf = mutt_strlen (path);
968 for (count = 0; count < 3; count++) {
970 * can't use strtok() because we use it in an inner loop below, so use
971 * a switch statement here instead.
975 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
978 strfcpy (buf, SYSCONFDIR "/mime.types", sizeof (buf));
981 strfcpy (buf, PKGDATADIR "/mime.types", sizeof (buf));
986 "mutt_lookup_mime_type: Internal error, count = %d.\n",
988 goto bye; /* shouldn't happen */
991 if ((f = fopen (buf, "r")) != NULL) {
992 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
993 /* weed out any comments */
994 if ((p = strchr (buf, '#')))
997 /* remove any leading space. */
1001 /* position on the next field in this line */
1002 if ((p = strpbrk (ct, " \t")) == NULL)
1007 /* cycle through the file extensions */
1008 while ((p = strtok (p, " \t\n"))) {
1009 sze = mutt_strlen (p);
1010 if ((sze > cur_sze) && (szf >= sze) &&
1011 (mutt_strcasecmp (path + szf - sze, p) == 0
1012 || ascii_strcasecmp (path + szf - sze, p) == 0) && (szf == sze
1019 /* get the content-type */
1021 if ((p = strchr (ct, '/')) == NULL) {
1022 /* malformed line, just skip it. */
1027 for (q = p; *q && !ISSPACE (*q); q++);
1029 mutt_substrcpy (subtype, p, q, sizeof (subtype));
1031 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
1032 strfcpy (xtype, ct, sizeof (xtype));
1045 if (type != TYPEOTHER || *xtype != '\0') {
1047 mutt_str_replace (&att->subtype, subtype);
1048 mutt_str_replace (&att->xtype, xtype);
1054 void mutt_message_to_7bit (BODY * a, FILE * fp)
1056 char temp[_POSIX_PATH_MAX];
1062 if (!a->filename && fp)
1064 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
1065 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
1070 if (stat (a->filename, &sb) == -1) {
1071 mutt_perror ("stat");
1074 a->length = sb.st_size;
1078 if (!(fpout = safe_fopen (temp, "w+"))) {
1079 mutt_perror ("fopen");
1083 fseek (fpin, a->offset, 0);
1084 a->parts = mutt_parse_messageRFC822 (fpin, a);
1086 transform_to_7bit (a->parts, fpin);
1088 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1089 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1091 fputs ("Mime-Version: 1.0\n", fpout);
1092 mutt_write_mime_header (a->parts, fpout);
1093 fputc ('\n', fpout);
1094 mutt_write_mime_body (a->parts, fpout);
1106 a->encoding = ENC7BIT;
1107 a->d_filename = a->filename;
1108 if (a->filename && a->unlink)
1109 unlink (a->filename);
1110 a->filename = safe_strdup (temp);
1112 if (stat (a->filename, &sb) == -1) {
1113 mutt_perror ("stat");
1116 a->length = sb.st_size;
1117 mutt_free_body (&a->parts);
1118 a->hdr->content = NULL;
1121 static void transform_to_7bit (BODY * a, FILE * fpin)
1123 char buff[_POSIX_PATH_MAX];
1127 memset (&s, 0, sizeof (s));
1128 for (; a; a = a->next) {
1129 if (a->type == TYPEMULTIPART) {
1130 if (a->encoding != ENC7BIT)
1131 a->encoding = ENC7BIT;
1133 transform_to_7bit (a->parts, fpin);
1135 else if (mutt_is_message_type (a->type, a->subtype)) {
1136 mutt_message_to_7bit (a, fpin);
1140 a->force_charset = 1;
1143 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1144 mutt_perror ("fopen");
1148 mutt_decode_attachment (a, &s);
1150 a->d_filename = a->filename;
1151 a->filename = safe_strdup (buff);
1153 if (stat (a->filename, &sb) == -1) {
1154 mutt_perror ("stat");
1157 a->length = sb.st_size;
1159 mutt_update_encoding (a);
1160 if (a->encoding == ENC8BIT)
1161 a->encoding = ENCQUOTEDPRINTABLE;
1162 else if (a->encoding == ENCBINARY)
1163 a->encoding = ENCBASE64;
1168 /* determine which Content-Transfer-Encoding to use */
1169 static void mutt_set_encoding (BODY * b, CONTENT * info)
1171 char send_charset[SHORT_STRING];
1173 if (b->type == TYPETEXT) {
1175 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1176 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1177 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1178 b->encoding = ENCQUOTEDPRINTABLE;
1179 else if (info->hibin)
1180 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1182 b->encoding = ENC7BIT;
1184 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1185 if (info->lobin || info->hibin) {
1186 if (option (OPTALLOW8BIT) && !info->lobin)
1187 b->encoding = ENC8BIT;
1189 mutt_message_to_7bit (b, NULL);
1192 b->encoding = ENC7BIT;
1194 else if (b->type == TYPEAPPLICATION
1195 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1196 b->encoding = ENC7BIT;
1199 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1200 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1203 /* Determine which encoding is smaller */
1204 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1205 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1206 b->encoding = ENCBASE64;
1208 b->encoding = ENCQUOTEDPRINTABLE;
1212 b->encoding = ENC7BIT;
1216 void mutt_stamp_attachment (BODY * a)
1218 a->stamp = time (NULL);
1221 /* Get a body's character set */
1223 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1227 if (b && b->type != TYPETEXT)
1231 p = mutt_get_parameter ("charset", b->parameter);
1234 mutt_canonical_charset (d, dlen, NONULL (p));
1236 strfcpy (d, "us-ascii", dlen);
1242 /* Assumes called from send mode where BODY->filename points to actual file */
1243 void mutt_update_encoding (BODY * a)
1246 char chsbuff[STRING];
1248 /* override noconv when it's us-ascii */
1249 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1252 if (!a->force_charset && !a->noconv)
1253 mutt_delete_parameter ("charset", &a->parameter);
1255 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1258 mutt_set_encoding (a, info);
1259 mutt_stamp_attachment (a);
1266 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1268 char buffer[LONG_STRING];
1271 int cmflags, chflags;
1272 int pgp = WithCrypto ? hdr->security : 0;
1275 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1276 (hdr->security & ENCRYPT)) {
1277 if (!crypt_valid_passphrase (hdr->security))
1282 mutt_mktemp (buffer);
1283 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1286 body = mutt_new_body ();
1287 body->type = TYPEMESSAGE;
1288 body->subtype = safe_strdup ("rfc822");
1289 body->filename = safe_strdup (buffer);
1292 body->disposition = DISPINLINE;
1295 mutt_parse_mime_message (ctx, hdr);
1300 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1301 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1302 chflags |= CH_MIME | CH_TXTPLAIN;
1303 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1304 if ((WithCrypto & APPLICATION_PGP))
1306 if ((WithCrypto & APPLICATION_SMIME))
1307 pgp &= ~SMIMEENCRYPT;
1309 else if (WithCrypto && option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1310 if ((WithCrypto & APPLICATION_PGP)
1311 && mutt_is_multipart_encrypted (hdr->content)) {
1312 chflags |= CH_MIME | CH_NONEWLINE;
1313 cmflags = M_CM_DECODE_PGP;
1316 else if ((WithCrypto & APPLICATION_PGP)
1317 && (mutt_is_application_pgp (hdr->content) & PGPENCRYPT)) {
1318 chflags |= CH_MIME | CH_TXTPLAIN;
1319 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1322 else if ((WithCrypto & APPLICATION_SMIME)
1323 && mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1324 chflags |= CH_MIME | CH_TXTPLAIN;
1325 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1326 pgp &= ~SMIMEENCRYPT;
1330 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1335 body->hdr = mutt_new_header ();
1336 body->hdr->offset = 0;
1337 /* we don't need the user headers here */
1338 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1340 body->hdr->security = pgp;
1341 mutt_update_encoding (body);
1342 body->parts = body->hdr->content;
1349 BODY *mutt_make_file_attach (const char *path)
1354 att = mutt_new_body ();
1355 att->filename = safe_strdup (path);
1357 /* Attempt to determine the appropriate content-type based on the filename
1364 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1365 path)) != TYPEOTHER || *xbuf != '\0') {
1367 att->subtype = safe_strdup (buf);
1368 att->xtype = safe_strdup (xbuf);
1373 mutt_lookup_mime_type (att, path);
1377 if ((info = mutt_get_content_info (path, att)) == NULL) {
1378 mutt_free_body (&att);
1382 if (!att->subtype) {
1383 if (info->lobin == 0
1384 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1386 * Statistically speaking, there should be more than 10% "lobin"
1387 * chars if this is really a binary file...
1389 att->type = TYPETEXT;
1390 att->subtype = safe_strdup ("plain");
1393 att->type = TYPEAPPLICATION;
1394 att->subtype = safe_strdup ("octet-stream");
1398 mutt_update_encoding (att);
1402 static int get_toplevel_encoding (BODY * a)
1406 for (; a; a = a->next) {
1407 if (a->encoding == ENCBINARY)
1409 else if (a->encoding == ENC8BIT)
1416 BODY *mutt_make_multipart (BODY * b)
1420 new = mutt_new_body ();
1421 new->type = TYPEMULTIPART;
1422 new->subtype = safe_strdup ("mixed");
1423 new->encoding = get_toplevel_encoding (b);
1424 mutt_generate_boundary (&new->parameter);
1426 new->disposition = DISPINLINE;
1432 /* remove the multipart body if it exists */
1433 BODY *mutt_remove_multipart (BODY * b)
1441 mutt_free_body (&t);
1446 char *mutt_make_date (char *s, size_t len)
1448 time_t t = time (NULL);
1449 struct tm *l = localtime (&t);
1450 time_t tz = mutt_local_tz (t);
1454 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1455 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1456 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1457 (int) tz / 60, (int) abs (tz) % 60);
1461 /* wrapper around mutt_write_address() so we can handle very large
1462 recipient lists without needing a huge temporary buffer in memory */
1463 void mutt_write_address_list (ADDRESS * adr, FILE * fp, int linelen,
1467 char buf[LONG_STRING];
1475 rfc822_write_address (buf, sizeof (buf), adr, display);
1476 len = mutt_strlen (buf);
1477 if (count && linelen + len > 74) {
1479 linelen = len + 8; /* tab is usually about 8 spaces... */
1482 if (count && adr->mailbox) {
1490 if (!adr->group && adr->next && adr->next->mailbox) {
1500 /* arbitrary number of elements to grow the array by */
1505 /* need to write the list in reverse because they are stored in reverse order
1506 * when parsed to speed up threading
1508 void mutt_write_references (LIST * r, FILE * f)
1511 int refcnt = 0, refmax = 0;
1513 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1514 if (refcnt == refmax)
1515 safe_realloc (&ref, (refmax += REF_INC) * sizeof (LIST *));
1519 while (refcnt-- > 0) {
1521 fputs (ref[refcnt]->data, f);
1527 /* Note: all RFC2047 encoding should be done outside of this routine, except
1528 * for the "real name." This will allow this routine to be used more than
1529 * once, if necessary.
1531 * Likewise, all IDN processing should happen outside of this routine.
1533 * mode == 1 => "lite" mode (used for edit_hdrs)
1534 * mode == 0 => normal mode. write full header + MIME headers
1535 * mode == -1 => write just the envelope info (used for postponing messages)
1537 * privacy != 0 => will omit any headers which may identify the user.
1538 * Output generated is suitable for being sent through
1539 * anonymous remailer chains.
1543 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1544 int mode, int privacy)
1546 char buffer[LONG_STRING];
1548 LIST *tmp = env->userhdrs;
1549 int has_agent = 0; /* user defined user-agent header field exists */
1552 if (!option (OPTNEWSSEND))
1554 if (mode == 0 && !privacy)
1555 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1557 /* OPTUSEFROM is not consulted here so that we can still write a From:
1558 * field if the user sets it with the `my_hdr' command
1560 if (env->from && !privacy) {
1562 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1563 fprintf (fp, "From: %s\n", buffer);
1568 mutt_write_address_list (env->to, fp, 4, 0);
1572 if (!option (OPTNEWSSEND))
1574 fputs ("To: \n", fp);
1578 mutt_write_address_list (env->cc, fp, 4, 0);
1582 if (!option (OPTNEWSSEND))
1584 fputs ("Cc: \n", fp);
1587 if (mode != 0 || option (OPTWRITEBCC)) {
1588 fputs ("Bcc: ", fp);
1589 mutt_write_address_list (env->bcc, fp, 5, 0);
1594 if (!option (OPTNEWSSEND))
1596 fputs ("Bcc: \n", fp);
1599 if (env->newsgroups)
1600 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1601 else if (mode == 1 && option (OPTNEWSSEND))
1602 fputs ("Newsgroups: \n", fp);
1604 if (env->followup_to)
1605 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1606 else if (mode == 1 && option (OPTNEWSSEND))
1607 fputs ("Followup-To: \n", fp);
1609 if (env->x_comment_to)
1610 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1611 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO))
1612 fputs ("X-Comment-To: \n", fp);
1616 fprintf (fp, "Subject: %s\n", env->subject);
1618 fputs ("Subject: \n", fp);
1620 /* save message id if the user has set it */
1621 if (env->message_id && !privacy)
1622 fprintf (fp, "Message-ID: %s\n", env->message_id);
1624 if (env->reply_to) {
1625 fputs ("Reply-To: ", fp);
1626 mutt_write_address_list (env->reply_to, fp, 10, 0);
1629 fputs ("Reply-To: \n", fp);
1631 if (env->mail_followup_to)
1633 if (!option (OPTNEWSSEND))
1636 fputs ("Mail-Followup-To: ", fp);
1637 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1641 if (env->references) {
1642 fputs ("References:", fp);
1643 mutt_write_references (env->references, fp);
1647 /* Add the MIME headers */
1648 fputs ("Mime-Version: 1.0\n", fp);
1649 mutt_write_mime_header (attach, fp);
1652 if (env->in_reply_to) {
1653 fputs ("In-Reply-To:", fp);
1654 mutt_write_references (env->in_reply_to, fp);
1658 /* Add any user defined headers */
1659 for (; tmp; tmp = tmp->next) {
1660 if ((p = strchr (tmp->data, ':'))) {
1664 continue; /* don't emit empty fields. */
1666 /* check to see if the user has overridden the user-agent field */
1667 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1673 fputs (tmp->data, fp);
1678 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1682 if (OperatingSystem != NULL) {
1683 os = OperatingSystem;
1686 if (uname (&un) == -1) {
1693 /* Add a vanity header */
1694 fprintf (fp, "User-Agent: mutt-ng %s (%s)\n", MUTT_VERSION, os);
1697 return (ferror (fp) == 0 ? 0 : -1);
1700 static void encode_headers (LIST * h)
1706 for (; h; h = h->next) {
1707 if (!(p = strchr (h->data, ':')))
1713 tmp = safe_strdup (p);
1718 rfc2047_encode_string (&tmp);
1719 safe_realloc (&h->data,
1720 mutt_strlen (h->data) + 2 + mutt_strlen (tmp) + 1);
1722 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1728 const char *mutt_fqdn (short may_hide_host)
1732 if (Fqdn && Fqdn[0] != '@') {
1735 if (may_hide_host && option (OPTHIDDENHOST)) {
1736 if ((p = strchr (Fqdn, '.')))
1739 /* sanity check: don't hide the host if
1740 * the fqdn is something like detebe.org.
1743 if (!p || !(q = strchr (p, '.')))
1751 static char mutt_normalized_char (char c)
1755 if (strchr (".!#$%&'*+-/=?^_`{|}~", c))
1757 return '.'; /* normalized character (we're stricter than RFC2822, 3.6.4) */
1760 static void mutt_gen_localpart (char *buf, unsigned int len, char *fmt)
1764 char tmp[SHORT_STRING];
1771 for (; *fmt; ++fmt) {
1777 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mday);
1778 safe_strncat (buf, len, tmp, 2);
1781 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_hour);
1782 safe_strncat (buf, len, tmp, 2);
1785 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mon + 1);
1786 safe_strncat (buf, len, tmp, 2);
1789 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_min);
1790 safe_strncat (buf, len, tmp, 2);
1793 snprintf (tmp, sizeof (tmp), "%lo", (unsigned long) now);
1794 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1797 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) getpid ());
1798 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1801 snprintf (tmp, sizeof (tmp), "%c", MsgIdPfx);
1802 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1803 safe_strncat (buf, len, tmp, 1);
1806 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) rand ());
1807 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1810 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) rand ());
1811 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1814 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_sec);
1815 safe_strncat (buf, len, tmp, 2);
1818 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) now);
1819 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1822 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) now);
1823 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1826 snprintf (tmp, sizeof (tmp), "%04d", tm->tm_year + 1900); /* this will break in the year 10000 ;-) */
1827 safe_strncat (buf, len, tmp, 4);
1830 safe_strncat (buf, len, "%", 1);
1833 safe_strncat (buf, len, ".", 1); /* invalid formats are replaced by '.' */
1840 c = mutt_normalized_char (*fmt); /* @todo: filter out invalid characters */
1841 safe_strncat (buf, len, &c, 1);
1846 char *mutt_gen_msgid (void)
1848 char buf[SHORT_STRING];
1849 char localpart[SHORT_STRING];
1850 unsigned int localpart_length;
1857 if (!(fqdn = mutt_fqdn (0)))
1858 fqdn = NONULL (Hostname);
1860 localpart_length = sizeof (buf) - mutt_strlen (fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1862 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1864 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1865 return (safe_strdup (buf));
1868 static RETSIGTYPE alarm_handler (int sig)
1873 /* invoke sendmail in a subshell
1874 path (in) path to program to execute
1875 args (in) arguments to pass to program
1876 msg (in) temp file containing message to send
1877 tempfile (out) if sendmail is put in the background, this points
1878 to the temporary file containing the stdout of the
1881 send_msg (const char *path, char **args, const char *msg, char **tempfile)
1887 mutt_block_signals_system ();
1890 /* we also don't want to be stopped right now */
1891 sigaddset (&set, SIGTSTP);
1892 sigprocmask (SIG_BLOCK, &set, NULL);
1894 if (SendmailWait >= 0) {
1895 char tmp[_POSIX_PATH_MAX];
1898 *tempfile = safe_strdup (tmp);
1901 if ((pid = fork ()) == 0) {
1902 struct sigaction act, oldalrm;
1904 /* save parent's ID before setsid() */
1907 /* we want the delivery to continue even after the main process dies,
1908 * so we put ourselves into another session right away
1912 /* next we close all open files */
1913 #if defined(OPEN_MAX)
1914 for (fd = 0; fd < OPEN_MAX; fd++)
1916 #elif defined(_POSIX_OPEN_MAX)
1917 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1925 /* now the second fork() */
1926 if ((pid = fork ()) == 0) {
1927 /* "msg" will be opened as stdin */
1928 if (open (msg, O_RDONLY, 0) < 0) {
1934 if (SendmailWait >= 0) {
1935 /* *tempfile will be opened as stdout */
1936 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1939 /* redirect stderr to *tempfile too */
1944 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1946 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1953 else if (pid == -1) {
1959 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1960 * SendmailWait = 0: wait forever
1961 * SendmailWait < 0: don't wait
1963 if (SendmailWait > 0) {
1965 act.sa_handler = alarm_handler;
1967 /* need to make sure waitpid() is interrupted on SIGALRM */
1968 act.sa_flags = SA_INTERRUPT;
1972 sigemptyset (&act.sa_mask);
1973 sigaction (SIGALRM, &act, &oldalrm);
1974 alarm (SendmailWait);
1976 else if (SendmailWait < 0)
1977 _exit (0xff & EX_OK);
1979 if (waitpid (pid, &st, 0) > 0) {
1980 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1981 if (SendmailWait && st == (0xff & EX_OK)) {
1982 unlink (*tempfile); /* no longer needed */
1987 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1988 if (SendmailWait > 0) {
1994 /* reset alarm; not really needed, but... */
1996 sigaction (SIGALRM, &oldalrm, NULL);
1998 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1999 /* the parent is already dead */
2007 sigprocmask (SIG_UNBLOCK, &set, NULL);
2009 if (pid != -1 && waitpid (pid, &st, 0) > 0)
2010 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
2012 st = S_ERR; /* error */
2014 mutt_unblock_signals_system (1);
2019 static char **add_args (char **args, size_t * argslen, size_t * argsmax,
2022 for (; addr; addr = addr->next) {
2023 /* weed out group mailboxes, since those are for display only */
2024 if (addr->mailbox && !addr->group) {
2025 if (*argslen == *argsmax)
2026 safe_realloc (&args, (*argsmax += 5) * sizeof (char *));
2027 args[(*argslen)++] = addr->mailbox;
2033 static char **add_option (char **args, size_t * argslen, size_t * argsmax,
2036 if (*argslen == *argsmax)
2037 safe_realloc (&args, (*argsmax += 5) * sizeof (char *));
2038 args[(*argslen)++] = s;
2042 static const char *strsysexit (int e)
2046 for (i = 0; sysexits_h[i].str; i++) {
2047 if (e == sysexits_h[i].v)
2051 return sysexits_h[i].str;
2055 static int mutt_invoke_sendmail (ADDRESS * from, /* the sender */
2056 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
2057 const char *msg, /* file containing message */
2059 { /* message contains 8bit chars */
2060 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
2062 size_t argslen = 0, argsmax = 0;
2066 if (option (OPTNEWSSEND)) {
2067 char cmd[LONG_STRING];
2069 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
2072 i = nntp_post (msg);
2077 s = safe_strdup (cmd);
2081 s = safe_strdup (Sendmail);
2085 while ((ps = strtok (ps, " "))) {
2086 if (argslen == argsmax)
2087 safe_realloc (&args, sizeof (char *) * (argsmax += 5));
2090 args[argslen++] = ps;
2092 path = safe_strdup (ps);
2093 ps = strrchr (ps, '/');
2098 args[argslen++] = ps;
2105 if (!option (OPTNEWSSEND)) {
2107 if (eightbit && option (OPTUSE8BITMIME))
2108 args = add_option (args, &argslen, &argsmax, "-B8BITMIME");
2110 if (option (OPTENVFROM) && from && !from->next) {
2111 args = add_option (args, &argslen, &argsmax, "-f");
2112 args = add_args (args, &argslen, &argsmax, from);
2115 args = add_option (args, &argslen, &argsmax, "-N");
2116 args = add_option (args, &argslen, &argsmax, DsnNotify);
2119 args = add_option (args, &argslen, &argsmax, "-R");
2120 args = add_option (args, &argslen, &argsmax, DsnReturn);
2122 args = add_option (args, &argslen, &argsmax, "--");
2123 args = add_args (args, &argslen, &argsmax, to);
2124 args = add_args (args, &argslen, &argsmax, cc);
2125 args = add_args (args, &argslen, &argsmax, bcc);
2130 if (argslen == argsmax)
2131 safe_realloc (&args, sizeof (char *) * (++argsmax));
2133 args[argslen++] = NULL;
2135 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2137 const char *e = strsysexit (i);
2140 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2145 if (stat (childout, &st) == 0 && st.st_size > 0)
2146 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2159 if (i == (EX_OK & 0xff))
2161 else if (i == S_BKG)
2168 int mutt_invoke_mta (ADDRESS * from, /* the sender */
2169 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
2170 const char *msg, /* file containing message */
2172 { /* message contains 8bit chars */
2175 return mutt_invoke_libesmtp (from, to, cc, bcc, msg, eightbit);
2178 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2181 /* appends string 'b' to string 'a', and returns the pointer to the new
2183 char *mutt_append_string (char *a, const char *b)
2185 size_t la = mutt_strlen (a);
2187 safe_realloc (&a, la + mutt_strlen (b) + 1);
2188 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2192 /* returns 1 if char `c' needs to be quoted to protect from shell
2193 interpretation when executing commands in a subshell */
2194 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2196 /* returns 1 if string `s' contains characters which could cause problems
2197 when used on a command line to execute a command */
2198 int mutt_needs_quote (const char *s)
2201 if (INVALID_CHAR (*s))
2208 /* Quote a string to prevent shell escapes when this string is used on the
2209 command line to send mail. */
2210 char *mutt_quote_string (const char *s)
2215 rlen = mutt_strlen (s) + 3;
2216 pr = r = (char *) safe_malloc (rlen);
2219 if (INVALID_CHAR (*s)) {
2222 safe_realloc (&r, ++rlen);
2233 /* For postponing (!final) do the necessary encodings only */
2234 void mutt_prepare_envelope (ENVELOPE * env, int final)
2236 char buffer[LONG_STRING];
2239 if (env->bcc && !(env->to || env->cc)) {
2240 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2241 * recipients if there is no To: or Cc: field, so attempt to suppress
2242 * it by using an empty To: field.
2244 env->to = rfc822_new_address ();
2246 env->to->next = rfc822_new_address ();
2249 rfc822_cat (buffer, sizeof (buffer), "undisclosed-recipients",
2252 env->to->mailbox = safe_strdup (buffer);
2255 mutt_set_followup_to (env);
2257 if (!env->message_id)
2258 env->message_id = mutt_gen_msgid ();
2261 /* Take care of 8-bit => 7-bit conversion. */
2262 rfc2047_encode_adrlist (env->to, "To");
2263 rfc2047_encode_adrlist (env->cc, "Cc");
2264 rfc2047_encode_adrlist (env->bcc, "Bcc");
2265 rfc2047_encode_adrlist (env->from, "From");
2266 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2267 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2271 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2274 rfc2047_encode_string (&env->subject);
2276 encode_headers (env->userhdrs);
2279 void mutt_unprepare_envelope (ENVELOPE * env)
2283 for (item = env->userhdrs; item; item = item->next)
2284 rfc2047_decode (&item->data);
2286 rfc822_free_address (&env->mail_followup_to);
2288 /* back conversions */
2289 rfc2047_decode_adrlist (env->to);
2290 rfc2047_decode_adrlist (env->cc);
2291 rfc2047_decode_adrlist (env->bcc);
2292 rfc2047_decode_adrlist (env->from);
2293 rfc2047_decode_adrlist (env->reply_to);
2294 rfc2047_decode (&env->subject);
2297 static int _mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to,
2298 const char *resent_from, ADDRESS * env_from)
2302 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2303 MESSAGE *msg = NULL;
2306 /* Try to bounce each message out, aborting if we get any failures. */
2307 for (i = 0; i < Context->msgcount; i++)
2308 if (Context->hdrs[i]->tagged)
2310 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2315 /* If we failed to open a message, return with error */
2316 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2322 mutt_mktemp (tempfile);
2323 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2324 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2326 if (!option (OPTBOUNCEDELIVERED))
2327 ch_flags |= CH_WEED_DELIVERED;
2329 fseek (fp, h->offset, 0);
2330 fprintf (f, "Resent-From: %s", resent_from);
2331 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2332 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2333 fputs ("Resent-To: ", f);
2334 mutt_write_address_list (to, f, 11, 0);
2335 mutt_copy_header (fp, h, f, ch_flags, NULL);
2337 mutt_copy_bytes (fp, f, h->content->length);
2340 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2341 h->content->encoding == ENC8BIT);
2345 mx_close_message (&msg);
2350 int mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to)
2353 const char *fqdn = mutt_fqdn (1);
2354 char resent_from[STRING];
2358 resent_from[0] = '\0';
2359 from = mutt_default_from ();
2362 rfc822_qualify (from, fqdn);
2364 rfc2047_encode_adrlist (from, "Resent-From");
2365 if (mutt_addrlist_to_idna (from, &err)) {
2366 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2369 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2372 unset_option (OPTNEWSSEND);
2375 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2377 rfc822_free_address (&from);
2383 /* given a list of addresses, return a list of unique addresses */
2384 ADDRESS *mutt_remove_duplicates (ADDRESS * addr)
2386 ADDRESS *top = addr;
2387 ADDRESS **last = ⊤
2392 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2393 if (tmp->mailbox && addr->mailbox &&
2394 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2401 dprint (2, (debugfile, "mutt_remove_duplicates: Removing %s\n",
2407 rfc822_free_address (&addr);
2420 static void set_noconv_flags (BODY * b, short flag)
2422 for (; b; b = b->next) {
2423 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2424 set_noconv_flags (b->parts, flag);
2425 else if (b->type == TYPETEXT && b->noconv) {
2427 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2429 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2434 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2435 int post, char *fcc)
2439 char tempfile[_POSIX_PATH_MAX];
2440 FILE *tempfp = NULL;
2444 set_noconv_flags (hdr->content, 1);
2446 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2449 "mutt_write_fcc(): unable to open mailbox %s in append-mode, aborting.\n",
2454 /* We need to add a Content-Length field to avoid problems where a line in
2455 * the message body begins with "From "
2457 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2458 mutt_mktemp (tempfile);
2459 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2460 mutt_perror (tempfile);
2461 mx_close_mailbox (&f, NULL);
2466 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2467 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2468 mx_close_mailbox (&f, NULL);
2472 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2473 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2475 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2478 /* (postponment) if this was a reply of some sort, <msgid> contians the
2479 * Message-ID: of message replied to. Save it using a special X-Mutt-
2480 * header so it can be picked up if the message is recalled at a later
2481 * point in time. This will allow the message to be marked as replied if
2482 * the same mailbox is still open.
2485 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2487 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2488 * it can be picked up when the message is recalled
2491 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2492 fprintf (msg->fp, "Status: RO\n");
2496 /* (postponment) if the mail is to be signed or encrypted, save this info */
2497 if ((WithCrypto & APPLICATION_PGP)
2498 && post && (hdr->security & APPLICATION_PGP)) {
2499 fputs ("X-Mutt-PGP: ", msg->fp);
2500 if (hdr->security & ENCRYPT)
2501 fputc ('E', msg->fp);
2502 if (hdr->security & SIGN) {
2503 fputc ('S', msg->fp);
2504 if (PgpSignAs && *PgpSignAs)
2505 fprintf (msg->fp, "<%s>", PgpSignAs);
2507 if (hdr->security & INLINE)
2508 fputc ('I', msg->fp);
2509 fputc ('\n', msg->fp);
2512 /* (postponment) if the mail is to be signed or encrypted, save this info */
2513 if ((WithCrypto & APPLICATION_SMIME)
2514 && post && (hdr->security & APPLICATION_SMIME)) {
2515 fputs ("X-Mutt-SMIME: ", msg->fp);
2516 if (hdr->security & ENCRYPT) {
2517 fputc ('E', msg->fp);
2518 if (SmimeCryptAlg && *SmimeCryptAlg)
2519 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2521 if (hdr->security & SIGN) {
2522 fputc ('S', msg->fp);
2523 if (SmimeDefaultKey && *SmimeDefaultKey)
2524 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2526 if (hdr->security & INLINE)
2527 fputc ('I', msg->fp);
2528 fputc ('\n', msg->fp);
2532 /* (postponement) if the mail is to be sent through a mixmaster
2533 * chain, save that information
2536 if (post && hdr->chain && hdr->chain) {
2539 fputs ("X-Mutt-Mix:", msg->fp);
2540 for (p = hdr->chain; p; p = p->next)
2541 fprintf (msg->fp, " %s", (char *) p->data);
2543 fputc ('\n', msg->fp);
2548 char sasha[LONG_STRING];
2551 mutt_write_mime_body (hdr->content, tempfp);
2553 /* make sure the last line ends with a newline. Emacs doesn't ensure
2554 * this will happen, and it can cause problems parsing the mailbox
2557 fseek (tempfp, -1, 2);
2558 if (fgetc (tempfp) != '\n') {
2559 fseek (tempfp, 0, 2);
2560 fputc ('\n', tempfp);
2564 if (ferror (tempfp)) {
2566 (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 */
2592 fputc ('\n', msg->fp); /* finish off the header */
2593 r = mutt_write_mime_body (hdr->content, msg->fp);
2596 if (mx_commit_message (msg, &f) != 0)
2598 mx_close_message (&msg);
2599 mx_close_mailbox (&f, NULL);
2602 set_noconv_flags (hdr->content, 0);