2 * Copyright notice from original mutt:
3 * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
5 * This file is part of mutt-ng, see http://www.muttng.org/.
6 * It's licensed under the GNU General Public License,
7 * please see the file GPL in the top level source directory.
17 #include "mutt_curses.h"
25 #include "mutt_crypt.h"
26 #include "mutt_idna.h"
31 #include "lib/debug.h"
42 #include <sys/utsname.h>
45 # include "mutt_libesmtp.h"
46 #endif /* USE_LIBESMTP */
52 #ifdef HAVE_SYSEXITS_H
54 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
58 /* If you are debugging this file, comment out the following line. */
67 extern char RFC822Specials[];
69 static struct sysexits {
75 0xff & EX_USAGE, "Bad usage."},
79 0xff & EX_DATAERR, "Data format error."},
83 0xff & EX_NOINPUT, "Cannot open input."},
87 0xff & EX_NOUSER, "User unknown."},
91 0xff & EX_NOHOST, "Host unknown."},
95 0xff & EX_UNAVAILABLE, "Service unavailable."},
99 0xff & EX_SOFTWARE, "Internal error."},
103 0xff & EX_OSERR, "Operating system error."},
107 0xff & EX_OSFILE, "System file missing."},
111 0xff & EX_CANTCREAT, "Can't create output."},
115 0xff & EX_IOERR, "I/O error."},
119 0xff & EX_TEMPFAIL, "Deferred."},
123 0xff & EX_PROTOCOL, "Remote protocol error."},
127 0xff & EX_NOPERM, "Insufficient permission."},
131 0xff & EX_NOPERM, "Local configuration error."},
134 S_ERR, "Exec error."}, {
140 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
142 const char MimeSpecials[] = "@.,;:<>[]\\\"()?/= \t";
144 char B64Chars[64] = {
145 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
146 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
147 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
148 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
152 static char MsgIdPfx = 'A';
154 static void transform_to_7bit (BODY * a, FILE * fpin);
156 static void encode_quoted (FGETCONV * fc, FILE * fout, int istext)
159 char line[77], savechar;
161 while ((c = fgetconv (fc)) != EOF) {
162 /* Wrap the line if needed. */
163 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
164 /* If the last character is "quoted", then be sure to move all three
165 * characters to the next line. Otherwise, just move the last
168 if (line[linelen - 3] == '=') {
169 line[linelen - 3] = 0;
174 line[1] = line[linelen - 2];
175 line[2] = line[linelen - 1];
179 savechar = line[linelen - 1];
180 line[linelen - 1] = '=';
189 /* Escape lines that begin with/only contain "the message separator". */
190 if (linelen == 4 && !safe_strncmp ("From", line, 4)) {
191 strfcpy (line, "=46rom", sizeof (line));
194 else if (linelen == 4 && !safe_strncmp ("from", line, 4)) {
195 strfcpy (line, "=66rom", sizeof (line));
198 else if (linelen == 1 && line[0] == '.') {
199 strfcpy (line, "=2E", sizeof (line));
204 if (c == '\n' && istext) {
205 /* Check to make sure there is no trailing space on this line. */
207 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
209 sprintf (line + linelen - 1, "=%2.2X",
210 (unsigned char) line[linelen - 1]);
214 int savechar = line[linelen - 1];
216 line[linelen - 1] = '=';
219 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
229 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
230 /* Check to make sure there is enough room for the quoted character.
231 * If not, wrap to the next line.
234 line[linelen++] = '=';
240 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
244 /* Don't worry about wrapping the line here. That will happen during
245 * the next iteration when I'll also know what the next character is.
251 /* Take care of anything left in the buffer */
253 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
254 /* take care of trailing whitespace */
256 sprintf (line + linelen - 1, "=%2.2X",
257 (unsigned char) line[linelen - 1]);
259 savechar = line[linelen - 1];
260 line[linelen - 1] = '=';
264 sprintf (line, "=%2.2X", (unsigned char) savechar);
273 static char b64_buffer[3];
274 static short b64_num;
275 static short b64_linelen;
277 static void b64_flush (FILE * fout)
284 if (b64_linelen >= 72) {
289 for (i = b64_num; i < 3; i++)
290 b64_buffer[i] = '\0';
292 fputc (B64Chars[(b64_buffer[0] >> 2) & 0x3f], fout);
295 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
300 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
304 fputc (B64Chars[b64_buffer[2] & 0x3f], fout);
309 while (b64_linelen % 4) {
318 static void b64_putc (char c, FILE * fout)
323 b64_buffer[b64_num++] = c;
327 static void encode_base64 (FGETCONV * fc, FILE * fout, int istext)
331 b64_num = b64_linelen = 0;
333 while ((ch = fgetconv (fc)) != EOF) {
334 if (istext && ch == '\n' && ch1 != '\r')
335 b64_putc ('\r', fout);
343 static void encode_8bit (FGETCONV * fc, FILE * fout, int istext)
347 while ((ch = fgetconv (fc)) != EOF)
352 int mutt_write_mime_header (BODY * a, FILE * f)
362 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
365 len = 25 + safe_strlen (a->subtype); /* approximate len. of content-type */
367 for (p = a->parameter; p; p = p->next) {
376 tmp = safe_strdup (p->value);
377 encode = rfc2231_encode_string (&tmp);
378 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
380 /* Dirty hack to make messages readable by Outlook Express
381 * for the Mac: force quotes around the boundary parameter
382 * even when they aren't needed.
385 if (!ascii_strcasecmp (p->attribute, "boundary")
386 && !strcmp (buffer, tmp))
387 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
391 tmplen = safe_strlen (buffer) + safe_strlen (p->attribute) + 1;
393 if (len + tmplen + 2 > 76) {
402 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
410 fprintf (f, "Content-Description: %s\n", a->description);
412 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
415 if (!(fn = a->d_filename))
421 /* Strip off the leading path... */
422 if ((t = strrchr (fn, '/')))
428 tmp = safe_strdup (t);
429 encode = rfc2231_encode_string (&tmp);
430 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
432 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
438 if (a->encoding != ENC7BIT)
439 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
441 /* Do NOT add the terminator here!!! */
442 return (ferror (f) ? -1 : 0);
445 # define write_as_text_part(a) (mutt_is_text_part(a) \
446 || ((WithCrypto & APPLICATION_PGP)\
447 && mutt_is_application_pgp(a)))
449 int mutt_write_mime_body (BODY * a, FILE * f)
451 char *p, boundary[SHORT_STRING];
452 char send_charset[SHORT_STRING];
457 if (a->type == TYPEMULTIPART) {
458 /* First, find the boundary to use */
459 if (!(p = mutt_get_parameter ("boundary", a->parameter))) {
460 debug_print (1, ("no boundary parameter found!\n"));
461 mutt_error _("No boundary parameter found! [report this error]");
465 strfcpy (boundary, p, sizeof (boundary));
467 for (t = a->parts; t; t = t->next) {
468 fprintf (f, "\n--%s\n", boundary);
469 if (mutt_write_mime_header (t, f) == -1)
472 if (mutt_write_mime_body (t, f) == -1)
475 fprintf (f, "\n--%s--\n", boundary);
476 return (ferror (f) ? -1 : 0);
479 /* This is pretty gross, but it's the best solution for now... */
480 if ((WithCrypto & APPLICATION_PGP)
481 && a->type == TYPEAPPLICATION
482 && safe_strcmp (a->subtype, "pgp-encrypted") == 0) {
483 fputs ("Version: 1\n", f);
487 if ((fpin = fopen (a->filename, "r")) == NULL) {
488 debug_print (1, ("%s no longer exists!\n", a->filename));
489 mutt_error (_("%s no longer exists!"), a->filename);
493 if (a->type == TYPETEXT && (!a->noconv))
494 fc = fgetconv_open (fpin, a->file_charset,
495 mutt_get_body_charset (send_charset,
496 sizeof (send_charset), a), 0);
498 fc = fgetconv_open (fpin, 0, 0, 0);
500 if (a->encoding == ENCQUOTEDPRINTABLE)
501 encode_quoted (fc, f, write_as_text_part (a));
502 else if (a->encoding == ENCBASE64)
503 encode_base64 (fc, f, write_as_text_part (a));
504 else if (a->type == TYPETEXT && (!a->noconv))
505 encode_8bit (fc, f, write_as_text_part (a));
507 mutt_copy_stream (fpin, f);
509 fgetconv_close (&fc);
512 return (ferror (f) ? -1 : 0);
515 #undef write_as_text_part
517 #define BOUNDARYLEN 16
518 void mutt_generate_boundary (PARAMETER ** parm)
520 char rs[BOUNDARYLEN + 1];
525 for (i = 0; i < BOUNDARYLEN; i++)
526 *p++ = B64Chars[LRAND () % sizeof (B64Chars)];
529 mutt_set_parameter ("boundary", rs, parm);
541 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
545 int whitespace = s->whitespace;
547 int linelen = s->linelen;
548 int was_cr = s->was_cr;
550 if (!d) { /* This signals EOF */
553 if (linelen > info->linemax)
554 info->linemax = linelen;
559 for (; dlen; d++, dlen--) {
572 if (linelen > info->linemax)
573 info->linemax = linelen;
588 if (linelen > info->linemax)
589 info->linemax = linelen;
594 else if (ch == '\r') {
602 else if (ch == '\t' || ch == '\f') {
606 else if (ch < 32 || ch == 127)
610 if ((ch == 'F') || (ch == 'f'))
620 if (linelen == 2 && ch != 'r')
622 else if (linelen == 3 && ch != 'o')
624 else if (linelen == 4) {
637 if (ch != ' ' && ch != '\t')
642 s->whitespace = whitespace;
644 s->linelen = linelen;
649 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
650 #define BUGGY_ICONV 1
653 * Find the best charset conversion of the file from fromcode into one
654 * of the tocodes. If successful, set *tocode and CONTENT *info and
655 * return the number of characters converted inexactly. If no
656 * conversion was possible, return -1.
658 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
659 * which would otherwise prevent us from knowing the number of inexact
660 * conversions. Where the candidate target charset is UTF-8 we avoid
661 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
662 * fails with some libraries.
664 * We assume that the output from iconv is never more than 4 times as
665 * long as the input for any pair of charsets we might be interested
668 static size_t convert_file_to (FILE * file, const char *fromcode,
669 int ncodes, const char **tocodes,
670 int *tocode, CONTENT * info)
674 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
675 ICONV_CONST char *ib, *ub;
677 size_t ibl, obl, ubl, ubl1, n, ret;
680 CONTENT_STATE *states;
683 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
684 if (cd1 == (iconv_t) (-1))
687 cd = safe_calloc (ncodes, sizeof (iconv_t));
688 score = safe_calloc (ncodes, sizeof (size_t));
689 states = safe_calloc (ncodes, sizeof (CONTENT_STATE));
690 infos = safe_calloc (ncodes, sizeof (CONTENT));
692 for (i = 0; i < ncodes; i++)
693 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
694 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
696 /* Special case for conversion to UTF-8 */
697 cd[i] = (iconv_t) (-1), score[i] = (size_t) (-1);
703 /* Try to fill input buffer */
704 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
707 /* Convert to UTF-8 */
709 ob = bufu, obl = sizeof (bufu);
710 n = iconv (cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
711 assert (n == (size_t) (-1) || !n || ICONV_NONTRANS);
712 if (n == (size_t) (-1) &&
713 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
714 assert (errno == EILSEQ ||
715 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
721 /* Convert from UTF-8 */
722 for (i = 0; i < ncodes; i++)
723 if (cd[i] != (iconv_t) (-1) && score[i] != (size_t) (-1)) {
724 ub = bufu, ubl = ubl1;
725 ob = bufo, obl = sizeof (bufo);
726 n = iconv (cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
727 if (n == (size_t) (-1)) {
728 assert (errno == E2BIG ||
729 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
730 score[i] = (size_t) (-1);
734 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
737 else if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1))
738 /* Special case for conversion to UTF-8 */
739 update_content_info (&infos[i], &states[i], bufu, ubl1);
742 /* Save unused input */
743 memmove (bufi, ib, ibl);
744 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
751 /* Find best score */
753 for (i = 0; i < ncodes; i++) {
754 if (cd[i] == (iconv_t) (-1) && score[i] == (size_t) (-1)) {
755 /* Special case for conversion to UTF-8 */
760 else if (cd[i] == (iconv_t) (-1) || score[i] == (size_t) (-1))
762 else if (ret == (size_t) (-1) || score[i] < ret) {
769 if (ret != (size_t) (-1)) {
770 memcpy (info, &infos[*tocode], sizeof (CONTENT));
771 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
775 for (i = 0; i < ncodes; i++)
776 if (cd[i] != (iconv_t) (-1))
788 #endif /* !HAVE_ICONV */
792 * Find the first of the fromcodes that gives a valid conversion and
793 * the best charset conversion of the file into one of the tocodes. If
794 * successful, set *fromcode and *tocode to dynamically allocated
795 * strings, set CONTENT *info, and return the number of characters
796 * converted inexactly. If no conversion was possible, return -1.
798 * Both fromcodes and tocodes may be colon-separated lists of charsets.
799 * However, if fromcode is zero then fromcodes is assumed to be the
800 * name of a single charset even if it contains a colon.
802 static size_t convert_file_from_to (FILE * file,
803 const char *fromcodes,
804 const char *tocodes, char **fromcode,
805 char **tocode, CONTENT * info)
813 /* Count the tocodes */
815 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
816 if ((c1 = strchr (c, ':')) == c)
822 tcode = safe_malloc (ncodes * sizeof (char *));
823 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
824 if ((c1 = strchr (c, ':')) == c)
826 tcode[i] = str_substrdup (c, c1);
831 /* Try each fromcode in turn */
832 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
833 if ((c1 = strchr (c, ':')) == c)
835 fcode = str_substrdup (c, c1);
837 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
839 if (ret != (size_t) (-1)) {
849 /* There is only one fromcode */
850 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
852 if (ret != (size_t) (-1)) {
859 for (i = 0; i < ncodes; i++)
868 * Analyze the contents of a file to determine which MIME encoding to use.
869 * Also set the body charset, sometimes, or not.
871 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
887 if (stat (fname, &sb) == -1) {
888 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
892 if (!S_ISREG (sb.st_mode)) {
893 mutt_error (_("%s isn't a regular file."), fname);
897 if ((fp = fopen (fname, "r")) == NULL) {
898 debug_print (1, ("%s: %s (errno %d).\n", fname, strerror (errno), errno));
902 info = safe_calloc (1, sizeof (CONTENT));
903 memset (&state, 0, sizeof (state));
905 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
906 char *chs = mutt_get_parameter ("charset", b->parameter);
907 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
908 FileCharset : Charset) : Charset;
909 if (Charset && (chs || SendCharset) &&
910 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
911 &fromcode, &tocode, info) != (size_t) (-1)) {
913 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
914 mutt_set_parameter ("charset", chsbuf, &b->parameter);
916 b->file_charset = fromcode;
924 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
925 update_content_info (info, &state, buffer, r);
926 update_content_info (info, &state, 0, 0);
930 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
931 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
933 && !mutt_is_us_ascii (Charset) ? Charset :
934 "unknown-8bit"), &b->parameter);
939 /* Given a file with path ``s'', see if there is a registered MIME type.
940 * returns the major MIME type, and copies the subtype to ``d''. First look
941 * for ~/.mime.types, then look in a system mime.types if we can find one.
942 * The longest match is used so that we can match `ps.gz' when `gz' also
946 int mutt_lookup_mime_type (BODY * att, const char *path)
950 char buf[LONG_STRING];
951 char subtype[STRING], xtype[STRING];
953 int szf, sze, cur_sze;
961 szf = safe_strlen (path);
963 for (count = 0; count < 3; count++) {
965 * can't use strtok() because we use it in an inner loop below, so use
966 * a switch statement here instead.
970 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
973 strfcpy (buf, SYSCONFDIR "/muttng-mime.types", sizeof (buf));
976 strfcpy (buf, PKGDATADIR "/mime.types", sizeof (buf));
979 debug_print (1, ("Internal error, count = %d.\n", count));
980 goto bye; /* shouldn't happen */
983 if ((f = fopen (buf, "r")) != NULL) {
984 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
985 /* weed out any comments */
986 if ((p = strchr (buf, '#')))
989 /* remove any leading space. */
993 /* position on the next field in this line */
994 if ((p = strpbrk (ct, " \t")) == NULL)
999 /* cycle through the file extensions */
1000 while ((p = strtok (p, " \t\n"))) {
1001 sze = safe_strlen (p);
1002 if ((sze > cur_sze) && (szf >= sze) &&
1003 (safe_strcasecmp (path + szf - sze, p) == 0
1004 || ascii_strcasecmp (path + szf - sze, p) == 0) && (szf == sze
1011 /* get the content-type */
1013 if ((p = strchr (ct, '/')) == NULL) {
1014 /* malformed line, just skip it. */
1019 for (q = p; *q && !ISSPACE (*q); q++);
1021 str_substrcpy (subtype, p, q, sizeof (subtype));
1023 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
1024 strfcpy (xtype, ct, sizeof (xtype));
1037 if (type != TYPEOTHER || *xtype != '\0') {
1039 str_replace (&att->subtype, subtype);
1040 str_replace (&att->xtype, xtype);
1046 void mutt_message_to_7bit (BODY * a, FILE * fp)
1048 char temp[_POSIX_PATH_MAX];
1054 if (!a->filename && fp)
1056 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
1057 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
1062 if (stat (a->filename, &sb) == -1) {
1063 mutt_perror ("stat");
1066 a->length = sb.st_size;
1070 if (!(fpout = safe_fopen (temp, "w+"))) {
1071 mutt_perror ("fopen");
1075 fseek (fpin, a->offset, 0);
1076 a->parts = mutt_parse_messageRFC822 (fpin, a);
1078 transform_to_7bit (a->parts, fpin);
1080 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1081 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1083 fputs ("Mime-Version: 1.0\n", fpout);
1084 mutt_write_mime_header (a->parts, fpout);
1085 fputc ('\n', fpout);
1086 mutt_write_mime_body (a->parts, fpout);
1098 a->encoding = ENC7BIT;
1099 a->d_filename = a->filename;
1100 if (a->filename && a->unlink)
1101 unlink (a->filename);
1102 a->filename = safe_strdup (temp);
1104 if (stat (a->filename, &sb) == -1) {
1105 mutt_perror ("stat");
1108 a->length = sb.st_size;
1109 mutt_free_body (&a->parts);
1110 a->hdr->content = NULL;
1113 static void transform_to_7bit (BODY * a, FILE * fpin)
1115 char buff[_POSIX_PATH_MAX];
1119 memset (&s, 0, sizeof (s));
1120 for (; a; a = a->next) {
1121 if (a->type == TYPEMULTIPART) {
1122 if (a->encoding != ENC7BIT)
1123 a->encoding = ENC7BIT;
1125 transform_to_7bit (a->parts, fpin);
1127 else if (mutt_is_message_type (a->type, a->subtype)) {
1128 mutt_message_to_7bit (a, fpin);
1132 a->force_charset = 1;
1135 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1136 mutt_perror ("fopen");
1140 mutt_decode_attachment (a, &s);
1142 a->d_filename = a->filename;
1143 a->filename = safe_strdup (buff);
1145 if (stat (a->filename, &sb) == -1) {
1146 mutt_perror ("stat");
1149 a->length = sb.st_size;
1151 mutt_update_encoding (a);
1152 if (a->encoding == ENC8BIT)
1153 a->encoding = ENCQUOTEDPRINTABLE;
1154 else if (a->encoding == ENCBINARY)
1155 a->encoding = ENCBASE64;
1160 /* determine which Content-Transfer-Encoding to use */
1161 static void mutt_set_encoding (BODY * b, CONTENT * info)
1163 char send_charset[SHORT_STRING];
1165 if (b->type == TYPETEXT) {
1167 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1168 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1169 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1170 b->encoding = ENCQUOTEDPRINTABLE;
1171 else if (info->hibin)
1172 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1174 b->encoding = ENC7BIT;
1176 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1177 if (info->lobin || info->hibin) {
1178 if (option (OPTALLOW8BIT) && !info->lobin)
1179 b->encoding = ENC8BIT;
1181 mutt_message_to_7bit (b, NULL);
1184 b->encoding = ENC7BIT;
1186 else if (b->type == TYPEAPPLICATION
1187 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1188 b->encoding = ENC7BIT;
1191 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1192 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1195 /* Determine which encoding is smaller */
1196 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1197 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1198 b->encoding = ENCBASE64;
1200 b->encoding = ENCQUOTEDPRINTABLE;
1204 b->encoding = ENC7BIT;
1208 void mutt_stamp_attachment (BODY * a)
1210 a->stamp = time (NULL);
1213 /* Get a body's character set */
1215 char *mutt_get_body_charset (char *d, size_t dlen, BODY * b)
1219 if (b && b->type != TYPETEXT)
1223 p = mutt_get_parameter ("charset", b->parameter);
1226 mutt_canonical_charset (d, dlen, NONULL (p));
1228 strfcpy (d, "us-ascii", dlen);
1234 /* Assumes called from send mode where BODY->filename points to actual file */
1235 void mutt_update_encoding (BODY * a)
1238 char chsbuff[STRING];
1240 /* override noconv when it's us-ascii */
1241 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1244 if (!a->force_charset && !a->noconv)
1245 mutt_delete_parameter ("charset", &a->parameter);
1247 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1250 mutt_set_encoding (a, info);
1251 mutt_stamp_attachment (a);
1258 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1260 char buffer[LONG_STRING];
1263 int cmflags, chflags;
1264 int pgp = WithCrypto ? hdr->security : 0;
1267 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1268 (hdr->security & ENCRYPT)) {
1269 if (!crypt_valid_passphrase (hdr->security))
1274 mutt_mktemp (buffer);
1275 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1278 body = mutt_new_body ();
1279 body->type = TYPEMESSAGE;
1280 body->subtype = safe_strdup ("rfc822");
1281 body->filename = safe_strdup (buffer);
1284 body->disposition = DISPINLINE;
1287 mutt_parse_mime_message (ctx, hdr);
1292 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1293 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1294 chflags |= CH_MIME | CH_TXTPLAIN;
1295 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1296 if ((WithCrypto & APPLICATION_PGP))
1298 if ((WithCrypto & APPLICATION_SMIME))
1299 pgp &= ~SMIMEENCRYPT;
1301 else if (WithCrypto && option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1302 if ((WithCrypto & APPLICATION_PGP)
1303 && mutt_is_multipart_encrypted (hdr->content)) {
1304 chflags |= CH_MIME | CH_NONEWLINE;
1305 cmflags = M_CM_DECODE_PGP;
1308 else if ((WithCrypto & APPLICATION_PGP)
1309 && (mutt_is_application_pgp (hdr->content) & PGPENCRYPT)) {
1310 chflags |= CH_MIME | CH_TXTPLAIN;
1311 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1314 else if ((WithCrypto & APPLICATION_SMIME)
1315 && mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1316 chflags |= CH_MIME | CH_TXTPLAIN;
1317 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1318 pgp &= ~SMIMEENCRYPT;
1322 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1327 body->hdr = mutt_new_header ();
1328 body->hdr->offset = 0;
1329 /* we don't need the user headers here */
1330 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1332 body->hdr->security = pgp;
1333 mutt_update_encoding (body);
1334 body->parts = body->hdr->content;
1341 BODY *mutt_make_file_attach (const char *path)
1346 att = mutt_new_body ();
1347 att->filename = safe_strdup (path);
1349 /* Attempt to determine the appropriate content-type based on the filename
1356 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1357 path)) != TYPEOTHER || *xbuf != '\0') {
1359 att->subtype = safe_strdup (buf);
1360 att->xtype = safe_strdup (xbuf);
1365 mutt_lookup_mime_type (att, path);
1369 if ((info = mutt_get_content_info (path, att)) == NULL) {
1370 mutt_free_body (&att);
1374 if (!att->subtype) {
1375 if (info->lobin == 0
1376 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1378 * Statistically speaking, there should be more than 10% "lobin"
1379 * chars if this is really a binary file...
1381 att->type = TYPETEXT;
1382 att->subtype = safe_strdup ("plain");
1385 att->type = TYPEAPPLICATION;
1386 att->subtype = safe_strdup ("octet-stream");
1390 mutt_update_encoding (att);
1394 static int get_toplevel_encoding (BODY * a)
1398 for (; a; a = a->next) {
1399 if (a->encoding == ENCBINARY)
1401 else if (a->encoding == ENC8BIT)
1408 BODY *mutt_make_multipart (BODY * b)
1412 new = mutt_new_body ();
1413 new->type = TYPEMULTIPART;
1414 new->subtype = safe_strdup ("mixed");
1415 new->encoding = get_toplevel_encoding (b);
1416 mutt_generate_boundary (&new->parameter);
1418 new->disposition = DISPINLINE;
1424 /* remove the multipart body if it exists */
1425 BODY *mutt_remove_multipart (BODY * b)
1433 mutt_free_body (&t);
1438 char *mutt_make_date (char *s, size_t len)
1440 time_t t = time (NULL);
1441 struct tm *l = localtime (&t);
1442 time_t tz = mutt_local_tz (t);
1446 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1447 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1448 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1449 (int) tz / 60, (int) abs (tz) % 60);
1453 /* wrapper around mutt_write_address() so we can handle very large
1454 recipient lists without needing a huge temporary buffer in memory */
1455 void mutt_write_address_list (ADDRESS * adr, FILE * fp, int linelen,
1459 char buf[LONG_STRING];
1467 rfc822_write_address (buf, sizeof (buf), adr, display);
1468 len = safe_strlen (buf);
1469 if (count && linelen + len > 74) {
1471 linelen = len + 8; /* tab is usually about 8 spaces... */
1474 if (count && adr->mailbox) {
1482 if (!adr->group && adr->next && adr->next->mailbox) {
1492 /* arbitrary number of elements to grow the array by */
1497 /* need to write the list in reverse because they are stored in reverse order
1498 * when parsed to speed up threading
1500 void mutt_write_references (LIST * r, FILE * f)
1503 int refcnt = 0, refmax = 0;
1505 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1506 if (refcnt == refmax)
1507 safe_realloc (&ref, (refmax += REF_INC) * sizeof (LIST *));
1511 while (refcnt-- > 0) {
1513 fputs (ref[refcnt]->data, f);
1519 /* Note: all RFC2047 encoding should be done outside of this routine, except
1520 * for the "real name." This will allow this routine to be used more than
1521 * once, if necessary.
1523 * Likewise, all IDN processing should happen outside of this routine.
1525 * mode == 1 => "lite" mode (used for edit_hdrs)
1526 * mode == 0 => normal mode. write full header + MIME headers
1527 * mode == -1 => write just the envelope info (used for postponing messages)
1529 * privacy != 0 => will omit any headers which may identify the user.
1530 * Output generated is suitable for being sent through
1531 * anonymous remailer chains.
1535 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1536 int mode, int privacy)
1538 char buffer[LONG_STRING];
1540 LIST *tmp = env->userhdrs;
1541 int has_agent = 0; /* user defined user-agent header field exists */
1544 if (!option (OPTNEWSSEND))
1546 if (mode == 0 && !privacy)
1547 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1549 /* OPTUSEFROM is not consulted here so that we can still write a From:
1550 * field if the user sets it with the `my_hdr' command
1552 if (env->from && !privacy) {
1554 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1555 fprintf (fp, "From: %s\n", buffer);
1560 mutt_write_address_list (env->to, fp, 4, 0);
1564 if (!option (OPTNEWSSEND))
1566 fputs ("To: \n", fp);
1570 mutt_write_address_list (env->cc, fp, 4, 0);
1574 if (!option (OPTNEWSSEND))
1576 fputs ("Cc: \n", fp);
1579 if (mode != 0 || option (OPTWRITEBCC)) {
1580 fputs ("Bcc: ", fp);
1581 mutt_write_address_list (env->bcc, fp, 5, 0);
1586 if (!option (OPTNEWSSEND))
1588 fputs ("Bcc: \n", fp);
1591 if (env->newsgroups)
1592 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1593 else if (mode == 1 && option (OPTNEWSSEND))
1594 fputs ("Newsgroups: \n", fp);
1596 if (env->followup_to)
1597 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1598 else if (mode == 1 && option (OPTNEWSSEND))
1599 fputs ("Followup-To: \n", fp);
1601 if (env->x_comment_to)
1602 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1603 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO))
1604 fputs ("X-Comment-To: \n", fp);
1608 fprintf (fp, "Subject: %s\n", env->subject);
1610 fputs ("Subject: \n", fp);
1612 /* save message id if the user has set it */
1613 if (env->message_id && !privacy)
1614 fprintf (fp, "Message-ID: %s\n", env->message_id);
1616 if (env->reply_to) {
1617 fputs ("Reply-To: ", fp);
1618 mutt_write_address_list (env->reply_to, fp, 10, 0);
1621 fputs ("Reply-To: \n", fp);
1623 if (env->mail_followup_to)
1625 if (!option (OPTNEWSSEND))
1628 fputs ("Mail-Followup-To: ", fp);
1629 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1633 if (env->references) {
1634 fputs ("References:", fp);
1635 mutt_write_references (env->references, fp);
1639 /* Add the MIME headers */
1640 fputs ("Mime-Version: 1.0\n", fp);
1641 mutt_write_mime_header (attach, fp);
1644 if (env->in_reply_to) {
1645 fputs ("In-Reply-To:", fp);
1646 mutt_write_references (env->in_reply_to, fp);
1650 /* Add any user defined headers */
1651 for (; tmp; tmp = tmp->next) {
1652 if ((p = strchr (tmp->data, ':'))) {
1656 continue; /* don't emit empty fields. */
1658 /* check to see if the user has overridden the user-agent field */
1659 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1665 fputs (tmp->data, fp);
1670 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1674 if (OperatingSystem != NULL) {
1675 os = OperatingSystem;
1678 if (uname (&un) == -1) {
1685 /* Add a vanity header */
1686 fprintf (fp, "User-Agent: mutt-ng %s (%s)\n", MUTT_VERSION, os);
1689 return (ferror (fp) == 0 ? 0 : -1);
1692 static void encode_headers (LIST * h)
1698 for (; h; h = h->next) {
1699 if (!(p = strchr (h->data, ':')))
1705 tmp = safe_strdup (p);
1710 rfc2047_encode_string (&tmp);
1711 safe_realloc (&h->data,
1712 safe_strlen (h->data) + 2 + safe_strlen (tmp) + 1);
1714 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1720 const char *mutt_fqdn (short may_hide_host)
1724 if (Fqdn && Fqdn[0] != '@') {
1727 if (may_hide_host && option (OPTHIDDENHOST)) {
1728 if ((p = strchr (Fqdn, '.')))
1731 /* sanity check: don't hide the host if
1732 * the fqdn is something like detebe.org.
1735 if (!p || !(q = strchr (p, '.')))
1743 static char mutt_normalized_char (char c)
1747 if (strchr (".!#$%&'*+-/=?^_`{|}~", c))
1749 return '.'; /* normalized character (we're stricter than RFC2822, 3.6.4) */
1752 static void mutt_gen_localpart (char *buf, unsigned int len, char *fmt)
1756 char tmp[SHORT_STRING];
1763 for (; *fmt; ++fmt) {
1769 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mday);
1770 safe_strncat (buf, len, tmp, 2);
1773 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_hour);
1774 safe_strncat (buf, len, tmp, 2);
1777 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_mon + 1);
1778 safe_strncat (buf, len, tmp, 2);
1781 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_min);
1782 safe_strncat (buf, len, tmp, 2);
1785 snprintf (tmp, sizeof (tmp), "%lo", (unsigned long) now);
1786 safe_strncat (buf, len, tmp, safe_strlen (tmp));
1789 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) getpid ());
1790 safe_strncat (buf, len, tmp, safe_strlen (tmp));
1793 snprintf (tmp, sizeof (tmp), "%c", MsgIdPfx);
1794 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1795 safe_strncat (buf, len, tmp, 1);
1798 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) rand ());
1799 safe_strncat (buf, len, tmp, safe_strlen (tmp));
1802 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) rand ());
1803 safe_strncat (buf, len, tmp, safe_strlen (tmp));
1806 snprintf (tmp, sizeof (tmp), "%02d", tm->tm_sec);
1807 safe_strncat (buf, len, tmp, 2);
1810 snprintf (tmp, sizeof (tmp), "%u", (unsigned int) now);
1811 safe_strncat (buf, len, tmp, safe_strlen (tmp));
1814 snprintf (tmp, sizeof (tmp), "%x", (unsigned int) now);
1815 safe_strncat (buf, len, tmp, safe_strlen (tmp));
1818 snprintf (tmp, sizeof (tmp), "%04d", tm->tm_year + 1900); /* this will break in the year 10000 ;-) */
1819 safe_strncat (buf, len, tmp, 4);
1822 safe_strncat (buf, len, "%", 1);
1825 safe_strncat (buf, len, ".", 1); /* invalid formats are replaced by '.' */
1832 c = mutt_normalized_char (*fmt); /* @todo: filter out invalid characters */
1833 safe_strncat (buf, len, &c, 1);
1838 char *mutt_gen_msgid (void)
1840 char buf[SHORT_STRING];
1841 char localpart[SHORT_STRING];
1842 unsigned int localpart_length;
1849 if (!(fqdn = mutt_fqdn (0)))
1850 fqdn = NONULL (Hostname);
1852 localpart_length = sizeof (buf) - safe_strlen (fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1854 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1856 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1857 return (safe_strdup (buf));
1860 static RETSIGTYPE alarm_handler (int sig)
1865 /* invoke sendmail in a subshell
1866 path (in) path to program to execute
1867 args (in) arguments to pass to program
1868 msg (in) temp file containing message to send
1869 tempfile (out) if sendmail is put in the background, this points
1870 to the temporary file containing the stdout of the
1873 send_msg (const char *path, char **args, const char *msg, char **tempfile)
1879 mutt_block_signals_system ();
1882 /* we also don't want to be stopped right now */
1883 sigaddset (&set, SIGTSTP);
1884 sigprocmask (SIG_BLOCK, &set, NULL);
1886 if (SendmailWait >= 0) {
1887 char tmp[_POSIX_PATH_MAX];
1890 *tempfile = safe_strdup (tmp);
1893 if ((pid = fork ()) == 0) {
1894 struct sigaction act, oldalrm;
1896 /* save parent's ID before setsid() */
1899 /* we want the delivery to continue even after the main process dies,
1900 * so we put ourselves into another session right away
1904 /* next we close all open files */
1905 #if defined(OPEN_MAX)
1906 for (fd = 0; fd < OPEN_MAX; fd++)
1908 #elif defined(_POSIX_OPEN_MAX)
1909 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1917 /* now the second fork() */
1918 if ((pid = fork ()) == 0) {
1919 /* "msg" will be opened as stdin */
1920 if (open (msg, O_RDONLY, 0) < 0) {
1926 if (SendmailWait >= 0) {
1927 /* *tempfile will be opened as stdout */
1928 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1931 /* redirect stderr to *tempfile too */
1936 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1938 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1945 else if (pid == -1) {
1951 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1952 * SendmailWait = 0: wait forever
1953 * SendmailWait < 0: don't wait
1955 if (SendmailWait > 0) {
1957 act.sa_handler = alarm_handler;
1959 /* need to make sure waitpid() is interrupted on SIGALRM */
1960 act.sa_flags = SA_INTERRUPT;
1964 sigemptyset (&act.sa_mask);
1965 sigaction (SIGALRM, &act, &oldalrm);
1966 alarm (SendmailWait);
1968 else if (SendmailWait < 0)
1969 _exit (0xff & EX_OK);
1971 if (waitpid (pid, &st, 0) > 0) {
1972 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1973 if (SendmailWait && st == (0xff & EX_OK)) {
1974 unlink (*tempfile); /* no longer needed */
1979 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1980 if (SendmailWait > 0) {
1986 /* reset alarm; not really needed, but... */
1988 sigaction (SIGALRM, &oldalrm, NULL);
1990 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1991 /* the parent is already dead */
1999 sigprocmask (SIG_UNBLOCK, &set, NULL);
2001 if (pid != -1 && waitpid (pid, &st, 0) > 0)
2002 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
2004 st = S_ERR; /* error */
2006 mutt_unblock_signals_system (1);
2011 static char **add_args (char **args, size_t * argslen, size_t * argsmax,
2014 for (; addr; addr = addr->next) {
2015 /* weed out group mailboxes, since those are for display only */
2016 if (addr->mailbox && !addr->group) {
2017 if (*argslen == *argsmax)
2018 safe_realloc (&args, (*argsmax += 5) * sizeof (char *));
2019 args[(*argslen)++] = addr->mailbox;
2025 static char **add_option (char **args, size_t * argslen, size_t * argsmax,
2028 if (*argslen == *argsmax)
2029 safe_realloc (&args, (*argsmax += 5) * sizeof (char *));
2030 args[(*argslen)++] = s;
2034 static const char *strsysexit (int e)
2038 for (i = 0; sysexits_h[i].str; i++) {
2039 if (e == sysexits_h[i].v)
2043 return sysexits_h[i].str;
2047 static int mutt_invoke_sendmail (ADDRESS * from, /* the sender */
2048 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
2049 const char *msg, /* file containing message */
2051 { /* message contains 8bit chars */
2052 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
2054 size_t argslen = 0, argsmax = 0;
2058 if (option (OPTNEWSSEND)) {
2059 char cmd[LONG_STRING];
2061 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
2064 i = nntp_post (msg);
2069 s = safe_strdup (cmd);
2073 s = safe_strdup (Sendmail);
2077 while ((ps = strtok (ps, " "))) {
2078 if (argslen == argsmax)
2079 safe_realloc (&args, sizeof (char *) * (argsmax += 5));
2082 args[argslen++] = ps;
2084 path = safe_strdup (ps);
2085 ps = strrchr (ps, '/');
2090 args[argslen++] = ps;
2097 if (!option (OPTNEWSSEND)) {
2099 if (eightbit && option (OPTUSE8BITMIME))
2100 args = add_option (args, &argslen, &argsmax, "-B8BITMIME");
2102 if (option (OPTENVFROM) && from && !from->next) {
2103 args = add_option (args, &argslen, &argsmax, "-f");
2104 args = add_args (args, &argslen, &argsmax, from);
2107 args = add_option (args, &argslen, &argsmax, "-N");
2108 args = add_option (args, &argslen, &argsmax, DsnNotify);
2111 args = add_option (args, &argslen, &argsmax, "-R");
2112 args = add_option (args, &argslen, &argsmax, DsnReturn);
2114 args = add_option (args, &argslen, &argsmax, "--");
2115 args = add_args (args, &argslen, &argsmax, to);
2116 args = add_args (args, &argslen, &argsmax, cc);
2117 args = add_args (args, &argslen, &argsmax, bcc);
2122 if (argslen == argsmax)
2123 safe_realloc (&args, sizeof (char *) * (++argsmax));
2125 args[argslen++] = NULL;
2127 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2129 const char *e = strsysexit (i);
2132 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2137 if (stat (childout, &st) == 0 && st.st_size > 0)
2138 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2151 if (i == (EX_OK & 0xff))
2153 else if (i == S_BKG)
2160 int mutt_invoke_mta (ADDRESS * from, /* the sender */
2161 ADDRESS * to, ADDRESS * cc, ADDRESS * bcc, /* recips */
2162 const char *msg, /* file containing message */
2164 { /* message contains 8bit chars */
2167 return mutt_invoke_libesmtp (from, to, cc, bcc, msg, eightbit);
2170 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2173 /* appends string 'b' to string 'a', and returns the pointer to the new
2175 char *mutt_append_string (char *a, const char *b)
2177 size_t la = safe_strlen (a);
2179 safe_realloc (&a, la + safe_strlen (b) + 1);
2180 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2184 /* returns 1 if char `c' needs to be quoted to protect from shell
2185 interpretation when executing commands in a subshell */
2186 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2188 /* returns 1 if string `s' contains characters which could cause problems
2189 when used on a command line to execute a command */
2190 int mutt_needs_quote (const char *s)
2193 if (INVALID_CHAR (*s))
2200 /* Quote a string to prevent shell escapes when this string is used on the
2201 command line to send mail. */
2202 char *mutt_quote_string (const char *s)
2207 rlen = safe_strlen (s) + 3;
2208 pr = r = (char *) safe_malloc (rlen);
2211 if (INVALID_CHAR (*s)) {
2214 safe_realloc (&r, ++rlen);
2225 /* For postponing (!final) do the necessary encodings only */
2226 void mutt_prepare_envelope (ENVELOPE * env, int final)
2228 char buffer[LONG_STRING];
2231 if (env->bcc && !(env->to || env->cc)) {
2232 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2233 * recipients if there is no To: or Cc: field, so attempt to suppress
2234 * it by using an empty To: field.
2236 env->to = rfc822_new_address ();
2238 env->to->next = rfc822_new_address ();
2241 rfc822_cat (buffer, sizeof (buffer), "undisclosed-recipients",
2244 env->to->mailbox = safe_strdup (buffer);
2247 mutt_set_followup_to (env);
2249 if (!env->message_id)
2250 env->message_id = mutt_gen_msgid ();
2253 /* Take care of 8-bit => 7-bit conversion. */
2254 rfc2047_encode_adrlist (env->to, "To");
2255 rfc2047_encode_adrlist (env->cc, "Cc");
2256 rfc2047_encode_adrlist (env->bcc, "Bcc");
2257 rfc2047_encode_adrlist (env->from, "From");
2258 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2259 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2263 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2266 rfc2047_encode_string (&env->subject);
2268 encode_headers (env->userhdrs);
2271 void mutt_unprepare_envelope (ENVELOPE * env)
2275 for (item = env->userhdrs; item; item = item->next)
2276 rfc2047_decode (&item->data);
2278 rfc822_free_address (&env->mail_followup_to);
2280 /* back conversions */
2281 rfc2047_decode_adrlist (env->to);
2282 rfc2047_decode_adrlist (env->cc);
2283 rfc2047_decode_adrlist (env->bcc);
2284 rfc2047_decode_adrlist (env->from);
2285 rfc2047_decode_adrlist (env->reply_to);
2286 rfc2047_decode (&env->subject);
2289 static int _mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to,
2290 const char *resent_from, ADDRESS * env_from)
2294 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2295 MESSAGE *msg = NULL;
2298 /* Try to bounce each message out, aborting if we get any failures. */
2299 for (i = 0; i < Context->msgcount; i++)
2300 if (Context->hdrs[i]->tagged)
2302 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2307 /* If we failed to open a message, return with error */
2308 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2314 mutt_mktemp (tempfile);
2315 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2316 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2318 if (!option (OPTBOUNCEDELIVERED))
2319 ch_flags |= CH_WEED_DELIVERED;
2321 fseek (fp, h->offset, 0);
2322 fprintf (f, "Resent-From: %s", resent_from);
2323 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2324 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2325 fputs ("Resent-To: ", f);
2326 mutt_write_address_list (to, f, 11, 0);
2327 mutt_copy_header (fp, h, f, ch_flags, NULL);
2329 mutt_copy_bytes (fp, f, h->content->length);
2332 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2333 h->content->encoding == ENC8BIT);
2337 mx_close_message (&msg);
2342 int mutt_bounce_message (FILE * fp, HEADER * h, ADDRESS * to)
2345 const char *fqdn = mutt_fqdn (1);
2346 char resent_from[STRING];
2350 resent_from[0] = '\0';
2351 from = mutt_default_from ();
2354 rfc822_qualify (from, fqdn);
2356 rfc2047_encode_adrlist (from, "Resent-From");
2357 if (mutt_addrlist_to_idna (from, &err)) {
2358 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2361 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2364 unset_option (OPTNEWSSEND);
2367 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2369 rfc822_free_address (&from);
2375 /* given a list of addresses, return a list of unique addresses */
2376 ADDRESS *mutt_remove_duplicates (ADDRESS * addr)
2378 ADDRESS *top = addr;
2379 ADDRESS **last = ⊤
2384 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2385 if (tmp->mailbox && addr->mailbox &&
2386 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2393 debug_print (2, ("Removing %s\n", addr->mailbox));
2398 rfc822_free_address (&addr);
2411 static void set_noconv_flags (BODY * b, short flag)
2413 for (; b; b = b->next) {
2414 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2415 set_noconv_flags (b->parts, flag);
2416 else if (b->type == TYPETEXT && b->noconv) {
2418 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2420 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2425 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2426 int post, char *fcc)
2430 char tempfile[_POSIX_PATH_MAX];
2431 FILE *tempfp = NULL;
2435 set_noconv_flags (hdr->content, 1);
2437 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2438 debug_print (1, ("unable to open mailbox %s in append-mode, aborting.\n", path));
2442 /* We need to add a Content-Length field to avoid problems where a line in
2443 * the message body begins with "From "
2445 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2446 mutt_mktemp (tempfile);
2447 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2448 mutt_perror (tempfile);
2449 mx_close_mailbox (&f, NULL);
2454 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2455 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2456 mx_close_mailbox (&f, NULL);
2460 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2461 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2463 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2466 /* (postponment) if this was a reply of some sort, <msgid> contians the
2467 * Message-ID: of message replied to. Save it using a special X-Mutt-
2468 * header so it can be picked up if the message is recalled at a later
2469 * point in time. This will allow the message to be marked as replied if
2470 * the same mailbox is still open.
2473 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2475 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2476 * it can be picked up when the message is recalled
2479 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2480 fprintf (msg->fp, "Status: RO\n");
2484 /* (postponment) if the mail is to be signed or encrypted, save this info */
2485 if ((WithCrypto & APPLICATION_PGP)
2486 && post && (hdr->security & APPLICATION_PGP)) {
2487 fputs ("X-Mutt-PGP: ", msg->fp);
2488 if (hdr->security & ENCRYPT)
2489 fputc ('E', msg->fp);
2490 if (hdr->security & SIGN) {
2491 fputc ('S', msg->fp);
2492 if (PgpSignAs && *PgpSignAs)
2493 fprintf (msg->fp, "<%s>", PgpSignAs);
2495 if (hdr->security & INLINE)
2496 fputc ('I', msg->fp);
2497 fputc ('\n', msg->fp);
2500 /* (postponment) if the mail is to be signed or encrypted, save this info */
2501 if ((WithCrypto & APPLICATION_SMIME)
2502 && post && (hdr->security & APPLICATION_SMIME)) {
2503 fputs ("X-Mutt-SMIME: ", msg->fp);
2504 if (hdr->security & ENCRYPT) {
2505 fputc ('E', msg->fp);
2506 if (SmimeCryptAlg && *SmimeCryptAlg)
2507 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2509 if (hdr->security & SIGN) {
2510 fputc ('S', msg->fp);
2511 if (SmimeDefaultKey && *SmimeDefaultKey)
2512 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2514 if (hdr->security & INLINE)
2515 fputc ('I', msg->fp);
2516 fputc ('\n', msg->fp);
2520 /* (postponement) if the mail is to be sent through a mixmaster
2521 * chain, save that information
2524 if (post && hdr->chain && hdr->chain) {
2527 fputs ("X-Mutt-Mix:", msg->fp);
2528 for (p = hdr->chain; p; p = p->next)
2529 fprintf (msg->fp, " %s", (char *) p->data);
2531 fputc ('\n', msg->fp);
2536 char sasha[LONG_STRING];
2539 mutt_write_mime_body (hdr->content, tempfp);
2541 /* make sure the last line ends with a newline. Emacs doesn't ensure
2542 * this will happen, and it can cause problems parsing the mailbox
2545 fseek (tempfp, -1, 2);
2546 if (fgetc (tempfp) != '\n') {
2547 fseek (tempfp, 0, 2);
2548 fputc ('\n', tempfp);
2552 if (ferror (tempfp)) {
2553 debug_print (1, ("%s: write failed.\n", tempfile));
2556 mx_commit_message (msg, &f); /* XXX - really? */
2557 mx_close_message (&msg);
2558 mx_close_mailbox (&f, NULL);
2562 /* count the number of lines */
2564 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2566 fprintf (msg->fp, "Content-Length: %ld\n", (long) ftell (tempfp));
2567 fprintf (msg->fp, "Lines: %d\n\n", lines);
2569 /* copy the body and clean up */
2571 r = mutt_copy_stream (tempfp, msg->fp);
2572 if (fclose (tempfp) != 0)
2574 /* if there was an error, leave the temp version */
2579 fputc ('\n', msg->fp); /* finish off the header */
2580 r = mutt_write_mime_body (hdr->content, msg->fp);
2583 if (mx_commit_message (msg, &f) != 0)
2585 mx_close_message (&msg);
2586 mx_close_mailbox (&f, NULL);
2589 set_noconv_flags (hdr->content, 0);