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.
26 #include "mutt_curses.h"
35 #include "mutt_crypt.h"
36 #include "mutt_idna.h"
47 #include <sys/utsname.h>
50 # include "mutt_libesmtp.h"
51 #endif /* USE_LIBESMTP */
57 #ifdef HAVE_SYSEXITS_H
59 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
63 /* If you are debugging this file, comment out the following line. */
72 extern char RFC822Specials[];
74 static struct sysexits
82 { 0xff & EX_USAGE, "Bad usage." },
85 { 0xff & EX_DATAERR, "Data format error." },
88 { 0xff & EX_NOINPUT, "Cannot open input." },
91 { 0xff & EX_NOUSER, "User unknown." },
94 { 0xff & EX_NOHOST, "Host unknown." },
97 { 0xff & EX_UNAVAILABLE, "Service unavailable." },
100 { 0xff & EX_SOFTWARE, "Internal error." },
103 { 0xff & EX_OSERR, "Operating system error." },
106 { 0xff & EX_OSFILE, "System file missing." },
109 { 0xff & EX_CANTCREAT, "Can't create output." },
112 { 0xff & EX_IOERR, "I/O error." },
115 { 0xff & EX_TEMPFAIL, "Deferred." },
118 { 0xff & EX_PROTOCOL, "Remote protocol error." },
121 { 0xff & EX_NOPERM, "Insufficient permission." },
124 { 0xff & EX_NOPERM, "Local configuration error." },
126 { S_ERR, "Exec error." },
132 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
134 const char MimeSpecials[] = "@.,;:<>[]\\\"()?/= \t";
136 char B64Chars[64] = {
137 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
138 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
139 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
140 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
144 static char MsgIdPfx = 'A';
146 static void transform_to_7bit (BODY *a, FILE *fpin);
148 static void encode_quoted (FGETCONV * fc, FILE *fout, int istext)
151 char line[77], savechar;
153 while ((c = fgetconv (fc)) != EOF)
155 /* Wrap the line if needed. */
156 if (linelen == 76 && ((istext && c != '\n') || !istext))
158 /* If the last character is "quoted", then be sure to move all three
159 * characters to the next line. Otherwise, just move the last
162 if (line[linelen-3] == '=')
169 line[1] = line[linelen-2];
170 line[2] = line[linelen-1];
175 savechar = line[linelen-1];
176 line[linelen-1] = '=';
185 /* Escape lines that begin with/only contain "the message separator". */
186 if (linelen == 4 && !mutt_strncmp ("From", line, 4))
188 strfcpy (line, "=46rom", sizeof (line));
191 else if (linelen == 4 && !mutt_strncmp ("from", line, 4))
193 strfcpy (line, "=66rom", sizeof (line));
196 else if (linelen == 1 && line[0] == '.')
198 strfcpy (line, "=2E", sizeof (line));
203 if (c == '\n' && istext)
205 /* Check to make sure there is no trailing space on this line. */
206 if (linelen > 0 && (line[linelen-1] == ' ' || line[linelen-1] == '\t'))
210 sprintf (line+linelen-1, "=%2.2X", (unsigned char) line[linelen-1]);
215 int savechar = line[linelen-1];
217 line[linelen-1] = '=';
220 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
231 else if (c != 9 && (c < 32 || c > 126 || c == '='))
233 /* Check to make sure there is enough room for the quoted character.
234 * If not, wrap to the next line.
238 line[linelen++] = '=';
244 sprintf (line+linelen,"=%2.2X", (unsigned char) c);
249 /* Don't worry about wrapping the line here. That will happen during
250 * the next iteration when I'll also know what the next character is.
256 /* Take care of anything left in the buffer */
259 if (line[linelen-1] == ' ' || line[linelen-1] == '\t')
261 /* take care of trailing whitespace */
263 sprintf (line+linelen-1, "=%2.2X", (unsigned char) line[linelen-1]);
266 savechar = line[linelen-1];
267 line[linelen-1] = '=';
271 sprintf (line, "=%2.2X", (unsigned char) savechar);
280 static char b64_buffer[3];
281 static short b64_num;
282 static short b64_linelen;
284 static void b64_flush(FILE *fout)
291 if(b64_linelen >= 72)
297 for(i = b64_num; i < 3; i++)
298 b64_buffer[i] = '\0';
300 fputc(B64Chars[(b64_buffer[0] >> 2) & 0x3f], fout);
302 fputc(B64Chars[((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf) ], fout);
307 fputc(B64Chars[((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3) ], fout);
311 fputc(B64Chars[b64_buffer[2] & 0x3f], fout);
316 while(b64_linelen % 4)
326 static void b64_putc(char c, FILE *fout)
331 b64_buffer[b64_num++] = c;
335 static void encode_base64 (FGETCONV * fc, FILE *fout, int istext)
339 b64_num = b64_linelen = 0;
341 while ((ch = fgetconv (fc)) != EOF)
343 if (istext && ch == '\n' && ch1 != '\r')
344 b64_putc('\r', fout);
352 static void encode_8bit (FGETCONV *fc, FILE *fout, int istext)
356 while ((ch = fgetconv (fc)) != EOF)
361 int mutt_write_mime_header (BODY *a, FILE *f)
371 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
375 len = 25 + mutt_strlen (a->subtype); /* approximate len. of content-type */
377 for(p = a->parameter; p; p = p->next)
387 tmp = safe_strdup (p->value);
388 encode = rfc2231_encode_string (&tmp);
389 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
391 /* Dirty hack to make messages readable by Outlook Express
392 * for the Mac: force quotes around the boundary parameter
393 * even when they aren't needed.
396 if (!ascii_strcasecmp (p->attribute, "boundary") && !strcmp (buffer, tmp))
397 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
401 tmplen = mutt_strlen (buffer) + mutt_strlen (p->attribute) + 1;
403 if (len + tmplen + 2 > 76)
414 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
422 fprintf(f, "Content-Description: %s\n", a->description);
424 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
428 if(!(fn = a->d_filename))
435 /* Strip off the leading path... */
436 if ((t = strrchr (fn, '/')))
442 tmp = safe_strdup (t);
443 encode = rfc2231_encode_string (&tmp);
444 rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
446 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
452 if (a->encoding != ENC7BIT)
453 fprintf(f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
455 /* Do NOT add the terminator here!!! */
456 return (ferror (f) ? -1 : 0);
459 # define write_as_text_part(a) (mutt_is_text_part(a) \
460 || ((WithCrypto & APPLICATION_PGP)\
461 && mutt_is_application_pgp(a)))
463 int mutt_write_mime_body (BODY *a, FILE *f)
465 char *p, boundary[SHORT_STRING];
466 char send_charset[SHORT_STRING];
471 if (a->type == TYPEMULTIPART)
473 /* First, find the boundary to use */
474 if (!(p = mutt_get_parameter ("boundary", a->parameter)))
476 dprint (1, (debugfile, "mutt_write_mime_body(): no boundary parameter found!\n"));
477 mutt_error _("No boundary parameter found! [report this error]");
480 strfcpy (boundary, p, sizeof (boundary));
482 for (t = a->parts; t ; t = t->next)
484 fprintf (f, "\n--%s\n", boundary);
485 if (mutt_write_mime_header (t, f) == -1)
488 if (mutt_write_mime_body (t, f) == -1)
491 fprintf (f, "\n--%s--\n", boundary);
492 return (ferror (f) ? -1 : 0);
495 /* This is pretty gross, but it's the best solution for now... */
496 if ((WithCrypto & APPLICATION_PGP)
497 && a->type == TYPEAPPLICATION
498 && mutt_strcmp (a->subtype, "pgp-encrypted") == 0)
500 fputs ("Version: 1\n", f);
504 if ((fpin = fopen (a->filename, "r")) == NULL)
506 dprint(1,(debugfile, "write_mime_body: %s no longer exists!\n",a->filename));
507 mutt_error (_("%s no longer exists!"), a->filename);
511 if (a->type == TYPETEXT && (!a->noconv))
512 fc = fgetconv_open (fpin, Charset,
513 mutt_get_body_charset (send_charset, sizeof (send_charset), a),
516 fc = fgetconv_open (fpin, 0, 0, 0);
518 if (a->encoding == ENCQUOTEDPRINTABLE)
519 encode_quoted (fc, f, write_as_text_part (a));
520 else if (a->encoding == ENCBASE64)
521 encode_base64 (fc, f, write_as_text_part (a));
522 else if (a->type == TYPETEXT && (!a->noconv))
523 encode_8bit (fc, f, write_as_text_part (a));
525 mutt_copy_stream (fpin, f);
527 fgetconv_close (&fc);
530 return (ferror (f) ? -1 : 0);
533 #undef write_as_text_part
535 #define BOUNDARYLEN 16
536 void mutt_generate_boundary (PARAMETER **parm)
538 char rs[BOUNDARYLEN + 1];
543 for (i=0;i<BOUNDARYLEN;i++)
544 *p++ = B64Chars[LRAND() % sizeof (B64Chars)];
547 mutt_set_parameter ("boundary", rs, parm);
561 static void update_content_info (CONTENT *info, CONTENT_STATE *s, char *d, size_t dlen)
564 int whitespace = s->whitespace;
566 int linelen = s->linelen;
567 int was_cr = s->was_cr;
569 if (!d) /* This signals EOF */
573 if (linelen > info->linemax)
574 info->linemax = linelen;
579 for (; dlen; d++, dlen--)
592 if (whitespace) info->space = 1;
593 if (dot) info->dot = 1;
594 if (linelen > info->linemax) info->linemax = linelen;
606 if (whitespace) info->space = 1;
607 if (dot) info->dot = 1;
608 if (linelen > info->linemax) info->linemax = linelen;
622 else if (ch == '\t' || ch == '\f')
627 else if (ch < 32 || ch == 127)
633 if ((ch == 'F') || (ch == 'f'))
644 if (linelen == 2 && ch != 'r') from = 0;
645 else if (linelen == 3 && ch != 'o') from = 0;
646 else if (linelen == 4)
648 if (ch == 'm') info->from = 1;
652 if (ch == ' ') whitespace++;
656 if (linelen > 1) dot = 0;
657 if (ch != ' ' && ch != '\t') whitespace = 0;
661 s->whitespace = whitespace;
663 s->linelen = linelen;
668 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
669 #define BUGGY_ICONV 1
672 * Find the best charset conversion of the file from fromcode into one
673 * of the tocodes. If successful, set *tocode and CONTENT *info and
674 * return the number of characters converted inexactly. If no
675 * conversion was possible, return -1.
677 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
678 * which would otherwise prevent us from knowing the number of inexact
679 * conversions. Where the candidate target charset is UTF-8 we avoid
680 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
681 * fails with some libraries.
683 * We assume that the output from iconv is never more than 4 times as
684 * long as the input for any pair of charsets we might be interested
687 static size_t convert_file_to (FILE *file, const char *fromcode,
688 int ncodes, const char **tocodes,
689 int *tocode, CONTENT *info)
693 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
694 ICONV_CONST char *ib, *ub;
696 size_t ibl, obl, ubl, ubl1, n, ret;
699 CONTENT_STATE *states;
702 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
703 if (cd1 == (iconv_t)(-1))
706 cd = safe_calloc (ncodes, sizeof (iconv_t));
707 score = safe_calloc (ncodes, sizeof (size_t));
708 states = safe_calloc (ncodes, sizeof (CONTENT_STATE));
709 infos = safe_calloc (ncodes, sizeof (CONTENT));
711 for (i = 0; i < ncodes; i++)
712 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
713 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
715 /* Special case for conversion to UTF-8 */
716 cd[i] = (iconv_t)(-1), score[i] = (size_t)(-1);
723 /* Try to fill input buffer */
724 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
727 /* Convert to UTF-8 */
729 ob = bufu, obl = sizeof (bufu);
730 n = iconv (cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
731 assert (n == (size_t)(-1) || !n || ICONV_NONTRANS);
732 if (n == (size_t)(-1) &&
733 ((errno != EINVAL && errno != E2BIG) || ib == bufi))
735 assert (errno == EILSEQ ||
736 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
742 /* Convert from UTF-8 */
743 for (i = 0; i < ncodes; i++)
744 if (cd[i] != (iconv_t)(-1) && score[i] != (size_t)(-1))
746 ub = bufu, ubl = ubl1;
747 ob = bufo, obl = sizeof (bufo);
748 n = iconv (cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
749 if (n == (size_t)(-1))
751 assert (errno == E2BIG ||
752 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
753 score[i] = (size_t)(-1);
758 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
761 else if (cd[i] == (iconv_t)(-1) && score[i] == (size_t)(-1))
762 /* Special case for conversion to UTF-8 */
763 update_content_info (&infos[i], &states[i], bufu, ubl1);
766 /* Save unused input */
767 memmove (bufi, ib, ibl);
768 else if (!ubl1 && ib < bufi + sizeof (bufi))
777 /* Find best score */
779 for (i = 0; i < ncodes; i++)
781 if (cd[i] == (iconv_t)(-1) && score[i] == (size_t)(-1))
783 /* Special case for conversion to UTF-8 */
788 else if (cd[i] == (iconv_t)(-1) || score[i] == (size_t)(-1))
790 else if (ret == (size_t)(-1) || score[i] < ret)
798 if (ret != (size_t)(-1))
800 memcpy (info, &infos[*tocode], sizeof(CONTENT));
801 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
805 for (i = 0; i < ncodes; i++)
806 if (cd[i] != (iconv_t)(-1))
818 #endif /* !HAVE_ICONV */
822 * Find the first of the fromcodes that gives a valid conversion and
823 * the best charset conversion of the file into one of the tocodes. If
824 * successful, set *fromcode and *tocode to dynamically allocated
825 * strings, set CONTENT *info, and return the number of characters
826 * converted inexactly. If no conversion was possible, return -1.
828 * Both fromcodes and tocodes may be colon-separated lists of charsets.
829 * However, if fromcode is zero then fromcodes is assumed to be the
830 * name of a single charset even if it contains a colon.
832 static size_t convert_file_from_to (FILE *file,
833 const char *fromcodes, const char *tocodes,
834 char **fromcode, char **tocode, CONTENT *info)
842 /* Count the tocodes */
844 for (c = tocodes; c; c = c1 ? c1 + 1 : 0)
846 if ((c1 = strchr (c, ':')) == c)
852 tcode = safe_malloc (ncodes * sizeof (char *));
853 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++)
855 if ((c1 = strchr (c, ':')) == c)
857 tcode[i] = mutt_substrdup (c, c1);
863 /* Try each fromcode in turn */
864 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0)
866 if ((c1 = strchr (c, ':')) == c)
868 fcode = mutt_substrdup (c, c1);
870 ret = convert_file_to (file, fcode, ncodes, (const char **)tcode,
872 if (ret != (size_t)(-1))
884 /* There is only one fromcode */
885 ret = convert_file_to (file, fromcodes, ncodes, (const char **)tcode,
887 if (ret != (size_t)(-1))
895 for (i = 0; i < ncodes; i++)
904 * Analyze the contents of a file to determine which MIME encoding to use.
905 * Also set the body charset, sometimes, or not.
907 CONTENT *mutt_get_content_info (const char *fname, BODY *b)
919 if(b && !fname) fname = b->filename;
921 if (stat (fname, &sb) == -1)
923 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
927 if (!S_ISREG(sb.st_mode))
929 mutt_error (_("%s isn't a regular file."), fname);
933 if ((fp = fopen (fname, "r")) == NULL)
935 dprint (1, (debugfile, "mutt_get_content_info: %s: %s (errno %d).\n",
936 fname, strerror (errno), errno));
940 info = safe_calloc (1, sizeof (CONTENT));
941 memset (&state, 0, sizeof (state));
943 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
945 char *chs = mutt_get_parameter ("charset", b->parameter);
946 if (Charset && (chs || SendCharset) &&
947 convert_file_from_to (fp, Charset, chs ? chs : SendCharset,
948 0, &tocode, info) != (size_t)(-1))
952 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
953 mutt_set_parameter ("charset", chsbuf, &b->parameter);
962 while ((r = fread (buffer, 1, sizeof(buffer), fp)))
963 update_content_info (info, &state, buffer, r);
964 update_content_info (info, &state, 0, 0);
968 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
969 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
970 Charset && !mutt_is_us_ascii (Charset) ? Charset : "unknown-8bit"),
976 /* Given a file with path ``s'', see if there is a registered MIME type.
977 * returns the major MIME type, and copies the subtype to ``d''. First look
978 * for ~/.mime.types, then look in a system mime.types if we can find one.
979 * The longest match is used so that we can match `ps.gz' when `gz' also
983 int mutt_lookup_mime_type (BODY *att, const char *path)
987 char buf[LONG_STRING];
988 char subtype[STRING], xtype[STRING];
990 int szf, sze, cur_sze;
998 szf = mutt_strlen (path);
1000 for (count = 0 ; count < 3 ; count++)
1003 * can't use strtok() because we use it in an inner loop below, so use
1004 * a switch statement here instead.
1009 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL(Homedir));
1012 strfcpy (buf, SYSCONFDIR"/mime.types", sizeof(buf));
1015 strfcpy (buf, PKGDATADIR"/mime.types", sizeof (buf));
1018 dprint (1, (debugfile, "mutt_lookup_mime_type: Internal error, count = %d.\n", count));
1019 goto bye; /* shouldn't happen */
1022 if ((f = fopen (buf, "r")) != NULL)
1024 while (fgets (buf, sizeof (buf) - 1, f) != NULL)
1026 /* weed out any comments */
1027 if ((p = strchr (buf, '#')))
1030 /* remove any leading space. */
1034 /* position on the next field in this line */
1035 if ((p = strpbrk (ct, " \t")) == NULL)
1040 /* cycle through the file extensions */
1041 while ((p = strtok (p, " \t\n")))
1043 sze = mutt_strlen (p);
1044 if ((sze > cur_sze) && (szf >= sze) &&
1045 (mutt_strcasecmp (path + szf - sze, p) == 0 || ascii_strcasecmp (path + szf - sze, p) == 0) &&
1046 (szf == sze || path[szf - sze - 1] == '.'))
1048 /* get the content-type */
1050 if ((p = strchr (ct, '/')) == NULL)
1052 /* malformed line, just skip it. */
1057 for (q = p; *q && !ISSPACE (*q); q++)
1060 mutt_substrcpy (subtype, p, q, sizeof (subtype));
1062 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
1063 strfcpy (xtype, ct, sizeof (xtype));
1076 if (type != TYPEOTHER || *xtype != '\0')
1079 mutt_str_replace (&att->subtype, subtype);
1080 mutt_str_replace (&att->xtype, xtype);
1086 void mutt_message_to_7bit (BODY *a, FILE *fp)
1088 char temp[_POSIX_PATH_MAX];
1094 if (!a->filename && fp)
1096 else if (!a->filename || !(fpin = fopen (a->filename, "r")))
1098 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
1104 if (stat (a->filename, &sb) == -1)
1106 mutt_perror ("stat");
1109 a->length = sb.st_size;
1113 if (!(fpout = safe_fopen (temp, "w+")))
1115 mutt_perror ("fopen");
1119 fseek (fpin, a->offset, 0);
1120 a->parts = mutt_parse_messageRFC822 (fpin, a);
1122 transform_to_7bit (a->parts, fpin);
1124 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1125 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1127 fputs ("Mime-Version: 1.0\n", fpout);
1128 mutt_write_mime_header (a->parts, fpout);
1129 fputc ('\n', fpout);
1130 mutt_write_mime_body (a->parts, fpout);
1142 a->encoding = ENC7BIT;
1143 a->d_filename = a->filename;
1144 if (a->filename && a->unlink)
1145 unlink (a->filename);
1146 a->filename = safe_strdup (temp);
1148 if(stat (a->filename, &sb) == -1)
1150 mutt_perror ("stat");
1153 a->length = sb.st_size;
1154 mutt_free_body (&a->parts);
1155 a->hdr->content = NULL;
1158 static void transform_to_7bit (BODY *a, FILE *fpin)
1160 char buff[_POSIX_PATH_MAX];
1164 memset (&s, 0, sizeof (s));
1165 for (; a; a = a->next)
1167 if (a->type == TYPEMULTIPART)
1169 if (a->encoding != ENC7BIT)
1170 a->encoding = ENC7BIT;
1172 transform_to_7bit (a->parts, fpin);
1174 else if (mutt_is_message_type(a->type, a->subtype))
1176 mutt_message_to_7bit (a, fpin);
1181 if ((s.fpout = safe_fopen (buff, "w")) == NULL)
1183 mutt_perror ("fopen");
1187 mutt_decode_attachment (a, &s);
1189 a->d_filename = a->filename;
1190 a->filename = safe_strdup (buff);
1192 if (stat (a->filename, &sb) == -1)
1194 mutt_perror ("stat");
1197 a->length = sb.st_size;
1199 mutt_update_encoding (a);
1200 if (a->encoding == ENC8BIT)
1201 a->encoding = ENCQUOTEDPRINTABLE;
1202 else if(a->encoding == ENCBINARY)
1203 a->encoding = ENCBASE64;
1208 /* determine which Content-Transfer-Encoding to use */
1209 static void mutt_set_encoding (BODY *b, CONTENT *info)
1211 char send_charset[SHORT_STRING];
1213 if (b->type == TYPETEXT)
1215 char *chsname = mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1216 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8)) || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1217 b->encoding = ENCQUOTEDPRINTABLE;
1218 else if (info->hibin)
1219 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1221 b->encoding = ENC7BIT;
1223 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
1225 if (info->lobin || info->hibin)
1227 if (option (OPTALLOW8BIT) && !info->lobin)
1228 b->encoding = ENC8BIT;
1230 mutt_message_to_7bit (b, NULL);
1233 b->encoding = ENC7BIT;
1235 else if (b->type == TYPEAPPLICATION && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1236 b->encoding = ENC7BIT;
1239 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1240 || info->cr || (/* option (OPTENCODEFROM) && */ info->from))
1243 /* Determine which encoding is smaller */
1244 if (1.33 * (float)(info->lobin+info->hibin+info->ascii) <
1245 3.0 * (float)(info->lobin + info->hibin) + (float)info->ascii)
1246 b->encoding = ENCBASE64;
1248 b->encoding = ENCQUOTEDPRINTABLE;
1252 b->encoding = ENC7BIT;
1256 void mutt_stamp_attachment(BODY *a)
1258 a->stamp = time(NULL);
1261 /* Get a body's character set */
1263 char *mutt_get_body_charset (char *d, size_t dlen, BODY *b)
1267 if (b && b->type != TYPETEXT)
1271 p = mutt_get_parameter ("charset", b->parameter);
1274 mutt_canonical_charset (d, dlen, NONULL(p));
1276 strfcpy (d, "us-ascii", dlen);
1282 /* Assumes called from send mode where BODY->filename points to actual file */
1283 void mutt_update_encoding (BODY *a)
1286 char chsbuff[STRING];
1288 /* override noconv when it's us-ascii */
1289 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1292 if (!a->force_charset && !a->noconv)
1293 mutt_delete_parameter ("charset", &a->parameter);
1295 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1298 mutt_set_encoding (a, info);
1299 mutt_stamp_attachment(a);
1306 BODY *mutt_make_message_attach (CONTEXT *ctx, HEADER *hdr, int attach_msg)
1308 char buffer[LONG_STRING];
1311 int cmflags, chflags;
1312 int pgp = WithCrypto? hdr->security : 0;
1316 if ((option(OPTMIMEFORWDECODE) || option(OPTFORWDECRYPT)) &&
1317 (hdr->security & ENCRYPT)) {
1318 if (!crypt_valid_passphrase(hdr->security))
1323 mutt_mktemp (buffer);
1324 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1327 body = mutt_new_body ();
1328 body->type = TYPEMESSAGE;
1329 body->subtype = safe_strdup ("rfc822");
1330 body->filename = safe_strdup (buffer);
1333 body->disposition = DISPINLINE;
1335 mutt_parse_mime_message (ctx, hdr);
1340 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1341 if (!attach_msg && option (OPTMIMEFORWDECODE))
1343 chflags |= CH_MIME | CH_TXTPLAIN;
1344 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1345 if ((WithCrypto & APPLICATION_PGP))
1347 if ((WithCrypto & APPLICATION_SMIME))
1348 pgp &= ~SMIMEENCRYPT;
1351 && option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT))
1353 if ((WithCrypto & APPLICATION_PGP)
1354 && mutt_is_multipart_encrypted (hdr->content))
1356 chflags |= CH_MIME | CH_NONEWLINE;
1357 cmflags = M_CM_DECODE_PGP;
1360 else if ((WithCrypto & APPLICATION_PGP)
1361 && (mutt_is_application_pgp (hdr->content) & PGPENCRYPT))
1363 chflags |= CH_MIME | CH_TXTPLAIN;
1364 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1367 else if ((WithCrypto & APPLICATION_SMIME)
1368 && mutt_is_application_smime (hdr->content) & SMIMEENCRYPT)
1370 chflags |= CH_MIME | CH_TXTPLAIN;
1371 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1372 pgp &= ~SMIMEENCRYPT;
1376 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1381 body->hdr = mutt_new_header();
1382 body->hdr->offset = 0;
1383 /* we don't need the user headers here */
1384 body->hdr->env = mutt_read_rfc822_header(fp, body->hdr, 0, 0);
1386 body->hdr->security = pgp;
1387 mutt_update_encoding (body);
1388 body->parts = body->hdr->content;
1395 BODY *mutt_make_file_attach (const char *path)
1400 att = mutt_new_body ();
1401 att->filename = safe_strdup (path);
1403 /* Attempt to determine the appropriate content-type based on the filename
1409 if ((n = mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf), path)) != TYPEOTHER
1413 att->subtype = safe_strdup (buf);
1414 att->xtype = safe_strdup (xbuf);
1419 mutt_lookup_mime_type (att, path);
1423 if ((info = mutt_get_content_info (path, att)) == NULL)
1425 mutt_free_body (&att);
1431 if (info->lobin == 0 || (info->lobin + info->hibin + info->ascii)/ info->lobin >= 10)
1434 * Statistically speaking, there should be more than 10% "lobin"
1435 * chars if this is really a binary file...
1437 att->type = TYPETEXT;
1438 att->subtype = safe_strdup ("plain");
1442 att->type = TYPEAPPLICATION;
1443 att->subtype = safe_strdup ("octet-stream");
1447 mutt_update_encoding (att);
1451 static int get_toplevel_encoding (BODY *a)
1455 for (; a; a = a->next)
1457 if (a->encoding == ENCBINARY)
1459 else if (a->encoding == ENC8BIT)
1466 BODY *mutt_make_multipart (BODY *b)
1470 new = mutt_new_body ();
1471 new->type = TYPEMULTIPART;
1472 new->subtype = safe_strdup ("mixed");
1473 new->encoding = get_toplevel_encoding (b);
1474 mutt_generate_boundary (&new->parameter);
1476 new->disposition = DISPINLINE;
1482 /* remove the multipart body if it exists */
1483 BODY *mutt_remove_multipart (BODY *b)
1492 mutt_free_body (&t);
1497 char *mutt_make_date (char *s, size_t len)
1499 time_t t = time (NULL);
1500 struct tm *l = localtime (&t);
1501 time_t tz = mutt_local_tz (t);
1505 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1506 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1507 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1508 (int) tz / 60, (int) abs (tz) % 60);
1512 /* wrapper around mutt_write_address() so we can handle very large
1513 recipient lists without needing a huge temporary buffer in memory */
1514 void mutt_write_address_list (ADDRESS *adr, FILE *fp, int linelen, int display)
1517 char buf[LONG_STRING];
1526 rfc822_write_address (buf, sizeof (buf), adr, display);
1527 len = mutt_strlen (buf);
1528 if (count && linelen + len > 74)
1531 linelen = len + 8; /* tab is usually about 8 spaces... */
1535 if (count && adr->mailbox)
1544 if (!adr->group && adr->next && adr->next->mailbox)
1555 /* arbitrary number of elements to grow the array by */
1560 /* need to write the list in reverse because they are stored in reverse order
1561 * when parsed to speed up threading
1563 void mutt_write_references (LIST *r, FILE *f)
1566 int refcnt = 0, refmax = 0;
1568 for ( ; (TrimRef == 0 || refcnt < TrimRef) && r ; r = r->next)
1570 if (refcnt == refmax)
1571 safe_realloc (&ref, (refmax += REF_INC) * sizeof (LIST *));
1575 while (refcnt-- > 0)
1578 fputs (ref[refcnt]->data, f);
1584 /* Note: all RFC2047 encoding should be done outside of this routine, except
1585 * for the "real name." This will allow this routine to be used more than
1586 * once, if necessary.
1588 * Likewise, all IDN processing should happen outside of this routine.
1590 * mode == 1 => "lite" mode (used for edit_hdrs)
1591 * mode == 0 => normal mode. write full header + MIME headers
1592 * mode == -1 => write just the envelope info (used for postponing messages)
1594 * privacy != 0 => will omit any headers which may identify the user.
1595 * Output generated is suitable for being sent through
1596 * anonymous remailer chains.
1600 int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach,
1601 int mode, int privacy)
1603 char buffer[LONG_STRING];
1605 LIST *tmp = env->userhdrs;
1606 int has_agent = 0; /* user defined user-agent header field exists */
1609 if (!option (OPTNEWSSEND))
1611 if (mode == 0 && !privacy)
1612 fputs (mutt_make_date (buffer, sizeof(buffer)), fp);
1614 /* OPTUSEFROM is not consulted here so that we can still write a From:
1615 * field if the user sets it with the `my_hdr' command
1617 if (env->from && !privacy)
1620 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1621 fprintf (fp, "From: %s\n", buffer);
1627 mutt_write_address_list (env->to, fp, 4, 0);
1631 if (!option (OPTNEWSSEND))
1633 fputs ("To: \n", fp);
1638 mutt_write_address_list (env->cc, fp, 4, 0);
1642 if (!option (OPTNEWSSEND))
1644 fputs ("Cc: \n", fp);
1648 if(mode != 0 || option(OPTWRITEBCC))
1650 fputs ("Bcc: ", fp);
1651 mutt_write_address_list (env->bcc, fp, 5, 0);
1656 if (!option (OPTNEWSSEND))
1658 fputs ("Bcc: \n", fp);
1661 if (env->newsgroups)
1662 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1663 else if (mode == 1 && option (OPTNEWSSEND))
1664 fputs ("Newsgroups: \n", fp);
1666 if (env->followup_to)
1667 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1668 else if (mode == 1 && option (OPTNEWSSEND))
1669 fputs ("Followup-To: \n", fp);
1671 if (env->x_comment_to)
1672 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1673 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO))
1674 fputs ("X-Comment-To: \n", fp);
1678 fprintf (fp, "Subject: %s\n", env->subject);
1680 fputs ("Subject: \n", fp);
1682 /* save message id if the user has set it */
1683 if (env->message_id && !privacy)
1684 fprintf (fp, "Message-ID: %s\n", env->message_id);
1688 fputs ("Reply-To: ", fp);
1689 mutt_write_address_list (env->reply_to, fp, 10, 0);
1692 fputs ("Reply-To: \n", fp);
1694 if (env->mail_followup_to)
1696 if (!option (OPTNEWSSEND))
1699 fputs ("Mail-Followup-To: ", fp);
1700 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1705 if (env->references)
1707 fputs ("References:", fp);
1708 mutt_write_references (env->references, fp);
1712 /* Add the MIME headers */
1713 fputs ("Mime-Version: 1.0\n", fp);
1714 mutt_write_mime_header (attach, fp);
1717 if (env->in_reply_to)
1719 fputs ("In-Reply-To:", fp);
1720 mutt_write_references (env->in_reply_to, fp);
1724 /* Add any user defined headers */
1725 for (; tmp; tmp = tmp->next)
1727 if ((p = strchr (tmp->data, ':')))
1730 if (!*p) continue; /* don't emit empty fields. */
1732 /* check to see if the user has overridden the user-agent field */
1733 if (!ascii_strncasecmp ("user-agent", tmp->data, 10))
1740 fputs (tmp->data, fp);
1745 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent)
1749 if (OperatingSystem!=NULL) {
1750 os = OperatingSystem;
1752 if (uname(&un)==-1) {
1758 /* Add a vanity header */
1759 fprintf (fp, "User-Agent: mutt-ng %s (%s)\n", MUTT_VERSION,os);
1762 return (ferror (fp) == 0 ? 0 : -1);
1765 static void encode_headers (LIST *h)
1771 for (; h; h = h->next)
1773 if (!(p = strchr (h->data, ':')))
1778 tmp = safe_strdup (p);
1783 rfc2047_encode_string (&tmp);
1784 safe_realloc (&h->data, mutt_strlen (h->data) + 2 + mutt_strlen (tmp) + 1);
1786 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1792 const char *mutt_fqdn(short may_hide_host)
1796 if(Fqdn && Fqdn[0] != '@')
1800 if(may_hide_host && option(OPTHIDDENHOST))
1802 if((p = strchr(Fqdn, '.')))
1805 /* sanity check: don't hide the host if
1806 * the fqdn is something like detebe.org.
1809 if(!p || !(q = strchr(p, '.')))
1817 static char mutt_normalized_char(char c) {
1820 if (strchr(".!#$%&'*+-/=?^_`{|}~",c))
1822 return '.'; /* normalized character (we're stricter than RFC2822, 3.6.4) */
1825 static void mutt_gen_localpart(char * buf, unsigned int len, char * fmt) {
1828 char tmp[SHORT_STRING];
1841 snprintf(tmp,sizeof(tmp),"%02d",tm->tm_mday);
1842 safe_strncat(buf,len,tmp,2);
1845 snprintf(tmp,sizeof(tmp),"%02d",tm->tm_hour);
1846 safe_strncat(buf,len,tmp,2);
1849 snprintf(tmp,sizeof(tmp),"%02d",tm->tm_mon+1);
1850 safe_strncat(buf,len,tmp,2);
1853 snprintf(tmp,sizeof(tmp),"%02d",tm->tm_min);
1854 safe_strncat(buf,len,tmp,2);
1857 snprintf(tmp,sizeof(tmp),"%lo",(unsigned long)now);
1858 safe_strncat(buf,len,tmp,strlen(tmp));
1861 snprintf(tmp,sizeof(tmp),"%u",(unsigned int)getpid());
1862 safe_strncat(buf,len,tmp,strlen(tmp));
1865 snprintf(tmp,sizeof(tmp),"%c",MsgIdPfx);
1866 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1867 safe_strncat(buf,len,tmp,1);
1870 snprintf(tmp,sizeof(tmp),"%u",(unsigned int)rand());
1871 safe_strncat(buf,len,tmp,strlen(tmp));
1874 snprintf(tmp,sizeof(tmp),"%x",(unsigned int)rand());
1875 safe_strncat(buf,len,tmp,strlen(tmp));
1878 snprintf(tmp,sizeof(tmp),"%02d",tm->tm_sec);
1879 safe_strncat(buf,len,tmp,2);
1882 snprintf(tmp,sizeof(tmp),"%u",(unsigned int)now);
1883 safe_strncat(buf,len,tmp,strlen(tmp));
1886 snprintf(tmp,sizeof(tmp),"%x",(unsigned int)now);
1887 safe_strncat(buf,len,tmp,strlen(tmp));
1890 snprintf(tmp,sizeof(tmp),"%04d",tm->tm_year+1900); /* this will break in the year 10000 ;-) */
1891 safe_strncat(buf,len,tmp,4);
1894 safe_strncat(buf,len,"%",1);
1897 safe_strncat(buf,len,".",1); /* invalid formats are replaced by '.' */
1902 c = mutt_normalized_char(*fmt); /* @todo: filter out invalid characters */
1903 safe_strncat(buf,len,&c,1);
1908 char *mutt_gen_msgid (void)
1910 char buf[SHORT_STRING];
1911 char localpart[SHORT_STRING];
1912 unsigned int localpart_length;
1919 if(!(fqdn = mutt_fqdn(0)))
1920 fqdn = NONULL(Hostname);
1922 localpart_length = sizeof(buf) - strlen(fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1924 mutt_gen_localpart(localpart,localpart_length,MsgIdFormat);
1926 snprintf(buf,sizeof(buf),"<%s@%s>",localpart,fqdn);
1927 return (safe_strdup (buf));
1930 static RETSIGTYPE alarm_handler (int sig)
1935 /* invoke sendmail in a subshell
1936 path (in) path to program to execute
1937 args (in) arguments to pass to program
1938 msg (in) temp file containing message to send
1939 tempfile (out) if sendmail is put in the background, this points
1940 to the temporary file containing the stdout of the
1943 send_msg (const char *path, char **args, const char *msg, char **tempfile)
1949 mutt_block_signals_system ();
1952 /* we also don't want to be stopped right now */
1953 sigaddset (&set, SIGTSTP);
1954 sigprocmask (SIG_BLOCK, &set, NULL);
1956 if (SendmailWait >= 0)
1958 char tmp[_POSIX_PATH_MAX];
1961 *tempfile = safe_strdup (tmp);
1964 if ((pid = fork ()) == 0)
1966 struct sigaction act, oldalrm;
1968 /* save parent's ID before setsid() */
1971 /* we want the delivery to continue even after the main process dies,
1972 * so we put ourselves into another session right away
1976 /* next we close all open files */
1977 #if defined(OPEN_MAX)
1978 for (fd = 0; fd < OPEN_MAX; fd++)
1980 #elif defined(_POSIX_OPEN_MAX)
1981 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1989 /* now the second fork() */
1990 if ((pid = fork ()) == 0)
1992 /* "msg" will be opened as stdin */
1993 if (open (msg, O_RDONLY, 0) < 0)
2000 if (SendmailWait >= 0)
2002 /* *tempfile will be opened as stdout */
2003 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) < 0)
2005 /* redirect stderr to *tempfile too */
2011 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
2013 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
2027 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
2028 * SendmailWait = 0: wait forever
2029 * SendmailWait < 0: don't wait
2031 if (SendmailWait > 0)
2034 act.sa_handler = alarm_handler;
2036 /* need to make sure waitpid() is interrupted on SIGALRM */
2037 act.sa_flags = SA_INTERRUPT;
2041 sigemptyset (&act.sa_mask);
2042 sigaction (SIGALRM, &act, &oldalrm);
2043 alarm (SendmailWait);
2045 else if (SendmailWait < 0)
2046 _exit (0xff & EX_OK);
2048 if (waitpid (pid, &st, 0) > 0)
2050 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
2051 if (SendmailWait && st == (0xff & EX_OK))
2053 unlink (*tempfile); /* no longer needed */
2059 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ?
2061 if (SendmailWait > 0)
2068 /* reset alarm; not really needed, but... */
2070 sigaction (SIGALRM, &oldalrm, NULL);
2072 if (kill (ppid, 0) == -1 && errno == ESRCH)
2074 /* the parent is already dead */
2082 sigprocmask (SIG_UNBLOCK, &set, NULL);
2084 if (pid != -1 && waitpid (pid, &st, 0) > 0)
2085 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
2087 st = S_ERR; /* error */
2089 mutt_unblock_signals_system (1);
2095 add_args (char **args, size_t *argslen, size_t *argsmax, ADDRESS *addr)
2097 for (; addr; addr = addr->next)
2099 /* weed out group mailboxes, since those are for display only */
2100 if (addr->mailbox && !addr->group)
2102 if (*argslen == *argsmax)
2103 safe_realloc (&args, (*argsmax += 5) * sizeof (char *));
2104 args[(*argslen)++] = addr->mailbox;
2111 add_option (char **args, size_t *argslen, size_t *argsmax, char *s)
2113 if (*argslen == *argsmax)
2114 safe_realloc (&args, (*argsmax += 5) * sizeof (char *));
2115 args[(*argslen)++] = s;
2124 for(i = 0; sysexits_h[i].str; i++)
2126 if(e == sysexits_h[i].v)
2130 return sysexits_h[i].str;
2135 mutt_invoke_sendmail (ADDRESS *from, /* the sender */
2136 ADDRESS *to, ADDRESS *cc, ADDRESS *bcc, /* recips */
2137 const char *msg, /* file containing message */
2138 int eightbit) /* message contains 8bit chars */
2140 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
2142 size_t argslen = 0, argsmax = 0;
2146 if (option (OPTNEWSSEND))
2148 char cmd[LONG_STRING];
2150 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0, 0);
2153 i = nntp_post (msg);
2158 s = safe_strdup (cmd);
2162 s = safe_strdup (Sendmail);
2166 while ((ps = strtok (ps, " ")))
2168 if (argslen == argsmax)
2169 safe_realloc (&args, sizeof (char *) * (argsmax += 5));
2172 args[argslen++] = ps;
2175 path = safe_strdup (ps);
2176 ps = strrchr (ps, '/');
2181 args[argslen++] = ps;
2188 if (!option (OPTNEWSSEND))
2191 if (eightbit && option (OPTUSE8BITMIME))
2192 args = add_option (args, &argslen, &argsmax, "-B8BITMIME");
2194 if (option (OPTENVFROM) && from && !from->next)
2196 args = add_option (args, &argslen, &argsmax, "-f");
2197 args = add_args (args, &argslen, &argsmax, from);
2201 args = add_option (args, &argslen, &argsmax, "-N");
2202 args = add_option (args, &argslen, &argsmax, DsnNotify);
2206 args = add_option (args, &argslen, &argsmax, "-R");
2207 args = add_option (args, &argslen, &argsmax, DsnReturn);
2209 args = add_option (args, &argslen, &argsmax, "--");
2210 args = add_args (args, &argslen, &argsmax, to);
2211 args = add_args (args, &argslen, &argsmax, cc);
2212 args = add_args (args, &argslen, &argsmax, bcc);
2217 if (argslen == argsmax)
2218 safe_realloc (&args, sizeof (char *) * (++argsmax));
2220 args[argslen++] = NULL;
2222 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff))
2226 const char *e = strsysexit (i);
2229 mutt_error (_("Error sending message, child exited %d (%s)."), i, NONULL (e));
2234 if (stat (childout, &st) == 0 && st.st_size > 0)
2235 mutt_do_pager (_("Output of the delivery process"), childout, 0, NULL);
2247 if (i == (EX_OK & 0xff))
2249 else if (i == S_BKG)
2257 mutt_invoke_mta (ADDRESS *from, /* the sender */
2258 ADDRESS *to, ADDRESS *cc, ADDRESS *bcc, /* recips */
2259 const char *msg, /* file containing message */
2260 int eightbit) /* message contains 8bit chars */
2264 return mutt_invoke_libesmtp(from, to, cc, bcc, msg, eightbit);
2267 return mutt_invoke_sendmail(from, to, cc, bcc, msg, eightbit);
2270 /* appends string 'b' to string 'a', and returns the pointer to the new
2272 char *mutt_append_string (char *a, const char *b)
2274 size_t la = mutt_strlen (a);
2275 safe_realloc (&a, la + mutt_strlen (b) + 1);
2276 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2280 /* returns 1 if char `c' needs to be quoted to protect from shell
2281 interpretation when executing commands in a subshell */
2282 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2284 /* returns 1 if string `s' contains characters which could cause problems
2285 when used on a command line to execute a command */
2286 int mutt_needs_quote (const char *s)
2290 if (INVALID_CHAR (*s))
2297 /* Quote a string to prevent shell escapes when this string is used on the
2298 command line to send mail. */
2299 char *mutt_quote_string (const char *s)
2304 rlen = mutt_strlen (s) + 3;
2305 pr = r = (char *) safe_malloc (rlen);
2309 if (INVALID_CHAR (*s))
2312 safe_realloc (&r, ++rlen);
2323 /* For postponing (!final) do the necessary encodings only */
2324 void mutt_prepare_envelope (ENVELOPE *env, int final)
2326 char buffer[LONG_STRING];
2330 if (env->bcc && !(env->to || env->cc))
2332 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2333 * recipients if there is no To: or Cc: field, so attempt to suppress
2334 * it by using an empty To: field.
2336 env->to = rfc822_new_address ();
2338 env->to->next = rfc822_new_address ();
2341 rfc822_cat (buffer, sizeof (buffer), "undisclosed-recipients",
2344 env->to->mailbox = safe_strdup (buffer);
2347 mutt_set_followup_to (env);
2349 if (!env->message_id)
2350 env->message_id = mutt_gen_msgid ();
2353 /* Take care of 8-bit => 7-bit conversion. */
2354 rfc2047_encode_adrlist (env->to, "To");
2355 rfc2047_encode_adrlist (env->cc, "Cc");
2356 rfc2047_encode_adrlist (env->from, "From");
2357 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2358 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2362 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2365 rfc2047_encode_string (&env->subject);
2367 encode_headers (env->userhdrs);
2370 void mutt_unprepare_envelope (ENVELOPE *env)
2374 for (item = env->userhdrs; item; item = item->next)
2375 rfc2047_decode (&item->data);
2377 rfc822_free_address (&env->mail_followup_to);
2379 /* back conversions */
2380 rfc2047_decode_adrlist (env->to);
2381 rfc2047_decode_adrlist (env->cc);
2382 rfc2047_decode_adrlist (env->from);
2383 rfc2047_decode_adrlist (env->reply_to);
2384 rfc2047_decode (&env->subject);
2387 static int _mutt_bounce_message (FILE *fp, HEADER *h, ADDRESS *to, const char *resent_from,
2392 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2393 MESSAGE *msg = NULL;
2397 /* Try to bounce each message out, aborting if we get any failures. */
2398 for (i=0; i<Context->msgcount; i++)
2399 if (Context->hdrs[i]->tagged)
2400 ret |= _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from, env_from);
2404 /* If we failed to open a message, return with error */
2405 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2408 if (!fp) fp = msg->fp;
2410 mutt_mktemp (tempfile);
2411 if ((f = safe_fopen (tempfile, "w")) != NULL)
2413 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2415 if (!option (OPTBOUNCEDELIVERED))
2416 ch_flags |= CH_WEED_DELIVERED;
2418 fseek (fp, h->offset, 0);
2419 fprintf (f, "Resent-From: %s", resent_from);
2420 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof(date)));
2421 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid());
2422 fputs ("Resent-To: ", f);
2423 mutt_write_address_list (to, f, 11, 0);
2424 mutt_copy_header (fp, h, f, ch_flags, NULL);
2426 mutt_copy_bytes (fp, f, h->content->length);
2429 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2430 h->content->encoding == ENC8BIT);
2434 mx_close_message (&msg);
2439 int mutt_bounce_message (FILE *fp, HEADER *h, ADDRESS *to)
2442 const char *fqdn = mutt_fqdn (1);
2443 char resent_from[STRING];
2447 resent_from[0] = '\0';
2448 from = mutt_default_from ();
2451 rfc822_qualify (from, fqdn);
2453 rfc2047_encode_adrlist (from, "Resent-From");
2454 if (mutt_addrlist_to_idna (from, &err))
2456 mutt_error (_("Bad IDN %s while preparing resent-from."),
2460 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2463 unset_option (OPTNEWSSEND);
2466 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2468 rfc822_free_address (&from);
2474 /* given a list of addresses, return a list of unique addresses */
2475 ADDRESS *mutt_remove_duplicates (ADDRESS *addr)
2477 ADDRESS *top = addr;
2478 ADDRESS **last = ⊤
2484 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next)
2486 if (tmp->mailbox && addr->mailbox &&
2487 !ascii_strcasecmp (addr->mailbox, tmp->mailbox))
2496 dprint (2, (debugfile, "mutt_remove_duplicates: Removing %s\n",
2502 rfc822_free_address(&addr);
2516 static void set_noconv_flags (BODY *b, short flag)
2518 for(; b; b = b->next)
2520 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2521 set_noconv_flags (b->parts, flag);
2522 else if (b->type == TYPETEXT && b->noconv)
2525 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2527 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2532 int mutt_write_fcc (const char *path, HEADER *hdr, const char *msgid, int post, char *fcc)
2536 char tempfile[_POSIX_PATH_MAX];
2537 FILE *tempfp = NULL;
2541 set_noconv_flags (hdr->content, 1);
2543 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL)
2545 dprint (1, (debugfile, "mutt_write_fcc(): unable to open mailbox %s in append-mode, aborting.\n",
2550 /* We need to add a Content-Length field to avoid problems where a line in
2551 * the message body begins with "From "
2553 if (f.magic == M_MMDF || f.magic == M_MBOX)
2555 mutt_mktemp (tempfile);
2556 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL)
2558 mutt_perror (tempfile);
2559 mx_close_mailbox (&f, NULL);
2564 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2565 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL)
2567 mx_close_mailbox (&f, NULL);
2571 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2572 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2574 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0, 0);
2576 /* (postponment) if this was a reply of some sort, <msgid> contians the
2577 * Message-ID: of message replied to. Save it using a special X-Mutt-
2578 * header so it can be picked up if the message is recalled at a later
2579 * point in time. This will allow the message to be marked as replied if
2580 * the same mailbox is still open.
2583 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2585 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2586 * it can be picked up when the message is recalled
2589 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2590 fprintf (msg->fp, "Status: RO\n");
2594 /* (postponment) if the mail is to be signed or encrypted, save this info */
2595 if ((WithCrypto & APPLICATION_PGP)
2596 && post && (hdr->security & APPLICATION_PGP))
2598 fputs ("X-Mutt-PGP: ", msg->fp);
2599 if (hdr->security & ENCRYPT)
2600 fputc ('E', msg->fp);
2601 if (hdr->security & SIGN)
2603 fputc ('S', msg->fp);
2604 if (PgpSignAs && *PgpSignAs)
2605 fprintf (msg->fp, "<%s>", PgpSignAs);
2607 if (hdr->security & INLINE)
2608 fputc ('I', msg->fp);
2609 fputc ('\n', msg->fp);
2612 /* (postponment) if the mail is to be signed or encrypted, save this info */
2613 if ((WithCrypto & APPLICATION_SMIME)
2614 && post && (hdr->security & APPLICATION_SMIME))
2616 fputs ("X-Mutt-SMIME: ", msg->fp);
2617 if (hdr->security & ENCRYPT) {
2618 fputc ('E', msg->fp);
2619 if (SmimeCryptAlg && *SmimeCryptAlg)
2620 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2622 if (hdr->security & SIGN) {
2623 fputc ('S', msg->fp);
2624 if (SmimeDefaultKey && *SmimeDefaultKey)
2625 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2627 if (hdr->security & INLINE)
2628 fputc ('I', msg->fp);
2629 fputc ('\n', msg->fp);
2633 /* (postponement) if the mail is to be sent through a mixmaster
2634 * chain, save that information
2637 if (post && hdr->chain && hdr->chain)
2641 fputs ("X-Mutt-Mix:", msg->fp);
2642 for (p = hdr->chain; p; p = p->next)
2643 fprintf (msg->fp, " %s", (char *) p->data);
2645 fputc ('\n', msg->fp);
2651 char sasha[LONG_STRING];
2654 mutt_write_mime_body (hdr->content, tempfp);
2656 /* make sure the last line ends with a newline. Emacs doesn't ensure
2657 * this will happen, and it can cause problems parsing the mailbox
2660 fseek (tempfp, -1, 2);
2661 if (fgetc (tempfp) != '\n')
2663 fseek (tempfp, 0, 2);
2664 fputc ('\n', tempfp);
2668 if (ferror (tempfp))
2670 dprint (1, (debugfile, "mutt_write_fcc(): %s: write failed.\n", tempfile));
2673 mx_commit_message (msg, &f); /* XXX - really? */
2674 mx_close_message (&msg);
2675 mx_close_mailbox (&f, NULL);
2679 /* count the number of lines */
2681 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2683 fprintf (msg->fp, "Content-Length: %ld\n", (long) ftell (tempfp));
2684 fprintf (msg->fp, "Lines: %d\n\n", lines);
2686 /* copy the body and clean up */
2688 r = mutt_copy_stream (tempfp, msg->fp);
2689 if (fclose (tempfp) != 0)
2691 /* if there was an error, leave the temp version */
2697 fputc ('\n', msg->fp); /* finish off the header */
2698 r = mutt_write_mime_body (hdr->content, msg->fp);
2701 if (mx_commit_message (msg, &f) != 0)
2703 mx_close_message (&msg);
2704 mx_close_mailbox (&f, NULL);
2707 set_noconv_flags (hdr->content, 0);