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 * 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 * 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 * fc, FILE * fout, int istext)
270 while ((ch = fgetconv (fc)) != EOF)
275 int mutt_write_mime_header (BODY * a, FILE * f)
285 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
288 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
290 for (p = a->parameter; p; p = p->next) {
299 tmp = m_strdup(p->value);
300 encode = rfc2231_encode_string (&tmp);
301 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
303 /* Dirty hack to make messages readable by Outlook Express
304 * for the Mac: force quotes around the boundary parameter
305 * even when they aren't needed.
308 if (!ascii_strcasecmp (p->attribute, "boundary")
309 && !strcmp (buffer, tmp))
310 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
314 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
316 if (len + tmplen + 2 > 76) {
325 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
333 fprintf (f, "Content-Description: %s\n", a->description);
335 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
338 if (!(fn = a->d_filename))
344 /* Strip off the leading path... */
345 if ((t = strrchr (fn, '/')))
352 encode = rfc2231_encode_string (&tmp);
353 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
355 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
361 if (a->encoding != ENC7BIT)
362 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
364 /* Do NOT add the terminator here!!! */
365 return (ferror (f) ? -1 : 0);
368 # define write_as_text_part(a) (mutt_is_text_part(a) || mutt_is_application_pgp(a))
370 int mutt_write_mime_body (BODY * a, FILE * f)
372 char *p, boundary[SHORT_STRING];
373 char send_charset[SHORT_STRING];
378 if (a->type == TYPEMULTIPART) {
379 /* First, find the boundary to use */
380 if (!(p = mutt_get_parameter ("boundary", a->parameter))) {
381 mutt_error _("No boundary parameter found! [report this error]");
385 m_strcpy(boundary, sizeof(boundary), p);
387 for (t = a->parts; t; t = t->next) {
388 fprintf (f, "\n--%s\n", boundary);
389 if (mutt_write_mime_header (t, f) == -1)
392 if (mutt_write_mime_body (t, f) == -1)
395 fprintf (f, "\n--%s--\n", boundary);
396 return (ferror (f) ? -1 : 0);
399 /* This is pretty gross, but it's the best solution for now... */
400 if (a->type == TYPEAPPLICATION && !m_strcmp(a->subtype, "pgp-encrypted")) {
401 fputs ("Version: 1\n", f);
405 if ((fpin = fopen (a->filename, "r")) == NULL) {
406 mutt_error (_("%s no longer exists!"), a->filename);
410 if (a->type == TYPETEXT && (!a->noconv))
411 fc = fgetconv_open (fpin, a->file_charset,
412 mutt_get_body_charset (send_charset,
413 sizeof (send_charset), a), 0);
415 fc = fgetconv_open (fpin, 0, 0, 0);
417 if (a->encoding == ENCQUOTEDPRINTABLE)
418 encode_quoted (fc, f, write_as_text_part (a));
419 else if (a->encoding == ENCBASE64)
420 encode_base64 (fc, f, write_as_text_part (a));
421 else if (a->type == TYPETEXT && (!a->noconv))
422 encode_8bit (fc, f, write_as_text_part (a));
424 mutt_copy_stream (fpin, f);
426 fgetconv_close (&fc);
429 return (ferror (f) ? -1 : 0);
432 #undef write_as_text_part
434 #define BOUNDARYLEN 16
435 void mutt_generate_boundary (PARAMETER ** parm)
437 char rs[BOUNDARYLEN + 1];
442 for (i = 0; i < BOUNDARYLEN; i++)
443 *p++ = __m_b64chars[lrand48() % sizeof(__m_b64chars)];
446 mutt_set_parameter ("boundary", rs, parm);
458 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
462 int whitespace = s->whitespace;
464 int linelen = s->linelen;
465 int was_cr = s->was_cr;
467 if (!d) { /* This signals EOF */
470 if (linelen > info->linemax)
471 info->linemax = linelen;
476 for (; dlen; d++, dlen--) {
489 if (linelen > info->linemax)
490 info->linemax = linelen;
505 if (linelen > info->linemax)
506 info->linemax = linelen;
511 else if (ch == '\r') {
519 else if (ch == '\t' || ch == '\f') {
523 else if (ch < 32 || ch == 127)
527 if ((ch == 'F') || (ch == 'f'))
537 if (linelen == 2 && ch != 'r')
539 else if (linelen == 3 && ch != 'o')
541 else if (linelen == 4) {
554 if (ch != ' ' && ch != '\t')
559 s->whitespace = whitespace;
561 s->linelen = linelen;
566 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
567 #define BUGGY_ICONV 1
570 * Find the best charset conversion of the file from fromcode into one
571 * of the tocodes. If successful, set *tocode and CONTENT *info and
572 * return the number of characters converted inexactly. If no
573 * conversion was possible, return -1.
575 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
576 * which would otherwise prevent us from knowing the number of inexact
577 * conversions. Where the candidate target charset is UTF-8 we avoid
578 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
579 * fails with some libraries.
581 * We assume that the output from iconv is never more than 4 times as
582 * long as the input for any pair of charsets we might be interested
585 static ssize_t convert_file_to (FILE * file, const char *fromcode,
586 int ncodes, const char **tocodes,
587 int *tocode, CONTENT * info)
591 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
594 ssize_t ibl, obl, ubl, ubl1, n, ret;
597 CONTENT_STATE *states;
600 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
601 if (cd1 == (iconv_t) (-1))
604 cd = p_new(iconv_t, ncodes);
605 score = p_new(ssize_t, ncodes);
606 states = p_new(CONTENT_STATE, ncodes);
607 infos = p_new(CONTENT, ncodes);
609 for (i = 0; i < ncodes; i++)
610 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
611 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
613 /* Special case for conversion to UTF-8 */
614 cd[i] = (iconv_t) (-1), score[i] = -1;
620 /* Try to fill input buffer */
621 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
624 /* Convert to UTF-8 */
626 ob = bufu, obl = sizeof (bufu);
627 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
628 assert (n == -1 || !n);
630 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
631 assert (errno == EILSEQ ||
632 (errno == EINVAL && ib == bufi && ibl < ssizeof (bufi)));
638 /* Convert from UTF-8 */
639 for (i = 0; i < ncodes; i++)
640 if (cd[i] != (iconv_t) (-1) && score[i] != -1) {
641 ub = bufu, ubl = ubl1;
642 ob = bufo, obl = sizeof (bufo);
643 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
645 assert (errno == E2BIG ||
646 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
651 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
654 else if (cd[i] == (iconv_t) (-1) && score[i] == -1)
655 /* Special case for conversion to UTF-8 */
656 update_content_info (&infos[i], &states[i], bufu, ubl1);
659 /* Save unused input */
660 memmove (bufi, ib, ibl);
661 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
668 /* Find best score */
670 for (i = 0; i < ncodes; i++) {
671 if (cd[i] == (iconv_t) (-1) && score[i] == -1) {
672 /* Special case for conversion to UTF-8 */
677 else if (cd[i] == (iconv_t) (-1) || score[i] == -1)
679 else if (ret == -1 || score[i] < ret) {
687 memcpy (info, &infos[*tocode], sizeof (CONTENT));
688 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
692 for (i = 0; i < ncodes; i++)
693 if (cd[i] != (iconv_t) (-1))
705 #endif /* !HAVE_ICONV */
709 * Find the first of the fromcodes that gives a valid conversion and
710 * the best charset conversion of the file into one of the tocodes. If
711 * successful, set *fromcode and *tocode to dynamically allocated
712 * strings, set CONTENT *info, and return the number of characters
713 * converted inexactly. If no conversion was possible, return -1.
715 * Both fromcodes and tocodes may be colon-separated lists of charsets.
716 * However, if fromcode is zero then fromcodes is assumed to be the
717 * name of a single charset even if it contains a colon.
719 static ssize_t convert_file_from_to (FILE * file,
720 const char *fromcodes,
721 const char *tocodes, char **fromcode,
722 char **tocode, CONTENT * info)
730 /* Count the tocodes */
732 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
733 if ((c1 = strchr (c, ':')) == c)
739 tcode = p_new(char *, ncodes);
740 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
741 if ((c1 = strchr (c, ':')) == c)
743 tcode[i] = m_substrdup(c, c1);
748 /* Try each fromcode in turn */
749 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
750 if ((c1 = strchr (c, ':')) == c)
752 fcode = m_substrdup(c, c1);
754 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
766 /* There is only one fromcode */
767 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
776 for (i = 0; i < ncodes; i++)
785 * Analyze the contents of a file to determine which MIME encoding to use.
786 * Also set the body charset, sometimes, or not.
788 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
793 char *fromcode = NULL;
804 if (stat (fname, &sb) == -1) {
805 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
809 if (!S_ISREG (sb.st_mode)) {
810 mutt_error (_("%s isn't a regular file."), fname);
814 if ((fp = fopen (fname, "r")) == NULL) {
818 info = p_new(CONTENT, 1);
821 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
822 char *chs = mutt_get_parameter ("charset", b->parameter);
823 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
824 FileCharset : Charset) : Charset;
825 if (Charset && (chs || SendCharset) &&
826 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
827 &fromcode, &tocode, info) != -1) {
829 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
830 mutt_set_parameter ("charset", chsbuf, &b->parameter);
832 b->file_charset = fromcode;
840 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
841 update_content_info (info, &state, buffer, r);
842 update_content_info (info, &state, 0, 0);
846 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
847 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
849 && !mutt_is_us_ascii (Charset) ? Charset :
850 "unknown-8bit"), &b->parameter);
855 /* Given a file with path ``s'', see if there is a registered MIME type.
856 * returns the major MIME type, and copies the subtype to ``d''. First look
857 * for ~/.mime.types, then look in a system mime.types if we can find one.
858 * The longest match is used so that we can match `ps.gz' when `gz' also
862 int mutt_lookup_mime_type (BODY * att, const char *path)
866 char buf[LONG_STRING];
867 char subtype[STRING], xtype[STRING];
869 int szf, sze, cur_sze;
877 szf = m_strlen(path);
879 for (count = 0; count < 4; count++) {
881 * can't use strtok() because we use it in an inner loop below, so use
882 * a switch statement here instead.
886 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
889 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
892 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
895 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
898 goto bye; /* shouldn't happen */
901 if ((f = fopen (buf, "r")) != NULL) {
902 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
903 /* weed out any comments */
904 if ((p = strchr (buf, '#')))
907 /* remove any leading space. */
908 ct = vskipspaces(buf);
910 /* position on the next field in this line */
911 if ((p = strpbrk (ct, " \t")) == NULL)
916 /* cycle through the file extensions */
917 while ((p = strtok (p, " \t\n"))) {
919 if ((sze > cur_sze) && (szf >= sze) &&
920 (m_strcasecmp(path + szf - sze, p) == 0
921 || ascii_strcasecmp (path + szf - sze, p) == 0)
922 && (szf == sze || path[szf - sze - 1] == '.'))
924 /* get the content-type */
926 if ((p = strchr (ct, '/')) == NULL) {
927 /* malformed line, just skip it. */
932 for (q = p; *q && !ISSPACE (*q); q++);
934 m_strncpy(subtype, sizeof(subtype), p, q - p);
936 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
937 m_strcpy(xtype, sizeof(xtype), ct);
950 if (type != TYPEOTHER || *xtype != '\0') {
952 m_strreplace(&att->subtype, subtype);
953 m_strreplace(&att->xtype, xtype);
959 void mutt_message_to_7bit (BODY * a, FILE * fp)
961 char temp[_POSIX_PATH_MAX];
967 if (!a->filename && fp)
969 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
970 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
975 if (stat (a->filename, &sb) == -1) {
976 mutt_perror ("stat");
979 a->length = sb.st_size;
983 if (!(fpout = safe_fopen (temp, "w+"))) {
984 mutt_perror ("fopen");
988 fseeko (fpin, a->offset, 0);
989 a->parts = mutt_parse_messageRFC822 (fpin, a);
991 transform_to_7bit (a->parts, fpin);
993 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
994 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
996 fputs ("MIME-Version: 1.0\n", fpout);
997 mutt_write_mime_header (a->parts, fpout);
999 mutt_write_mime_body (a->parts, fpout);
1011 a->encoding = ENC7BIT;
1012 a->d_filename = a->filename;
1013 if (a->filename && a->unlink)
1014 unlink (a->filename);
1015 a->filename = m_strdup(temp);
1017 if (stat (a->filename, &sb) == -1) {
1018 mutt_perror ("stat");
1021 a->length = sb.st_size;
1022 mutt_free_body (&a->parts);
1023 a->hdr->content = NULL;
1026 static void transform_to_7bit (BODY * a, FILE * fpin)
1028 char buff[_POSIX_PATH_MAX];
1033 for (; a; a = a->next) {
1034 if (a->type == TYPEMULTIPART) {
1035 if (a->encoding != ENC7BIT)
1036 a->encoding = ENC7BIT;
1038 transform_to_7bit (a->parts, fpin);
1040 else if (mutt_is_message_type (a->type, a->subtype)) {
1041 mutt_message_to_7bit (a, fpin);
1045 a->force_charset = 1;
1048 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1049 mutt_perror ("fopen");
1053 mutt_decode_attachment (a, &s);
1055 a->d_filename = a->filename;
1056 a->filename = m_strdup(buff);
1058 if (stat (a->filename, &sb) == -1) {
1059 mutt_perror ("stat");
1062 a->length = sb.st_size;
1064 mutt_update_encoding (a);
1065 if (a->encoding == ENC8BIT)
1066 a->encoding = ENCQUOTEDPRINTABLE;
1067 else if (a->encoding == ENCBINARY)
1068 a->encoding = ENCBASE64;
1073 /* determine which Content-Transfer-Encoding to use */
1074 static void mutt_set_encoding (BODY * b, CONTENT * info)
1076 char send_charset[SHORT_STRING];
1078 if (b->type == TYPETEXT) {
1080 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1081 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1082 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1083 b->encoding = ENCQUOTEDPRINTABLE;
1084 else if (info->hibin)
1085 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1087 b->encoding = ENC7BIT;
1089 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1090 if (info->lobin || info->hibin) {
1091 if (option (OPTALLOW8BIT) && !info->lobin)
1092 b->encoding = ENC8BIT;
1094 mutt_message_to_7bit (b, NULL);
1097 b->encoding = ENC7BIT;
1099 else if (b->type == TYPEAPPLICATION
1100 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1101 b->encoding = ENC7BIT;
1104 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1105 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1108 /* Determine which encoding is smaller */
1109 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1110 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1111 b->encoding = ENCBASE64;
1113 b->encoding = ENCQUOTEDPRINTABLE;
1117 b->encoding = ENC7BIT;
1121 void mutt_stamp_attachment (BODY * a)
1123 a->stamp = time (NULL);
1126 /* Get a body's character set */
1128 char *mutt_get_body_charset (char *d, ssize_t dlen, BODY * b)
1132 if (b && b->type != TYPETEXT)
1136 p = mutt_get_parameter ("charset", b->parameter);
1139 mutt_canonical_charset (d, dlen, NONULL (p));
1141 m_strcpy(d, dlen, "us-ascii");
1147 /* Assumes called from send mode where BODY->filename points to actual file */
1148 void mutt_update_encoding (BODY * a)
1151 char chsbuff[STRING];
1153 /* override noconv when it's us-ascii */
1154 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1157 if (!a->force_charset && !a->noconv)
1158 mutt_delete_parameter ("charset", &a->parameter);
1160 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1163 mutt_set_encoding (a, info);
1164 mutt_stamp_attachment (a);
1166 p_delete(&a->content);
1171 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1173 char buffer[LONG_STRING];
1176 int cmflags, chflags;
1177 int pgp = hdr->security;
1179 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1180 (hdr->security & ENCRYPT)) {
1181 if (!crypt_valid_passphrase (hdr->security))
1185 mutt_mktemp (buffer);
1186 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1189 body = mutt_new_body ();
1190 body->type = TYPEMESSAGE;
1191 body->subtype = m_strdup("rfc822");
1192 body->filename = m_strdup(buffer);
1195 body->disposition = DISPINLINE;
1198 mutt_parse_mime_message (ctx, hdr);
1203 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1204 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1205 chflags |= CH_MIME | CH_TXTPLAIN;
1206 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1207 pgp &= ~(PGPENCRYPT|SMIMEENCRYPT);
1209 else if (option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1210 if (mutt_is_multipart_encrypted (hdr->content)) {
1211 chflags |= CH_MIME | CH_NONEWLINE;
1212 cmflags = M_CM_DECODE_PGP;
1215 else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1216 chflags |= CH_MIME | CH_TXTPLAIN;
1217 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1220 else if (mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1221 chflags |= CH_MIME | CH_TXTPLAIN;
1222 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1223 pgp &= ~SMIMEENCRYPT;
1227 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1232 body->hdr = header_new();
1233 body->hdr->offset = 0;
1234 /* we don't need the user headers here */
1235 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1236 body->hdr->security = pgp;
1237 mutt_update_encoding (body);
1238 body->parts = body->hdr->content;
1245 BODY *mutt_make_file_attach (const char *path)
1250 att = mutt_new_body ();
1251 att->filename = m_strdup(path);
1253 /* Attempt to determine the appropriate content-type based on the filename
1260 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1261 path)) != TYPEOTHER || *xbuf != '\0') {
1263 att->subtype = m_strdup(buf);
1264 att->xtype = m_strdup(xbuf);
1269 mutt_lookup_mime_type (att, path);
1273 if ((info = mutt_get_content_info (path, att)) == NULL) {
1274 mutt_free_body (&att);
1278 if (!att->subtype) {
1279 if (info->lobin == 0
1280 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1282 * Statistically speaking, there should be more than 10% "lobin"
1283 * chars if this is really a binary file...
1285 att->type = TYPETEXT;
1286 att->subtype = m_strdup("plain");
1289 att->type = TYPEAPPLICATION;
1290 att->subtype = m_strdup("octet-stream");
1294 mutt_update_encoding (att);
1298 static int get_toplevel_encoding (BODY * a)
1302 for (; a; a = a->next) {
1303 if (a->encoding == ENCBINARY)
1305 else if (a->encoding == ENC8BIT)
1312 BODY *mutt_make_multipart (BODY * b)
1316 new = mutt_new_body ();
1317 new->type = TYPEMULTIPART;
1318 new->subtype = m_strdup("mixed");
1319 new->encoding = get_toplevel_encoding (b);
1320 mutt_generate_boundary (&new->parameter);
1322 new->disposition = DISPINLINE;
1328 /* remove the multipart body if it exists */
1329 BODY *mutt_remove_multipart (BODY * b)
1337 mutt_free_body (&t);
1342 char *mutt_make_date (char *s, ssize_t len)
1344 time_t t = time (NULL);
1345 struct tm *l = localtime (&t);
1346 time_t tz = mutt_local_tz (t);
1350 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1351 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1352 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1353 (int) tz / 60, (int) abs (tz) % 60);
1357 /* wrapper around mutt_write_address() so we can handle very large
1358 recipient lists without needing a huge temporary buffer in memory */
1359 void mutt_write_address_list (address_t * adr, FILE * fp, int linelen,
1363 char buf[LONG_STRING];
1371 rfc822_write_address (buf, sizeof (buf), adr, display);
1372 len = m_strlen(buf);
1373 if (count && linelen + len > 74) {
1375 linelen = len + 8; /* tab is usually about 8 spaces... */
1378 if (count && adr->mailbox) {
1386 if (!adr->group && adr->next && adr->next->mailbox) {
1396 /* arbitrary number of elements to grow the array by */
1401 /* need to write the list in reverse because they are stored in reverse order
1402 * when parsed to speed up threading
1404 void mutt_write_references (string_list_t * r, FILE * f)
1406 string_list_t **ref = NULL;
1407 int refcnt = 0, refmax = 0;
1409 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1410 if (refcnt == refmax)
1411 p_realloc(&ref, refmax += REF_INC);
1415 while (refcnt-- > 0) {
1417 fputs (ref[refcnt]->data, f);
1423 /* Note: all RFC2047 encoding should be done outside of this routine, except
1424 * for the "real name." This will allow this routine to be used more than
1425 * once, if necessary.
1427 * Likewise, all IDN processing should happen outside of this routine.
1429 * mode == 1 => "lite" mode (used for edit_hdrs)
1430 * mode == 0 => normal mode. write full header + MIME headers
1431 * mode == -1 => write just the envelope info (used for postponing messages)
1433 * privacy != 0 => will omit any headers which may identify the user.
1434 * Output generated is suitable for being sent through
1435 * anonymous remailer chains.
1439 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1440 int mode, int privacy)
1442 char buffer[LONG_STRING];
1444 string_list_t *tmp = env->userhdrs;
1445 int has_agent = 0; /* user defined user-agent header field exists */
1446 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1449 if (!option (OPTNEWSSEND))
1451 if (mode == 0 && !privacy)
1452 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1454 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1456 /* OPTUSEFROM is not consulted here so that we can still write a From:
1457 * field if the user sets it with the `my_hdr' command
1459 if (env->from && !privacy) {
1461 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1462 fprintf (fp, "From: %s\n", buffer);
1467 mutt_write_address_list (env->to, fp, 4, 0);
1471 if (!option (OPTNEWSSEND))
1473 if (EDIT_HEADER("To:"))
1474 fputs ("To:\n", fp);
1478 mutt_write_address_list (env->cc, fp, 4, 0);
1482 if (!option (OPTNEWSSEND))
1484 if (EDIT_HEADER("Cc:"))
1485 fputs ("Cc:\n", fp);
1488 if (mode != 0 || option (OPTWRITEBCC)) {
1489 fputs ("Bcc: ", fp);
1490 mutt_write_address_list (env->bcc, fp, 5, 0);
1495 if (!option (OPTNEWSSEND))
1497 if (EDIT_HEADER("Bcc:"))
1498 fputs ("Bcc:\n", fp);
1501 if (env->newsgroups)
1502 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1503 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1504 fputs ("Newsgroups:\n", fp);
1506 if (env->followup_to)
1507 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1508 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1509 fputs ("Followup-To:\n", fp);
1511 if (env->x_comment_to)
1512 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1513 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1514 EDIT_HEADER("X-Comment-To:"))
1515 fputs ("X-Comment-To:\n", fp);
1519 fprintf (fp, "Subject: %s\n", env->subject);
1520 else if (mode == 1 && EDIT_HEADER("Subject:"))
1521 fputs ("Subject:\n", fp);
1523 /* save message id if the user has set it */
1524 if (env->message_id && !privacy)
1525 fprintf (fp, "Message-ID: %s\n", env->message_id);
1527 if (env->reply_to) {
1528 fputs ("Reply-To: ", fp);
1529 mutt_write_address_list (env->reply_to, fp, 10, 0);
1531 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1532 fputs ("Reply-To:\n", fp);
1534 if (env->mail_followup_to)
1536 if (!option (OPTNEWSSEND))
1539 fputs ("Mail-Followup-To: ", fp);
1540 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1544 if (env->references) {
1545 fputs ("References:", fp);
1546 mutt_write_references (env->references, fp);
1550 /* Add the MIME headers */
1551 fputs ("MIME-Version: 1.0\n", fp);
1552 mutt_write_mime_header (attach, fp);
1555 if (env->in_reply_to) {
1556 fputs ("In-Reply-To:", fp);
1557 mutt_write_references (env->in_reply_to, fp);
1563 /* Add any user defined headers */
1564 for (; tmp; tmp = tmp->next) {
1565 if ((p = strchr (tmp->data, ':'))) {
1566 p = vskipspaces(p + 1);
1568 continue; /* don't emit empty fields. */
1570 /* check to see if the user has overridden the user-agent field */
1571 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1577 fputs (tmp->data, fp);
1582 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1585 if (OperatingSystem != NULL) {
1586 os = OperatingSystem;
1589 os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1591 /* Add a vanity header */
1592 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1595 list_del (&hdrs, (list_del_t*)xmemfree);
1597 return (ferror (fp) == 0 ? 0 : -1);
1600 static void encode_headers (string_list_t * h)
1606 for (; h; h = h->next) {
1607 if (!(p = strchr (h->data, ':')))
1611 p = vskipspaces(p + 1);
1617 rfc2047_encode_string (&tmp);
1618 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1620 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1626 const char *mutt_fqdn (short may_hide_host)
1630 if (Fqdn && Fqdn[0] != '@') {
1633 if (may_hide_host && option (OPTHIDDENHOST)) {
1634 if ((p = strchr (Fqdn, '.')))
1637 /* sanity check: don't hide the host if
1638 * the fqdn is something like detebe.org.
1641 if (!p || !(q = strchr (p, '.')))
1649 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1650 static char mutt_normalized_char(char c)
1652 return (isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.';
1655 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1657 #define APPEND_FMT(fmt, arg) \
1659 int snlen = snprintf(buf, len, fmt, arg); \
1664 #define APPEND_BYTE(c) \
1681 APPEND_BYTE(mutt_normalized_char(c));
1689 APPEND_FMT("%02d", tm->tm_mday);
1692 APPEND_FMT("%02d", tm->tm_hour);
1695 APPEND_FMT("%02d", tm->tm_mon + 1);
1698 APPEND_FMT("%02d", tm->tm_min);
1701 APPEND_FMT("%lo", (unsigned long)now);
1704 APPEND_FMT("%u", (unsigned int)getpid());
1707 APPEND_FMT("%c", MsgIdPfx);
1708 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1711 APPEND_FMT("%u", (unsigned int)rand());
1714 APPEND_FMT("%x", (unsigned int)rand());
1717 APPEND_FMT("%02d", tm->tm_sec);
1720 APPEND_FMT("%u", (unsigned int) now);
1723 APPEND_FMT("%x", (unsigned int) now);
1725 case 'Y': /* this will break in the year 10000 ;-) */
1726 APPEND_FMT("%04d", tm->tm_year + 1900);
1731 default: /* invalid formats are replaced by '.' */
1733 m_strncat(buf, len, ".", 1);
1743 char *mutt_gen_msgid (void)
1745 char buf[SHORT_STRING];
1746 char localpart[SHORT_STRING];
1747 unsigned int localpart_length;
1750 if (!(fqdn = mutt_fqdn (0)))
1751 fqdn = NONULL (Hostname);
1753 localpart_length = sizeof (buf) - m_strlen(fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1755 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1757 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1758 return (m_strdup(buf));
1761 static RETSIGTYPE alarm_handler (int sig)
1766 /* invoke sendmail in a subshell
1767 path (in) path to program to execute
1768 args (in) arguments to pass to program
1769 msg (in) temp file containing message to send
1770 tempfile (out) if sendmail is put in the background, this points
1771 to the temporary file containing the stdout of the
1774 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1780 mutt_block_signals_system ();
1783 /* we also don't want to be stopped right now */
1784 sigaddset (&set, SIGTSTP);
1785 sigprocmask (SIG_BLOCK, &set, NULL);
1787 if (SendmailWait >= 0) {
1788 char tmp[_POSIX_PATH_MAX];
1791 *tempfile = m_strdup(tmp);
1794 if ((pid = fork ()) == 0) {
1795 struct sigaction act, oldalrm;
1797 /* save parent's ID before setsid() */
1800 /* we want the delivery to continue even after the main process dies,
1801 * so we put ourselves into another session right away
1805 /* next we close all open files */
1806 #if defined(OPEN_MAX)
1807 for (fd = 0; fd < OPEN_MAX; fd++)
1809 #elif defined(_POSIX_OPEN_MAX)
1810 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1818 /* now the second fork() */
1819 if ((pid = fork ()) == 0) {
1820 /* "msg" will be opened as stdin */
1821 if (open (msg, O_RDONLY, 0) < 0) {
1827 if (SendmailWait >= 0) {
1828 /* *tempfile will be opened as stdout */
1829 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1832 /* redirect stderr to *tempfile too */
1837 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1839 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1846 else if (pid == -1) {
1852 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1853 * SendmailWait = 0: wait forever
1854 * SendmailWait < 0: don't wait
1856 if (SendmailWait > 0) {
1858 act.sa_handler = alarm_handler;
1860 /* need to make sure waitpid() is interrupted on SIGALRM */
1861 act.sa_flags = SA_INTERRUPT;
1865 sigemptyset (&act.sa_mask);
1866 sigaction (SIGALRM, &act, &oldalrm);
1867 alarm (SendmailWait);
1869 else if (SendmailWait < 0)
1870 _exit (0xff & EX_OK);
1872 if (waitpid (pid, &st, 0) > 0) {
1873 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1874 if (SendmailWait && st == (0xff & EX_OK)) {
1875 unlink (*tempfile); /* no longer needed */
1880 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1881 if (SendmailWait > 0) {
1887 /* reset alarm; not really needed, but... */
1889 sigaction (SIGALRM, &oldalrm, NULL);
1891 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1892 /* the parent is already dead */
1900 sigprocmask (SIG_UNBLOCK, &set, NULL);
1902 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1903 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1905 st = S_ERR; /* error */
1907 mutt_unblock_signals_system (1);
1912 static const char **
1913 add_args(const char **args, ssize_t *argslen, ssize_t *argsmax, address_t * addr)
1915 for (; addr; addr = addr->next) {
1916 /* weed out group mailboxes, since those are for display only */
1917 if (addr->mailbox && !addr->group) {
1918 if (*argslen == *argsmax)
1919 p_realloc(&args, *argsmax += 5);
1920 args[(*argslen)++] = addr->mailbox;
1926 static const char **
1927 add_option(const char **args, ssize_t *argslen, ssize_t *argsmax, const char *s)
1929 if (*argslen == *argsmax) {
1930 p_realloc(&args, *argsmax += 5);
1932 args[(*argslen)++] = s;
1936 static int mutt_invoke_sendmail (address_t * from, /* the sender */
1937 address_t * to, address_t * cc, address_t * bcc, /* recips */
1938 const char *msg, /* file containing message */
1940 { /* message contains 8bit chars */
1941 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1942 const char **args = NULL;
1943 ssize_t argslen = 0, argsmax = 0;
1947 if (option (OPTNEWSSEND)) {
1948 char cmd[LONG_STRING];
1950 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1953 i = nntp_post (msg);
1962 s = m_strdup(Sendmail);
1966 while ((ps = strtok (ps, " "))) {
1967 if (argslen == argsmax)
1968 p_realloc(&args, argsmax += 5);
1971 args[argslen++] = ps;
1973 path = m_strdup(ps);
1974 ps = strrchr (ps, '/');
1979 args[argslen++] = ps;
1986 if (!option (OPTNEWSSEND)) {
1988 if (eightbit && option (OPTUSE8BITMIME))
1989 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
1991 if (option (OPTENVFROM)) {
1992 address_t *f = NULL;
1995 else if (from && !from->next)
1998 args = add_option (args, &argslen, &argsmax, "-f");
1999 args = add_args (args, &argslen, &argsmax, f);
2003 args = add_option (args, &argslen, &argsmax, "-N");
2004 args = add_option (args, &argslen, &argsmax, DsnNotify);
2007 args = add_option (args, &argslen, &argsmax, "-R");
2008 args = add_option (args, &argslen, &argsmax, DsnReturn);
2010 args = add_option (args, &argslen, &argsmax, "--");
2011 args = add_args (args, &argslen, &argsmax, to);
2012 args = add_args (args, &argslen, &argsmax, cc);
2013 args = add_args (args, &argslen, &argsmax, bcc);
2018 if (argslen == argsmax)
2019 p_realloc(&args, ++argsmax);
2021 args[argslen++] = NULL;
2023 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2025 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2030 if (stat (childout, &st) == 0 && st.st_size > 0)
2031 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2039 p_delete(&childout);
2044 if (i == (EX_OK & 0xff))
2046 else if (i == S_BKG)
2053 int mutt_invoke_mta (address_t * from, /* the sender */
2054 address_t * to, address_t * cc, address_t * bcc, /* recips */
2055 const char *msg, /* file containing message */
2057 { /* message contains 8bit chars */
2060 if (!option (OPTNEWSSEND))
2063 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2066 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2069 /* appends string 'b' to string 'a', and returns the pointer to the new
2071 char *mutt_append_string (char *a, const char *b)
2073 ssize_t la = m_strlen(a);
2075 p_realloc(&a, la + m_strlen(b) + 1);
2076 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2080 /* returns 1 if char `c' needs to be quoted to protect from shell
2081 interpretation when executing commands in a subshell */
2082 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2084 /* returns 1 if string `s' contains characters which could cause problems
2085 when used on a command line to execute a command */
2086 int mutt_needs_quote (const char *s)
2089 if (INVALID_CHAR (*s))
2096 /* Quote a string to prevent shell escapes when this string is used on the
2097 command line to send mail. */
2098 char *mutt_quote_string (const char *s)
2103 rlen = m_strlen(s) + 3;
2104 pr = r = p_new(char, rlen);
2107 if (INVALID_CHAR (*s)) {
2110 p_realloc(&r, ++rlen);
2121 /* For postponing (!final) do the necessary encodings only */
2122 void mutt_prepare_envelope (ENVELOPE * env, int final)
2124 char buffer[LONG_STRING];
2127 if (env->bcc && !(env->to || env->cc)) {
2128 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2129 * recipients if there is no To: or Cc: field, so attempt to suppress
2130 * it by using an empty To: field.
2132 env->to = address_new ();
2134 env->to->next = address_new ();
2137 rfc822_strcpy(buffer, sizeof(buffer), "undisclosed-recipients",
2140 env->to->mailbox = m_strdup(buffer);
2143 mutt_set_followup_to (env);
2145 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2146 env->message_id = mutt_gen_msgid ();
2149 /* Take care of 8-bit => 7-bit conversion. */
2150 rfc2047_encode_adrlist (env->to, "To");
2151 rfc2047_encode_adrlist (env->cc, "Cc");
2152 rfc2047_encode_adrlist (env->bcc, "Bcc");
2153 rfc2047_encode_adrlist (env->from, "From");
2154 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2155 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2159 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2162 rfc2047_encode_string (&env->subject);
2164 encode_headers (env->userhdrs);
2167 void mutt_unprepare_envelope (ENVELOPE * env)
2169 string_list_t *item;
2171 for (item = env->userhdrs; item; item = item->next)
2172 rfc2047_decode (&item->data);
2174 address_list_wipe(&env->mail_followup_to);
2176 /* back conversions */
2177 rfc2047_decode_adrlist (env->to);
2178 rfc2047_decode_adrlist (env->cc);
2179 rfc2047_decode_adrlist (env->bcc);
2180 rfc2047_decode_adrlist (env->from);
2181 rfc2047_decode_adrlist (env->reply_to);
2182 rfc2047_decode (&env->subject);
2185 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2186 const char *resent_from, address_t * env_from)
2190 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2191 MESSAGE *msg = NULL;
2194 /* Try to bounce each message out, aborting if we get any failures. */
2195 for (i = 0; i < Context->msgcount; i++)
2196 if (Context->hdrs[i]->tagged)
2198 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2203 /* If we failed to open a message, return with error */
2204 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2210 mutt_mktemp (tempfile);
2211 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2212 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2214 if (!option (OPTBOUNCEDELIVERED))
2215 ch_flags |= CH_WEED_DELIVERED;
2217 fseeko (fp, h->offset, 0);
2218 fprintf (f, "Resent-From: %s", resent_from);
2219 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2220 if (MsgIdFormat && *MsgIdFormat)
2221 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2222 fputs ("Resent-To: ", f);
2223 mutt_write_address_list (to, f, 11, 0);
2224 mutt_copy_header (fp, h, f, ch_flags, NULL);
2226 mutt_copy_bytes (fp, f, h->content->length);
2229 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2230 h->content->encoding == ENC8BIT);
2234 mx_close_message (&msg);
2239 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2242 const char *fqdn = mutt_fqdn (1);
2243 char resent_from[STRING];
2247 resent_from[0] = '\0';
2248 from = mutt_default_from ();
2251 rfc822_qualify (from, fqdn);
2253 rfc2047_encode_adrlist (from, "Resent-From");
2254 if (mutt_addrlist_to_idna (from, &err)) {
2255 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2258 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2261 unset_option (OPTNEWSSEND);
2264 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2266 address_list_wipe(&from);
2272 /* given a list of addresses, return a list of unique addresses */
2273 address_t *mutt_remove_duplicates (address_t * addr)
2275 address_t *top = addr;
2276 address_t **last = ⊤
2281 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2282 if (tmp->mailbox && addr->mailbox &&
2283 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2293 address_list_wipe(&addr);
2306 static void set_noconv_flags (BODY * b, short flag)
2308 for (; b; b = b->next) {
2309 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2310 set_noconv_flags (b->parts, flag);
2311 else if (b->type == TYPETEXT && b->noconv) {
2313 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2315 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2320 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2321 int post, char *fcc)
2325 char tempfile[_POSIX_PATH_MAX];
2326 FILE *tempfp = NULL;
2330 set_noconv_flags (hdr->content, 1);
2332 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2336 /* We need to add a Content-Length field to avoid problems where a line in
2337 * the message body begins with "From "
2339 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2340 mutt_mktemp (tempfile);
2341 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2342 mutt_perror (tempfile);
2343 mx_close_mailbox (&f, NULL);
2348 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2349 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2350 mx_close_mailbox (&f, NULL);
2354 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2355 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2357 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2360 /* (postponment) if this was a reply of some sort, <msgid> contians the
2361 * Message-ID: of message replied to. Save it using a special X-Mutt-
2362 * header so it can be picked up if the message is recalled at a later
2363 * point in time. This will allow the message to be marked as replied if
2364 * the same mailbox is still open.
2367 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2369 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2370 * it can be picked up when the message is recalled
2373 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2374 fprintf (msg->fp, "Status: RO\n");
2378 /* (postponment) if the mail is to be signed or encrypted, save this info */
2379 if (post && (hdr->security & APPLICATION_PGP)) {
2380 fputs ("X-Mutt-PGP: ", msg->fp);
2381 if (hdr->security & ENCRYPT)
2382 fputc ('E', msg->fp);
2383 if (hdr->security & SIGN) {
2384 fputc ('S', msg->fp);
2385 if (PgpSignAs && *PgpSignAs)
2386 fprintf (msg->fp, "<%s>", PgpSignAs);
2388 if (hdr->security & INLINE)
2389 fputc ('I', msg->fp);
2390 fputc ('\n', msg->fp);
2393 /* (postponment) if the mail is to be signed or encrypted, save this info */
2394 if (post && (hdr->security & APPLICATION_SMIME)) {
2395 fputs ("X-Mutt-SMIME: ", msg->fp);
2396 if (hdr->security & ENCRYPT) {
2397 fputc ('E', msg->fp);
2398 if (SmimeCryptAlg && *SmimeCryptAlg)
2399 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2401 if (hdr->security & SIGN) {
2402 fputc ('S', msg->fp);
2403 if (SmimeDefaultKey && *SmimeDefaultKey)
2404 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2406 if (hdr->security & INLINE)
2407 fputc ('I', msg->fp);
2408 fputc ('\n', msg->fp);
2412 /* (postponement) if the mail is to be sent through a mixmaster
2413 * chain, save that information
2416 if (post && hdr->chain && hdr->chain) {
2419 fputs ("X-Mutt-Mix:", msg->fp);
2420 for (p = hdr->chain; p; p = p->next)
2421 fprintf (msg->fp, " %s", (char *) p->data);
2423 fputc ('\n', msg->fp);
2428 char sasha[LONG_STRING];
2431 mutt_write_mime_body (hdr->content, tempfp);
2433 /* make sure the last line ends with a newline. Emacs doesn't ensure
2434 * this will happen, and it can cause problems parsing the mailbox
2437 fseeko (tempfp, -1, 2);
2438 if (fgetc (tempfp) != '\n') {
2439 fseeko (tempfp, 0, 2);
2440 fputc ('\n', tempfp);
2444 if (ferror (tempfp)) {
2447 mx_commit_message (msg, &f); /* XXX - really? */
2448 mx_close_message (&msg);
2449 mx_close_mailbox (&f, NULL);
2453 /* count the number of lines */
2455 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2457 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2458 fprintf (msg->fp, "Lines: %d\n\n", lines);
2460 /* copy the body and clean up */
2462 r = mutt_copy_stream (tempfp, msg->fp);
2463 if (fclose (tempfp) != 0)
2465 /* if there was an error, leave the temp version */
2470 fputc ('\n', msg->fp); /* finish off the header */
2471 r = mutt_write_mime_body (hdr->content, msg->fp);
2474 if (mx_commit_message (msg, &f) != 0)
2476 mx_close_message (&msg);
2477 mx_close_mailbox (&f, NULL);
2480 set_noconv_flags (hdr->content, 0);