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)
373 char *p, boundary[SHORT_STRING];
374 char send_charset[SHORT_STRING];
379 if (a->type == TYPEMULTIPART) {
380 /* First, find the boundary to use */
381 if (!(p = mutt_get_parameter ("boundary", a->parameter))) {
382 mutt_error _("No boundary parameter found! [report this error]");
386 m_strcpy(boundary, sizeof(boundary), p);
388 for (t = a->parts; t; t = t->next) {
389 fprintf (f, "\n--%s\n", boundary);
390 if (mutt_write_mime_header (t, f) == -1)
393 if (mutt_write_mime_body (t, f) == -1)
396 fprintf (f, "\n--%s--\n", boundary);
397 return (ferror (f) ? -1 : 0);
400 /* This is pretty gross, but it's the best solution for now... */
401 if (a->type == TYPEAPPLICATION && !m_strcmp(a->subtype, "pgp-encrypted")) {
402 fputs ("Version: 1\n", f);
406 if ((fpin = fopen (a->filename, "r")) == NULL) {
407 mutt_error (_("%s no longer exists!"), a->filename);
411 if (a->type == TYPETEXT && (!a->noconv))
412 fc = fgetconv_open (fpin, a->file_charset,
413 mutt_get_body_charset (send_charset,
414 sizeof (send_charset), a), 0);
416 fc = fgetconv_open (fpin, 0, 0, 0);
418 if (a->encoding == ENCQUOTEDPRINTABLE)
419 encode_quoted (fc, f, write_as_text_part (a));
420 else if (a->encoding == ENCBASE64)
421 encode_base64 (fc, f, write_as_text_part (a));
422 else if (a->type == TYPETEXT && (!a->noconv))
423 encode_8bit (fc, f, write_as_text_part (a));
425 mutt_copy_stream (fpin, f);
427 fgetconv_close (&fc);
430 return (ferror (f) ? -1 : 0);
433 #undef write_as_text_part
435 #define BOUNDARYLEN 16
436 void mutt_generate_boundary (PARAMETER ** parm)
438 char rs[BOUNDARYLEN + 1];
443 for (i = 0; i < BOUNDARYLEN; i++)
444 *p++ = __m_b64chars[lrand48() % sizeof(__m_b64chars)];
447 mutt_set_parameter ("boundary", rs, parm);
459 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
463 int whitespace = s->whitespace;
465 int linelen = s->linelen;
466 int was_cr = s->was_cr;
468 if (!d) { /* This signals EOF */
471 if (linelen > info->linemax)
472 info->linemax = linelen;
477 for (; dlen; d++, dlen--) {
490 if (linelen > info->linemax)
491 info->linemax = linelen;
506 if (linelen > info->linemax)
507 info->linemax = linelen;
512 else if (ch == '\r') {
520 else if (ch == '\t' || ch == '\f') {
524 else if (ch < 32 || ch == 127)
528 if ((ch == 'F') || (ch == 'f'))
538 if (linelen == 2 && ch != 'r')
540 else if (linelen == 3 && ch != 'o')
542 else if (linelen == 4) {
555 if (ch != ' ' && ch != '\t')
560 s->whitespace = whitespace;
562 s->linelen = linelen;
567 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
568 #define BUGGY_ICONV 1
571 * Find the best charset conversion of the file from fromcode into one
572 * of the tocodes. If successful, set *tocode and CONTENT *info and
573 * return the number of characters converted inexactly. If no
574 * conversion was possible, return -1.
576 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
577 * which would otherwise prevent us from knowing the number of inexact
578 * conversions. Where the candidate target charset is UTF-8 we avoid
579 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
580 * fails with some libraries.
582 * We assume that the output from iconv is never more than 4 times as
583 * long as the input for any pair of charsets we might be interested
586 static ssize_t convert_file_to (FILE * file, const char *fromcode,
587 int ncodes, const char **tocodes,
588 int *tocode, CONTENT * info)
592 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
595 ssize_t ibl, obl, ubl, ubl1, n, ret;
598 CONTENT_STATE *states;
601 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
602 if (cd1 == MUTT_ICONV_ERROR)
605 cd = p_new(iconv_t, ncodes);
606 score = p_new(ssize_t, ncodes);
607 states = p_new(CONTENT_STATE, ncodes);
608 infos = p_new(CONTENT, ncodes);
610 for (i = 0; i < ncodes; i++)
611 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
612 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
614 /* Special case for conversion to UTF-8 */
615 cd[i] = MUTT_ICONV_ERROR, score[i] = -1;
621 /* Try to fill input buffer */
622 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
625 /* Convert to UTF-8 */
627 ob = bufu, obl = sizeof (bufu);
628 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
629 assert (n == -1 || !n);
631 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
632 assert (errno == EILSEQ ||
633 (errno == EINVAL && ib == bufi && ibl < ssizeof (bufi)));
639 /* Convert from UTF-8 */
640 for (i = 0; i < ncodes; i++)
641 if (cd[i] != MUTT_ICONV_ERROR && score[i] != -1) {
642 ub = bufu, ubl = ubl1;
643 ob = bufo, obl = sizeof (bufo);
644 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
646 assert (errno == E2BIG ||
647 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
652 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
655 else if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1)
656 /* Special case for conversion to UTF-8 */
657 update_content_info (&infos[i], &states[i], bufu, ubl1);
660 /* Save unused input */
661 memmove (bufi, ib, ibl);
662 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
669 /* Find best score */
671 for (i = 0; i < ncodes; i++) {
672 if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1) {
673 /* Special case for conversion to UTF-8 */
678 else if (cd[i] == MUTT_ICONV_ERROR || score[i] == -1)
680 else if (ret == -1 || score[i] < ret) {
688 memcpy (info, &infos[*tocode], sizeof (CONTENT));
689 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
693 for (i = 0; i < ncodes; i++)
694 if (cd[i] != MUTT_ICONV_ERROR)
706 #endif /* !HAVE_ICONV */
710 * Find the first of the fromcodes that gives a valid conversion and
711 * the best charset conversion of the file into one of the tocodes. If
712 * successful, set *fromcode and *tocode to dynamically allocated
713 * strings, set CONTENT *info, and return the number of characters
714 * converted inexactly. If no conversion was possible, return -1.
716 * Both fromcodes and tocodes may be colon-separated lists of charsets.
717 * However, if fromcode is zero then fromcodes is assumed to be the
718 * name of a single charset even if it contains a colon.
720 static ssize_t convert_file_from_to (FILE * file,
721 const char *fromcodes,
722 const char *tocodes, char **fromcode,
723 char **tocode, CONTENT * info)
731 /* Count the tocodes */
733 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
734 if ((c1 = strchr (c, ':')) == c)
740 tcode = p_new(char *, ncodes);
741 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
742 if ((c1 = strchr (c, ':')) == c)
744 tcode[i] = m_substrdup(c, c1);
749 /* Try each fromcode in turn */
750 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
751 if ((c1 = strchr (c, ':')) == c)
753 fcode = m_substrdup(c, c1);
755 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
767 /* There is only one fromcode */
768 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
777 for (i = 0; i < ncodes; i++)
786 * Analyze the contents of a file to determine which MIME encoding to use.
787 * Also set the body charset, sometimes, or not.
789 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
794 char *fromcode = NULL;
805 if (stat (fname, &sb) == -1) {
806 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
810 if (!S_ISREG (sb.st_mode)) {
811 mutt_error (_("%s isn't a regular file."), fname);
815 if ((fp = fopen (fname, "r")) == NULL) {
819 info = p_new(CONTENT, 1);
822 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
823 char *chs = mutt_get_parameter ("charset", b->parameter);
824 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
825 FileCharset : Charset) : Charset;
826 if (Charset && (chs || SendCharset) &&
827 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
828 &fromcode, &tocode, info) != -1) {
830 charset_canonicalize (chsbuf, sizeof (chsbuf), tocode);
831 mutt_set_parameter ("charset", chsbuf, &b->parameter);
833 b->file_charset = fromcode;
841 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
842 update_content_info (info, &state, buffer, r);
843 update_content_info (info, &state, 0, 0);
847 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
848 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
850 && !charset_is_us_ascii (Charset) ? Charset :
851 "unknown-8bit"), &b->parameter);
856 /* Given a file with path ``s'', see if there is a registered MIME type.
857 * returns the major MIME type, and copies the subtype to ``d''. First look
858 * for ~/.mime.types, then look in a system mime.types if we can find one.
859 * The longest match is used so that we can match `ps.gz' when `gz' also
863 int mutt_lookup_mime_type (BODY * att, const char *path)
867 char buf[LONG_STRING];
868 char subtype[STRING], xtype[STRING];
870 int szf, sze, cur_sze;
878 szf = m_strlen(path);
880 for (count = 0; count < 4; count++) {
882 * can't use strtok() because we use it in an inner loop below, so use
883 * a switch statement here instead.
887 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
890 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
893 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
896 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
899 goto bye; /* shouldn't happen */
902 if ((f = fopen (buf, "r")) != NULL) {
903 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
904 /* weed out any comments */
905 if ((p = strchr (buf, '#')))
908 /* remove any leading space. */
909 ct = vskipspaces(buf);
911 /* position on the next field in this line */
912 if ((p = strpbrk (ct, " \t")) == NULL)
917 /* cycle through the file extensions */
918 while ((p = strtok (p, " \t\n"))) {
920 if ((sze > cur_sze) && (szf >= sze) &&
921 (m_strcasecmp(path + szf - sze, p) == 0
922 || ascii_strcasecmp (path + szf - sze, p) == 0)
923 && (szf == sze || path[szf - sze - 1] == '.'))
925 /* get the content-type */
927 if ((p = strchr (ct, '/')) == NULL) {
928 /* malformed line, just skip it. */
933 for (q = p; *q && !ISSPACE (*q); q++);
935 m_strncpy(subtype, sizeof(subtype), p, q - p);
937 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
938 m_strcpy(xtype, sizeof(xtype), ct);
951 if (type != TYPEOTHER || *xtype != '\0') {
953 m_strreplace(&att->subtype, subtype);
954 m_strreplace(&att->xtype, xtype);
960 void mutt_message_to_7bit (BODY * a, FILE * fp)
962 char temp[_POSIX_PATH_MAX];
968 if (!a->filename && fp)
970 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
971 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
976 if (stat (a->filename, &sb) == -1) {
977 mutt_perror ("stat");
980 a->length = sb.st_size;
984 if (!(fpout = safe_fopen (temp, "w+"))) {
985 mutt_perror ("fopen");
989 fseeko (fpin, a->offset, 0);
990 a->parts = mutt_parse_messageRFC822 (fpin, a);
992 transform_to_7bit (a->parts, fpin);
994 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
995 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
997 fputs ("MIME-Version: 1.0\n", fpout);
998 mutt_write_mime_header (a->parts, fpout);
1000 mutt_write_mime_body (a->parts, fpout);
1012 a->encoding = ENC7BIT;
1013 a->d_filename = a->filename;
1014 if (a->filename && a->unlink)
1015 unlink (a->filename);
1016 a->filename = m_strdup(temp);
1018 if (stat (a->filename, &sb) == -1) {
1019 mutt_perror ("stat");
1022 a->length = sb.st_size;
1023 mutt_free_body (&a->parts);
1024 a->hdr->content = NULL;
1027 static void transform_to_7bit (BODY * a, FILE * fpin)
1029 char buff[_POSIX_PATH_MAX];
1034 for (; a; a = a->next) {
1035 if (a->type == TYPEMULTIPART) {
1036 if (a->encoding != ENC7BIT)
1037 a->encoding = ENC7BIT;
1039 transform_to_7bit (a->parts, fpin);
1041 else if (mutt_is_message_type (a->type, a->subtype)) {
1042 mutt_message_to_7bit (a, fpin);
1046 a->force_charset = 1;
1049 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1050 mutt_perror ("fopen");
1054 mutt_decode_attachment (a, &s);
1056 a->d_filename = a->filename;
1057 a->filename = m_strdup(buff);
1059 if (stat (a->filename, &sb) == -1) {
1060 mutt_perror ("stat");
1063 a->length = sb.st_size;
1065 mutt_update_encoding (a);
1066 if (a->encoding == ENC8BIT)
1067 a->encoding = ENCQUOTEDPRINTABLE;
1068 else if (a->encoding == ENCBINARY)
1069 a->encoding = ENCBASE64;
1074 /* determine which Content-Transfer-Encoding to use */
1075 static void mutt_set_encoding (BODY * b, CONTENT * info)
1077 char send_charset[SHORT_STRING];
1079 if (b->type == TYPETEXT) {
1081 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1082 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1083 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1084 b->encoding = ENCQUOTEDPRINTABLE;
1085 else if (info->hibin)
1086 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1088 b->encoding = ENC7BIT;
1090 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1091 if (info->lobin || info->hibin) {
1092 if (option (OPTALLOW8BIT) && !info->lobin)
1093 b->encoding = ENC8BIT;
1095 mutt_message_to_7bit (b, NULL);
1098 b->encoding = ENC7BIT;
1100 else if (b->type == TYPEAPPLICATION
1101 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1102 b->encoding = ENC7BIT;
1105 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1106 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1109 /* Determine which encoding is smaller */
1110 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1111 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1112 b->encoding = ENCBASE64;
1114 b->encoding = ENCQUOTEDPRINTABLE;
1118 b->encoding = ENC7BIT;
1122 void mutt_stamp_attachment (BODY * a)
1124 a->stamp = time (NULL);
1127 /* Get a body's character set */
1129 char *mutt_get_body_charset (char *d, ssize_t dlen, BODY * b)
1133 if (b && b->type != TYPETEXT)
1137 p = mutt_get_parameter ("charset", b->parameter);
1140 charset_canonicalize (d, dlen, NONULL (p));
1142 m_strcpy(d, dlen, "us-ascii");
1148 /* Assumes called from send mode where BODY->filename points to actual file */
1149 void mutt_update_encoding (BODY * a)
1152 char chsbuff[STRING];
1154 /* override noconv when it's us-ascii */
1155 if (charset_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1158 if (!a->force_charset && !a->noconv)
1159 mutt_delete_parameter ("charset", &a->parameter);
1161 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1164 mutt_set_encoding (a, info);
1165 mutt_stamp_attachment (a);
1167 p_delete(&a->content);
1172 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1174 char buffer[LONG_STRING];
1177 int cmflags, chflags;
1178 int pgp = hdr->security;
1180 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1181 (hdr->security & ENCRYPT)) {
1182 if (!crypt_valid_passphrase (hdr->security))
1186 mutt_mktemp (buffer);
1187 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1190 body = mutt_new_body ();
1191 body->type = TYPEMESSAGE;
1192 body->subtype = m_strdup("rfc822");
1193 body->filename = m_strdup(buffer);
1196 body->disposition = DISPINLINE;
1199 mutt_parse_mime_message (ctx, hdr);
1204 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1205 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1206 chflags |= CH_MIME | CH_TXTPLAIN;
1207 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1208 pgp &= ~(PGPENCRYPT|SMIMEENCRYPT);
1210 else if (option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1211 if (mutt_is_multipart_encrypted (hdr->content)) {
1212 chflags |= CH_MIME | CH_NONEWLINE;
1213 cmflags = M_CM_DECODE_PGP;
1216 else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1217 chflags |= CH_MIME | CH_TXTPLAIN;
1218 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1221 else if (mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1222 chflags |= CH_MIME | CH_TXTPLAIN;
1223 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1224 pgp &= ~SMIMEENCRYPT;
1228 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1233 body->hdr = header_new();
1234 body->hdr->offset = 0;
1235 /* we don't need the user headers here */
1236 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1237 body->hdr->security = pgp;
1238 mutt_update_encoding (body);
1239 body->parts = body->hdr->content;
1246 BODY *mutt_make_file_attach (const char *path)
1251 att = mutt_new_body ();
1252 att->filename = m_strdup(path);
1254 /* Attempt to determine the appropriate content-type based on the filename
1261 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1262 path)) != TYPEOTHER || *xbuf != '\0') {
1264 att->subtype = m_strdup(buf);
1265 att->xtype = m_strdup(xbuf);
1270 mutt_lookup_mime_type (att, path);
1274 if ((info = mutt_get_content_info (path, att)) == NULL) {
1275 mutt_free_body (&att);
1279 if (!att->subtype) {
1280 if (info->lobin == 0
1281 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1283 * Statistically speaking, there should be more than 10% "lobin"
1284 * chars if this is really a binary file...
1286 att->type = TYPETEXT;
1287 att->subtype = m_strdup("plain");
1290 att->type = TYPEAPPLICATION;
1291 att->subtype = m_strdup("octet-stream");
1295 mutt_update_encoding (att);
1299 static int get_toplevel_encoding (BODY * a)
1303 for (; a; a = a->next) {
1304 if (a->encoding == ENCBINARY)
1306 else if (a->encoding == ENC8BIT)
1313 BODY *mutt_make_multipart (BODY * b)
1317 new = mutt_new_body ();
1318 new->type = TYPEMULTIPART;
1319 new->subtype = m_strdup("mixed");
1320 new->encoding = get_toplevel_encoding (b);
1321 mutt_generate_boundary (&new->parameter);
1323 new->disposition = DISPINLINE;
1329 /* remove the multipart body if it exists */
1330 BODY *mutt_remove_multipart (BODY * b)
1338 mutt_free_body (&t);
1343 char *mutt_make_date (char *s, ssize_t len)
1345 time_t t = time (NULL);
1346 struct tm *l = localtime (&t);
1347 time_t tz = mutt_local_tz (t);
1351 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1352 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1353 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1354 (int) tz / 60, (int) abs (tz) % 60);
1358 /* wrapper around mutt_write_address() so we can handle very large
1359 recipient lists without needing a huge temporary buffer in memory */
1360 void mutt_write_address_list (address_t * adr, FILE * fp, int linelen,
1364 char buf[LONG_STRING];
1372 rfc822_write_address (buf, sizeof (buf), adr, display);
1373 len = m_strlen(buf);
1374 if (count && linelen + len > 74) {
1376 linelen = len + 8; /* tab is usually about 8 spaces... */
1379 if (count && adr->mailbox) {
1387 if (!adr->group && adr->next && adr->next->mailbox) {
1397 /* arbitrary number of elements to grow the array by */
1402 /* need to write the list in reverse because they are stored in reverse order
1403 * when parsed to speed up threading
1405 void mutt_write_references (string_list_t * r, FILE * f)
1407 string_list_t **ref = NULL;
1408 int refcnt = 0, refmax = 0;
1410 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1411 if (refcnt == refmax)
1412 p_realloc(&ref, refmax += REF_INC);
1416 while (refcnt-- > 0) {
1418 fputs (ref[refcnt]->data, f);
1424 /* Note: all RFC2047 encoding should be done outside of this routine, except
1425 * for the "real name." This will allow this routine to be used more than
1426 * once, if necessary.
1428 * Likewise, all IDN processing should happen outside of this routine.
1430 * mode == 1 => "lite" mode (used for edit_hdrs)
1431 * mode == 0 => normal mode. write full header + MIME headers
1432 * mode == -1 => write just the envelope info (used for postponing messages)
1434 * privacy != 0 => will omit any headers which may identify the user.
1435 * Output generated is suitable for being sent through
1436 * anonymous remailer chains.
1440 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1441 int mode, int privacy)
1443 char buffer[LONG_STRING];
1445 string_list_t *tmp = env->userhdrs;
1446 int has_agent = 0; /* user defined user-agent header field exists */
1447 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1450 if (!option (OPTNEWSSEND))
1452 if (mode == 0 && !privacy)
1453 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1455 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1457 /* OPTUSEFROM is not consulted here so that we can still write a From:
1458 * field if the user sets it with the `my_hdr' command
1460 if (env->from && !privacy) {
1462 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1463 fprintf (fp, "From: %s\n", buffer);
1468 mutt_write_address_list (env->to, fp, 4, 0);
1472 if (!option (OPTNEWSSEND))
1474 if (EDIT_HEADER("To:"))
1475 fputs ("To:\n", fp);
1479 mutt_write_address_list (env->cc, fp, 4, 0);
1483 if (!option (OPTNEWSSEND))
1485 if (EDIT_HEADER("Cc:"))
1486 fputs ("Cc:\n", fp);
1489 if (mode != 0 || option (OPTWRITEBCC)) {
1490 fputs ("Bcc: ", fp);
1491 mutt_write_address_list (env->bcc, fp, 5, 0);
1496 if (!option (OPTNEWSSEND))
1498 if (EDIT_HEADER("Bcc:"))
1499 fputs ("Bcc:\n", fp);
1502 if (env->newsgroups)
1503 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1504 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1505 fputs ("Newsgroups:\n", fp);
1507 if (env->followup_to)
1508 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1509 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1510 fputs ("Followup-To:\n", fp);
1512 if (env->x_comment_to)
1513 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1514 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1515 EDIT_HEADER("X-Comment-To:"))
1516 fputs ("X-Comment-To:\n", fp);
1520 fprintf (fp, "Subject: %s\n", env->subject);
1521 else if (mode == 1 && EDIT_HEADER("Subject:"))
1522 fputs ("Subject:\n", fp);
1524 /* save message id if the user has set it */
1525 if (env->message_id && !privacy)
1526 fprintf (fp, "Message-ID: %s\n", env->message_id);
1528 if (env->reply_to) {
1529 fputs ("Reply-To: ", fp);
1530 mutt_write_address_list (env->reply_to, fp, 10, 0);
1532 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1533 fputs ("Reply-To:\n", fp);
1535 if (env->mail_followup_to)
1537 if (!option (OPTNEWSSEND))
1540 fputs ("Mail-Followup-To: ", fp);
1541 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1545 if (env->references) {
1546 fputs ("References:", fp);
1547 mutt_write_references (env->references, fp);
1551 /* Add the MIME headers */
1552 fputs ("MIME-Version: 1.0\n", fp);
1553 mutt_write_mime_header (attach, fp);
1556 if (env->in_reply_to) {
1557 fputs ("In-Reply-To:", fp);
1558 mutt_write_references (env->in_reply_to, fp);
1564 /* Add any user defined headers */
1565 for (; tmp; tmp = tmp->next) {
1566 if ((p = strchr (tmp->data, ':'))) {
1567 p = vskipspaces(p + 1);
1569 continue; /* don't emit empty fields. */
1571 /* check to see if the user has overridden the user-agent field */
1572 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1578 fputs (tmp->data, fp);
1583 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1586 if (OperatingSystem != NULL) {
1587 os = OperatingSystem;
1590 os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1592 /* Add a vanity header */
1593 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1596 list_del (&hdrs, (list_del_t*)xmemfree);
1598 return (ferror (fp) == 0 ? 0 : -1);
1601 static void encode_headers (string_list_t * h)
1607 for (; h; h = h->next) {
1608 if (!(p = strchr (h->data, ':')))
1612 p = vskipspaces(p + 1);
1618 rfc2047_encode_string (&tmp);
1619 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1621 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1627 const char *mutt_fqdn (short may_hide_host)
1631 if (Fqdn && Fqdn[0] != '@') {
1634 if (may_hide_host && option (OPTHIDDENHOST)) {
1635 if ((p = strchr (Fqdn, '.')))
1638 /* sanity check: don't hide the host if
1639 * the fqdn is something like detebe.org.
1642 if (!p || !(q = strchr (p, '.')))
1650 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1651 static char mutt_normalized_char(char c)
1653 return (isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.';
1656 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1658 #define APPEND_FMT(fmt, arg) \
1660 int snlen = snprintf(buf, len, fmt, arg); \
1665 #define APPEND_BYTE(c) \
1682 APPEND_BYTE(mutt_normalized_char(c));
1690 APPEND_FMT("%02d", tm->tm_mday);
1693 APPEND_FMT("%02d", tm->tm_hour);
1696 APPEND_FMT("%02d", tm->tm_mon + 1);
1699 APPEND_FMT("%02d", tm->tm_min);
1702 APPEND_FMT("%lo", (unsigned long)now);
1705 APPEND_FMT("%u", (unsigned int)getpid());
1708 APPEND_FMT("%c", MsgIdPfx);
1709 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1712 APPEND_FMT("%u", (unsigned int)rand());
1715 APPEND_FMT("%x", (unsigned int)rand());
1718 APPEND_FMT("%02d", tm->tm_sec);
1721 APPEND_FMT("%u", (unsigned int) now);
1724 APPEND_FMT("%x", (unsigned int) now);
1726 case 'Y': /* this will break in the year 10000 ;-) */
1727 APPEND_FMT("%04d", tm->tm_year + 1900);
1732 default: /* invalid formats are replaced by '.' */
1734 m_strncat(buf, len, ".", 1);
1744 char *mutt_gen_msgid (void)
1746 char buf[SHORT_STRING];
1747 char localpart[SHORT_STRING];
1748 unsigned int localpart_length;
1751 if (!(fqdn = mutt_fqdn (0)))
1752 fqdn = NONULL (Hostname);
1754 localpart_length = sizeof (buf) - m_strlen(fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1756 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1758 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1759 return (m_strdup(buf));
1762 static RETSIGTYPE alarm_handler (int sig)
1767 /* invoke sendmail in a subshell
1768 path (in) path to program to execute
1769 args (in) arguments to pass to program
1770 msg (in) temp file containing message to send
1771 tempfile (out) if sendmail is put in the background, this points
1772 to the temporary file containing the stdout of the
1775 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1781 mutt_block_signals_system ();
1784 /* we also don't want to be stopped right now */
1785 sigaddset (&set, SIGTSTP);
1786 sigprocmask (SIG_BLOCK, &set, NULL);
1788 if (SendmailWait >= 0) {
1789 char tmp[_POSIX_PATH_MAX];
1792 *tempfile = m_strdup(tmp);
1795 if ((pid = fork ()) == 0) {
1796 struct sigaction act, oldalrm;
1798 /* save parent's ID before setsid() */
1801 /* we want the delivery to continue even after the main process dies,
1802 * so we put ourselves into another session right away
1806 /* next we close all open files */
1807 #if defined(OPEN_MAX)
1808 for (fd = 0; fd < OPEN_MAX; fd++)
1810 #elif defined(_POSIX_OPEN_MAX)
1811 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1819 /* now the second fork() */
1820 if ((pid = fork ()) == 0) {
1821 /* "msg" will be opened as stdin */
1822 if (open (msg, O_RDONLY, 0) < 0) {
1828 if (SendmailWait >= 0) {
1829 /* *tempfile will be opened as stdout */
1830 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1833 /* redirect stderr to *tempfile too */
1838 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1840 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1847 else if (pid == -1) {
1853 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1854 * SendmailWait = 0: wait forever
1855 * SendmailWait < 0: don't wait
1857 if (SendmailWait > 0) {
1859 act.sa_handler = alarm_handler;
1861 /* need to make sure waitpid() is interrupted on SIGALRM */
1862 act.sa_flags = SA_INTERRUPT;
1866 sigemptyset (&act.sa_mask);
1867 sigaction (SIGALRM, &act, &oldalrm);
1868 alarm (SendmailWait);
1870 else if (SendmailWait < 0)
1871 _exit (0xff & EX_OK);
1873 if (waitpid (pid, &st, 0) > 0) {
1874 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1875 if (SendmailWait && st == (0xff & EX_OK)) {
1876 unlink (*tempfile); /* no longer needed */
1881 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1882 if (SendmailWait > 0) {
1888 /* reset alarm; not really needed, but... */
1890 sigaction (SIGALRM, &oldalrm, NULL);
1892 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1893 /* the parent is already dead */
1901 sigprocmask (SIG_UNBLOCK, &set, NULL);
1903 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1904 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1906 st = S_ERR; /* error */
1908 mutt_unblock_signals_system (1);
1913 static const char **
1914 add_args(const char **args, ssize_t *argslen, ssize_t *argsmax, address_t * addr)
1916 for (; addr; addr = addr->next) {
1917 /* weed out group mailboxes, since those are for display only */
1918 if (addr->mailbox && !addr->group) {
1919 if (*argslen == *argsmax)
1920 p_realloc(&args, *argsmax += 5);
1921 args[(*argslen)++] = addr->mailbox;
1927 static const char **
1928 add_option(const char **args, ssize_t *argslen, ssize_t *argsmax, const char *s)
1930 if (*argslen == *argsmax) {
1931 p_realloc(&args, *argsmax += 5);
1933 args[(*argslen)++] = s;
1937 static int mutt_invoke_sendmail (address_t * from, /* the sender */
1938 address_t * to, address_t * cc, address_t * bcc, /* recips */
1939 const char *msg, /* file containing message */
1941 { /* message contains 8bit chars */
1942 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1943 const char **args = NULL;
1944 ssize_t argslen = 0, argsmax = 0;
1948 if (option (OPTNEWSSEND)) {
1949 char cmd[LONG_STRING];
1951 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1954 i = nntp_post (msg);
1963 s = m_strdup(Sendmail);
1967 while ((ps = strtok (ps, " "))) {
1968 if (argslen == argsmax)
1969 p_realloc(&args, argsmax += 5);
1972 args[argslen++] = ps;
1974 path = m_strdup(ps);
1975 ps = strrchr (ps, '/');
1980 args[argslen++] = ps;
1987 if (!option (OPTNEWSSEND)) {
1989 if (eightbit && option (OPTUSE8BITMIME))
1990 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
1992 if (option (OPTENVFROM)) {
1993 address_t *f = NULL;
1996 else if (from && !from->next)
1999 args = add_option (args, &argslen, &argsmax, "-f");
2000 args = add_args (args, &argslen, &argsmax, f);
2004 args = add_option (args, &argslen, &argsmax, "-N");
2005 args = add_option (args, &argslen, &argsmax, DsnNotify);
2008 args = add_option (args, &argslen, &argsmax, "-R");
2009 args = add_option (args, &argslen, &argsmax, DsnReturn);
2011 args = add_option (args, &argslen, &argsmax, "--");
2012 args = add_args (args, &argslen, &argsmax, to);
2013 args = add_args (args, &argslen, &argsmax, cc);
2014 args = add_args (args, &argslen, &argsmax, bcc);
2019 if (argslen == argsmax)
2020 p_realloc(&args, ++argsmax);
2022 args[argslen++] = NULL;
2024 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2026 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2031 if (stat (childout, &st) == 0 && st.st_size > 0)
2032 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2040 p_delete(&childout);
2045 if (i == (EX_OK & 0xff))
2047 else if (i == S_BKG)
2054 int mutt_invoke_mta (address_t * from, /* the sender */
2055 address_t * to, address_t * cc, address_t * bcc, /* recips */
2056 const char *msg, /* file containing message */
2058 { /* message contains 8bit chars */
2061 if (!option (OPTNEWSSEND))
2064 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2067 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2070 /* appends string 'b' to string 'a', and returns the pointer to the new
2072 char *mutt_append_string (char *a, const char *b)
2074 ssize_t la = m_strlen(a);
2076 p_realloc(&a, la + m_strlen(b) + 1);
2077 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2081 /* returns 1 if char `c' needs to be quoted to protect from shell
2082 interpretation when executing commands in a subshell */
2083 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2085 /* returns 1 if string `s' contains characters which could cause problems
2086 when used on a command line to execute a command */
2087 int mutt_needs_quote (const char *s)
2090 if (INVALID_CHAR (*s))
2097 /* Quote a string to prevent shell escapes when this string is used on the
2098 command line to send mail. */
2099 char *mutt_quote_string (const char *s)
2104 rlen = m_strlen(s) + 3;
2105 pr = r = p_new(char, rlen);
2108 if (INVALID_CHAR (*s)) {
2111 p_realloc(&r, ++rlen);
2122 /* For postponing (!final) do the necessary encodings only */
2123 void mutt_prepare_envelope (ENVELOPE * env, int final)
2125 char buffer[LONG_STRING];
2128 if (env->bcc && !(env->to || env->cc)) {
2129 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2130 * recipients if there is no To: or Cc: field, so attempt to suppress
2131 * it by using an empty To: field.
2133 env->to = address_new ();
2135 env->to->next = address_new ();
2138 rfc822_strcpy(buffer, sizeof(buffer), "undisclosed-recipients",
2141 env->to->mailbox = m_strdup(buffer);
2144 mutt_set_followup_to (env);
2146 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2147 env->message_id = mutt_gen_msgid ();
2150 /* Take care of 8-bit => 7-bit conversion. */
2151 rfc2047_encode_adrlist (env->to, "To");
2152 rfc2047_encode_adrlist (env->cc, "Cc");
2153 rfc2047_encode_adrlist (env->bcc, "Bcc");
2154 rfc2047_encode_adrlist (env->from, "From");
2155 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2156 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2160 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2163 rfc2047_encode_string (&env->subject);
2165 encode_headers (env->userhdrs);
2168 void mutt_unprepare_envelope (ENVELOPE * env)
2170 string_list_t *item;
2172 for (item = env->userhdrs; item; item = item->next)
2173 rfc2047_decode (&item->data);
2175 address_list_wipe(&env->mail_followup_to);
2177 /* back conversions */
2178 rfc2047_decode_adrlist (env->to);
2179 rfc2047_decode_adrlist (env->cc);
2180 rfc2047_decode_adrlist (env->bcc);
2181 rfc2047_decode_adrlist (env->from);
2182 rfc2047_decode_adrlist (env->reply_to);
2183 rfc2047_decode (&env->subject);
2186 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2187 const char *resent_from, address_t * env_from)
2191 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2192 MESSAGE *msg = NULL;
2195 /* Try to bounce each message out, aborting if we get any failures. */
2196 for (i = 0; i < Context->msgcount; i++)
2197 if (Context->hdrs[i]->tagged)
2199 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2204 /* If we failed to open a message, return with error */
2205 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2211 mutt_mktemp (tempfile);
2212 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2213 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2215 if (!option (OPTBOUNCEDELIVERED))
2216 ch_flags |= CH_WEED_DELIVERED;
2218 fseeko (fp, h->offset, 0);
2219 fprintf (f, "Resent-From: %s", resent_from);
2220 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2221 if (MsgIdFormat && *MsgIdFormat)
2222 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2223 fputs ("Resent-To: ", f);
2224 mutt_write_address_list (to, f, 11, 0);
2225 mutt_copy_header (fp, h, f, ch_flags, NULL);
2227 mutt_copy_bytes (fp, f, h->content->length);
2230 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2231 h->content->encoding == ENC8BIT);
2235 mx_close_message (&msg);
2240 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2243 const char *fqdn = mutt_fqdn (1);
2244 char resent_from[STRING];
2248 resent_from[0] = '\0';
2249 from = mutt_default_from ();
2252 rfc822_qualify (from, fqdn);
2254 rfc2047_encode_adrlist (from, "Resent-From");
2255 if (mutt_addrlist_to_idna (from, &err)) {
2256 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2259 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2262 unset_option (OPTNEWSSEND);
2265 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2267 address_list_wipe(&from);
2273 /* given a list of addresses, return a list of unique addresses */
2274 address_t *mutt_remove_duplicates (address_t * addr)
2276 address_t *top = addr;
2277 address_t **last = ⊤
2282 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2283 if (tmp->mailbox && addr->mailbox &&
2284 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2294 address_list_wipe(&addr);
2307 static void set_noconv_flags (BODY * b, short flag)
2309 for (; b; b = b->next) {
2310 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2311 set_noconv_flags (b->parts, flag);
2312 else if (b->type == TYPETEXT && b->noconv) {
2314 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2316 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2321 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2322 int post, char *fcc)
2326 char tempfile[_POSIX_PATH_MAX];
2327 FILE *tempfp = NULL;
2331 set_noconv_flags (hdr->content, 1);
2333 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2337 /* We need to add a Content-Length field to avoid problems where a line in
2338 * the message body begins with "From "
2340 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2341 mutt_mktemp (tempfile);
2342 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2343 mutt_perror (tempfile);
2344 mx_close_mailbox (&f, NULL);
2349 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2350 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2351 mx_close_mailbox (&f, NULL);
2355 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2356 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2358 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2361 /* (postponment) if this was a reply of some sort, <msgid> contians the
2362 * Message-ID: of message replied to. Save it using a special X-Mutt-
2363 * header so it can be picked up if the message is recalled at a later
2364 * point in time. This will allow the message to be marked as replied if
2365 * the same mailbox is still open.
2368 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2370 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2371 * it can be picked up when the message is recalled
2374 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2375 fprintf (msg->fp, "Status: RO\n");
2379 /* (postponment) if the mail is to be signed or encrypted, save this info */
2380 if (post && (hdr->security & APPLICATION_PGP)) {
2381 fputs ("X-Mutt-PGP: ", msg->fp);
2382 if (hdr->security & ENCRYPT)
2383 fputc ('E', msg->fp);
2384 if (hdr->security & SIGN) {
2385 fputc ('S', msg->fp);
2386 if (PgpSignAs && *PgpSignAs)
2387 fprintf (msg->fp, "<%s>", PgpSignAs);
2389 if (hdr->security & INLINE)
2390 fputc ('I', msg->fp);
2391 fputc ('\n', msg->fp);
2394 /* (postponment) if the mail is to be signed or encrypted, save this info */
2395 if (post && (hdr->security & APPLICATION_SMIME)) {
2396 fputs ("X-Mutt-SMIME: ", msg->fp);
2397 if (hdr->security & ENCRYPT) {
2398 fputc ('E', msg->fp);
2399 if (SmimeCryptAlg && *SmimeCryptAlg)
2400 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2402 if (hdr->security & SIGN) {
2403 fputc ('S', msg->fp);
2404 if (SmimeDefaultKey && *SmimeDefaultKey)
2405 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2407 if (hdr->security & INLINE)
2408 fputc ('I', msg->fp);
2409 fputc ('\n', msg->fp);
2413 /* (postponement) if the mail is to be sent through a mixmaster
2414 * chain, save that information
2417 if (post && hdr->chain && hdr->chain) {
2420 fputs ("X-Mutt-Mix:", msg->fp);
2421 for (p = hdr->chain; p; p = p->next)
2422 fprintf (msg->fp, " %s", (char *) p->data);
2424 fputc ('\n', msg->fp);
2429 char sasha[LONG_STRING];
2432 mutt_write_mime_body (hdr->content, tempfp);
2434 /* make sure the last line ends with a newline. Emacs doesn't ensure
2435 * this will happen, and it can cause problems parsing the mailbox
2438 fseeko (tempfp, -1, 2);
2439 if (fgetc (tempfp) != '\n') {
2440 fseeko (tempfp, 0, 2);
2441 fputc ('\n', tempfp);
2445 if (ferror (tempfp)) {
2448 mx_commit_message (msg, &f); /* XXX - really? */
2449 mx_close_message (&msg);
2450 mx_close_mailbox (&f, NULL);
2454 /* count the number of lines */
2456 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2458 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2459 fprintf (msg->fp, "Lines: %d\n\n", lines);
2461 /* copy the body and clean up */
2463 r = mutt_copy_stream (tempfp, msg->fp);
2464 if (fclose (tempfp) != 0)
2466 /* if there was an error, leave the temp version */
2471 fputc ('\n', msg->fp); /* finish off the header */
2472 r = mutt_write_mime_body (hdr->content, msg->fp);
2475 if (mx_commit_message (msg, &f) != 0)
2477 mx_close_message (&msg);
2478 mx_close_mailbox (&f, NULL);
2481 set_noconv_flags (hdr->content, 0);