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"
41 #include <sys/utsname.h>
44 # include "mutt_libesmtp.h"
45 #endif /* USE_LIBESMTP */
51 #ifdef HAVE_SYSEXITS_H
53 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
57 /* If you are debugging this file, comment out the following line. */
66 extern char RFC822Specials[];
68 static struct sysexits {
74 0xff & EX_USAGE, "Bad usage."},
78 0xff & EX_DATAERR, "Data format error."},
82 0xff & EX_NOINPUT, "Cannot open input."},
86 0xff & EX_NOUSER, "User unknown."},
90 0xff & EX_NOHOST, "Host unknown."},
94 0xff & EX_UNAVAILABLE, "Service unavailable."},
98 0xff & EX_SOFTWARE, "Internal error."},
102 0xff & EX_OSERR, "Operating system error."},
106 0xff & EX_OSFILE, "System file missing."},
110 0xff & EX_CANTCREAT, "Can't create output."},
114 0xff & EX_IOERR, "I/O error."},
118 0xff & EX_TEMPFAIL, "Deferred."},
122 0xff & EX_PROTOCOL, "Remote protocol error."},
126 0xff & EX_NOPERM, "Insufficient permission."},
130 0xff & EX_NOPERM, "Local configuration error."},
133 S_ERR, "Exec error."}, {
139 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
141 const char MimeSpecials[] = "@.,;:<>[]\\\"()?/= \t";
143 char B64Chars[64] = {
144 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
145 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
146 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
147 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
151 static char MsgIdPfx = 'A';
153 static void transform_to_7bit (BODY * a, FILE * fpin);
155 static void encode_quoted (FGETCONV * fc, FILE * fout, int istext)
158 char line[77], savechar;
160 while ((c = fgetconv (fc)) != EOF) {
161 /* Wrap the line if needed. */
162 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
163 /* If the last character is "quoted", then be sure to move all three
164 * characters to the next line. Otherwise, just move the last
167 if (line[linelen - 3] == '=') {
168 line[linelen - 3] = 0;
173 line[1] = line[linelen - 2];
174 line[2] = line[linelen - 1];
178 savechar = line[linelen - 1];
179 line[linelen - 1] = '=';
188 /* Escape lines that begin with/only contain "the message separator". */
189 if (linelen == 4 && !safe_strncmp ("From", line, 4)) {
190 strfcpy (line, "=46rom", sizeof (line));
193 else if (linelen == 4 && !safe_strncmp ("from", line, 4)) {
194 strfcpy (line, "=66rom", sizeof (line));
197 else if (linelen == 1 && line[0] == '.') {
198 strfcpy (line, "=2E", sizeof (line));
203 if (c == '\n' && istext) {
204 /* Check to make sure there is no trailing space on this line. */
206 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
208 sprintf (line + linelen - 1, "=%2.2X",
209 (unsigned char) line[linelen - 1]);
213 int savechar = line[linelen - 1];
215 line[linelen - 1] = '=';
218 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
228 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
229 /* Check to make sure there is enough room for the quoted character.
230 * If not, wrap to the next line.
233 line[linelen++] = '=';
239 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
243 /* Don't worry about wrapping the line here. That will happen during
244 * the next iteration when I'll also know what the next character is.
250 /* Take care of anything left in the buffer */
252 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
253 /* take care of trailing whitespace */
255 sprintf (line + linelen - 1, "=%2.2X",
256 (unsigned char) line[linelen - 1]);
258 savechar = line[linelen - 1];
259 line[linelen - 1] = '=';
263 sprintf (line, "=%2.2X", (unsigned char) savechar);
272 static char b64_buffer[3];
273 static short b64_num;
274 static short b64_linelen;
276 static void b64_flush (FILE * fout)
283 if (b64_linelen >= 72) {
288 for (i = b64_num; i < 3; i++)
289 b64_buffer[i] = '\0';
291 fputc (B64Chars[(b64_buffer[0] >> 2) & 0x3f], fout);
294 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
299 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
303 fputc (B64Chars[b64_buffer[2] & 0x3f], fout);
308 while (b64_linelen % 4) {
317 static void b64_putc (char c, FILE * fout)
322 b64_buffer[b64_num++] = c;
326 static void encode_base64 (FGETCONV * fc, FILE * fout, int istext)
330 b64_num = b64_linelen = 0;
332 while ((ch = fgetconv (fc)) != EOF) {
333 if (istext && ch == '\n' && ch1 != '\r')
334 b64_putc ('\r', fout);
342 static void encode_8bit (FGETCONV * fc, FILE * fout, int istext)
346 while ((ch = fgetconv (fc)) != EOF)
351 int mutt_write_mime_header (BODY * a, FILE * f)
361 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
364 len = 25 + safe_strlen (a->subtype); /* approximate len. of content-type */
366 for (p = a->parameter; p; p = p->next) {
375 tmp = safe_strdup (p->value);
376 encode = rfc2231_encode_string (&tmp);
377 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
379 /* Dirty hack to make messages readable by Outlook Express
380 * for the Mac: force quotes around the boundary parameter
381 * even when they aren't needed.
384 if (!ascii_strcasecmp (p->attribute, "boundary")
385 && !strcmp (buffer, tmp))
386 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
390 tmplen = safe_strlen (buffer) + safe_strlen (p->attribute) + 1;
392 if (len + tmplen + 2 > 76) {
401 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
409 fprintf (f, "Content-Description: %s\n", a->description);
411 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
414 if (!(fn = a->d_filename))
420 /* Strip off the leading path... */
421 if ((t = strrchr (fn, '/')))
427 tmp = safe_strdup (t);
428 encode = rfc2231_encode_string (&tmp);
429 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
431 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
437 if (a->encoding != ENC7BIT)
438 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
440 /* Do NOT add the terminator here!!! */
441 return (ferror (f) ? -1 : 0);
444 # define write_as_text_part(a) (mutt_is_text_part(a) \
445 || ((WithCrypto & APPLICATION_PGP)\
446 && mutt_is_application_pgp(a)))
448 int mutt_write_mime_body (BODY * a, FILE * f)
450 char *p, boundary[SHORT_STRING];
451 char send_charset[SHORT_STRING];
456 if (a->type == TYPEMULTIPART) {
457 /* First, find the boundary to use */
458 if (!(p = mutt_get_parameter ("boundary", a->parameter))) {
461 "mutt_write_mime_body(): no boundary parameter found!\n"));
462 mutt_error _("No boundary parameter found! [report this error]");
466 strfcpy (boundary, p, sizeof (boundary));
468 for (t = a->parts; t; t = t->next) {
469 fprintf (f, "\n--%s\n", boundary);
470 if (mutt_write_mime_header (t, f) == -1)
473 if (mutt_write_mime_body (t, f) == -1)
476 fprintf (f, "\n--%s--\n", boundary);
477 return (ferror (f) ? -1 : 0);
480 /* This is pretty gross, but it's the best solution for now... */
481 if ((WithCrypto & APPLICATION_PGP)
482 && a->type == TYPEAPPLICATION
483 && safe_strcmp (a->subtype, "pgp-encrypted") == 0) {
484 fputs ("Version: 1\n", f);
488 if ((fpin = fopen (a->filename, "r")) == NULL) {
490 (debugfile, "write_mime_body: %s no longer exists!\n",
492 mutt_error (_("%s no longer exists!"), a->filename);
496 if (a->type == TYPETEXT && (!a->noconv))
497 fc = fgetconv_open (fpin, a->file_charset,
498 mutt_get_body_charset (send_charset,
499 sizeof (send_charset), a), 0);
501 fc = fgetconv_open (fpin, 0, 0, 0);
503 if (a->encoding == ENCQUOTEDPRINTABLE)
504 encode_quoted (fc, f, write_as_text_part (a));
505 else if (a->encoding == ENCBASE64)
506 encode_base64 (fc, f, write_as_text_part (a));
507 else if (a->type == TYPETEXT && (!a->noconv))
508 encode_8bit (fc, f, write_as_text_part (a));
510 mutt_copy_stream (fpin, f);
512 fgetconv_close (&fc);
515 return (ferror (f) ? -1 : 0);
518 #undef write_as_text_part
520 #define BOUNDARYLEN 16
521 void mutt_generate_boundary (PARAMETER ** parm)
523 char rs[BOUNDARYLEN + 1];
528 for (i = 0; i < BOUNDARYLEN; i++)
529 *p++ = B64Chars[LRAND () % sizeof (B64Chars)];
532 mutt_set_parameter ("boundary", rs, parm);
544 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
548 int whitespace = s->whitespace;
550 int linelen = s->linelen;
551 int was_cr = s->was_cr;
553 if (!d) { /* This signals EOF */
556 if (linelen > info->linemax)
557 info->linemax = linelen;
562 for (; dlen; d++, dlen--) {
575 if (linelen > info->linemax)
576 info->linemax = linelen;
591 if (linelen > info->linemax)
592 info->linemax = linelen;
597 else if (ch == '\r') {
605 else if (ch == '\t' || ch == '\f') {
609 else if (ch < 32 || ch == 127)
613 if ((ch == 'F') || (ch == 'f'))
623 if (linelen == 2 && ch != 'r')
625 else if (linelen == 3 && ch != 'o')
627 else if (linelen == 4) {
640 if (ch != ' ' && ch != '\t')
645 s->whitespace = whitespace;
647 s->linelen = linelen;
652 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
653 #define BUGGY_ICONV 1
656 * Find the best charset conversion of the file from fromcode into one
657 * of the tocodes. If successful, set *tocode and CONTENT *info and
658 * return the number of characters converted inexactly. If no
659 * conversion was possible, return -1.
661 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
662 * which would otherwise prevent us from knowing the number of inexact
663 * conversions. Where the candidate target charset is UTF-8 we avoid
664 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
665 * fails with some libraries.
667 * We assume that the output from iconv is never more than 4 times as
668 * long as the input for any pair of charsets we might be interested
671 static size_t convert_file_to (FILE * file, const char *fromcode,
672 int ncodes, const char **tocodes,
673 int *tocode, CONTENT * info)
677 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
678 ICONV_CONST char *ib, *ub;
680 size_t ibl, obl, ubl, ubl1, n, ret;
683 CONTENT_STATE *states;
686 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
687 if (cd1 == (iconv_t) (-1))
690 cd = safe_calloc (ncodes, sizeof (iconv_t));
691 score = safe_calloc (ncodes, sizeof (size_t));
692 states = safe_calloc (ncodes, sizeof (CONTENT_STATE));
693 infos = safe_calloc (ncodes, sizeof (CONTENT));
695 for (i = 0; i < ncodes; i++)
696 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
697 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
699 /* Special case for conversion to UTF-8 */
700 cd[i] = (iconv_t) (-1), score[i] = (size_t) (-1);
706 /* Try to fill input buffer */
707 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
710 /* Convert to UTF-8 */
712 ob = bufu, obl = sizeof (bufu);
713 n = iconv (cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
714 assert (n == (size_t) (-1) || !n || ICONV_NONTRANS);
715 if (n == (size_t) (-1) &&
716 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
717 assert (errno == EILSEQ ||
718 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
724 /* Convert from UTF-8 */
725 for (i = 0; i < ncodes; i++)
726 if (cd[i] != (iconv_t) (-1) && score[i] != (size_t) (-1)) {
727 ub = bufu, ubl = ubl1;
728 ob = bufo, obl = sizeof (bufo);
729 n = iconv (cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
730 if (n == (size_t) (-1)) {
731 assert (errno == E2BIG ||
732 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
733 score[i] = (size_t) (-1);
737 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
740 else if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1))
741 /* Special case for conversion to UTF-8 */
742 update_content_info (&infos[i], &states[i], bufu, ubl1);
745 /* Save unused input */
746 memmove (bufi, ib, ibl);
747 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
754 /* Find best score */
756 for (i = 0; i < ncodes; i++) {
757 if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1)) {
758 /* Special case for conversion to UTF-8 */
763 else if (cd[i] == (iconv_t) (-1) || score[i] == (size_t) (-1))
765 else if (ret == (size_t) (-1) || score[i] < ret) {
772 if (ret != (size_t) (-1)) {
773 memcpy (info, &infos[*tocode], sizeof (CONTENT));
774 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
778 for (i = 0; i < ncodes; i++)
779 if (cd[i] != (iconv_t) (-1))
791 #endif /* !HAVE_ICONV */
795 * Find the first of the fromcodes that gives a valid conversion and
796 * the best charset conversion of the file into one of the tocodes. If
797 * successful, set *fromcode and *tocode to dynamically allocated
798 * strings, set CONTENT *info, and return the number of characters
799 * converted inexactly. If no conversion was possible, return -1.
801 * Both fromcodes and tocodes may be colon-separated lists of charsets.
802 * However, if fromcode is zero then fromcodes is assumed to be the
803 * name of a single charset even if it contains a colon.
805 static size_t convert_file_from_to (FILE * file,
806 const char *fromcodes,
807 const char *tocodes, char **fromcode,
808 char **tocode, CONTENT * info)
816 /* Count the tocodes */
818 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
819 if ((c1 = strchr (c, ':')) == c)
825 tcode = safe_malloc (ncodes * sizeof (char *));
826 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
827 if ((c1 = strchr (c, ':')) == c)
829 tcode[i] = str_substrdup (c, c1);
834 /* Try each fromcode in turn */
835 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
836 if ((c1 = strchr (c, ':')) == c)
838 fcode = str_substrdup (c, c1);
840 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
842 if (ret != (size_t) (-1)) {
852 /* There is only one fromcode */
853 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
855 if (ret != (size_t) (-1)) {
862 for (i = 0; i < ncodes; i++)
871 * Analyze the contents of a file to determine which MIME encoding to use.
872 * Also set the body charset, sometimes, or not.
874 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
890 if (stat (fname, &sb) == -1) {
891 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
895 if (!S_ISREG (sb.st_mode)) {
896 mutt_error (_("%s isn't a regular file."), fname);
900 if ((fp = fopen (fname, "r")) == NULL) {
901 dprint (1, (debugfile, "mutt_get_content_info: %s: %s (errno %d).\n",
902 fname, strerror (errno), errno));
906 info = safe_calloc (1, sizeof (CONTENT));
907 memset (&state, 0, sizeof (state));
909 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
910 char *chs = mutt_get_parameter ("charset", b->parameter);
911 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
912 FileCharset : Charset) : Charset;
913 if (Charset && (chs || SendCharset) &&
914 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
915 &fromcode, &tocode, info) != (size_t) (-1)) {
917 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
918 mutt_set_parameter ("charset", chsbuf, &b->parameter);
920 b->file_charset = fromcode;
928 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
929 update_content_info (info, &state, buffer, r);
930 update_content_info (info, &state, 0, 0);
934 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
935 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
937 && !mutt_is_us_ascii (Charset) ? Charset :
938 "unknown-8bit"), &b->parameter);
943 /* Given a file with path ``s'', see if there is a registered MIME type.
944 * returns the major MIME type, and copies the subtype to ``d''. First look
945 * for ~/.mime.types, then look in a system mime.types if we can find one.
946 * The longest match is used so that we can match `ps.gz' when `gz' also
950 int mutt_lookup_mime_type (BODY * att, const char *path)
954 char buf[LONG_STRING];
955 char subtype[STRING], xtype[STRING];
957 int szf, sze, cur_sze;
965 szf = safe_strlen (path);
967 for (count = 0; count < 3; count++) {
969 * can't use strtok() because we use it in an inner loop below, so use
970 * a switch statement here instead.
974 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
977 strfcpy (buf, SYSCONFDIR "/mime.types", sizeof (buf));
980 strfcpy (buf, PKGDATADIR "/mime.types", sizeof (buf));
985 "mutt_lookup_mime_type: Internal error, count = %d.\n",
987 goto bye; /* shouldn't happen */
990 if ((f = fopen (buf, "r")) != NULL) {
991 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
992 /* weed out any comments */
993 if ((p = strchr (buf, '#')))
996 /* remove any leading space. */
1000 /* position on the next field in this line */
1001 if ((p = strpbrk (ct, " \t")) == NULL)
1006 /* cycle through the file extensions */
1007 while ((p = strtok (p, " \t\n"))) {
1008 sze = safe_strlen (p);
1009 if ((sze > cur_sze) && (szf >= sze) &&
1010 (safe_strcasecmp (path + szf - sze, p) == 0
1011 || ascii_strcasecmp (path + szf - sze, p) == 0) && (szf == sze
1018 /* get the content-type */
1020 if ((p = strchr (ct, '/')) == NULL) {
1021 /* malformed line, just skip it. */
1026 for (q = p; *q && !ISSPACE (*q); q++);
1028 str_substrcpy (subtype, p, q, sizeof (subtype));
1030 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
1031 strfcpy (xtype, ct, sizeof (xtype));
1044 if (type != TYPEOTHER || *xtype != '\0') {
1046 str_replace (&att->subtype, subtype);
1047 str_replace (&att->xtype, xtype);
1053 void mutt_message_to_7bit (BODY * a, FILE * fp)
1055 char temp[_POSIX_PATH_MAX];
1061 if (!a->filename && fp)
1063 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
1064 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
1069 if (stat (a->filename, &sb) == -1) {
1070 mutt_perror ("stat");
1073 a->length = sb.st_size;
1077 if (!(fpout = safe_fopen (temp, "w+"))) {
1078 mutt_perror ("fopen");
1082 fseek (fpin, a->offset, 0);
1083 a->parts = mutt_parse_messageRFC822 (fpin, a);
1085 transform_to_7bit (a->parts, fpin);
1087 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1088 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1090 fputs ("Mime-Version: 1.0\n", fpout);
1091 mutt_write_mime_header (a->parts, fpout);
1092 fputc ('\n', fpout);
1093 mutt_write_mime_body (a->parts, fpout);
1105 a->encoding = ENC7BIT;
1106 a->d_filename = a->filename;
1107 if (a->filename && a->unlink)
1108 unlink (a->filename);
1109 a->filename = safe_strdup (temp);
1111 if (stat (a->filename, &sb) == -1) {
1112 mutt_perror ("stat");
1115 a->length = sb.st_size;
1116 mutt_free_body (&a->parts);
1117 a->hdr->content = NULL;
1120 static void transform_to_7bit (BODY * a, FILE * fpin)
1122 char buff[_POSIX_PATH_MAX];
1126 memset (&s, 0, sizeof (s));
1127 for (; a; a = a->next) {
1128 if (a->type == TYPEMULTIPART) {
1129 if (a->encoding != ENC7BIT)
1130 a->encoding = ENC7BIT;
1132 transform_to_7bit (a->parts, fpin);
1134 else if (mutt_is_message_type (a->type, a->subtype)) {
1135 mutt_message_to_7bit (a, fpin);
1139 a->force_charset = 1;
1142 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1143 mutt_perror ("fopen");
1147 mutt_decode_attachment (a, &s);
1149 a->d_filename = a->filename;
1150 a->filename = safe_strdup (buff);
1152 if (stat (a->filename, &sb) == -1) {
1153 mutt_perror ("stat");
1156 a->length = sb.st_size;
1158 mutt_update_encoding (a);
1159 if (a->encoding == ENC8BIT)
1160 a->encoding = ENCQUOTEDPRINTABLE;
1161 else if (a->encoding == ENCBINARY)
1162 a->encoding = ENCBASE64;
1167 /* determine which Content-Transfer-Encoding to use */
1168 static void mutt_set_encoding (BODY * b, CONTENT * info)
1170 char send_charset[SHORT_STRING];
1172 if (b->type == TYPETEXT) {
1174 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1175 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1176 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1177 b->encoding = ENCQUOTEDPRINTABLE;
1178 else if (info->hibin)
1179 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1181 b->encoding = ENC7BIT;
1183 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1184 if (info->lobin || info->hibin) {
1185 if (option (OPTALLOW8BIT) && !info->lobin)
1186 b->encoding = ENC8BIT;
1188 mutt_message_to_7bit (b, NULL);
1191 b->encoding = ENC7BIT;
1193 else if (b->type == TYPEAPPLICATION
1194 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1195 b->encoding = ENC7BIT;
1198 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1199 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1202 /* Determine which encoding is smaller */
1203 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1204 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1205 b->encoding = ENCBASE64;
1207 b->encoding = ENCQUOTEDPRINTABLE;
1211 b->encoding = ENC7BIT;
1215 void mutt_stamp_attachment (BODY * a)
1217 a->stamp = time (NULL);
1220 /* Get a body's character set */
1222 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1226 if (b && b->type != TYPETEXT)
1230 p = mutt_get_parameter ("charset", b->parameter);
1233 mutt_canonical_charset (d, dlen, NONULL (p));
1235 strfcpy (d, "us-ascii", dlen);
1241 /* Assumes called from send mode where BODY->filename points to actual file */
1242 void mutt_update_encoding (BODY * a)
1245 char chsbuff[STRING];
1247 /* override noconv when it's us-ascii */
1248 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1251 if (!a->force_charset && !a->noconv)
1252 mutt_delete_parameter ("charset", &a->parameter);
1254 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1257 mutt_set_encoding (a, info);
1258 mutt_stamp_attachment (a);
1265 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1267 char buffer[LONG_STRING];
1270 int cmflags, chflags;
1271 int pgp = WithCrypto ? hdr->security : 0;
1274 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1275 (hdr->security & ENCRYPT)) {
1276 if (!crypt_valid_passphrase (hdr->security))
1281 mutt_mktemp (buffer);
1282 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1285 body = mutt_new_body ();
1286 body->type = TYPEMESSAGE;
1287 body->subtype = safe_strdup ("rfc822");
1288 body->filename = safe_strdup (buffer);
1291 body->disposition = DISPINLINE;
1294 mutt_parse_mime_message (ctx, hdr);
1299 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1300 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1301 chflags |= CH_MIME | CH_TXTPLAIN;
1302 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1303 if ((WithCrypto & APPLICATION_PGP))
1305 if ((WithCrypto & APPLICATION_SMIME))
1306 pgp &= ~SMIMEENCRYPT;
1308 else if (WithCrypto && option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1309 if ((WithCrypto & APPLICATION_PGP)
1310 && mutt_is_multipart_encrypted (hdr->content)) {
1311 chflags |= CH_MIME | CH_NONEWLINE;
1312 cmflags = M_CM_DECODE_PGP;
1315 else if ((WithCrypto & APPLICATION_PGP)
1316 && (mutt_is_application_pgp (hdr->content) & PGPENCRYPT)) {
1317 chflags |= CH_MIME | CH_TXTPLAIN;
1318 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1321 else if ((WithCrypto & APPLICATION_SMIME)
1322 && mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1323 chflags |= CH_MIME | CH_TXTPLAIN;
1324 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1325 pgp &= ~SMIMEENCRYPT;
1329 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1334 body->hdr = mutt_new_header ();
1335 body->hdr->offset = 0;
1336 /* we don't need the user headers here */
1337 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1339 body->hdr->security = pgp;
1340 mutt_update_encoding (body);
1341 body->parts = body->hdr->content;
1348 BODY *mutt_make_file_attach (const char *path)
1353 att = mutt_new_body ();
1354 att->filename = safe_strdup (path);
1356 /* Attempt to determine the appropriate content-type based on the filename
1363 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1364 path)) != TYPEOTHER || *xbuf != '\0') {
1366 att->subtype = safe_strdup (buf);
1367 att->xtype = safe_strdup (xbuf);
1372 mutt_lookup_mime_type (att, path);
1376 if ((info = mutt_get_content_info (path, att)) == NULL) {
1377 mutt_free_body (&att);
1381 if (!att->subtype) {
1382 if (info->lobin == 0
1383 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1385 * Statistically speaking, there should be more than 10% "lobin"
1386 * chars if this is really a binary file...
1388 att->type = TYPETEXT;
1389 att->subtype = safe_strdup ("plain");
1392 att->type = TYPEAPPLICATION;
1393 att->subtype = safe_strdup ("octet-stream");
1397 mutt_update_encoding (att);
1401 static int get_toplevel_encoding (BODY * a)
1405 for (; a; a = a->next) {
1406 if (a->encoding == ENCBINARY)
1408 else if (a->encoding == ENC8BIT)
1415 BODY *mutt_make_multipart (BODY * b)
1419 new = mutt_new_body ();
1420 new->type = TYPEMULTIPART;
1421 new->subtype = safe_strdup ("mixed");
1422 new->encoding = get_toplevel_encoding (b);
1423 mutt_generate_boundary (&new->parameter);
1425 new->disposition = DISPINLINE;
1431 /* remove the multipart body if it exists */
1432 BODY *mutt_remove_multipart (BODY * b)
1440 mutt_free_body (&t);
1445 char *mutt_make_date (char *s, size_t len)
1447 time_t t = time (NULL);
1448 struct tm *l = localtime (&t);
1449 time_t tz = mutt_local_tz (t);
1453 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1454 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1455 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1456 (int) tz / 60, (int) abs (tz) % 60);
1460 /* wrapper around mutt_write_address() so we can handle very large
1461 recipient lists without needing a huge temporary buffer in memory */
1462 void mutt_write_address_list (ADDRESS * adr, FILE * fp, int linelen,
1466 char buf[LONG_STRING];
1474 rfc822_write_address (buf, sizeof (buf), adr, display);
1475 len = safe_strlen (buf);
1476 if (count && linelen + len > 74) {
1478 linelen = len + 8; /* tab is usually about 8 spaces... */
1481 if (count && adr->mailbox) {
1489 if (!adr->group && adr->next && adr->next->mailbox) {
1499 /* arbitrary number of elements to grow the array by */
1504 /* need to write the list in reverse because they are stored in reverse order
1505 * when parsed to speed up threading
1507 void mutt_write_references (LIST * r, FILE * f)
1510 int refcnt = 0, refmax = 0;
1512 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1513 if (refcnt == refmax)
1514 safe_realloc (&ref, (refmax += REF_INC) * sizeof (LIST *));
1518 while (refcnt-- > 0) {
1520 fputs (ref[refcnt]->data, f);
1526 /* Note: all RFC2047 encoding should be done outside of this routine, except
1527 * for the "real name." This will allow this routine to be used more than
1528 * once, if necessary.
1530 * Likewise, all IDN processing should happen outside of this routine.
1532 * mode == 1 => "lite" mode (used for edit_hdrs)
1533 * mode == 0 => normal mode. write full header + MIME headers
1534 * mode == -1 => write just the envelope info (used for postponing messages)
1536 * privacy != 0 => will omit any headers which may identify the user.
1537 * Output generated is suitable for being sent through
1538 * anonymous remailer chains.
1542 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1543 int mode, int privacy)
1545 char buffer[LONG_STRING];
1547 LIST *tmp = env->userhdrs;
1548 int has_agent = 0; /* user defined user-agent header field exists */
1551 if (!option (OPTNEWSSEND))
1553 if (mode == 0 && !privacy)
1554 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1556 /* OPTUSEFROM is not consulted here so that we can still write a From:
1557 * field if the user sets it with the `my_hdr' command
1559 if (env->from && !privacy) {
1561 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1562 fprintf (fp, "From: %s\n", buffer);
1567 mutt_write_address_list (env->to, fp, 4, 0);
1571 if (!option (OPTNEWSSEND))
1573 fputs ("To: \n", fp);
1577 mutt_write_address_list (env->cc, fp, 4, 0);
1581 if (!option (OPTNEWSSEND))
1583 fputs ("Cc: \n", fp);
1586 if (mode != 0 || option (OPTWRITEBCC)) {
1587 fputs ("Bcc: ", fp);
1588 mutt_write_address_list (env->bcc, fp, 5, 0);
1593 if (!option (OPTNEWSSEND))
1595 fputs ("Bcc: \n", fp);
1598 if (env->newsgroups)
1599 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1600 else if (mode == 1 && option (OPTNEWSSEND))
1601 fputs ("Newsgroups: \n", fp);
1603 if (env->followup_to)
1604 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1605 else if (mode == 1 && option (OPTNEWSSEND))
1606 fputs ("Followup-To: \n", fp);
1608 if (env->x_comment_to)
1609 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1610 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO))
1611 fputs ("X-Comment-To: \n", fp);
1615 fprintf (fp, "Subject: %s\n", env->subject);
1617 fputs ("Subject: \n", fp);
1619 /* save message id if the user has set it */
1620 if (env->message_id && !privacy)
1621 fprintf (fp, "Message-ID: %s\n", env->message_id);
1623 if (env->reply_to) {
1624 fputs ("Reply-To: ", fp);
1625 mutt_write_address_list (env->reply_to, fp, 10, 0);
1628 fputs ("Reply-To: \n", fp);
1630 if (env->mail_followup_to)
1632 if (!option (OPTNEWSSEND))
1635 fputs ("Mail-Followup-To: ", fp);
1636 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1640 if (env->references) {
1641 fputs ("References:", fp);
1642 mutt_write_references (env->references, fp);
1646 /* Add the MIME headers */
1647 fputs ("Mime-Version: 1.0\n", fp);
1648 mutt_write_mime_header (attach, fp);
1651 if (env->in_reply_to) {
1652 fputs ("In-Reply-To:", fp);
1653 mutt_write_references (env->in_reply_to, fp);
1657 /* Add any user defined headers */
1658 for (; tmp; tmp = tmp->next) {
1659 if ((p = strchr (tmp->data, ':'))) {
1663 continue; /* don't emit empty fields. */
1665 /* check to see if the user has overridden the user-agent field */
1666 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1672 fputs (tmp->data, fp);
1677 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1681 if (OperatingSystem != NULL) {
1682 os = OperatingSystem;
1685 if (uname (&un) == -1) {
1692 /* Add a vanity header */
1693 fprintf (fp, "User-Agent: mutt-ng %s (%s)\n", MUTT_VERSION, os);
1696 return (ferror (fp) == 0 ? 0 : -1);
1699 static void encode_headers (LIST * h)
1705 for (; h; h = h->next) {
1706 if (!(p = strchr (h->data, ':')))
1712 tmp = safe_strdup (p);
1717 rfc2047_encode_string (&tmp);
1718 safe_realloc (&h->data,
1719 safe_strlen (h->data) + 2 + safe_strlen (tmp) + 1);
1721 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1727 const char *mutt_fqdn (short may_hide_host)
1731 if (Fqdn && Fqdn[0] != '@') {
1734 if (may_hide_host && option (OPTHIDDENHOST)) {
1735 if ((p = strchr (Fqdn, '.')))
1738 /* sanity check: don't hide the host if
1739 * the fqdn is something like detebe.org.
1742 if (!p || !(q = strchr (p, '.')))
1750 static char mutt_normalized_char (char c)
1754 if (strchr (".!#$%&'*+-/=?^_`{|}~", c))
1756 return '.'; /* normalized character (we're stricter than RFC2822, 3.6.4) */
1759 static void mutt_gen_localpart (char *buf, unsigned int len, char *fmt)
1763 char tmp[SHORT_STRING];
1770 for (; *fmt; ++fmt) {
1776 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mday);
1777 safe_strncat (buf, len, tmp, 2);
1780 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_hour);
1781 safe_strncat (buf, len, tmp, 2);
1784 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mon + 1);
1785 safe_strncat (buf, len, tmp, 2);
1788 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_min);
1789 safe_strncat (buf, len, tmp, 2);
1792 snprintf (tmp, sizeof (tmp), "%lo", (unsigned long) now);
1793 safe_strncat (buf, len, tmp, safe_strlen (tmp));
1796 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) getpid ());
1797 safe_strncat (buf, len, tmp, safe_strlen (tmp));
1800 snprintf (tmp, sizeof (tmp), "%c", MsgIdPfx);
1801 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1802 safe_strncat (buf, len, tmp, 1);
1805 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) rand ());
1806 safe_strncat (buf, len, tmp, safe_strlen (tmp));
1809 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) rand ());
1810 safe_strncat (buf, len, tmp, safe_strlen (tmp));
1813 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_sec);
1814 safe_strncat (buf, len, tmp, 2);
1817 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) now);
1818 safe_strncat (buf, len, tmp, safe_strlen (tmp));
1821 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) now);
1822 safe_strncat (buf, len, tmp, safe_strlen (tmp));
1825 snprintf (tmp, sizeof (tmp), "%04d", tm->tm_year + 1900); /* this will break in the year 10000 ;-) */
1826 safe_strncat (buf, len, tmp, 4);
1829 safe_strncat (buf, len, "%", 1);
1832 safe_strncat (buf, len, ".", 1); /* invalid formats are replaced by '.' */
1839 c = mutt_normalized_char (*fmt); /* @todo: filter out invalid characters */
1840 safe_strncat (buf, len, &c, 1);
1845 char *mutt_gen_msgid (void)
1847 char buf[SHORT_STRING];
1848 char localpart[SHORT_STRING];
1849 unsigned int localpart_length;
1856 if (!(fqdn = mutt_fqdn (0)))
1857 fqdn = NONULL (Hostname);
1859 localpart_length = sizeof (buf) - safe_strlen (fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1861 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1863 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1864 return (safe_strdup (buf));
1867 static RETSIGTYPE alarm_handler (int sig)
1872 /* invoke sendmail in a subshell
1873 path (in) path to program to execute
1874 args (in) arguments to pass to program
1875 msg (in) temp file containing message to send
1876 tempfile (out) if sendmail is put in the background, this points
1877 to the temporary file containing the stdout of the
1880 send_msg (const char *path, char **args, const char *msg, char **tempfile)
1886 mutt_block_signals_system ();
1889 /* we also don't want to be stopped right now */
1890 sigaddset (&set, SIGTSTP);
1891 sigprocmask (SIG_BLOCK, &set, NULL);
1893 if (SendmailWait >= 0) {
1894 char tmp[_POSIX_PATH_MAX];
1897 *tempfile = safe_strdup (tmp);
1900 if ((pid = fork ()) == 0) {
1901 struct sigaction act, oldalrm;
1903 /* save parent's ID before setsid() */
1906 /* we want the delivery to continue even after the main process dies,
1907 * so we put ourselves into another session right away
1911 /* next we close all open files */
1912 #if defined(OPEN_MAX)
1913 for (fd = 0; fd < OPEN_MAX; fd++)
1915 #elif defined(_POSIX_OPEN_MAX)
1916 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1924 /* now the second fork() */
1925 if ((pid = fork ()) == 0) {
1926 /* "msg" will be opened as stdin */
1927 if (open (msg, O_RDONLY, 0) < 0) {
1933 if (SendmailWait >= 0) {
1934 /* *tempfile will be opened as stdout */
1935 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1938 /* redirect stderr to *tempfile too */
1943 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1945 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1952 else if (pid == -1) {
1958 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1959 * SendmailWait = 0: wait forever
1960 * SendmailWait < 0: don't wait
1962 if (SendmailWait > 0) {
1964 act.sa_handler = alarm_handler;
1966 /* need to make sure waitpid() is interrupted on SIGALRM */
1967 act.sa_flags = SA_INTERRUPT;
1971 sigemptyset (&act.sa_mask);
1972 sigaction (SIGALRM, &act, &oldalrm);
1973 alarm (SendmailWait);
1975 else if (SendmailWait < 0)
1976 _exit (0xff & EX_OK);
1978 if (waitpid (pid, &st, 0) > 0) {
1979 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1980 if (SendmailWait && st == (0xff & EX_OK)) {
1981 unlink (*tempfile); /* no longer needed */
1986 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1987 if (SendmailWait > 0) {
1993 /* reset alarm; not really needed, but... */
1995 sigaction (SIGALRM, &oldalrm, NULL);
1997 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1998 /* the parent is already dead */
2006 sigprocmask (SIG_UNBLOCK, &set, NULL);
2008 if (pid != -1 && waitpid (pid, &st, 0) > 0)
2009 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
2011 st = S_ERR; /* error */
2013 mutt_unblock_signals_system (1);
2018 static char **add_args (char **args, size_t * argslen, size_t * argsmax,
2021 for (; addr; addr = addr->next) {
2022 /* weed out group mailboxes, since those are for display only */
2023 if (addr->mailbox && !addr->group) {
2024 if (*argslen == *argsmax)
2025 safe_realloc (&args, (*argsmax += 5) * sizeof (char *));
2026 args[(*argslen)++] = addr->mailbox;
2032 static char **add_option (char **args, size_t * argslen, size_t * argsmax,
2035 if (*argslen == *argsmax)
2036 safe_realloc (&args, (*argsmax += 5) * sizeof (char *));
2037 args[(*argslen)++] = s;
2041 static const char *strsysexit (int e)
2045 for (i = 0; sysexits_h[i].str; i++) {
2046 if (e == sysexits_h[i].v)
2050 return sysexits_h[i].str;
2054 static int mutt_invoke_sendmail (ADDRESS * from, /* the sender */
2055 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
2056 const char *msg, /* file containing message */
2058 { /* message contains 8bit chars */
2059 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
2061 size_t argslen = 0, argsmax = 0;
2065 if (option (OPTNEWSSEND)) {
2066 char cmd[LONG_STRING];
2068 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
2071 i = nntp_post (msg);
2076 s = safe_strdup (cmd);
2080 s = safe_strdup (Sendmail);
2084 while ((ps = strtok (ps, " "))) {
2085 if (argslen == argsmax)
2086 safe_realloc (&args, sizeof (char *) * (argsmax += 5));
2089 args[argslen++] = ps;
2091 path = safe_strdup (ps);
2092 ps = strrchr (ps, '/');
2097 args[argslen++] = ps;
2104 if (!option (OPTNEWSSEND)) {
2106 if (eightbit && option (OPTUSE8BITMIME))
2107 args = add_option (args, &argslen, &argsmax, "-B8BITMIME");
2109 if (option (OPTENVFROM) && from && !from->next) {
2110 args = add_option (args, &argslen, &argsmax, "-f");
2111 args = add_args (args, &argslen, &argsmax, from);
2114 args = add_option (args, &argslen, &argsmax, "-N");
2115 args = add_option (args, &argslen, &argsmax, DsnNotify);
2118 args = add_option (args, &argslen, &argsmax, "-R");
2119 args = add_option (args, &argslen, &argsmax, DsnReturn);
2121 args = add_option (args, &argslen, &argsmax, "--");
2122 args = add_args (args, &argslen, &argsmax, to);
2123 args = add_args (args, &argslen, &argsmax, cc);
2124 args = add_args (args, &argslen, &argsmax, bcc);
2129 if (argslen == argsmax)
2130 safe_realloc (&args, sizeof (char *) * (++argsmax));
2132 args[argslen++] = NULL;
2134 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2136 const char *e = strsysexit (i);
2139 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2144 if (stat (childout, &st) == 0 && st.st_size > 0)
2145 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2158 if (i == (EX_OK & 0xff))
2160 else if (i == S_BKG)
2167 int mutt_invoke_mta (ADDRESS * from, /* the sender */
2168 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
2169 const char *msg, /* file containing message */
2171 { /* message contains 8bit chars */
2174 return mutt_invoke_libesmtp (from, to, cc, bcc, msg, eightbit);
2177 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2180 /* appends string 'b' to string 'a', and returns the pointer to the new
2182 char *mutt_append_string (char *a, const char *b)
2184 size_t la = safe_strlen (a);
2186 safe_realloc (&a, la + safe_strlen (b) + 1);
2187 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2191 /* returns 1 if char `c' needs to be quoted to protect from shell
2192 interpretation when executing commands in a subshell */
2193 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2195 /* returns 1 if string `s' contains characters which could cause problems
2196 when used on a command line to execute a command */
2197 int mutt_needs_quote (const char *s)
2200 if (INVALID_CHAR (*s))
2207 /* Quote a string to prevent shell escapes when this string is used on the
2208 command line to send mail. */
2209 char *mutt_quote_string (const char *s)
2214 rlen = safe_strlen (s) + 3;
2215 pr = r = (char *) safe_malloc (rlen);
2218 if (INVALID_CHAR (*s)) {
2221 safe_realloc (&r, ++rlen);
2232 /* For postponing (!final) do the necessary encodings only */
2233 void mutt_prepare_envelope (ENVELOPE * env, int final)
2235 char buffer[LONG_STRING];
2238 if (env->bcc && !(env->to || env->cc)) {
2239 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2240 * recipients if there is no To: or Cc: field, so attempt to suppress
2241 * it by using an empty To: field.
2243 env->to = rfc822_new_address ();
2245 env->to->next = rfc822_new_address ();
2248 rfc822_cat (buffer, sizeof (buffer), "undisclosed-recipients",
2251 env->to->mailbox = safe_strdup (buffer);
2254 mutt_set_followup_to (env);
2256 if (!env->message_id)
2257 env->message_id = mutt_gen_msgid ();
2260 /* Take care of 8-bit => 7-bit conversion. */
2261 rfc2047_encode_adrlist (env->to, "To");
2262 rfc2047_encode_adrlist (env->cc, "Cc");
2263 rfc2047_encode_adrlist (env->bcc, "Bcc");
2264 rfc2047_encode_adrlist (env->from, "From");
2265 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2266 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2270 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2273 rfc2047_encode_string (&env->subject);
2275 encode_headers (env->userhdrs);
2278 void mutt_unprepare_envelope (ENVELOPE * env)
2282 for (item = env->userhdrs; item; item = item->next)
2283 rfc2047_decode (&item->data);
2285 rfc822_free_address (&env->mail_followup_to);
2287 /* back conversions */
2288 rfc2047_decode_adrlist (env->to);
2289 rfc2047_decode_adrlist (env->cc);
2290 rfc2047_decode_adrlist (env->bcc);
2291 rfc2047_decode_adrlist (env->from);
2292 rfc2047_decode_adrlist (env->reply_to);
2293 rfc2047_decode (&env->subject);
2296 static int _mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to,
2297 const char *resent_from, ADDRESS * env_from)
2301 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2302 MESSAGE *msg = NULL;
2305 /* Try to bounce each message out, aborting if we get any failures. */
2306 for (i = 0; i < Context->msgcount; i++)
2307 if (Context->hdrs[i]->tagged)
2309 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2314 /* If we failed to open a message, return with error */
2315 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2321 mutt_mktemp (tempfile);
2322 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2323 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2325 if (!option (OPTBOUNCEDELIVERED))
2326 ch_flags |= CH_WEED_DELIVERED;
2328 fseek (fp, h->offset, 0);
2329 fprintf (f, "Resent-From: %s", resent_from);
2330 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2331 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2332 fputs ("Resent-To: ", f);
2333 mutt_write_address_list (to, f, 11, 0);
2334 mutt_copy_header (fp, h, f, ch_flags, NULL);
2336 mutt_copy_bytes (fp, f, h->content->length);
2339 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2340 h->content->encoding == ENC8BIT);
2344 mx_close_message (&msg);
2349 int mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to)
2352 const char *fqdn = mutt_fqdn (1);
2353 char resent_from[STRING];
2357 resent_from[0] = '\0';
2358 from = mutt_default_from ();
2361 rfc822_qualify (from, fqdn);
2363 rfc2047_encode_adrlist (from, "Resent-From");
2364 if (mutt_addrlist_to_idna (from, &err)) {
2365 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2368 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2371 unset_option (OPTNEWSSEND);
2374 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2376 rfc822_free_address (&from);
2382 /* given a list of addresses, return a list of unique addresses */
2383 ADDRESS *mutt_remove_duplicates (ADDRESS * addr)
2385 ADDRESS *top = addr;
2386 ADDRESS **last = ⊤
2391 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2392 if (tmp->mailbox && addr->mailbox &&
2393 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2400 dprint (2, (debugfile, "mutt_remove_duplicates: Removing %s\n",
2406 rfc822_free_address (&addr);
2419 static void set_noconv_flags (BODY * b, short flag)
2421 for (; b; b = b->next) {
2422 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2423 set_noconv_flags (b->parts, flag);
2424 else if (b->type == TYPETEXT && b->noconv) {
2426 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2428 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2433 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2434 int post, char *fcc)
2438 char tempfile[_POSIX_PATH_MAX];
2439 FILE *tempfp = NULL;
2443 set_noconv_flags (hdr->content, 1);
2445 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2448 "mutt_write_fcc(): unable to open mailbox %s in append-mode, aborting.\n",
2453 /* We need to add a Content-Length field to avoid problems where a line in
2454 * the message body begins with "From "
2456 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2457 mutt_mktemp (tempfile);
2458 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2459 mutt_perror (tempfile);
2460 mx_close_mailbox (&f, NULL);
2465 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2466 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2467 mx_close_mailbox (&f, NULL);
2471 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2472 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2474 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2477 /* (postponment) if this was a reply of some sort, <msgid> contians the
2478 * Message-ID: of message replied to. Save it using a special X-Mutt-
2479 * header so it can be picked up if the message is recalled at a later
2480 * point in time. This will allow the message to be marked as replied if
2481 * the same mailbox is still open.
2484 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2486 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2487 * it can be picked up when the message is recalled
2490 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2491 fprintf (msg->fp, "Status: RO\n");
2495 /* (postponment) if the mail is to be signed or encrypted, save this info */
2496 if ((WithCrypto & APPLICATION_PGP)
2497 && post && (hdr->security & APPLICATION_PGP)) {
2498 fputs ("X-Mutt-PGP: ", msg->fp);
2499 if (hdr->security & ENCRYPT)
2500 fputc ('E', msg->fp);
2501 if (hdr->security & SIGN) {
2502 fputc ('S', msg->fp);
2503 if (PgpSignAs && *PgpSignAs)
2504 fprintf (msg->fp, "<%s>", PgpSignAs);
2506 if (hdr->security & INLINE)
2507 fputc ('I', msg->fp);
2508 fputc ('\n', msg->fp);
2511 /* (postponment) if the mail is to be signed or encrypted, save this info */
2512 if ((WithCrypto & APPLICATION_SMIME)
2513 && post && (hdr->security & APPLICATION_SMIME)) {
2514 fputs ("X-Mutt-SMIME: ", msg->fp);
2515 if (hdr->security & ENCRYPT) {
2516 fputc ('E', msg->fp);
2517 if (SmimeCryptAlg && *SmimeCryptAlg)
2518 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2520 if (hdr->security & SIGN) {
2521 fputc ('S', msg->fp);
2522 if (SmimeDefaultKey && *SmimeDefaultKey)
2523 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2525 if (hdr->security & INLINE)
2526 fputc ('I', msg->fp);
2527 fputc ('\n', msg->fp);
2531 /* (postponement) if the mail is to be sent through a mixmaster
2532 * chain, save that information
2535 if (post && hdr->chain && hdr->chain) {
2538 fputs ("X-Mutt-Mix:", msg->fp);
2539 for (p = hdr->chain; p; p = p->next)
2540 fprintf (msg->fp, " %s", (char *) p->data);
2542 fputc ('\n', msg->fp);
2547 char sasha[LONG_STRING];
2550 mutt_write_mime_body (hdr->content, tempfp);
2552 /* make sure the last line ends with a newline. Emacs doesn't ensure
2553 * this will happen, and it can cause problems parsing the mailbox
2556 fseek (tempfp, -1, 2);
2557 if (fgetc (tempfp) != '\n') {
2558 fseek (tempfp, 0, 2);
2559 fputc ('\n', tempfp);
2563 if (ferror (tempfp)) {
2565 (debugfile, "mutt_write_fcc(): %s: write failed.\n", tempfile));
2568 mx_commit_message (msg, &f); /* XXX - really? */
2569 mx_close_message (&msg);
2570 mx_close_mailbox (&f, NULL);
2574 /* count the number of lines */
2576 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2578 fprintf (msg->fp, "Content-Length: %ld\n", (long) ftell (tempfp));
2579 fprintf (msg->fp, "Lines: %d\n\n", lines);
2581 /* copy the body and clean up */
2583 r = mutt_copy_stream (tempfp, msg->fp);
2584 if (fclose (tempfp) != 0)
2586 /* if there was an error, leave the temp version */
2591 fputc ('\n', msg->fp); /* finish off the header */
2592 r = mutt_write_mime_body (hdr->content, msg->fp);
2595 if (mx_commit_message (msg, &f) != 0)
2597 mx_close_message (&msg);
2598 mx_close_mailbox (&f, NULL);
2601 set_noconv_flags (hdr->content, 0);