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.
25 #include <sys/utsname.h>
27 #include <lib-lib/mem.h>
28 #include <lib-lib/ascii.h>
29 #include <lib-lib/str.h>
30 #include <lib-lib/macros.h>
31 #include <lib-lib/file.h>
33 #include <lib-sys/exit.h>
34 #include <lib-sys/mutt_signal.h>
36 #include <lib-mime/mime.h>
38 #include <lib-ui/curses.h>
42 #include "recvattach.h"
47 #include <lib-crypt/crypt.h>
48 #include "mutt_idna.h"
51 # include "mutt_libesmtp.h"
52 #endif /* USE_LIBESMTP */
58 #ifdef HAVE_SYSEXITS_H
60 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
64 /* If you are debugging this file, comment out the following line. */
73 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
75 static char MsgIdPfx = 'A';
77 static void transform_to_7bit (BODY * a, FILE * fpin);
79 static void encode_quoted (fgetconv_t * fc, FILE * fout, int istext)
82 char line[77], savechar;
84 while ((c = fgetconv (fc)) != EOF) {
85 /* Wrap the line if needed. */
86 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
87 /* If the last character is "quoted", then be sure to move all three
88 * characters to the next line. Otherwise, just move the last
91 if (line[linelen - 3] == '=') {
92 line[linelen - 3] = 0;
97 line[1] = line[linelen - 2];
98 line[2] = line[linelen - 1];
102 savechar = line[linelen - 1];
103 line[linelen - 1] = '=';
112 /* Escape lines that begin with/only contain "the message separator". */
113 if (linelen == 4 && !m_strncmp("From", line, 4)) {
114 m_strcpy(line, sizeof(line), "=46rom");
117 else if (linelen == 4 && !m_strncmp("from", line, 4)) {
118 m_strcpy(line, sizeof(line), "=66rom");
121 else if (linelen == 1 && line[0] == '.') {
122 m_strcpy(line, sizeof(line), "=2E");
127 if (c == '\n' && istext) {
128 /* Check to make sure there is no trailing space on this line. */
130 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
132 sprintf (line + linelen - 1, "=%2.2X",
133 (unsigned char) line[linelen - 1]);
137 int savechar = line[linelen - 1];
139 line[linelen - 1] = '=';
142 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
152 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
153 /* Check to make sure there is enough room for the quoted character.
154 * If not, wrap to the next line.
157 line[linelen++] = '=';
163 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
167 /* Don't worry about wrapping the line here. That will happen during
168 * the next iteration when I'll also know what the next character is.
174 /* Take care of anything left in the buffer */
176 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
177 /* take care of trailing whitespace */
179 sprintf (line + linelen - 1, "=%2.2X",
180 (unsigned char) line[linelen - 1]);
182 savechar = line[linelen - 1];
183 line[linelen - 1] = '=';
187 sprintf (line, "=%2.2X", (unsigned char) savechar);
196 static char b64_buffer[3];
197 static short b64_num;
198 static short b64_linelen;
200 static void b64_flush (FILE * fout)
207 if (b64_linelen >= 72) {
212 for (i = b64_num; i < 3; i++)
213 b64_buffer[i] = '\0';
215 fputc(__m_b64chars[(b64_buffer[0] >> 2) & 0x3f], fout);
218 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
223 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
227 fputc (__m_b64chars[b64_buffer[2] & 0x3f], fout);
232 while (b64_linelen % 4) {
241 static void b64_putc (char c, FILE * fout)
246 b64_buffer[b64_num++] = c;
250 static void encode_base64 (fgetconv_t * fc, FILE * fout, int istext)
254 b64_num = b64_linelen = 0;
256 while ((ch = fgetconv (fc)) != EOF) {
257 if (istext && ch == '\n' && ch1 != '\r')
258 b64_putc ('\r', fout);
266 static void encode_8bit (fgetconv_t * fc, FILE * fout, int istext)
270 while ((ch = fgetconv (fc)) != EOF)
275 int mutt_write_mime_header (BODY * a, FILE * f)
284 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
289 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
291 for (p = a->parameter; p; p = p->next) {
300 tmp = m_strdup(p->value);
301 encode = rfc2231_encode_string (&tmp);
302 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
304 /* Dirty hack to make messages readable by Outlook Express
305 * for the Mac: force quotes around the boundary parameter
306 * even when they aren't needed.
309 if (!ascii_strcasecmp (p->attribute, "boundary")
310 && !strcmp (buffer, tmp))
311 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
315 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
317 if (len + tmplen + 2 > 76) {
326 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
334 fprintf (f, "Content-Description: %s\n", a->description);
336 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
339 if (!(fn = a->d_filename))
345 /* Strip off the leading path... */
346 if ((t = strrchr (fn, '/')))
353 encode = rfc2231_encode_string (&tmp);
354 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
356 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
362 if (a->encoding != ENC7BIT)
363 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
365 /* Do NOT add the terminator here!!! */
366 return (ferror (f) ? -1 : 0);
369 # define write_as_text_part(a) (mutt_is_text_part(a) || mutt_is_application_pgp(a))
371 int mutt_write_mime_body (BODY * a, FILE * f)
374 char boundary[SHORT_STRING];
375 char send_charset[SHORT_STRING];
380 if (a->type == TYPEMULTIPART) {
381 /* First, find the boundary to use */
382 if (!(p = parameter_getval(a->parameter, "boundary"))) {
383 mutt_error _("No boundary parameter found! [report this error]");
387 m_strcpy(boundary, sizeof(boundary), p);
389 for (t = a->parts; t; t = t->next) {
390 fprintf (f, "\n--%s\n", boundary);
391 if (mutt_write_mime_header (t, f) == -1)
394 if (mutt_write_mime_body (t, f) == -1)
397 fprintf (f, "\n--%s--\n", boundary);
398 return (ferror (f) ? -1 : 0);
401 /* This is pretty gross, but it's the best solution for now... */
402 if (a->type == TYPEAPPLICATION && !m_strcmp(a->subtype, "pgp-encrypted")) {
403 fputs ("Version: 1\n", f);
407 if ((fpin = fopen (a->filename, "r")) == NULL) {
408 mutt_error (_("%s no longer exists!"), a->filename);
412 if (a->type == TYPETEXT && (!a->noconv))
413 fc = fgetconv_open (fpin, a->file_charset,
414 mutt_get_body_charset (send_charset,
415 sizeof (send_charset), a), 0);
417 fc = fgetconv_open (fpin, 0, 0, 0);
419 if (a->encoding == ENCQUOTEDPRINTABLE)
420 encode_quoted (fc, f, write_as_text_part (a));
421 else if (a->encoding == ENCBASE64)
422 encode_base64 (fc, f, write_as_text_part (a));
423 else if (a->type == TYPETEXT && (!a->noconv))
424 encode_8bit (fc, f, write_as_text_part (a));
426 mutt_copy_stream (fpin, f);
428 fgetconv_close (&fc);
431 return (ferror (f) ? -1 : 0);
434 #undef write_as_text_part
445 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
449 int whitespace = s->whitespace;
451 int linelen = s->linelen;
452 int was_cr = s->was_cr;
454 if (!d) { /* This signals EOF */
457 if (linelen > info->linemax)
458 info->linemax = linelen;
463 for (; dlen; d++, dlen--) {
476 if (linelen > info->linemax)
477 info->linemax = linelen;
492 if (linelen > info->linemax)
493 info->linemax = linelen;
498 else if (ch == '\r') {
506 else if (ch == '\t' || ch == '\f') {
510 else if (ch < 32 || ch == 127)
514 if ((ch == 'F') || (ch == 'f'))
524 if (linelen == 2 && ch != 'r')
526 else if (linelen == 3 && ch != 'o')
528 else if (linelen == 4) {
541 if (ch != ' ' && ch != '\t')
546 s->whitespace = whitespace;
548 s->linelen = linelen;
553 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
554 #define BUGGY_ICONV 1
557 * Find the best charset conversion of the file from fromcode into one
558 * of the tocodes. If successful, set *tocode and CONTENT *info and
559 * return the number of characters converted inexactly. If no
560 * conversion was possible, return -1.
562 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
563 * which would otherwise prevent us from knowing the number of inexact
564 * conversions. Where the candidate target charset is UTF-8 we avoid
565 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
566 * fails with some libraries.
568 * We assume that the output from iconv is never more than 4 times as
569 * long as the input for any pair of charsets we might be interested
572 static ssize_t convert_file_to (FILE * file, const char *fromcode,
573 int ncodes, const char **tocodes,
574 int *tocode, CONTENT * info)
578 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
581 ssize_t ibl, obl, ubl, ubl1, n, ret;
584 CONTENT_STATE *states;
587 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
588 if (cd1 == MUTT_ICONV_ERROR)
591 cd = p_new(iconv_t, ncodes);
592 score = p_new(ssize_t, ncodes);
593 states = p_new(CONTENT_STATE, ncodes);
594 infos = p_new(CONTENT, ncodes);
596 for (i = 0; i < ncodes; i++)
597 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
598 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
600 /* Special case for conversion to UTF-8 */
601 cd[i] = MUTT_ICONV_ERROR, score[i] = -1;
607 /* Try to fill input buffer */
608 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
611 /* Convert to UTF-8 */
613 ob = bufu, obl = sizeof (bufu);
614 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
615 assert (n == -1 || !n);
617 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
618 assert (errno == EILSEQ ||
619 (errno == EINVAL && ib == bufi && ibl < ssizeof (bufi)));
625 /* Convert from UTF-8 */
626 for (i = 0; i < ncodes; i++)
627 if (cd[i] != MUTT_ICONV_ERROR && score[i] != -1) {
628 ub = bufu, ubl = ubl1;
629 ob = bufo, obl = sizeof (bufo);
630 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
632 assert (errno == E2BIG ||
633 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
638 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
641 else if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1)
642 /* Special case for conversion to UTF-8 */
643 update_content_info (&infos[i], &states[i], bufu, ubl1);
646 /* Save unused input */
647 memmove (bufi, ib, ibl);
648 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
655 /* Find best score */
657 for (i = 0; i < ncodes; i++) {
658 if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1) {
659 /* Special case for conversion to UTF-8 */
664 else if (cd[i] == MUTT_ICONV_ERROR || score[i] == -1)
666 else if (ret == -1 || score[i] < ret) {
674 memcpy (info, &infos[*tocode], sizeof (CONTENT));
675 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
679 for (i = 0; i < ncodes; i++)
680 if (cd[i] != MUTT_ICONV_ERROR)
692 #endif /* !HAVE_ICONV */
696 * Find the first of the fromcodes that gives a valid conversion and
697 * the best charset conversion of the file into one of the tocodes. If
698 * successful, set *fromcode and *tocode to dynamically allocated
699 * strings, set CONTENT *info, and return the number of characters
700 * converted inexactly. If no conversion was possible, return -1.
702 * Both fromcodes and tocodes may be colon-separated lists of charsets.
703 * However, if fromcode is zero then fromcodes is assumed to be the
704 * name of a single charset even if it contains a colon.
706 static ssize_t convert_file_from_to (FILE * file,
707 const char *fromcodes,
708 const char *tocodes, char **fromcode,
709 char **tocode, CONTENT * info)
717 /* Count the tocodes */
719 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
720 if ((c1 = strchr (c, ':')) == c)
726 tcode = p_new(char *, ncodes);
727 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
728 if ((c1 = strchr (c, ':')) == c)
730 tcode[i] = m_substrdup(c, c1);
735 /* Try each fromcode in turn */
736 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
737 if ((c1 = strchr (c, ':')) == c)
739 fcode = m_substrdup(c, c1);
741 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
753 /* There is only one fromcode */
754 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
763 for (i = 0; i < ncodes; i++)
772 * Analyze the contents of a file to determine which MIME encoding to use.
773 * Also set the body charset, sometimes, or not.
775 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
780 char *fromcode = NULL;
791 if (stat (fname, &sb) == -1) {
792 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
796 if (!S_ISREG (sb.st_mode)) {
797 mutt_error (_("%s isn't a regular file."), fname);
801 if ((fp = fopen (fname, "r")) == NULL) {
805 info = p_new(CONTENT, 1);
808 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
809 const char *chs = parameter_getval(b->parameter, "charset");
810 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
811 FileCharset : Charset) : Charset;
812 if (Charset && (chs || SendCharset) &&
813 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
814 &fromcode, &tocode, info) != -1) {
816 charset_canonicalize (chsbuf, sizeof (chsbuf), tocode);
817 parameter_setval(&b->parameter, "charset", chsbuf);
819 b->file_charset = fromcode;
827 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
828 update_content_info (info, &state, buffer, r);
829 update_content_info (info, &state, 0, 0);
833 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
834 parameter_setval(&b->parameter, "charset",
835 (!info->hibin ? "us-ascii"
836 : Charset && !charset_is_us_ascii(Charset) ? Charset : "unknown-8bit"));
841 /* Given a file with path ``s'', see if there is a registered MIME type.
842 * returns the major MIME type, and copies the subtype to ``d''. First look
843 * for ~/.mime.types, then look in a system mime.types if we can find one.
844 * The longest match is used so that we can match `ps.gz' when `gz' also
848 int mutt_lookup_mime_type (BODY * att, const char *path)
852 char buf[LONG_STRING];
853 char subtype[STRING], xtype[STRING];
855 int szf, sze, cur_sze;
863 szf = m_strlen(path);
865 for (count = 0; count < 4; count++) {
867 * can't use strtok() because we use it in an inner loop below, so use
868 * a switch statement here instead.
872 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
875 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
878 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
881 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
884 goto bye; /* shouldn't happen */
887 if ((f = fopen (buf, "r")) != NULL) {
888 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
889 /* weed out any comments */
890 if ((p = strchr (buf, '#')))
893 /* remove any leading space. */
894 ct = vskipspaces(buf);
896 /* position on the next field in this line */
897 if ((p = strpbrk (ct, " \t")) == NULL)
902 /* cycle through the file extensions */
903 while ((p = strtok (p, " \t\n"))) {
905 if ((sze > cur_sze) && (szf >= sze) &&
906 (m_strcasecmp(path + szf - sze, p) == 0
907 || ascii_strcasecmp (path + szf - sze, p) == 0)
908 && (szf == sze || path[szf - sze - 1] == '.'))
910 /* get the content-type */
912 if ((p = strchr (ct, '/')) == NULL) {
913 /* malformed line, just skip it. */
918 for (q = p; *q && !ISSPACE (*q); q++);
920 m_strncpy(subtype, sizeof(subtype), p, q - p);
922 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
923 m_strcpy(xtype, sizeof(xtype), ct);
936 if (type != TYPEOTHER || *xtype != '\0') {
938 m_strreplace(&att->subtype, subtype);
939 m_strreplace(&att->xtype, xtype);
945 void mutt_message_to_7bit (BODY * a, FILE * fp)
947 char temp[_POSIX_PATH_MAX];
953 if (!a->filename && fp)
955 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
956 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
961 if (stat (a->filename, &sb) == -1) {
962 mutt_perror ("stat");
965 a->length = sb.st_size;
969 if (!(fpout = safe_fopen (temp, "w+"))) {
970 mutt_perror ("fopen");
974 fseeko (fpin, a->offset, 0);
975 a->parts = mutt_parse_messageRFC822 (fpin, a);
977 transform_to_7bit (a->parts, fpin);
979 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
980 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
982 fputs ("MIME-Version: 1.0\n", fpout);
983 mutt_write_mime_header (a->parts, fpout);
985 mutt_write_mime_body (a->parts, fpout);
997 a->encoding = ENC7BIT;
998 a->d_filename = a->filename;
999 if (a->filename && a->unlink)
1000 unlink (a->filename);
1001 a->filename = m_strdup(temp);
1003 if (stat (a->filename, &sb) == -1) {
1004 mutt_perror ("stat");
1007 a->length = sb.st_size;
1008 mutt_free_body (&a->parts);
1009 a->hdr->content = NULL;
1012 static void transform_to_7bit (BODY * a, FILE * fpin)
1014 char buff[_POSIX_PATH_MAX];
1019 for (; a; a = a->next) {
1020 if (a->type == TYPEMULTIPART) {
1021 if (a->encoding != ENC7BIT)
1022 a->encoding = ENC7BIT;
1024 transform_to_7bit (a->parts, fpin);
1026 else if (mutt_is_message_type (a->type, a->subtype)) {
1027 mutt_message_to_7bit (a, fpin);
1031 a->force_charset = 1;
1034 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1035 mutt_perror ("fopen");
1039 mutt_decode_attachment (a, &s);
1041 a->d_filename = a->filename;
1042 a->filename = m_strdup(buff);
1044 if (stat (a->filename, &sb) == -1) {
1045 mutt_perror ("stat");
1048 a->length = sb.st_size;
1050 mutt_update_encoding (a);
1051 if (a->encoding == ENC8BIT)
1052 a->encoding = ENCQUOTEDPRINTABLE;
1053 else if (a->encoding == ENCBINARY)
1054 a->encoding = ENCBASE64;
1059 /* determine which Content-Transfer-Encoding to use */
1060 static void mutt_set_encoding (BODY * b, CONTENT * info)
1062 char send_charset[SHORT_STRING];
1064 if (b->type == TYPETEXT) {
1066 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1067 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1068 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1069 b->encoding = ENCQUOTEDPRINTABLE;
1070 else if (info->hibin)
1071 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1073 b->encoding = ENC7BIT;
1075 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1076 if (info->lobin || info->hibin) {
1077 if (option (OPTALLOW8BIT) && !info->lobin)
1078 b->encoding = ENC8BIT;
1080 mutt_message_to_7bit (b, NULL);
1083 b->encoding = ENC7BIT;
1085 else if (b->type == TYPEAPPLICATION
1086 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1087 b->encoding = ENC7BIT;
1090 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1091 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1094 /* Determine which encoding is smaller */
1095 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1096 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1097 b->encoding = ENCBASE64;
1099 b->encoding = ENCQUOTEDPRINTABLE;
1103 b->encoding = ENC7BIT;
1107 void mutt_stamp_attachment (BODY * a)
1109 a->stamp = time (NULL);
1112 /* Get a body's character set */
1114 char *mutt_get_body_charset (char *d, ssize_t dlen, BODY * b)
1116 const char *p = NULL;
1118 if (b && b->type != TYPETEXT)
1122 p = parameter_getval(b->parameter, "charset");
1125 charset_canonicalize (d, dlen, p);
1127 m_strcpy(d, dlen, "us-ascii");
1133 /* Assumes called from send mode where BODY->filename points to actual file */
1134 void mutt_update_encoding (BODY * a)
1137 char chsbuff[STRING];
1139 /* override noconv when it's us-ascii */
1140 if (charset_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1143 if (!a->force_charset && !a->noconv)
1144 parameter_delval(&a->parameter, "charset");
1146 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1149 mutt_set_encoding (a, info);
1150 mutt_stamp_attachment (a);
1152 p_delete(&a->content);
1157 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1159 char buffer[LONG_STRING];
1162 int cmflags, chflags;
1163 int pgp = hdr->security;
1165 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1166 (hdr->security & ENCRYPT)) {
1167 if (!crypt_valid_passphrase (hdr->security))
1171 mutt_mktemp (buffer);
1172 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1175 body = mutt_new_body ();
1176 body->type = TYPEMESSAGE;
1177 body->subtype = m_strdup("rfc822");
1178 body->filename = m_strdup(buffer);
1181 body->disposition = DISPINLINE;
1184 mutt_parse_mime_message (ctx, hdr);
1189 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1190 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1191 chflags |= CH_MIME | CH_TXTPLAIN;
1192 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1193 pgp &= ~(PGPENCRYPT|SMIMEENCRYPT);
1195 else if (option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1196 if (mutt_is_multipart_encrypted (hdr->content)) {
1197 chflags |= CH_MIME | CH_NONEWLINE;
1198 cmflags = M_CM_DECODE_PGP;
1201 else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1202 chflags |= CH_MIME | CH_TXTPLAIN;
1203 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1206 else if (mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1207 chflags |= CH_MIME | CH_TXTPLAIN;
1208 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1209 pgp &= ~SMIMEENCRYPT;
1213 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1218 body->hdr = header_new();
1219 body->hdr->offset = 0;
1220 /* we don't need the user headers here */
1221 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1222 body->hdr->security = pgp;
1223 mutt_update_encoding (body);
1224 body->parts = body->hdr->content;
1231 BODY *mutt_make_file_attach (const char *path)
1236 att = mutt_new_body ();
1237 att->filename = m_strdup(path);
1239 /* Attempt to determine the appropriate content-type based on the filename
1246 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1247 path)) != TYPEOTHER || *xbuf != '\0') {
1249 att->subtype = m_strdup(buf);
1250 att->xtype = m_strdup(xbuf);
1255 mutt_lookup_mime_type (att, path);
1259 if ((info = mutt_get_content_info (path, att)) == NULL) {
1260 mutt_free_body (&att);
1264 if (!att->subtype) {
1265 if (info->lobin == 0
1266 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1268 * Statistically speaking, there should be more than 10% "lobin"
1269 * chars if this is really a binary file...
1271 att->type = TYPETEXT;
1272 att->subtype = m_strdup("plain");
1275 att->type = TYPEAPPLICATION;
1276 att->subtype = m_strdup("octet-stream");
1280 mutt_update_encoding (att);
1284 static int get_toplevel_encoding (BODY * a)
1288 for (; a; a = a->next) {
1289 if (a->encoding == ENCBINARY)
1291 else if (a->encoding == ENC8BIT)
1298 BODY *mutt_make_multipart (BODY * b)
1302 new = mutt_new_body ();
1303 new->type = TYPEMULTIPART;
1304 new->subtype = m_strdup("mixed");
1305 new->encoding = get_toplevel_encoding (b);
1306 parameter_set_boundary(&new->parameter);
1308 new->disposition = DISPINLINE;
1314 /* remove the multipart body if it exists */
1315 BODY *mutt_remove_multipart (BODY * b)
1323 mutt_free_body (&t);
1328 char *mutt_make_date (char *s, ssize_t len)
1330 time_t t = time (NULL);
1331 struct tm *l = localtime (&t);
1332 time_t tz = mutt_local_tz (t);
1336 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1337 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1338 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1339 (int) tz / 60, (int) abs (tz) % 60);
1343 /* wrapper around mutt_write_address() so we can handle very large
1344 recipient lists without needing a huge temporary buffer in memory */
1345 void mutt_write_address_list (address_t * adr, FILE * fp, int linelen,
1349 char buf[LONG_STRING];
1357 rfc822_write_address (buf, sizeof (buf), adr, display);
1358 len = m_strlen(buf);
1359 if (count && linelen + len > 74) {
1361 linelen = len + 8; /* tab is usually about 8 spaces... */
1364 if (count && adr->mailbox) {
1372 if (!adr->group && adr->next && adr->next->mailbox) {
1382 /* arbitrary number of elements to grow the array by */
1387 /* need to write the list in reverse because they are stored in reverse order
1388 * when parsed to speed up threading
1390 void mutt_write_references (string_list_t * r, FILE * f)
1392 string_list_t **ref = NULL;
1393 int refcnt = 0, refmax = 0;
1395 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1396 if (refcnt == refmax)
1397 p_realloc(&ref, refmax += REF_INC);
1401 while (refcnt-- > 0) {
1403 fputs (ref[refcnt]->data, f);
1409 /* Note: all RFC2047 encoding should be done outside of this routine, except
1410 * for the "real name." This will allow this routine to be used more than
1411 * once, if necessary.
1413 * Likewise, all IDN processing should happen outside of this routine.
1415 * mode == 1 => "lite" mode (used for edit_hdrs)
1416 * mode == 0 => normal mode. write full header + MIME headers
1417 * mode == -1 => write just the envelope info (used for postponing messages)
1419 * privacy != 0 => will omit any headers which may identify the user.
1420 * Output generated is suitable for being sent through
1421 * anonymous remailer chains.
1425 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1426 int mode, int privacy)
1428 char buffer[LONG_STRING];
1430 string_list_t *tmp = env->userhdrs;
1431 int has_agent = 0; /* user defined user-agent header field exists */
1432 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1435 if (!option (OPTNEWSSEND))
1437 if (mode == 0 && !privacy)
1438 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1440 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1442 /* OPTUSEFROM is not consulted here so that we can still write a From:
1443 * field if the user sets it with the `my_hdr' command
1445 if (env->from && !privacy) {
1447 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1448 fprintf (fp, "From: %s\n", buffer);
1453 mutt_write_address_list (env->to, fp, 4, 0);
1457 if (!option (OPTNEWSSEND))
1459 if (EDIT_HEADER("To:"))
1460 fputs ("To:\n", fp);
1464 mutt_write_address_list (env->cc, fp, 4, 0);
1468 if (!option (OPTNEWSSEND))
1470 if (EDIT_HEADER("Cc:"))
1471 fputs ("Cc:\n", fp);
1474 if (mode != 0 || option (OPTWRITEBCC)) {
1475 fputs ("Bcc: ", fp);
1476 mutt_write_address_list (env->bcc, fp, 5, 0);
1481 if (!option (OPTNEWSSEND))
1483 if (EDIT_HEADER("Bcc:"))
1484 fputs ("Bcc:\n", fp);
1487 if (env->newsgroups)
1488 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1489 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1490 fputs ("Newsgroups:\n", fp);
1492 if (env->followup_to)
1493 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1494 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1495 fputs ("Followup-To:\n", fp);
1497 if (env->x_comment_to)
1498 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1499 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1500 EDIT_HEADER("X-Comment-To:"))
1501 fputs ("X-Comment-To:\n", fp);
1505 fprintf (fp, "Subject: %s\n", env->subject);
1506 else if (mode == 1 && EDIT_HEADER("Subject:"))
1507 fputs ("Subject:\n", fp);
1509 /* save message id if the user has set it */
1510 if (env->message_id && !privacy)
1511 fprintf (fp, "Message-ID: %s\n", env->message_id);
1513 if (env->reply_to) {
1514 fputs ("Reply-To: ", fp);
1515 mutt_write_address_list (env->reply_to, fp, 10, 0);
1517 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1518 fputs ("Reply-To:\n", fp);
1520 if (env->mail_followup_to)
1522 if (!option (OPTNEWSSEND))
1525 fputs ("Mail-Followup-To: ", fp);
1526 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1530 if (env->references) {
1531 fputs ("References:", fp);
1532 mutt_write_references (env->references, fp);
1536 /* Add the MIME headers */
1537 fputs ("MIME-Version: 1.0\n", fp);
1538 mutt_write_mime_header (attach, fp);
1541 if (env->in_reply_to) {
1542 fputs ("In-Reply-To:", fp);
1543 mutt_write_references (env->in_reply_to, fp);
1549 /* Add any user defined headers */
1550 for (; tmp; tmp = tmp->next) {
1551 if ((p = strchr (tmp->data, ':'))) {
1552 p = vskipspaces(p + 1);
1554 continue; /* don't emit empty fields. */
1556 /* check to see if the user has overridden the user-agent field */
1557 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1563 fputs (tmp->data, fp);
1568 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1571 if (OperatingSystem != NULL) {
1572 os = OperatingSystem;
1575 os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1577 /* Add a vanity header */
1578 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1581 list_del (&hdrs, (list_del_t*)xmemfree);
1583 return (ferror (fp) == 0 ? 0 : -1);
1586 static void encode_headers (string_list_t * h)
1592 for (; h; h = h->next) {
1593 if (!(p = strchr (h->data, ':')))
1597 p = vskipspaces(p + 1);
1603 rfc2047_encode_string (&tmp);
1604 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1606 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1612 const char *mutt_fqdn (short may_hide_host)
1616 if (Fqdn && Fqdn[0] != '@') {
1619 if (may_hide_host && option (OPTHIDDENHOST)) {
1620 if ((p = strchr (Fqdn, '.')))
1623 /* sanity check: don't hide the host if
1624 * the fqdn is something like detebe.org.
1627 if (!p || !(q = strchr (p, '.')))
1635 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1636 static char mutt_normalized_char(char c)
1638 return (isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.';
1641 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1643 #define APPEND_FMT(fmt, arg) \
1645 int snlen = snprintf(buf, len, fmt, arg); \
1650 #define APPEND_BYTE(c) \
1667 APPEND_BYTE(mutt_normalized_char(c));
1675 APPEND_FMT("%02d", tm->tm_mday);
1678 APPEND_FMT("%02d", tm->tm_hour);
1681 APPEND_FMT("%02d", tm->tm_mon + 1);
1684 APPEND_FMT("%02d", tm->tm_min);
1687 APPEND_FMT("%lo", (unsigned long)now);
1690 APPEND_FMT("%u", (unsigned int)getpid());
1693 APPEND_FMT("%c", MsgIdPfx);
1694 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1697 APPEND_FMT("%u", (unsigned int)rand());
1700 APPEND_FMT("%x", (unsigned int)rand());
1703 APPEND_FMT("%02d", tm->tm_sec);
1706 APPEND_FMT("%u", (unsigned int) now);
1709 APPEND_FMT("%x", (unsigned int) now);
1711 case 'Y': /* this will break in the year 10000 ;-) */
1712 APPEND_FMT("%04d", tm->tm_year + 1900);
1717 default: /* invalid formats are replaced by '.' */
1719 m_strncat(buf, len, ".", 1);
1729 char *mutt_gen_msgid (void)
1731 char buf[SHORT_STRING];
1732 char localpart[SHORT_STRING];
1733 unsigned int localpart_length;
1736 if (!(fqdn = mutt_fqdn (0)))
1737 fqdn = NONULL (Hostname);
1739 localpart_length = sizeof (buf) - m_strlen(fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1741 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1743 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1744 return (m_strdup(buf));
1747 static RETSIGTYPE alarm_handler (int sig)
1752 /* invoke sendmail in a subshell
1753 path (in) path to program to execute
1754 args (in) arguments to pass to program
1755 msg (in) temp file containing message to send
1756 tempfile (out) if sendmail is put in the background, this points
1757 to the temporary file containing the stdout of the
1760 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1766 mutt_block_signals_system ();
1769 /* we also don't want to be stopped right now */
1770 sigaddset (&set, SIGTSTP);
1771 sigprocmask (SIG_BLOCK, &set, NULL);
1773 if (SendmailWait >= 0) {
1774 char tmp[_POSIX_PATH_MAX];
1777 *tempfile = m_strdup(tmp);
1780 if ((pid = fork ()) == 0) {
1781 struct sigaction act, oldalrm;
1783 /* save parent's ID before setsid() */
1786 /* we want the delivery to continue even after the main process dies,
1787 * so we put ourselves into another session right away
1791 /* next we close all open files */
1792 #if defined(OPEN_MAX)
1793 for (fd = 0; fd < OPEN_MAX; fd++)
1795 #elif defined(_POSIX_OPEN_MAX)
1796 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1804 /* now the second fork() */
1805 if ((pid = fork ()) == 0) {
1806 /* "msg" will be opened as stdin */
1807 if (open (msg, O_RDONLY, 0) < 0) {
1813 if (SendmailWait >= 0) {
1814 /* *tempfile will be opened as stdout */
1815 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1818 /* redirect stderr to *tempfile too */
1823 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1825 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1832 else if (pid == -1) {
1838 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1839 * SendmailWait = 0: wait forever
1840 * SendmailWait < 0: don't wait
1842 if (SendmailWait > 0) {
1844 act.sa_handler = alarm_handler;
1846 /* need to make sure waitpid() is interrupted on SIGALRM */
1847 act.sa_flags = SA_INTERRUPT;
1851 sigemptyset (&act.sa_mask);
1852 sigaction (SIGALRM, &act, &oldalrm);
1853 alarm (SendmailWait);
1855 else if (SendmailWait < 0)
1856 _exit (0xff & EX_OK);
1858 if (waitpid (pid, &st, 0) > 0) {
1859 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1860 if (SendmailWait && st == (0xff & EX_OK)) {
1861 unlink (*tempfile); /* no longer needed */
1866 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1867 if (SendmailWait > 0) {
1873 /* reset alarm; not really needed, but... */
1875 sigaction (SIGALRM, &oldalrm, NULL);
1877 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1878 /* the parent is already dead */
1886 sigprocmask (SIG_UNBLOCK, &set, NULL);
1888 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1889 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1891 st = S_ERR; /* error */
1893 mutt_unblock_signals_system (1);
1898 static const char **
1899 add_args(const char **args, ssize_t *argslen, ssize_t *argsmax, address_t * addr)
1901 for (; addr; addr = addr->next) {
1902 /* weed out group mailboxes, since those are for display only */
1903 if (addr->mailbox && !addr->group) {
1904 if (*argslen == *argsmax)
1905 p_realloc(&args, *argsmax += 5);
1906 args[(*argslen)++] = addr->mailbox;
1912 static const char **
1913 add_option(const char **args, ssize_t *argslen, ssize_t *argsmax, const char *s)
1915 if (*argslen == *argsmax) {
1916 p_realloc(&args, *argsmax += 5);
1918 args[(*argslen)++] = s;
1922 static int mutt_invoke_sendmail (address_t * from, /* the sender */
1923 address_t * to, address_t * cc, address_t * bcc, /* recips */
1924 const char *msg, /* file containing message */
1926 { /* message contains 8bit chars */
1927 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1928 const char **args = NULL;
1929 ssize_t argslen = 0, argsmax = 0;
1933 if (option (OPTNEWSSEND)) {
1934 char cmd[LONG_STRING];
1936 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1939 i = nntp_post (msg);
1948 s = m_strdup(Sendmail);
1952 while ((ps = strtok (ps, " "))) {
1953 if (argslen == argsmax)
1954 p_realloc(&args, argsmax += 5);
1957 args[argslen++] = ps;
1959 path = m_strdup(ps);
1960 ps = strrchr (ps, '/');
1965 args[argslen++] = ps;
1972 if (!option (OPTNEWSSEND)) {
1974 if (eightbit && option (OPTUSE8BITMIME))
1975 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
1977 if (option (OPTENVFROM)) {
1978 address_t *f = NULL;
1981 else if (from && !from->next)
1984 args = add_option (args, &argslen, &argsmax, "-f");
1985 args = add_args (args, &argslen, &argsmax, f);
1989 args = add_option (args, &argslen, &argsmax, "-N");
1990 args = add_option (args, &argslen, &argsmax, DsnNotify);
1993 args = add_option (args, &argslen, &argsmax, "-R");
1994 args = add_option (args, &argslen, &argsmax, DsnReturn);
1996 args = add_option (args, &argslen, &argsmax, "--");
1997 args = add_args (args, &argslen, &argsmax, to);
1998 args = add_args (args, &argslen, &argsmax, cc);
1999 args = add_args (args, &argslen, &argsmax, bcc);
2004 if (argslen == argsmax)
2005 p_realloc(&args, ++argsmax);
2007 args[argslen++] = NULL;
2009 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2011 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2016 if (stat (childout, &st) == 0 && st.st_size > 0)
2017 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2025 p_delete(&childout);
2030 if (i == (EX_OK & 0xff))
2032 else if (i == S_BKG)
2039 int mutt_invoke_mta (address_t * from, /* the sender */
2040 address_t * to, address_t * cc, address_t * bcc, /* recips */
2041 const char *msg, /* file containing message */
2043 { /* message contains 8bit chars */
2046 if (!option (OPTNEWSSEND))
2049 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2052 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2055 /* appends string 'b' to string 'a', and returns the pointer to the new
2057 char *mutt_append_string (char *a, const char *b)
2059 ssize_t la = m_strlen(a);
2061 p_realloc(&a, la + m_strlen(b) + 1);
2062 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2066 /* returns 1 if char `c' needs to be quoted to protect from shell
2067 interpretation when executing commands in a subshell */
2068 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2070 /* returns 1 if string `s' contains characters which could cause problems
2071 when used on a command line to execute a command */
2072 int mutt_needs_quote (const char *s)
2075 if (INVALID_CHAR (*s))
2082 /* Quote a string to prevent shell escapes when this string is used on the
2083 command line to send mail. */
2084 char *mutt_quote_string (const char *s)
2089 rlen = m_strlen(s) + 3;
2090 pr = r = p_new(char, rlen);
2093 if (INVALID_CHAR (*s)) {
2096 p_realloc(&r, ++rlen);
2107 /* For postponing (!final) do the necessary encodings only */
2108 void mutt_prepare_envelope (ENVELOPE * env, int final)
2110 char buffer[LONG_STRING];
2113 if (env->bcc && !(env->to || env->cc)) {
2114 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2115 * recipients if there is no To: or Cc: field, so attempt to suppress
2116 * it by using an empty To: field.
2118 env->to = address_new ();
2120 env->to->next = address_new ();
2123 rfc822_strcpy(buffer, sizeof(buffer), "undisclosed-recipients",
2126 env->to->mailbox = m_strdup(buffer);
2129 mutt_set_followup_to (env);
2131 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2132 env->message_id = mutt_gen_msgid ();
2135 /* Take care of 8-bit => 7-bit conversion. */
2136 rfc2047_encode_adrlist (env->to, "To");
2137 rfc2047_encode_adrlist (env->cc, "Cc");
2138 rfc2047_encode_adrlist (env->bcc, "Bcc");
2139 rfc2047_encode_adrlist (env->from, "From");
2140 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2141 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2145 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2148 rfc2047_encode_string (&env->subject);
2150 encode_headers (env->userhdrs);
2153 void mutt_unprepare_envelope (ENVELOPE * env)
2155 string_list_t *item;
2157 for (item = env->userhdrs; item; item = item->next)
2158 rfc2047_decode (&item->data);
2160 address_list_wipe(&env->mail_followup_to);
2162 /* back conversions */
2163 rfc2047_decode_adrlist (env->to);
2164 rfc2047_decode_adrlist (env->cc);
2165 rfc2047_decode_adrlist (env->bcc);
2166 rfc2047_decode_adrlist (env->from);
2167 rfc2047_decode_adrlist (env->reply_to);
2168 rfc2047_decode (&env->subject);
2171 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2172 const char *resent_from, address_t * env_from)
2176 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2177 MESSAGE *msg = NULL;
2180 /* Try to bounce each message out, aborting if we get any failures. */
2181 for (i = 0; i < Context->msgcount; i++)
2182 if (Context->hdrs[i]->tagged)
2184 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2189 /* If we failed to open a message, return with error */
2190 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2196 mutt_mktemp (tempfile);
2197 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2198 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2200 if (!option (OPTBOUNCEDELIVERED))
2201 ch_flags |= CH_WEED_DELIVERED;
2203 fseeko (fp, h->offset, 0);
2204 fprintf (f, "Resent-From: %s", resent_from);
2205 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2206 if (MsgIdFormat && *MsgIdFormat)
2207 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2208 fputs ("Resent-To: ", f);
2209 mutt_write_address_list (to, f, 11, 0);
2210 mutt_copy_header (fp, h, f, ch_flags, NULL);
2212 mutt_copy_bytes (fp, f, h->content->length);
2215 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2216 h->content->encoding == ENC8BIT);
2220 mx_close_message (&msg);
2225 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2228 const char *fqdn = mutt_fqdn (1);
2229 char resent_from[STRING];
2233 resent_from[0] = '\0';
2234 from = mutt_default_from ();
2237 rfc822_qualify (from, fqdn);
2239 rfc2047_encode_adrlist (from, "Resent-From");
2240 if (mutt_addrlist_to_idna (from, &err)) {
2241 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2244 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2247 unset_option (OPTNEWSSEND);
2250 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2252 address_list_wipe(&from);
2258 /* given a list of addresses, return a list of unique addresses */
2259 address_t *mutt_remove_duplicates (address_t * addr)
2261 address_t *top = addr;
2262 address_t **last = ⊤
2267 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2268 if (tmp->mailbox && addr->mailbox &&
2269 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2279 address_list_wipe(&addr);
2292 static void set_noconv_flags (BODY * b, short flag)
2294 for (; b; b = b->next) {
2295 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2296 set_noconv_flags (b->parts, flag);
2297 else if (b->type == TYPETEXT && b->noconv) {
2298 parameter_setval(&b->parameter, "x-mutt-noconv", flag ? "yes" : NULL);
2303 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2304 int post, char *fcc)
2308 char tempfile[_POSIX_PATH_MAX];
2309 FILE *tempfp = NULL;
2313 set_noconv_flags (hdr->content, 1);
2315 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2319 /* We need to add a Content-Length field to avoid problems where a line in
2320 * the message body begins with "From "
2322 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2323 mutt_mktemp (tempfile);
2324 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2325 mutt_perror (tempfile);
2326 mx_close_mailbox (&f, NULL);
2331 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2332 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2333 mx_close_mailbox (&f, NULL);
2337 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2338 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2340 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2343 /* (postponment) if this was a reply of some sort, <msgid> contians the
2344 * Message-ID: of message replied to. Save it using a special X-Mutt-
2345 * header so it can be picked up if the message is recalled at a later
2346 * point in time. This will allow the message to be marked as replied if
2347 * the same mailbox is still open.
2350 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2352 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2353 * it can be picked up when the message is recalled
2356 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2357 fprintf (msg->fp, "Status: RO\n");
2361 /* (postponment) if the mail is to be signed or encrypted, save this info */
2362 if (post && (hdr->security & APPLICATION_PGP)) {
2363 fputs ("X-Mutt-PGP: ", msg->fp);
2364 if (hdr->security & ENCRYPT)
2365 fputc ('E', msg->fp);
2366 if (hdr->security & SIGN) {
2367 fputc ('S', msg->fp);
2368 if (PgpSignAs && *PgpSignAs)
2369 fprintf (msg->fp, "<%s>", PgpSignAs);
2371 if (hdr->security & INLINE)
2372 fputc ('I', msg->fp);
2373 fputc ('\n', msg->fp);
2376 /* (postponment) if the mail is to be signed or encrypted, save this info */
2377 if (post && (hdr->security & APPLICATION_SMIME)) {
2378 fputs ("X-Mutt-SMIME: ", msg->fp);
2379 if (hdr->security & ENCRYPT) {
2380 fputc ('E', msg->fp);
2381 if (SmimeCryptAlg && *SmimeCryptAlg)
2382 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2384 if (hdr->security & SIGN) {
2385 fputc ('S', msg->fp);
2386 if (SmimeDefaultKey && *SmimeDefaultKey)
2387 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2389 if (hdr->security & INLINE)
2390 fputc ('I', msg->fp);
2391 fputc ('\n', msg->fp);
2395 /* (postponement) if the mail is to be sent through a mixmaster
2396 * chain, save that information
2399 if (post && hdr->chain && hdr->chain) {
2402 fputs ("X-Mutt-Mix:", msg->fp);
2403 for (p = hdr->chain; p; p = p->next)
2404 fprintf (msg->fp, " %s", (char *) p->data);
2406 fputc ('\n', msg->fp);
2411 char sasha[LONG_STRING];
2414 mutt_write_mime_body (hdr->content, tempfp);
2416 /* make sure the last line ends with a newline. Emacs doesn't ensure
2417 * this will happen, and it can cause problems parsing the mailbox
2420 fseeko (tempfp, -1, 2);
2421 if (fgetc (tempfp) != '\n') {
2422 fseeko (tempfp, 0, 2);
2423 fputc ('\n', tempfp);
2427 if (ferror (tempfp)) {
2430 mx_commit_message (msg, &f); /* XXX - really? */
2431 mx_close_message (&msg);
2432 mx_close_mailbox (&f, NULL);
2436 /* count the number of lines */
2438 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2440 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2441 fprintf (msg->fp, "Lines: %d\n\n", lines);
2443 /* copy the body and clean up */
2445 r = mutt_copy_stream (tempfp, msg->fp);
2446 if (fclose (tempfp) != 0)
2448 /* if there was an error, leave the temp version */
2453 fputc ('\n', msg->fp); /* finish off the header */
2454 r = mutt_write_mime_body (hdr->content, msg->fp);
2457 if (mx_commit_message (msg, &f) != 0)
2459 mx_close_message (&msg);
2460 mx_close_mailbox (&f, NULL);
2463 set_noconv_flags (hdr->content, 0);