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"
38 #include <sys/utsname.h>
41 # include "mutt_libesmtp.h"
42 #endif /* USE_LIBESMTP */
48 #ifdef HAVE_SYSEXITS_H
50 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
54 /* If you are debugging this file, comment out the following line. */
63 extern char RFC822Specials[];
65 static struct sysexits {
71 0xff & EX_USAGE, "Bad usage."},
75 0xff & EX_DATAERR, "Data format error."},
79 0xff & EX_NOINPUT, "Cannot open input."},
83 0xff & EX_NOUSER, "User unknown."},
87 0xff & EX_NOHOST, "Host unknown."},
91 0xff & EX_UNAVAILABLE, "Service unavailable."},
95 0xff & EX_SOFTWARE, "Internal error."},
99 0xff & EX_OSERR, "Operating system error."},
103 0xff & EX_OSFILE, "System file missing."},
107 0xff & EX_CANTCREAT, "Can't create output."},
111 0xff & EX_IOERR, "I/O error."},
115 0xff & EX_TEMPFAIL, "Deferred."},
119 0xff & EX_PROTOCOL, "Remote protocol error."},
123 0xff & EX_NOPERM, "Insufficient permission."},
127 0xff & EX_NOPERM, "Local configuration error."},
130 S_ERR, "Exec error."}, {
136 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
138 const char MimeSpecials[] = "@.,;:<>[]\\\"()?/= \t";
140 char B64Chars[64] = {
141 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
142 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
143 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
144 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
148 static char MsgIdPfx = 'A';
150 static void transform_to_7bit (BODY * a, FILE * fpin);
152 static void encode_quoted (FGETCONV * fc, FILE * fout, int istext)
155 char line[77], savechar;
157 while ((c = fgetconv (fc)) != EOF) {
158 /* Wrap the line if needed. */
159 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
160 /* If the last character is "quoted", then be sure to move all three
161 * characters to the next line. Otherwise, just move the last
164 if (line[linelen - 3] == '=') {
165 line[linelen - 3] = 0;
170 line[1] = line[linelen - 2];
171 line[2] = line[linelen - 1];
175 savechar = line[linelen - 1];
176 line[linelen - 1] = '=';
185 /* Escape lines that begin with/only contain "the message separator". */
186 if (linelen == 4 && !mutt_strncmp ("From", line, 4)) {
187 strfcpy (line, "=46rom", sizeof (line));
190 else if (linelen == 4 && !mutt_strncmp ("from", line, 4)) {
191 strfcpy (line, "=66rom", sizeof (line));
194 else if (linelen == 1 && line[0] == '.') {
195 strfcpy (line, "=2E", sizeof (line));
200 if (c == '\n' && istext) {
201 /* Check to make sure there is no trailing space on this line. */
203 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
205 sprintf (line + linelen - 1, "=%2.2X",
206 (unsigned char) line[linelen - 1]);
210 int savechar = line[linelen - 1];
212 line[linelen - 1] = '=';
215 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
225 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
226 /* Check to make sure there is enough room for the quoted character.
227 * If not, wrap to the next line.
230 line[linelen++] = '=';
236 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
240 /* Don't worry about wrapping the line here. That will happen during
241 * the next iteration when I'll also know what the next character is.
247 /* Take care of anything left in the buffer */
249 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
250 /* take care of trailing whitespace */
252 sprintf (line + linelen - 1, "=%2.2X",
253 (unsigned char) line[linelen - 1]);
255 savechar = line[linelen - 1];
256 line[linelen - 1] = '=';
260 sprintf (line, "=%2.2X", (unsigned char) savechar);
269 static char b64_buffer[3];
270 static short b64_num;
271 static short b64_linelen;
273 static void b64_flush (FILE * fout)
280 if (b64_linelen >= 72) {
285 for (i = b64_num; i < 3; i++)
286 b64_buffer[i] = '\0';
288 fputc (B64Chars[(b64_buffer[0] >> 2) & 0x3f], fout);
291 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
296 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
300 fputc (B64Chars[b64_buffer[2] & 0x3f], fout);
305 while (b64_linelen % 4) {
314 static void b64_putc (char c, FILE * fout)
319 b64_buffer[b64_num++] = c;
323 static void encode_base64 (FGETCONV * fc, FILE * fout, int istext)
327 b64_num = b64_linelen = 0;
329 while ((ch = fgetconv (fc)) != EOF) {
330 if (istext && ch == '\n' && ch1 != '\r')
331 b64_putc ('\r', fout);
339 static void encode_8bit (FGETCONV * fc, FILE * fout, int istext)
343 while ((ch = fgetconv (fc)) != EOF)
348 int mutt_write_mime_header (BODY * a, FILE * f)
358 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
361 len = 25 + mutt_strlen (a->subtype); /* approximate len. of content-type */
363 for (p = a->parameter; p; p = p->next) {
372 tmp = safe_strdup (p->value);
373 encode = rfc2231_encode_string (&tmp);
374 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
376 /* Dirty hack to make messages readable by Outlook Express
377 * for the Mac: force quotes around the boundary parameter
378 * even when they aren't needed.
381 if (!ascii_strcasecmp (p->attribute, "boundary")
382 && !strcmp (buffer, tmp))
383 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
387 tmplen = mutt_strlen (buffer) + mutt_strlen (p->attribute) + 1;
389 if (len + tmplen + 2 > 76) {
398 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
406 fprintf (f, "Content-Description: %s\n", a->description);
408 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
411 if (!(fn = a->d_filename))
417 /* Strip off the leading path... */
418 if ((t = strrchr (fn, '/')))
424 tmp = safe_strdup (t);
425 encode = rfc2231_encode_string (&tmp);
426 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
428 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
434 if (a->encoding != ENC7BIT)
435 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
437 /* Do NOT add the terminator here!!! */
438 return (ferror (f) ? -1 : 0);
441 # define write_as_text_part(a) (mutt_is_text_part(a) \
442 || ((WithCrypto & APPLICATION_PGP)\
443 && mutt_is_application_pgp(a)))
445 int mutt_write_mime_body (BODY * a, FILE * f)
447 char *p, boundary[SHORT_STRING];
448 char send_charset[SHORT_STRING];
453 if (a->type == TYPEMULTIPART) {
454 /* First, find the boundary to use */
455 if (!(p = mutt_get_parameter ("boundary", a->parameter))) {
458 "mutt_write_mime_body(): no boundary parameter found!\n"));
459 mutt_error _("No boundary parameter found! [report this error]");
463 strfcpy (boundary, p, sizeof (boundary));
465 for (t = a->parts; t; t = t->next) {
466 fprintf (f, "\n--%s\n", boundary);
467 if (mutt_write_mime_header (t, f) == -1)
470 if (mutt_write_mime_body (t, f) == -1)
473 fprintf (f, "\n--%s--\n", boundary);
474 return (ferror (f) ? -1 : 0);
477 /* This is pretty gross, but it's the best solution for now... */
478 if ((WithCrypto & APPLICATION_PGP)
479 && a->type == TYPEAPPLICATION
480 && mutt_strcmp (a->subtype, "pgp-encrypted") == 0) {
481 fputs ("Version: 1\n", f);
485 if ((fpin = fopen (a->filename, "r")) == NULL) {
487 (debugfile, "write_mime_body: %s no longer exists!\n",
489 mutt_error (_("%s no longer exists!"), a->filename);
493 if (a->type == TYPETEXT && (!a->noconv))
494 fc = fgetconv_open (fpin, a->file_charset,
495 mutt_get_body_charset (send_charset,
496 sizeof (send_charset), a), 0);
498 fc = fgetconv_open (fpin, 0, 0, 0);
500 if (a->encoding == ENCQUOTEDPRINTABLE)
501 encode_quoted (fc, f, write_as_text_part (a));
502 else if (a->encoding == ENCBASE64)
503 encode_base64 (fc, f, write_as_text_part (a));
504 else if (a->type == TYPETEXT && (!a->noconv))
505 encode_8bit (fc, f, write_as_text_part (a));
507 mutt_copy_stream (fpin, f);
509 fgetconv_close (&fc);
512 return (ferror (f) ? -1 : 0);
515 #undef write_as_text_part
517 #define BOUNDARYLEN 16
518 void mutt_generate_boundary (PARAMETER ** parm)
520 char rs[BOUNDARYLEN + 1];
525 for (i = 0; i < BOUNDARYLEN; i++)
526 *p++ = B64Chars[LRAND () % sizeof (B64Chars)];
529 mutt_set_parameter ("boundary", rs, parm);
541 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
545 int whitespace = s->whitespace;
547 int linelen = s->linelen;
548 int was_cr = s->was_cr;
550 if (!d) { /* This signals EOF */
553 if (linelen > info->linemax)
554 info->linemax = linelen;
559 for (; dlen; d++, dlen--) {
572 if (linelen > info->linemax)
573 info->linemax = linelen;
588 if (linelen > info->linemax)
589 info->linemax = linelen;
594 else if (ch == '\r') {
602 else if (ch == '\t' || ch == '\f') {
606 else if (ch < 32 || ch == 127)
610 if ((ch == 'F') || (ch == 'f'))
620 if (linelen == 2 && ch != 'r')
622 else if (linelen == 3 && ch != 'o')
624 else if (linelen == 4) {
637 if (ch != ' ' && ch != '\t')
642 s->whitespace = whitespace;
644 s->linelen = linelen;
649 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
650 #define BUGGY_ICONV 1
653 * Find the best charset conversion of the file from fromcode into one
654 * of the tocodes. If successful, set *tocode and CONTENT *info and
655 * return the number of characters converted inexactly. If no
656 * conversion was possible, return -1.
658 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
659 * which would otherwise prevent us from knowing the number of inexact
660 * conversions. Where the candidate target charset is UTF-8 we avoid
661 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
662 * fails with some libraries.
664 * We assume that the output from iconv is never more than 4 times as
665 * long as the input for any pair of charsets we might be interested
668 static size_t convert_file_to (FILE * file, const char *fromcode,
669 int ncodes, const char **tocodes,
670 int *tocode, CONTENT * info)
674 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
675 ICONV_CONST char *ib, *ub;
677 size_t ibl, obl, ubl, ubl1, n, ret;
680 CONTENT_STATE *states;
683 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
684 if (cd1 == (iconv_t) (-1))
687 cd = safe_calloc (ncodes, sizeof (iconv_t));
688 score = safe_calloc (ncodes, sizeof (size_t));
689 states = safe_calloc (ncodes, sizeof (CONTENT_STATE));
690 infos = safe_calloc (ncodes, sizeof (CONTENT));
692 for (i = 0; i < ncodes; i++)
693 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
694 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
696 /* Special case for conversion to UTF-8 */
697 cd[i] = (iconv_t) (-1), score[i] = (size_t) (-1);
703 /* Try to fill input buffer */
704 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
707 /* Convert to UTF-8 */
709 ob = bufu, obl = sizeof (bufu);
710 n = iconv (cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
711 assert (n == (size_t) (-1) || !n || ICONV_NONTRANS);
712 if (n == (size_t) (-1) &&
713 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
714 assert (errno == EILSEQ ||
715 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
721 /* Convert from UTF-8 */
722 for (i = 0; i < ncodes; i++)
723 if (cd[i] != (iconv_t) (-1) && score[i] != (size_t) (-1)) {
724 ub = bufu, ubl = ubl1;
725 ob = bufo, obl = sizeof (bufo);
726 n = iconv (cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
727 if (n == (size_t) (-1)) {
728 assert (errno == E2BIG ||
729 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
730 score[i] = (size_t) (-1);
734 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
737 else if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1))
738 /* Special case for conversion to UTF-8 */
739 update_content_info (&infos[i], &states[i], bufu, ubl1);
742 /* Save unused input */
743 memmove (bufi, ib, ibl);
744 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
751 /* Find best score */
753 for (i = 0; i < ncodes; i++) {
754 if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1)) {
755 /* Special case for conversion to UTF-8 */
760 else if (cd[i] == (iconv_t) (-1) || score[i] == (size_t) (-1))
762 else if (ret == (size_t) (-1) || score[i] < ret) {
769 if (ret != (size_t) (-1)) {
770 memcpy (info, &infos[*tocode], sizeof (CONTENT));
771 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
775 for (i = 0; i < ncodes; i++)
776 if (cd[i] != (iconv_t) (-1))
788 #endif /* !HAVE_ICONV */
792 * Find the first of the fromcodes that gives a valid conversion and
793 * the best charset conversion of the file into one of the tocodes. If
794 * successful, set *fromcode and *tocode to dynamically allocated
795 * strings, set CONTENT *info, and return the number of characters
796 * converted inexactly. If no conversion was possible, return -1.
798 * Both fromcodes and tocodes may be colon-separated lists of charsets.
799 * However, if fromcode is zero then fromcodes is assumed to be the
800 * name of a single charset even if it contains a colon.
802 static size_t convert_file_from_to (FILE * file,
803 const char *fromcodes,
804 const char *tocodes, char **fromcode,
805 char **tocode, CONTENT * info)
813 /* Count the tocodes */
815 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
816 if ((c1 = strchr (c, ':')) == c)
822 tcode = safe_malloc (ncodes * sizeof (char *));
823 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
824 if ((c1 = strchr (c, ':')) == c)
826 tcode[i] = mutt_substrdup (c, c1);
831 /* Try each fromcode in turn */
832 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
833 if ((c1 = strchr (c, ':')) == c)
835 fcode = mutt_substrdup (c, c1);
837 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
839 if (ret != (size_t) (-1)) {
849 /* There is only one fromcode */
850 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
852 if (ret != (size_t) (-1)) {
859 for (i = 0; i < ncodes; i++)
868 * Analyze the contents of a file to determine which MIME encoding to use.
869 * Also set the body charset, sometimes, or not.
871 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
887 if (stat (fname, &sb) == -1) {
888 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
892 if (!S_ISREG (sb.st_mode)) {
893 mutt_error (_("%s isn't a regular file."), fname);
897 if ((fp = fopen (fname, "r")) == NULL) {
898 dprint (1, (debugfile, "mutt_get_content_info: %s: %s (errno %d).\n",
899 fname, strerror (errno), errno));
903 info = safe_calloc (1, sizeof (CONTENT));
904 memset (&state, 0, sizeof (state));
906 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
907 char *chs = mutt_get_parameter ("charset", b->parameter);
908 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
909 FileCharset : Charset) : Charset;
910 if (Charset && (chs || SendCharset) &&
911 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
912 &fromcode, &tocode, info) != (size_t) (-1)) {
914 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
915 mutt_set_parameter ("charset", chsbuf, &b->parameter);
917 b->file_charset = fromcode;
925 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
926 update_content_info (info, &state, buffer, r);
927 update_content_info (info, &state, 0, 0);
931 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
932 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
934 && !mutt_is_us_ascii (Charset) ? Charset :
935 "unknown-8bit"), &b->parameter);
940 /* Given a file with path ``s'', see if there is a registered MIME type.
941 * returns the major MIME type, and copies the subtype to ``d''. First look
942 * for ~/.mime.types, then look in a system mime.types if we can find one.
943 * The longest match is used so that we can match `ps.gz' when `gz' also
947 int mutt_lookup_mime_type (BODY * att, const char *path)
951 char buf[LONG_STRING];
952 char subtype[STRING], xtype[STRING];
954 int szf, sze, cur_sze;
962 szf = mutt_strlen (path);
964 for (count = 0; count < 3; count++) {
966 * can't use strtok() because we use it in an inner loop below, so use
967 * a switch statement here instead.
971 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
974 strfcpy (buf, SYSCONFDIR "/mime.types", sizeof (buf));
977 strfcpy (buf, PKGDATADIR "/mime.types", sizeof (buf));
982 "mutt_lookup_mime_type: Internal error, count = %d.\n",
984 goto bye; /* shouldn't happen */
987 if ((f = fopen (buf, "r")) != NULL) {
988 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
989 /* weed out any comments */
990 if ((p = strchr (buf, '#')))
993 /* remove any leading space. */
997 /* position on the next field in this line */
998 if ((p = strpbrk (ct, " \t")) == NULL)
1003 /* cycle through the file extensions */
1004 while ((p = strtok (p, " \t\n"))) {
1005 sze = mutt_strlen (p);
1006 if ((sze > cur_sze) && (szf >= sze) &&
1007 (mutt_strcasecmp (path + szf - sze, p) == 0
1008 || ascii_strcasecmp (path + szf - sze, p) == 0) && (szf == sze
1015 /* get the content-type */
1017 if ((p = strchr (ct, '/')) == NULL) {
1018 /* malformed line, just skip it. */
1023 for (q = p; *q && !ISSPACE (*q); q++);
1025 mutt_substrcpy (subtype, p, q, sizeof (subtype));
1027 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
1028 strfcpy (xtype, ct, sizeof (xtype));
1041 if (type != TYPEOTHER || *xtype != '\0') {
1043 mutt_str_replace (&att->subtype, subtype);
1044 mutt_str_replace (&att->xtype, xtype);
1050 void mutt_message_to_7bit (BODY * a, FILE * fp)
1052 char temp[_POSIX_PATH_MAX];
1058 if (!a->filename && fp)
1060 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
1061 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
1066 if (stat (a->filename, &sb) == -1) {
1067 mutt_perror ("stat");
1070 a->length = sb.st_size;
1074 if (!(fpout = safe_fopen (temp, "w+"))) {
1075 mutt_perror ("fopen");
1079 fseek (fpin, a->offset, 0);
1080 a->parts = mutt_parse_messageRFC822 (fpin, a);
1082 transform_to_7bit (a->parts, fpin);
1084 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1085 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1087 fputs ("Mime-Version: 1.0\n", fpout);
1088 mutt_write_mime_header (a->parts, fpout);
1089 fputc ('\n', fpout);
1090 mutt_write_mime_body (a->parts, fpout);
1102 a->encoding = ENC7BIT;
1103 a->d_filename = a->filename;
1104 if (a->filename && a->unlink)
1105 unlink (a->filename);
1106 a->filename = safe_strdup (temp);
1108 if (stat (a->filename, &sb) == -1) {
1109 mutt_perror ("stat");
1112 a->length = sb.st_size;
1113 mutt_free_body (&a->parts);
1114 a->hdr->content = NULL;
1117 static void transform_to_7bit (BODY * a, FILE * fpin)
1119 char buff[_POSIX_PATH_MAX];
1123 memset (&s, 0, sizeof (s));
1124 for (; a; a = a->next) {
1125 if (a->type == TYPEMULTIPART) {
1126 if (a->encoding != ENC7BIT)
1127 a->encoding = ENC7BIT;
1129 transform_to_7bit (a->parts, fpin);
1131 else if (mutt_is_message_type (a->type, a->subtype)) {
1132 mutt_message_to_7bit (a, fpin);
1136 a->force_charset = 1;
1139 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1140 mutt_perror ("fopen");
1144 mutt_decode_attachment (a, &s);
1146 a->d_filename = a->filename;
1147 a->filename = safe_strdup (buff);
1149 if (stat (a->filename, &sb) == -1) {
1150 mutt_perror ("stat");
1153 a->length = sb.st_size;
1155 mutt_update_encoding (a);
1156 if (a->encoding == ENC8BIT)
1157 a->encoding = ENCQUOTEDPRINTABLE;
1158 else if (a->encoding == ENCBINARY)
1159 a->encoding = ENCBASE64;
1164 /* determine which Content-Transfer-Encoding to use */
1165 static void mutt_set_encoding (BODY * b, CONTENT * info)
1167 char send_charset[SHORT_STRING];
1169 if (b->type == TYPETEXT) {
1171 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1172 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1173 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1174 b->encoding = ENCQUOTEDPRINTABLE;
1175 else if (info->hibin)
1176 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1178 b->encoding = ENC7BIT;
1180 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1181 if (info->lobin || info->hibin) {
1182 if (option (OPTALLOW8BIT) && !info->lobin)
1183 b->encoding = ENC8BIT;
1185 mutt_message_to_7bit (b, NULL);
1188 b->encoding = ENC7BIT;
1190 else if (b->type == TYPEAPPLICATION
1191 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1192 b->encoding = ENC7BIT;
1195 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1196 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1199 /* Determine which encoding is smaller */
1200 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1201 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1202 b->encoding = ENCBASE64;
1204 b->encoding = ENCQUOTEDPRINTABLE;
1208 b->encoding = ENC7BIT;
1212 void mutt_stamp_attachment (BODY * a)
1214 a->stamp = time (NULL);
1217 /* Get a body's character set */
1219 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1223 if (b && b->type != TYPETEXT)
1227 p = mutt_get_parameter ("charset", b->parameter);
1230 mutt_canonical_charset (d, dlen, NONULL (p));
1232 strfcpy (d, "us-ascii", dlen);
1238 /* Assumes called from send mode where BODY->filename points to actual file */
1239 void mutt_update_encoding (BODY * a)
1242 char chsbuff[STRING];
1244 /* override noconv when it's us-ascii */
1245 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1248 if (!a->force_charset && !a->noconv)
1249 mutt_delete_parameter ("charset", &a->parameter);
1251 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1254 mutt_set_encoding (a, info);
1255 mutt_stamp_attachment (a);
1262 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1264 char buffer[LONG_STRING];
1267 int cmflags, chflags;
1268 int pgp = WithCrypto ? hdr->security : 0;
1271 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1272 (hdr->security & ENCRYPT)) {
1273 if (!crypt_valid_passphrase (hdr->security))
1278 mutt_mktemp (buffer);
1279 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1282 body = mutt_new_body ();
1283 body->type = TYPEMESSAGE;
1284 body->subtype = safe_strdup ("rfc822");
1285 body->filename = safe_strdup (buffer);
1288 body->disposition = DISPINLINE;
1291 mutt_parse_mime_message (ctx, hdr);
1296 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1297 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1298 chflags |= CH_MIME | CH_TXTPLAIN;
1299 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1300 if ((WithCrypto & APPLICATION_PGP))
1302 if ((WithCrypto & APPLICATION_SMIME))
1303 pgp &= ~SMIMEENCRYPT;
1305 else if (WithCrypto && option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1306 if ((WithCrypto & APPLICATION_PGP)
1307 && mutt_is_multipart_encrypted (hdr->content)) {
1308 chflags |= CH_MIME | CH_NONEWLINE;
1309 cmflags = M_CM_DECODE_PGP;
1312 else if ((WithCrypto & APPLICATION_PGP)
1313 && (mutt_is_application_pgp (hdr->content) & PGPENCRYPT)) {
1314 chflags |= CH_MIME | CH_TXTPLAIN;
1315 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1318 else if ((WithCrypto & APPLICATION_SMIME)
1319 && mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1320 chflags |= CH_MIME | CH_TXTPLAIN;
1321 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1322 pgp &= ~SMIMEENCRYPT;
1326 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1331 body->hdr = mutt_new_header ();
1332 body->hdr->offset = 0;
1333 /* we don't need the user headers here */
1334 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1336 body->hdr->security = pgp;
1337 mutt_update_encoding (body);
1338 body->parts = body->hdr->content;
1345 BODY *mutt_make_file_attach (const char *path)
1350 att = mutt_new_body ();
1351 att->filename = safe_strdup (path);
1353 /* Attempt to determine the appropriate content-type based on the filename
1360 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1361 path)) != TYPEOTHER || *xbuf != '\0') {
1363 att->subtype = safe_strdup (buf);
1364 att->xtype = safe_strdup (xbuf);
1369 mutt_lookup_mime_type (att, path);
1373 if ((info = mutt_get_content_info (path, att)) == NULL) {
1374 mutt_free_body (&att);
1378 if (!att->subtype) {
1379 if (info->lobin == 0
1380 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1382 * Statistically speaking, there should be more than 10% "lobin"
1383 * chars if this is really a binary file...
1385 att->type = TYPETEXT;
1386 att->subtype = safe_strdup ("plain");
1389 att->type = TYPEAPPLICATION;
1390 att->subtype = safe_strdup ("octet-stream");
1394 mutt_update_encoding (att);
1398 static int get_toplevel_encoding (BODY * a)
1402 for (; a; a = a->next) {
1403 if (a->encoding == ENCBINARY)
1405 else if (a->encoding == ENC8BIT)
1412 BODY *mutt_make_multipart (BODY * b)
1416 new = mutt_new_body ();
1417 new->type = TYPEMULTIPART;
1418 new->subtype = safe_strdup ("mixed");
1419 new->encoding = get_toplevel_encoding (b);
1420 mutt_generate_boundary (&new->parameter);
1422 new->disposition = DISPINLINE;
1428 /* remove the multipart body if it exists */
1429 BODY *mutt_remove_multipart (BODY * b)
1437 mutt_free_body (&t);
1442 char *mutt_make_date (char *s, size_t len)
1444 time_t t = time (NULL);
1445 struct tm *l = localtime (&t);
1446 time_t tz = mutt_local_tz (t);
1450 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1451 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1452 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1453 (int) tz / 60, (int) abs (tz) % 60);
1457 /* wrapper around mutt_write_address() so we can handle very large
1458 recipient lists without needing a huge temporary buffer in memory */
1459 void mutt_write_address_list (ADDRESS * adr, FILE * fp, int linelen,
1463 char buf[LONG_STRING];
1471 rfc822_write_address (buf, sizeof (buf), adr, display);
1472 len = mutt_strlen (buf);
1473 if (count && linelen + len > 74) {
1475 linelen = len + 8; /* tab is usually about 8 spaces... */
1478 if (count && adr->mailbox) {
1486 if (!adr->group && adr->next && adr->next->mailbox) {
1496 /* arbitrary number of elements to grow the array by */
1501 /* need to write the list in reverse because they are stored in reverse order
1502 * when parsed to speed up threading
1504 void mutt_write_references (LIST * r, FILE * f)
1507 int refcnt = 0, refmax = 0;
1509 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1510 if (refcnt == refmax)
1511 safe_realloc (&ref, (refmax += REF_INC) * sizeof (LIST *));
1515 while (refcnt-- > 0) {
1517 fputs (ref[refcnt]->data, f);
1523 /* Note: all RFC2047 encoding should be done outside of this routine, except
1524 * for the "real name." This will allow this routine to be used more than
1525 * once, if necessary.
1527 * Likewise, all IDN processing should happen outside of this routine.
1529 * mode == 1 => "lite" mode (used for edit_hdrs)
1530 * mode == 0 => normal mode. write full header + MIME headers
1531 * mode == -1 => write just the envelope info (used for postponing messages)
1533 * privacy != 0 => will omit any headers which may identify the user.
1534 * Output generated is suitable for being sent through
1535 * anonymous remailer chains.
1539 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1540 int mode, int privacy)
1542 char buffer[LONG_STRING];
1544 LIST *tmp = env->userhdrs;
1545 int has_agent = 0; /* user defined user-agent header field exists */
1548 if (!option (OPTNEWSSEND))
1550 if (mode == 0 && !privacy)
1551 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1553 /* OPTUSEFROM is not consulted here so that we can still write a From:
1554 * field if the user sets it with the `my_hdr' command
1556 if (env->from && !privacy) {
1558 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1559 fprintf (fp, "From: %s\n", buffer);
1564 mutt_write_address_list (env->to, fp, 4, 0);
1568 if (!option (OPTNEWSSEND))
1570 fputs ("To: \n", fp);
1574 mutt_write_address_list (env->cc, fp, 4, 0);
1578 if (!option (OPTNEWSSEND))
1580 fputs ("Cc: \n", fp);
1583 if (mode != 0 || option (OPTWRITEBCC)) {
1584 fputs ("Bcc: ", fp);
1585 mutt_write_address_list (env->bcc, fp, 5, 0);
1590 if (!option (OPTNEWSSEND))
1592 fputs ("Bcc: \n", fp);
1595 if (env->newsgroups)
1596 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1597 else if (mode == 1 && option (OPTNEWSSEND))
1598 fputs ("Newsgroups: \n", fp);
1600 if (env->followup_to)
1601 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1602 else if (mode == 1 && option (OPTNEWSSEND))
1603 fputs ("Followup-To: \n", fp);
1605 if (env->x_comment_to)
1606 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1607 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO))
1608 fputs ("X-Comment-To: \n", fp);
1612 fprintf (fp, "Subject: %s\n", env->subject);
1614 fputs ("Subject: \n", fp);
1616 /* save message id if the user has set it */
1617 if (env->message_id && !privacy)
1618 fprintf (fp, "Message-ID: %s\n", env->message_id);
1620 if (env->reply_to) {
1621 fputs ("Reply-To: ", fp);
1622 mutt_write_address_list (env->reply_to, fp, 10, 0);
1625 fputs ("Reply-To: \n", fp);
1627 if (env->mail_followup_to)
1629 if (!option (OPTNEWSSEND))
1632 fputs ("Mail-Followup-To: ", fp);
1633 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1637 if (env->references) {
1638 fputs ("References:", fp);
1639 mutt_write_references (env->references, fp);
1643 /* Add the MIME headers */
1644 fputs ("Mime-Version: 1.0\n", fp);
1645 mutt_write_mime_header (attach, fp);
1648 if (env->in_reply_to) {
1649 fputs ("In-Reply-To:", fp);
1650 mutt_write_references (env->in_reply_to, fp);
1654 /* Add any user defined headers */
1655 for (; tmp; tmp = tmp->next) {
1656 if ((p = strchr (tmp->data, ':'))) {
1660 continue; /* don't emit empty fields. */
1662 /* check to see if the user has overridden the user-agent field */
1663 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1669 fputs (tmp->data, fp);
1674 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1678 if (OperatingSystem != NULL) {
1679 os = OperatingSystem;
1682 if (uname (&un) == -1) {
1689 /* Add a vanity header */
1690 fprintf (fp, "User-Agent: mutt-ng %s (%s)\n", MUTT_VERSION, os);
1693 return (ferror (fp) == 0 ? 0 : -1);
1696 static void encode_headers (LIST * h)
1702 for (; h; h = h->next) {
1703 if (!(p = strchr (h->data, ':')))
1709 tmp = safe_strdup (p);
1714 rfc2047_encode_string (&tmp);
1715 safe_realloc (&h->data,
1716 mutt_strlen (h->data) + 2 + mutt_strlen (tmp) + 1);
1718 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1724 const char *mutt_fqdn (short may_hide_host)
1728 if (Fqdn && Fqdn[0] != '@') {
1731 if (may_hide_host && option (OPTHIDDENHOST)) {
1732 if ((p = strchr (Fqdn, '.')))
1735 /* sanity check: don't hide the host if
1736 * the fqdn is something like detebe.org.
1739 if (!p || !(q = strchr (p, '.')))
1747 static char mutt_normalized_char (char c)
1751 if (strchr (".!#$%&'*+-/=?^_`{|}~", c))
1753 return '.'; /* normalized character (we're stricter than RFC2822, 3.6.4) */
1756 static void mutt_gen_localpart (char *buf, unsigned int len, char *fmt)
1760 char tmp[SHORT_STRING];
1767 for (; *fmt; ++fmt) {
1773 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mday);
1774 safe_strncat (buf, len, tmp, 2);
1777 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_hour);
1778 safe_strncat (buf, len, tmp, 2);
1781 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mon + 1);
1782 safe_strncat (buf, len, tmp, 2);
1785 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_min);
1786 safe_strncat (buf, len, tmp, 2);
1789 snprintf (tmp, sizeof (tmp), "%lo", (unsigned long) now);
1790 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1793 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) getpid ());
1794 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1797 snprintf (tmp, sizeof (tmp), "%c", MsgIdPfx);
1798 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1799 safe_strncat (buf, len, tmp, 1);
1802 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) rand ());
1803 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1806 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) rand ());
1807 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1810 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_sec);
1811 safe_strncat (buf, len, tmp, 2);
1814 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) now);
1815 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1818 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) now);
1819 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1822 snprintf (tmp, sizeof (tmp), "%04d", tm->tm_year + 1900); /* this will break in the year 10000 ;-) */
1823 safe_strncat (buf, len, tmp, 4);
1826 safe_strncat (buf, len, "%", 1);
1829 safe_strncat (buf, len, ".", 1); /* invalid formats are replaced by '.' */
1836 c = mutt_normalized_char (*fmt); /* @todo: filter out invalid characters */
1837 safe_strncat (buf, len, &c, 1);
1842 char *mutt_gen_msgid (void)
1844 char buf[SHORT_STRING];
1845 char localpart[SHORT_STRING];
1846 unsigned int localpart_length;
1853 if (!(fqdn = mutt_fqdn (0)))
1854 fqdn = NONULL (Hostname);
1856 localpart_length = sizeof (buf) - mutt_strlen (fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1858 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1860 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1861 return (safe_strdup (buf));
1864 static RETSIGTYPE alarm_handler (int sig)
1869 /* invoke sendmail in a subshell
1870 path (in) path to program to execute
1871 args (in) arguments to pass to program
1872 msg (in) temp file containing message to send
1873 tempfile (out) if sendmail is put in the background, this points
1874 to the temporary file containing the stdout of the
1877 send_msg (const char *path, char **args, const char *msg, char **tempfile)
1883 mutt_block_signals_system ();
1886 /* we also don't want to be stopped right now */
1887 sigaddset (&set, SIGTSTP);
1888 sigprocmask (SIG_BLOCK, &set, NULL);
1890 if (SendmailWait >= 0) {
1891 char tmp[_POSIX_PATH_MAX];
1894 *tempfile = safe_strdup (tmp);
1897 if ((pid = fork ()) == 0) {
1898 struct sigaction act, oldalrm;
1900 /* save parent's ID before setsid() */
1903 /* we want the delivery to continue even after the main process dies,
1904 * so we put ourselves into another session right away
1908 /* next we close all open files */
1909 #if defined(OPEN_MAX)
1910 for (fd = 0; fd < OPEN_MAX; fd++)
1912 #elif defined(_POSIX_OPEN_MAX)
1913 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1921 /* now the second fork() */
1922 if ((pid = fork ()) == 0) {
1923 /* "msg" will be opened as stdin */
1924 if (open (msg, O_RDONLY, 0) < 0) {
1930 if (SendmailWait >= 0) {
1931 /* *tempfile will be opened as stdout */
1932 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1935 /* redirect stderr to *tempfile too */
1940 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1942 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1949 else if (pid == -1) {
1955 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1956 * SendmailWait = 0: wait forever
1957 * SendmailWait < 0: don't wait
1959 if (SendmailWait > 0) {
1961 act.sa_handler = alarm_handler;
1963 /* need to make sure waitpid() is interrupted on SIGALRM */
1964 act.sa_flags = SA_INTERRUPT;
1968 sigemptyset (&act.sa_mask);
1969 sigaction (SIGALRM, &act, &oldalrm);
1970 alarm (SendmailWait);
1972 else if (SendmailWait < 0)
1973 _exit (0xff & EX_OK);
1975 if (waitpid (pid, &st, 0) > 0) {
1976 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1977 if (SendmailWait && st == (0xff & EX_OK)) {
1978 unlink (*tempfile); /* no longer needed */
1983 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1984 if (SendmailWait > 0) {
1990 /* reset alarm; not really needed, but... */
1992 sigaction (SIGALRM, &oldalrm, NULL);
1994 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1995 /* the parent is already dead */
2003 sigprocmask (SIG_UNBLOCK, &set, NULL);
2005 if (pid != -1 && waitpid (pid, &st, 0) > 0)
2006 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
2008 st = S_ERR; /* error */
2010 mutt_unblock_signals_system (1);
2015 static char **add_args (char **args, size_t * argslen, size_t * argsmax,
2018 for (; addr; addr = addr->next) {
2019 /* weed out group mailboxes, since those are for display only */
2020 if (addr->mailbox && !addr->group) {
2021 if (*argslen == *argsmax)
2022 safe_realloc (&args, (*argsmax += 5) * sizeof (char *));
2023 args[(*argslen)++] = addr->mailbox;
2029 static char **add_option (char **args, size_t * argslen, size_t * argsmax,
2032 if (*argslen == *argsmax)
2033 safe_realloc (&args, (*argsmax += 5) * sizeof (char *));
2034 args[(*argslen)++] = s;
2038 static const char *strsysexit (int e)
2042 for (i = 0; sysexits_h[i].str; i++) {
2043 if (e == sysexits_h[i].v)
2047 return sysexits_h[i].str;
2051 static int mutt_invoke_sendmail (ADDRESS * from, /* the sender */
2052 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
2053 const char *msg, /* file containing message */
2055 { /* message contains 8bit chars */
2056 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
2058 size_t argslen = 0, argsmax = 0;
2062 if (option (OPTNEWSSEND)) {
2063 char cmd[LONG_STRING];
2065 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
2068 i = nntp_post (msg);
2073 s = safe_strdup (cmd);
2077 s = safe_strdup (Sendmail);
2081 while ((ps = strtok (ps, " "))) {
2082 if (argslen == argsmax)
2083 safe_realloc (&args, sizeof (char *) * (argsmax += 5));
2086 args[argslen++] = ps;
2088 path = safe_strdup (ps);
2089 ps = strrchr (ps, '/');
2094 args[argslen++] = ps;
2101 if (!option (OPTNEWSSEND)) {
2103 if (eightbit && option (OPTUSE8BITMIME))
2104 args = add_option (args, &argslen, &argsmax, "-B8BITMIME");
2106 if (option (OPTENVFROM) && from && !from->next) {
2107 args = add_option (args, &argslen, &argsmax, "-f");
2108 args = add_args (args, &argslen, &argsmax, from);
2111 args = add_option (args, &argslen, &argsmax, "-N");
2112 args = add_option (args, &argslen, &argsmax, DsnNotify);
2115 args = add_option (args, &argslen, &argsmax, "-R");
2116 args = add_option (args, &argslen, &argsmax, DsnReturn);
2118 args = add_option (args, &argslen, &argsmax, "--");
2119 args = add_args (args, &argslen, &argsmax, to);
2120 args = add_args (args, &argslen, &argsmax, cc);
2121 args = add_args (args, &argslen, &argsmax, bcc);
2126 if (argslen == argsmax)
2127 safe_realloc (&args, sizeof (char *) * (++argsmax));
2129 args[argslen++] = NULL;
2131 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2133 const char *e = strsysexit (i);
2136 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2141 if (stat (childout, &st) == 0 && st.st_size > 0)
2142 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2155 if (i == (EX_OK & 0xff))
2157 else if (i == S_BKG)
2164 int mutt_invoke_mta (ADDRESS * from, /* the sender */
2165 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
2166 const char *msg, /* file containing message */
2168 { /* message contains 8bit chars */
2171 return mutt_invoke_libesmtp (from, to, cc, bcc, msg, eightbit);
2174 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2177 /* appends string 'b' to string 'a', and returns the pointer to the new
2179 char *mutt_append_string (char *a, const char *b)
2181 size_t la = mutt_strlen (a);
2183 safe_realloc (&a, la + mutt_strlen (b) + 1);
2184 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2188 /* returns 1 if char `c' needs to be quoted to protect from shell
2189 interpretation when executing commands in a subshell */
2190 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2192 /* returns 1 if string `s' contains characters which could cause problems
2193 when used on a command line to execute a command */
2194 int mutt_needs_quote (const char *s)
2197 if (INVALID_CHAR (*s))
2204 /* Quote a string to prevent shell escapes when this string is used on the
2205 command line to send mail. */
2206 char *mutt_quote_string (const char *s)
2211 rlen = mutt_strlen (s) + 3;
2212 pr = r = (char *) safe_malloc (rlen);
2215 if (INVALID_CHAR (*s)) {
2218 safe_realloc (&r, ++rlen);
2229 /* For postponing (!final) do the necessary encodings only */
2230 void mutt_prepare_envelope (ENVELOPE * env, int final)
2232 char buffer[LONG_STRING];
2235 if (env->bcc && !(env->to || env->cc)) {
2236 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2237 * recipients if there is no To: or Cc: field, so attempt to suppress
2238 * it by using an empty To: field.
2240 env->to = rfc822_new_address ();
2242 env->to->next = rfc822_new_address ();
2245 rfc822_cat (buffer, sizeof (buffer), "undisclosed-recipients",
2248 env->to->mailbox = safe_strdup (buffer);
2251 mutt_set_followup_to (env);
2253 if (!env->message_id)
2254 env->message_id = mutt_gen_msgid ();
2257 /* Take care of 8-bit => 7-bit conversion. */
2258 rfc2047_encode_adrlist (env->to, "To");
2259 rfc2047_encode_adrlist (env->cc, "Cc");
2260 rfc2047_encode_adrlist (env->bcc, "Bcc");
2261 rfc2047_encode_adrlist (env->from, "From");
2262 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2263 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2267 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2270 rfc2047_encode_string (&env->subject);
2272 encode_headers (env->userhdrs);
2275 void mutt_unprepare_envelope (ENVELOPE * env)
2279 for (item = env->userhdrs; item; item = item->next)
2280 rfc2047_decode (&item->data);
2282 rfc822_free_address (&env->mail_followup_to);
2284 /* back conversions */
2285 rfc2047_decode_adrlist (env->to);
2286 rfc2047_decode_adrlist (env->cc);
2287 rfc2047_decode_adrlist (env->bcc);
2288 rfc2047_decode_adrlist (env->from);
2289 rfc2047_decode_adrlist (env->reply_to);
2290 rfc2047_decode (&env->subject);
2293 static int _mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to,
2294 const char *resent_from, ADDRESS * env_from)
2298 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2299 MESSAGE *msg = NULL;
2302 /* Try to bounce each message out, aborting if we get any failures. */
2303 for (i = 0; i < Context->msgcount; i++)
2304 if (Context->hdrs[i]->tagged)
2306 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2311 /* If we failed to open a message, return with error */
2312 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2318 mutt_mktemp (tempfile);
2319 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2320 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2322 if (!option (OPTBOUNCEDELIVERED))
2323 ch_flags |= CH_WEED_DELIVERED;
2325 fseek (fp, h->offset, 0);
2326 fprintf (f, "Resent-From: %s", resent_from);
2327 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2328 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2329 fputs ("Resent-To: ", f);
2330 mutt_write_address_list (to, f, 11, 0);
2331 mutt_copy_header (fp, h, f, ch_flags, NULL);
2333 mutt_copy_bytes (fp, f, h->content->length);
2336 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2337 h->content->encoding == ENC8BIT);
2341 mx_close_message (&msg);
2346 int mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to)
2349 const char *fqdn = mutt_fqdn (1);
2350 char resent_from[STRING];
2354 resent_from[0] = '\0';
2355 from = mutt_default_from ();
2358 rfc822_qualify (from, fqdn);
2360 rfc2047_encode_adrlist (from, "Resent-From");
2361 if (mutt_addrlist_to_idna (from, &err)) {
2362 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2365 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2368 unset_option (OPTNEWSSEND);
2371 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2373 rfc822_free_address (&from);
2379 /* given a list of addresses, return a list of unique addresses */
2380 ADDRESS *mutt_remove_duplicates (ADDRESS * addr)
2382 ADDRESS *top = addr;
2383 ADDRESS **last = ⊤
2388 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2389 if (tmp->mailbox && addr->mailbox &&
2390 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2397 dprint (2, (debugfile, "mutt_remove_duplicates: Removing %s\n",
2403 rfc822_free_address (&addr);
2416 static void set_noconv_flags (BODY * b, short flag)
2418 for (; b; b = b->next) {
2419 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2420 set_noconv_flags (b->parts, flag);
2421 else if (b->type == TYPETEXT && b->noconv) {
2423 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2425 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2430 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2431 int post, char *fcc)
2435 char tempfile[_POSIX_PATH_MAX];
2436 FILE *tempfp = NULL;
2440 set_noconv_flags (hdr->content, 1);
2442 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2445 "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) {
2454 mutt_mktemp (tempfile);
2455 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2456 mutt_perror (tempfile);
2457 mx_close_mailbox (&f, NULL);
2462 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2463 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2464 mx_close_mailbox (&f, NULL);
2468 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2469 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2471 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2474 /* (postponment) if this was a reply of some sort, <msgid> contians the
2475 * Message-ID: of message replied to. Save it using a special X-Mutt-
2476 * header so it can be picked up if the message is recalled at a later
2477 * point in time. This will allow the message to be marked as replied if
2478 * the same mailbox is still open.
2481 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2483 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2484 * it can be picked up when the message is recalled
2487 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2488 fprintf (msg->fp, "Status: RO\n");
2492 /* (postponment) if the mail is to be signed or encrypted, save this info */
2493 if ((WithCrypto & APPLICATION_PGP)
2494 && post && (hdr->security & APPLICATION_PGP)) {
2495 fputs ("X-Mutt-PGP: ", msg->fp);
2496 if (hdr->security & ENCRYPT)
2497 fputc ('E', msg->fp);
2498 if (hdr->security & SIGN) {
2499 fputc ('S', msg->fp);
2500 if (PgpSignAs && *PgpSignAs)
2501 fprintf (msg->fp, "<%s>", PgpSignAs);
2503 if (hdr->security & INLINE)
2504 fputc ('I', msg->fp);
2505 fputc ('\n', msg->fp);
2508 /* (postponment) if the mail is to be signed or encrypted, save this info */
2509 if ((WithCrypto & APPLICATION_SMIME)
2510 && post && (hdr->security & APPLICATION_SMIME)) {
2511 fputs ("X-Mutt-SMIME: ", msg->fp);
2512 if (hdr->security & ENCRYPT) {
2513 fputc ('E', msg->fp);
2514 if (SmimeCryptAlg && *SmimeCryptAlg)
2515 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2517 if (hdr->security & SIGN) {
2518 fputc ('S', msg->fp);
2519 if (SmimeDefaultKey && *SmimeDefaultKey)
2520 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2522 if (hdr->security & INLINE)
2523 fputc ('I', msg->fp);
2524 fputc ('\n', msg->fp);
2528 /* (postponement) if the mail is to be sent through a mixmaster
2529 * chain, save that information
2532 if (post && hdr->chain && hdr->chain) {
2535 fputs ("X-Mutt-Mix:", msg->fp);
2536 for (p = hdr->chain; p; p = p->next)
2537 fprintf (msg->fp, " %s", (char *) p->data);
2539 fputc ('\n', msg->fp);
2544 char sasha[LONG_STRING];
2547 mutt_write_mime_body (hdr->content, tempfp);
2549 /* make sure the last line ends with a newline. Emacs doesn't ensure
2550 * this will happen, and it can cause problems parsing the mailbox
2553 fseek (tempfp, -1, 2);
2554 if (fgetc (tempfp) != '\n') {
2555 fseek (tempfp, 0, 2);
2556 fputc ('\n', tempfp);
2560 if (ferror (tempfp)) {
2562 (debugfile, "mutt_write_fcc(): %s: write failed.\n", tempfile));
2565 mx_commit_message (msg, &f); /* XXX - really? */
2566 mx_close_message (&msg);
2567 mx_close_mailbox (&f, NULL);
2571 /* count the number of lines */
2573 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2575 fprintf (msg->fp, "Content-Length: %ld\n", (long) ftell (tempfp));
2576 fprintf (msg->fp, "Lines: %d\n\n", lines);
2578 /* copy the body and clean up */
2580 r = mutt_copy_stream (tempfp, msg->fp);
2581 if (fclose (tempfp) != 0)
2583 /* if there was an error, leave the temp version */
2588 fputc ('\n', msg->fp); /* finish off the header */
2589 r = mutt_write_mime_body (hdr->content, msg->fp);
2592 if (mx_commit_message (msg, &f) != 0)
2594 mx_close_message (&msg);
2595 mx_close_mailbox (&f, NULL);
2598 set_noconv_flags (hdr->content, 0);