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