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"
25 #include "mutt_crypt.h"
26 #include "mutt_idna.h"
31 #include "lib/debug.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 && !safe_strncmp ("From", line, 4)) {
191 strfcpy (line, "=46rom", sizeof (line));
194 else if (linelen == 4 && !safe_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))) {
460 debug_print (1, ("no boundary parameter found!\n"));
461 mutt_error _("No boundary parameter found! [report this error]");
465 strfcpy (boundary, p, sizeof (boundary));
467 for (t = a->parts; t; t = t->next) {
468 fprintf (f, "\n--%s\n", boundary);
469 if (mutt_write_mime_header (t, f) == -1)
472 if (mutt_write_mime_body (t, f) == -1)
475 fprintf (f, "\n--%s--\n", boundary);
476 return (ferror (f) ? -1 : 0);
479 /* This is pretty gross, but it's the best solution for now... */
480 if ((WithCrypto & APPLICATION_PGP)
481 && a->type == TYPEAPPLICATION
482 && mutt_strcmp (a->subtype, "pgp-encrypted") == 0) {
483 fputs ("Version: 1\n", f);
487 if ((fpin = fopen (a->filename, "r")) == NULL) {
488 debug_print (1, ("%s no longer exists!\n", a->filename));
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] = str_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 = str_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 debug_print (1, ("%s: %s (errno %d).\n", fname, strerror (errno), errno));
902 info = safe_calloc (1, sizeof (CONTENT));
903 memset (&state, 0, sizeof (state));
905 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
906 char *chs = mutt_get_parameter ("charset", b->parameter);
907 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
908 FileCharset : Charset) : Charset;
909 if (Charset && (chs || SendCharset) &&
910 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
911 &fromcode, &tocode, info) != (size_t) (-1)) {
913 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
914 mutt_set_parameter ("charset", chsbuf, &b->parameter);
916 b->file_charset = fromcode;
924 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
925 update_content_info (info, &state, buffer, r);
926 update_content_info (info, &state, 0, 0);
930 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
931 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
933 && !mutt_is_us_ascii (Charset) ? Charset :
934 "unknown-8bit"), &b->parameter);
939 /* Given a file with path ``s'', see if there is a registered MIME type.
940 * returns the major MIME type, and copies the subtype to ``d''. First look
941 * for ~/.mime.types, then look in a system mime.types if we can find one.
942 * The longest match is used so that we can match `ps.gz' when `gz' also
946 int mutt_lookup_mime_type (BODY * att, const char *path)
950 char buf[LONG_STRING];
951 char subtype[STRING], xtype[STRING];
953 int szf, sze, cur_sze;
961 szf = mutt_strlen (path);
963 for (count = 0; count < 3; count++) {
965 * can't use strtok() because we use it in an inner loop below, so use
966 * a switch statement here instead.
970 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
973 strfcpy (buf, SYSCONFDIR "/muttng-mime.types", sizeof (buf));
976 strfcpy (buf, PKGDATADIR "/mime.types", sizeof (buf));
979 debug_print (1, ("Internal error, count = %d.\n", count));
980 goto bye; /* shouldn't happen */
983 if ((f = fopen (buf, "r")) != NULL) {
984 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
985 /* weed out any comments */
986 if ((p = strchr (buf, '#')))
989 /* remove any leading space. */
993 /* position on the next field in this line */
994 if ((p = strpbrk (ct, " \t")) == NULL)
999 /* cycle through the file extensions */
1000 while ((p = strtok (p, " \t\n"))) {
1001 sze = mutt_strlen (p);
1002 if ((sze > cur_sze) && (szf >= sze) &&
1003 (safe_strcasecmp (path + szf - sze, p) == 0
1004 || ascii_strcasecmp (path + szf - sze, p) == 0) && (szf == sze
1011 /* get the content-type */
1013 if ((p = strchr (ct, '/')) == NULL) {
1014 /* malformed line, just skip it. */
1019 for (q = p; *q && !ISSPACE (*q); q++);
1021 str_substrcpy (subtype, p, q, sizeof (subtype));
1023 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
1024 strfcpy (xtype, ct, sizeof (xtype));
1037 if (type != TYPEOTHER || *xtype != '\0') {
1039 str_replace (&att->subtype, subtype);
1040 str_replace (&att->xtype, xtype);
1046 void mutt_message_to_7bit (BODY * a, FILE * fp)
1048 char temp[_POSIX_PATH_MAX];
1054 if (!a->filename && fp)
1056 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
1057 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
1062 if (stat (a->filename, &sb) == -1) {
1063 mutt_perror ("stat");
1066 a->length = sb.st_size;
1070 if (!(fpout = safe_fopen (temp, "w+"))) {
1071 mutt_perror ("fopen");
1075 fseek (fpin, a->offset, 0);
1076 a->parts = mutt_parse_messageRFC822 (fpin, a);
1078 transform_to_7bit (a->parts, fpin);
1080 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1081 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1083 fputs ("Mime-Version: 1.0\n", fpout);
1084 mutt_write_mime_header (a->parts, fpout);
1085 fputc ('\n', fpout);
1086 mutt_write_mime_body (a->parts, fpout);
1098 a->encoding = ENC7BIT;
1099 a->d_filename = a->filename;
1100 if (a->filename && a->unlink)
1101 unlink (a->filename);
1102 a->filename = safe_strdup (temp);
1104 if (stat (a->filename, &sb) == -1) {
1105 mutt_perror ("stat");
1108 a->length = sb.st_size;
1109 mutt_free_body (&a->parts);
1110 a->hdr->content = NULL;
1113 static void transform_to_7bit (BODY * a, FILE * fpin)
1115 char buff[_POSIX_PATH_MAX];
1119 memset (&s, 0, sizeof (s));
1120 for (; a; a = a->next) {
1121 if (a->type == TYPEMULTIPART) {
1122 if (a->encoding != ENC7BIT)
1123 a->encoding = ENC7BIT;
1125 transform_to_7bit (a->parts, fpin);
1127 else if (mutt_is_message_type (a->type, a->subtype)) {
1128 mutt_message_to_7bit (a, fpin);
1132 a->force_charset = 1;
1135 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1136 mutt_perror ("fopen");
1140 mutt_decode_attachment (a, &s);
1142 a->d_filename = a->filename;
1143 a->filename = safe_strdup (buff);
1145 if (stat (a->filename, &sb) == -1) {
1146 mutt_perror ("stat");
1149 a->length = sb.st_size;
1151 mutt_update_encoding (a);
1152 if (a->encoding == ENC8BIT)
1153 a->encoding = ENCQUOTEDPRINTABLE;
1154 else if (a->encoding == ENCBINARY)
1155 a->encoding = ENCBASE64;
1160 /* determine which Content-Transfer-Encoding to use */
1161 static void mutt_set_encoding (BODY * b, CONTENT * info)
1163 char send_charset[SHORT_STRING];
1165 if (b->type == TYPETEXT) {
1167 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1168 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1169 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1170 b->encoding = ENCQUOTEDPRINTABLE;
1171 else if (info->hibin)
1172 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1174 b->encoding = ENC7BIT;
1176 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1177 if (info->lobin || info->hibin) {
1178 if (option (OPTALLOW8BIT) && !info->lobin)
1179 b->encoding = ENC8BIT;
1181 mutt_message_to_7bit (b, NULL);
1184 b->encoding = ENC7BIT;
1186 else if (b->type == TYPEAPPLICATION
1187 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1188 b->encoding = ENC7BIT;
1191 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1192 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1195 /* Determine which encoding is smaller */
1196 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1197 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1198 b->encoding = ENCBASE64;
1200 b->encoding = ENCQUOTEDPRINTABLE;
1204 b->encoding = ENC7BIT;
1208 void mutt_stamp_attachment (BODY * a)
1210 a->stamp = time (NULL);
1213 /* Get a body's character set */
1215 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1219 if (b && b->type != TYPETEXT)
1223 p = mutt_get_parameter ("charset", b->parameter);
1226 mutt_canonical_charset (d, dlen, NONULL (p));
1228 strfcpy (d, "us-ascii", dlen);
1234 /* Assumes called from send mode where BODY->filename points to actual file */
1235 void mutt_update_encoding (BODY * a)
1238 char chsbuff[STRING];
1240 /* override noconv when it's us-ascii */
1241 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1244 if (!a->force_charset && !a->noconv)
1245 mutt_delete_parameter ("charset", &a->parameter);
1247 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1250 mutt_set_encoding (a, info);
1251 mutt_stamp_attachment (a);
1258 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1260 char buffer[LONG_STRING];
1263 int cmflags, chflags;
1264 int pgp = WithCrypto ? hdr->security : 0;
1267 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1268 (hdr->security & ENCRYPT)) {
1269 if (!crypt_valid_passphrase (hdr->security))
1274 mutt_mktemp (buffer);
1275 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1278 body = mutt_new_body ();
1279 body->type = TYPEMESSAGE;
1280 body->subtype = safe_strdup ("rfc822");
1281 body->filename = safe_strdup (buffer);
1284 body->disposition = DISPINLINE;
1287 mutt_parse_mime_message (ctx, hdr);
1292 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1293 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1294 chflags |= CH_MIME | CH_TXTPLAIN;
1295 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1296 if ((WithCrypto & APPLICATION_PGP))
1298 if ((WithCrypto & APPLICATION_SMIME))
1299 pgp &= ~SMIMEENCRYPT;
1301 else if (WithCrypto && option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1302 if ((WithCrypto & APPLICATION_PGP)
1303 && mutt_is_multipart_encrypted (hdr->content)) {
1304 chflags |= CH_MIME | CH_NONEWLINE;
1305 cmflags = M_CM_DECODE_PGP;
1308 else if ((WithCrypto & APPLICATION_PGP)
1309 && (mutt_is_application_pgp (hdr->content) & PGPENCRYPT)) {
1310 chflags |= CH_MIME | CH_TXTPLAIN;
1311 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1314 else if ((WithCrypto & APPLICATION_SMIME)
1315 && mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1316 chflags |= CH_MIME | CH_TXTPLAIN;
1317 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1318 pgp &= ~SMIMEENCRYPT;
1322 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1327 body->hdr = mutt_new_header ();
1328 body->hdr->offset = 0;
1329 /* we don't need the user headers here */
1330 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1332 body->hdr->security = pgp;
1333 mutt_update_encoding (body);
1334 body->parts = body->hdr->content;
1341 BODY *mutt_make_file_attach (const char *path)
1346 att = mutt_new_body ();
1347 att->filename = safe_strdup (path);
1349 /* Attempt to determine the appropriate content-type based on the filename
1356 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1357 path)) != TYPEOTHER || *xbuf != '\0') {
1359 att->subtype = safe_strdup (buf);
1360 att->xtype = safe_strdup (xbuf);
1365 mutt_lookup_mime_type (att, path);
1369 if ((info = mutt_get_content_info (path, att)) == NULL) {
1370 mutt_free_body (&att);
1374 if (!att->subtype) {
1375 if (info->lobin == 0
1376 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1378 * Statistically speaking, there should be more than 10% "lobin"
1379 * chars if this is really a binary file...
1381 att->type = TYPETEXT;
1382 att->subtype = safe_strdup ("plain");
1385 att->type = TYPEAPPLICATION;
1386 att->subtype = safe_strdup ("octet-stream");
1390 mutt_update_encoding (att);
1394 static int get_toplevel_encoding (BODY * a)
1398 for (; a; a = a->next) {
1399 if (a->encoding == ENCBINARY)
1401 else if (a->encoding == ENC8BIT)
1408 BODY *mutt_make_multipart (BODY * b)
1412 new = mutt_new_body ();
1413 new->type = TYPEMULTIPART;
1414 new->subtype = safe_strdup ("mixed");
1415 new->encoding = get_toplevel_encoding (b);
1416 mutt_generate_boundary (&new->parameter);
1418 new->disposition = DISPINLINE;
1424 /* remove the multipart body if it exists */
1425 BODY *mutt_remove_multipart (BODY * b)
1433 mutt_free_body (&t);
1438 char *mutt_make_date (char *s, size_t len)
1440 time_t t = time (NULL);
1441 struct tm *l = localtime (&t);
1442 time_t tz = mutt_local_tz (t);
1446 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1447 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1448 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1449 (int) tz / 60, (int) abs (tz) % 60);
1453 /* wrapper around mutt_write_address() so we can handle very large
1454 recipient lists without needing a huge temporary buffer in memory */
1455 void mutt_write_address_list (ADDRESS * adr, FILE * fp, int linelen,
1459 char buf[LONG_STRING];
1467 rfc822_write_address (buf, sizeof (buf), adr, display);
1468 len = mutt_strlen (buf);
1469 if (count && linelen + len > 74) {
1471 linelen = len + 8; /* tab is usually about 8 spaces... */
1474 if (count && adr->mailbox) {
1482 if (!adr->group && adr->next && adr->next->mailbox) {
1492 /* arbitrary number of elements to grow the array by */
1497 /* need to write the list in reverse because they are stored in reverse order
1498 * when parsed to speed up threading
1500 void mutt_write_references (LIST * r, FILE * f)
1503 int refcnt = 0, refmax = 0;
1505 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1506 if (refcnt == refmax)
1507 safe_realloc (&ref, (refmax += REF_INC) * sizeof (LIST *));
1511 while (refcnt-- > 0) {
1513 fputs (ref[refcnt]->data, f);
1519 /* Note: all RFC2047 encoding should be done outside of this routine, except
1520 * for the "real name." This will allow this routine to be used more than
1521 * once, if necessary.
1523 * Likewise, all IDN processing should happen outside of this routine.
1525 * mode == 1 => "lite" mode (used for edit_hdrs)
1526 * mode == 0 => normal mode. write full header + MIME headers
1527 * mode == -1 => write just the envelope info (used for postponing messages)
1529 * privacy != 0 => will omit any headers which may identify the user.
1530 * Output generated is suitable for being sent through
1531 * anonymous remailer chains.
1535 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1536 int mode, int privacy)
1538 char buffer[LONG_STRING];
1540 LIST *tmp = env->userhdrs;
1541 int has_agent = 0; /* user defined user-agent header field exists */
1544 if (!option (OPTNEWSSEND))
1546 if (mode == 0 && !privacy)
1547 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1549 /* OPTUSEFROM is not consulted here so that we can still write a From:
1550 * field if the user sets it with the `my_hdr' command
1552 if (env->from && !privacy) {
1554 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1555 fprintf (fp, "From: %s\n", buffer);
1560 mutt_write_address_list (env->to, fp, 4, 0);
1564 if (!option (OPTNEWSSEND))
1566 fputs ("To: \n", fp);
1570 mutt_write_address_list (env->cc, fp, 4, 0);
1574 if (!option (OPTNEWSSEND))
1576 fputs ("Cc: \n", fp);
1579 if (mode != 0 || option (OPTWRITEBCC)) {
1580 fputs ("Bcc: ", fp);
1581 mutt_write_address_list (env->bcc, fp, 5, 0);
1586 if (!option (OPTNEWSSEND))
1588 fputs ("Bcc: \n", fp);
1591 if (env->newsgroups)
1592 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1593 else if (mode == 1 && option (OPTNEWSSEND))
1594 fputs ("Newsgroups: \n", fp);
1596 if (env->followup_to)
1597 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1598 else if (mode == 1 && option (OPTNEWSSEND))
1599 fputs ("Followup-To: \n", fp);
1601 if (env->x_comment_to)
1602 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1603 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO))
1604 fputs ("X-Comment-To: \n", fp);
1608 fprintf (fp, "Subject: %s\n", env->subject);
1610 fputs ("Subject: \n", fp);
1612 /* save message id if the user has set it */
1613 if (env->message_id && !privacy)
1614 fprintf (fp, "Message-ID: %s\n", env->message_id);
1616 if (env->reply_to) {
1617 fputs ("Reply-To: ", fp);
1618 mutt_write_address_list (env->reply_to, fp, 10, 0);
1621 fputs ("Reply-To: \n", fp);
1623 if (env->mail_followup_to)
1625 if (!option (OPTNEWSSEND))
1628 fputs ("Mail-Followup-To: ", fp);
1629 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1633 if (env->references) {
1634 fputs ("References:", fp);
1635 mutt_write_references (env->references, fp);
1639 /* Add the MIME headers */
1640 fputs ("Mime-Version: 1.0\n", fp);
1641 mutt_write_mime_header (attach, fp);
1644 if (env->in_reply_to) {
1645 fputs ("In-Reply-To:", fp);
1646 mutt_write_references (env->in_reply_to, fp);
1650 /* Add any user defined headers */
1651 for (; tmp; tmp = tmp->next) {
1652 if ((p = strchr (tmp->data, ':'))) {
1656 continue; /* don't emit empty fields. */
1658 /* check to see if the user has overridden the user-agent field */
1659 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1665 fputs (tmp->data, fp);
1670 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1674 if (OperatingSystem != NULL) {
1675 os = OperatingSystem;
1678 if (uname (&un) == -1) {
1685 /* Add a vanity header */
1686 fprintf (fp, "User-Agent: mutt-ng/%s (%s)\n", MUTT_VERSION, os);
1689 return (ferror (fp) == 0 ? 0 : -1);
1692 static void encode_headers (LIST * h)
1698 for (; h; h = h->next) {
1699 if (!(p = strchr (h->data, ':')))
1705 tmp = safe_strdup (p);
1710 rfc2047_encode_string (&tmp);
1711 safe_realloc (&h->data,
1712 mutt_strlen (h->data) + 2 + mutt_strlen (tmp) + 1);
1714 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1720 const char *mutt_fqdn (short may_hide_host)
1724 if (Fqdn && Fqdn[0] != '@') {
1727 if (may_hide_host && option (OPTHIDDENHOST)) {
1728 if ((p = strchr (Fqdn, '.')))
1731 /* sanity check: don't hide the host if
1732 * the fqdn is something like detebe.org.
1735 if (!p || !(q = strchr (p, '.')))
1743 static char mutt_normalized_char (char c)
1747 if (strchr (".!#$%&'*+-/=?^_`{|}~", c))
1749 return '.'; /* normalized character (we're stricter than RFC2822, 3.6.4) */
1752 static void mutt_gen_localpart (char *buf, unsigned int len, char *fmt)
1756 char tmp[SHORT_STRING];
1763 for (; *fmt; ++fmt) {
1769 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mday);
1770 safe_strncat (buf, len, tmp, 2);
1773 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_hour);
1774 safe_strncat (buf, len, tmp, 2);
1777 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mon + 1);
1778 safe_strncat (buf, len, tmp, 2);
1781 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_min);
1782 safe_strncat (buf, len, tmp, 2);
1785 snprintf (tmp, sizeof (tmp), "%lo", (unsigned long) now);
1786 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1789 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) getpid ());
1790 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1793 snprintf (tmp, sizeof (tmp), "%c", MsgIdPfx);
1794 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1795 safe_strncat (buf, len, tmp, 1);
1798 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) rand ());
1799 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1802 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) rand ());
1803 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1806 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_sec);
1807 safe_strncat (buf, len, tmp, 2);
1810 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) now);
1811 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1814 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) now);
1815 safe_strncat (buf, len, tmp, mutt_strlen (tmp));
1818 snprintf (tmp, sizeof (tmp), "%04d", tm->tm_year + 1900); /* this will break in the year 10000 ;-) */
1819 safe_strncat (buf, len, tmp, 4);
1822 safe_strncat (buf, len, "%", 1);
1825 safe_strncat (buf, len, ".", 1); /* invalid formats are replaced by '.' */
1832 c = mutt_normalized_char (*fmt); /* @todo: filter out invalid characters */
1833 safe_strncat (buf, len, &c, 1);
1838 char *mutt_gen_msgid (void)
1840 char buf[SHORT_STRING];
1841 char localpart[SHORT_STRING];
1842 unsigned int localpart_length;
1845 if (!(fqdn = mutt_fqdn (0)))
1846 fqdn = NONULL (Hostname);
1848 localpart_length = sizeof (buf) - mutt_strlen (fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1850 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1852 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1853 return (safe_strdup (buf));
1856 static RETSIGTYPE alarm_handler (int sig)
1861 /* invoke sendmail in a subshell
1862 path (in) path to program to execute
1863 args (in) arguments to pass to program
1864 msg (in) temp file containing message to send
1865 tempfile (out) if sendmail is put in the background, this points
1866 to the temporary file containing the stdout of the
1869 send_msg (const char *path, char **args, const char *msg, char **tempfile)
1875 mutt_block_signals_system ();
1878 /* we also don't want to be stopped right now */
1879 sigaddset (&set, SIGTSTP);
1880 sigprocmask (SIG_BLOCK, &set, NULL);
1882 if (SendmailWait >= 0) {
1883 char tmp[_POSIX_PATH_MAX];
1886 *tempfile = safe_strdup (tmp);
1889 if ((pid = fork ()) == 0) {
1890 struct sigaction act, oldalrm;
1892 /* save parent's ID before setsid() */
1895 /* we want the delivery to continue even after the main process dies,
1896 * so we put ourselves into another session right away
1900 /* next we close all open files */
1901 #if defined(OPEN_MAX)
1902 for (fd = 0; fd < OPEN_MAX; fd++)
1904 #elif defined(_POSIX_OPEN_MAX)
1905 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1913 /* now the second fork() */
1914 if ((pid = fork ()) == 0) {
1915 /* "msg" will be opened as stdin */
1916 if (open (msg, O_RDONLY, 0) < 0) {
1922 if (SendmailWait >= 0) {
1923 /* *tempfile will be opened as stdout */
1924 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1927 /* redirect stderr to *tempfile too */
1932 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1934 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1941 else if (pid == -1) {
1947 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1948 * SendmailWait = 0: wait forever
1949 * SendmailWait < 0: don't wait
1951 if (SendmailWait > 0) {
1953 act.sa_handler = alarm_handler;
1955 /* need to make sure waitpid() is interrupted on SIGALRM */
1956 act.sa_flags = SA_INTERRUPT;
1960 sigemptyset (&act.sa_mask);
1961 sigaction (SIGALRM, &act, &oldalrm);
1962 alarm (SendmailWait);
1964 else if (SendmailWait < 0)
1965 _exit (0xff & EX_OK);
1967 if (waitpid (pid, &st, 0) > 0) {
1968 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1969 if (SendmailWait && st == (0xff & EX_OK)) {
1970 unlink (*tempfile); /* no longer needed */
1975 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1976 if (SendmailWait > 0) {
1982 /* reset alarm; not really needed, but... */
1984 sigaction (SIGALRM, &oldalrm, NULL);
1986 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1987 /* the parent is already dead */
1995 sigprocmask (SIG_UNBLOCK, &set, NULL);
1997 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1998 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
2000 st = S_ERR; /* error */
2002 mutt_unblock_signals_system (1);
2007 static char **add_args (char **args, size_t * argslen, size_t * argsmax,
2010 for (; addr; addr = addr->next) {
2011 /* weed out group mailboxes, since those are for display only */
2012 if (addr->mailbox && !addr->group) {
2013 if (*argslen == *argsmax)
2014 safe_realloc (&args, (*argsmax += 5) * sizeof (char *));
2015 args[(*argslen)++] = addr->mailbox;
2021 static char **add_option (char **args, size_t * argslen, size_t * argsmax,
2024 if (*argslen == *argsmax)
2025 safe_realloc (&args, (*argsmax += 5) * sizeof (char *));
2026 args[(*argslen)++] = s;
2030 static const char *strsysexit (int e)
2034 for (i = 0; sysexits_h[i].str; i++) {
2035 if (e == sysexits_h[i].v)
2039 return sysexits_h[i].str;
2043 static int mutt_invoke_sendmail (ADDRESS * from, /* the sender */
2044 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
2045 const char *msg, /* file containing message */
2047 { /* message contains 8bit chars */
2048 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
2050 size_t argslen = 0, argsmax = 0;
2054 if (option (OPTNEWSSEND)) {
2055 char cmd[LONG_STRING];
2057 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
2060 i = nntp_post (msg);
2065 s = safe_strdup (cmd);
2069 s = safe_strdup (Sendmail);
2073 while ((ps = strtok (ps, " "))) {
2074 if (argslen == argsmax)
2075 safe_realloc (&args, sizeof (char *) * (argsmax += 5));
2078 args[argslen++] = ps;
2080 path = safe_strdup (ps);
2081 ps = strrchr (ps, '/');
2086 args[argslen++] = ps;
2093 if (!option (OPTNEWSSEND)) {
2095 if (eightbit && option (OPTUSE8BITMIME))
2096 args = add_option (args, &argslen, &argsmax, "-B8BITMIME");
2098 if (option (OPTENVFROM) && from && !from->next) {
2099 args = add_option (args, &argslen, &argsmax, "-f");
2100 args = add_args (args, &argslen, &argsmax, from);
2103 args = add_option (args, &argslen, &argsmax, "-N");
2104 args = add_option (args, &argslen, &argsmax, DsnNotify);
2107 args = add_option (args, &argslen, &argsmax, "-R");
2108 args = add_option (args, &argslen, &argsmax, DsnReturn);
2110 args = add_option (args, &argslen, &argsmax, "--");
2111 args = add_args (args, &argslen, &argsmax, to);
2112 args = add_args (args, &argslen, &argsmax, cc);
2113 args = add_args (args, &argslen, &argsmax, bcc);
2118 if (argslen == argsmax)
2119 safe_realloc (&args, sizeof (char *) * (++argsmax));
2121 args[argslen++] = NULL;
2123 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2125 const char *e = strsysexit (i);
2128 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2133 if (stat (childout, &st) == 0 && st.st_size > 0)
2134 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2147 if (i == (EX_OK & 0xff))
2149 else if (i == S_BKG)
2156 int mutt_invoke_mta (ADDRESS * from, /* the sender */
2157 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
2158 const char *msg, /* file containing message */
2160 { /* message contains 8bit chars */
2163 return mutt_invoke_libesmtp (from, to, cc, bcc, msg, eightbit);
2166 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2169 /* appends string 'b' to string 'a', and returns the pointer to the new
2171 char *mutt_append_string (char *a, const char *b)
2173 size_t la = mutt_strlen (a);
2175 safe_realloc (&a, la + mutt_strlen (b) + 1);
2176 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2180 /* returns 1 if char `c' needs to be quoted to protect from shell
2181 interpretation when executing commands in a subshell */
2182 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2184 /* returns 1 if string `s' contains characters which could cause problems
2185 when used on a command line to execute a command */
2186 int mutt_needs_quote (const char *s)
2189 if (INVALID_CHAR (*s))
2196 /* Quote a string to prevent shell escapes when this string is used on the
2197 command line to send mail. */
2198 char *mutt_quote_string (const char *s)
2203 rlen = mutt_strlen (s) + 3;
2204 pr = r = (char *) safe_malloc (rlen);
2207 if (INVALID_CHAR (*s)) {
2210 safe_realloc (&r, ++rlen);
2221 /* For postponing (!final) do the necessary encodings only */
2222 void mutt_prepare_envelope (ENVELOPE * env, int final)
2224 char buffer[LONG_STRING];
2227 if (env->bcc && !(env->to || env->cc)) {
2228 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2229 * recipients if there is no To: or Cc: field, so attempt to suppress
2230 * it by using an empty To: field.
2232 env->to = rfc822_new_address ();
2234 env->to->next = rfc822_new_address ();
2237 rfc822_cat (buffer, sizeof (buffer), "undisclosed-recipients",
2240 env->to->mailbox = safe_strdup (buffer);
2243 mutt_set_followup_to (env);
2245 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2246 env->message_id = mutt_gen_msgid ();
2249 /* Take care of 8-bit => 7-bit conversion. */
2250 rfc2047_encode_adrlist (env->to, "To");
2251 rfc2047_encode_adrlist (env->cc, "Cc");
2252 rfc2047_encode_adrlist (env->bcc, "Bcc");
2253 rfc2047_encode_adrlist (env->from, "From");
2254 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2255 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2259 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2262 rfc2047_encode_string (&env->subject);
2264 encode_headers (env->userhdrs);
2267 void mutt_unprepare_envelope (ENVELOPE * env)
2271 for (item = env->userhdrs; item; item = item->next)
2272 rfc2047_decode (&item->data);
2274 rfc822_free_address (&env->mail_followup_to);
2276 /* back conversions */
2277 rfc2047_decode_adrlist (env->to);
2278 rfc2047_decode_adrlist (env->cc);
2279 rfc2047_decode_adrlist (env->bcc);
2280 rfc2047_decode_adrlist (env->from);
2281 rfc2047_decode_adrlist (env->reply_to);
2282 rfc2047_decode (&env->subject);
2285 static int _mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to,
2286 const char *resent_from, ADDRESS * env_from)
2290 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2291 MESSAGE *msg = NULL;
2294 /* Try to bounce each message out, aborting if we get any failures. */
2295 for (i = 0; i < Context->msgcount; i++)
2296 if (Context->hdrs[i]->tagged)
2298 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2303 /* If we failed to open a message, return with error */
2304 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2310 mutt_mktemp (tempfile);
2311 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2312 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2314 if (!option (OPTBOUNCEDELIVERED))
2315 ch_flags |= CH_WEED_DELIVERED;
2317 fseek (fp, h->offset, 0);
2318 fprintf (f, "Resent-From: %s", resent_from);
2319 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2320 if (MsgIdFormat && *MsgIdFormat)
2321 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2322 fputs ("Resent-To: ", f);
2323 mutt_write_address_list (to, f, 11, 0);
2324 mutt_copy_header (fp, h, f, ch_flags, NULL);
2326 mutt_copy_bytes (fp, f, h->content->length);
2329 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2330 h->content->encoding == ENC8BIT);
2334 mx_close_message (&msg);
2339 int mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to)
2342 const char *fqdn = mutt_fqdn (1);
2343 char resent_from[STRING];
2347 resent_from[0] = '\0';
2348 from = mutt_default_from ();
2351 rfc822_qualify (from, fqdn);
2353 rfc2047_encode_adrlist (from, "Resent-From");
2354 if (mutt_addrlist_to_idna (from, &err)) {
2355 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2358 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2361 unset_option (OPTNEWSSEND);
2364 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2366 rfc822_free_address (&from);
2372 /* given a list of addresses, return a list of unique addresses */
2373 ADDRESS *mutt_remove_duplicates (ADDRESS * addr)
2375 ADDRESS *top = addr;
2376 ADDRESS **last = ⊤
2381 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2382 if (tmp->mailbox && addr->mailbox &&
2383 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2390 debug_print (2, ("Removing %s\n", addr->mailbox));
2395 rfc822_free_address (&addr);
2408 static void set_noconv_flags (BODY * b, short flag)
2410 for (; b; b = b->next) {
2411 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2412 set_noconv_flags (b->parts, flag);
2413 else if (b->type == TYPETEXT && b->noconv) {
2415 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2417 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2422 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2423 int post, char *fcc)
2427 char tempfile[_POSIX_PATH_MAX];
2428 FILE *tempfp = NULL;
2432 set_noconv_flags (hdr->content, 1);
2434 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2435 debug_print (1, ("unable to open mailbox %s in append-mode, aborting.\n", path));
2439 /* We need to add a Content-Length field to avoid problems where a line in
2440 * the message body begins with "From "
2442 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2443 mutt_mktemp (tempfile);
2444 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2445 mutt_perror (tempfile);
2446 mx_close_mailbox (&f, NULL);
2451 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2452 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2453 mx_close_mailbox (&f, NULL);
2457 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2458 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2460 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2463 /* (postponment) if this was a reply of some sort, <msgid> contians the
2464 * Message-ID: of message replied to. Save it using a special X-Mutt-
2465 * header so it can be picked up if the message is recalled at a later
2466 * point in time. This will allow the message to be marked as replied if
2467 * the same mailbox is still open.
2470 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2472 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2473 * it can be picked up when the message is recalled
2476 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2477 fprintf (msg->fp, "Status: RO\n");
2481 /* (postponment) if the mail is to be signed or encrypted, save this info */
2482 if ((WithCrypto & APPLICATION_PGP)
2483 && post && (hdr->security & APPLICATION_PGP)) {
2484 fputs ("X-Mutt-PGP: ", msg->fp);
2485 if (hdr->security & ENCRYPT)
2486 fputc ('E', msg->fp);
2487 if (hdr->security & SIGN) {
2488 fputc ('S', msg->fp);
2489 if (PgpSignAs && *PgpSignAs)
2490 fprintf (msg->fp, "<%s>", PgpSignAs);
2492 if (hdr->security & INLINE)
2493 fputc ('I', msg->fp);
2494 fputc ('\n', msg->fp);
2497 /* (postponment) if the mail is to be signed or encrypted, save this info */
2498 if ((WithCrypto & APPLICATION_SMIME)
2499 && post && (hdr->security & APPLICATION_SMIME)) {
2500 fputs ("X-Mutt-SMIME: ", msg->fp);
2501 if (hdr->security & ENCRYPT) {
2502 fputc ('E', msg->fp);
2503 if (SmimeCryptAlg && *SmimeCryptAlg)
2504 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2506 if (hdr->security & SIGN) {
2507 fputc ('S', msg->fp);
2508 if (SmimeDefaultKey && *SmimeDefaultKey)
2509 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2511 if (hdr->security & INLINE)
2512 fputc ('I', msg->fp);
2513 fputc ('\n', msg->fp);
2517 /* (postponement) if the mail is to be sent through a mixmaster
2518 * chain, save that information
2521 if (post && hdr->chain && hdr->chain) {
2524 fputs ("X-Mutt-Mix:", msg->fp);
2525 for (p = hdr->chain; p; p = p->next)
2526 fprintf (msg->fp, " %s", (char *) p->data);
2528 fputc ('\n', msg->fp);
2533 char sasha[LONG_STRING];
2536 mutt_write_mime_body (hdr->content, tempfp);
2538 /* make sure the last line ends with a newline. Emacs doesn't ensure
2539 * this will happen, and it can cause problems parsing the mailbox
2542 fseek (tempfp, -1, 2);
2543 if (fgetc (tempfp) != '\n') {
2544 fseek (tempfp, 0, 2);
2545 fputc ('\n', tempfp);
2549 if (ferror (tempfp)) {
2550 debug_print (1, ("%s: write failed.\n", tempfile));
2553 mx_commit_message (msg, &f); /* XXX - really? */
2554 mx_close_message (&msg);
2555 mx_close_mailbox (&f, NULL);
2559 /* count the number of lines */
2561 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2563 fprintf (msg->fp, "Content-Length: %ld\n", (long) ftell (tempfp));
2564 fprintf (msg->fp, "Lines: %d\n\n", lines);
2566 /* copy the body and clean up */
2568 r = mutt_copy_stream (tempfp, msg->fp);
2569 if (fclose (tempfp) != 0)
2571 /* if there was an error, leave the temp version */
2576 fputc ('\n', msg->fp); /* finish off the header */
2577 r = mutt_write_mime_body (hdr->content, msg->fp);
2580 if (mx_commit_message (msg, &f) != 0)
2582 mx_close_message (&msg);
2583 mx_close_mailbox (&f, NULL);
2586 set_noconv_flags (hdr->content, 0);