2 * Copyright notice from original mutt:
3 * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
5 * This file is part of mutt-ng, see http://www.muttng.org/.
6 * It's licensed under the GNU General Public License,
7 * please see the file GPL in the top level source directory.
25 #include <sys/utsname.h>
27 #include <lib-lib/mem.h>
28 #include <lib-lib/ascii.h>
29 #include <lib-lib/str.h>
30 #include <lib-lib/macros.h>
31 #include <lib-lib/file.h>
33 #include <lib-sys/exit.h>
34 #include <lib-sys/mutt_signal.h>
36 #include <lib-mime/mime.h>
38 #include <lib-ui/curses.h>
42 #include "recvattach.h"
47 #include <lib-crypt/crypt.h>
48 #include "mutt_idna.h"
51 # include "mutt_libesmtp.h"
52 #endif /* USE_LIBESMTP */
58 #ifdef HAVE_SYSEXITS_H
60 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
64 /* If you are debugging this file, comment out the following line. */
73 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
75 static char MsgIdPfx = 'A';
77 static void transform_to_7bit (BODY * a, FILE * fpin);
79 static void encode_quoted (fgetconv_t * fc, FILE * fout, int istext)
82 char line[77], savechar;
84 while ((c = fgetconv (fc)) != EOF) {
85 /* Wrap the line if needed. */
86 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
87 /* If the last character is "quoted", then be sure to move all three
88 * characters to the next line. Otherwise, just move the last
91 if (line[linelen - 3] == '=') {
92 line[linelen - 3] = 0;
97 line[1] = line[linelen - 2];
98 line[2] = line[linelen - 1];
102 savechar = line[linelen - 1];
103 line[linelen - 1] = '=';
112 /* Escape lines that begin with/only contain "the message separator". */
113 if (linelen == 4 && !m_strncmp("From", line, 4)) {
114 m_strcpy(line, sizeof(line), "=46rom");
117 else if (linelen == 4 && !m_strncmp("from", line, 4)) {
118 m_strcpy(line, sizeof(line), "=66rom");
121 else if (linelen == 1 && line[0] == '.') {
122 m_strcpy(line, sizeof(line), "=2E");
127 if (c == '\n' && istext) {
128 /* Check to make sure there is no trailing space on this line. */
130 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
132 sprintf (line + linelen - 1, "=%2.2X",
133 (unsigned char) line[linelen - 1]);
137 int savechar = line[linelen - 1];
139 line[linelen - 1] = '=';
142 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
152 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
153 /* Check to make sure there is enough room for the quoted character.
154 * If not, wrap to the next line.
157 line[linelen++] = '=';
163 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
167 /* Don't worry about wrapping the line here. That will happen during
168 * the next iteration when I'll also know what the next character is.
174 /* Take care of anything left in the buffer */
176 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
177 /* take care of trailing whitespace */
179 sprintf (line + linelen - 1, "=%2.2X",
180 (unsigned char) line[linelen - 1]);
182 savechar = line[linelen - 1];
183 line[linelen - 1] = '=';
187 sprintf (line, "=%2.2X", (unsigned char) savechar);
196 static char b64_buffer[3];
197 static short b64_num;
198 static short b64_linelen;
200 static void b64_flush (FILE * fout)
207 if (b64_linelen >= 72) {
212 for (i = b64_num; i < 3; i++)
213 b64_buffer[i] = '\0';
215 fputc(__m_b64chars[(b64_buffer[0] >> 2) & 0x3f], fout);
218 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
223 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
227 fputc (__m_b64chars[b64_buffer[2] & 0x3f], fout);
232 while (b64_linelen % 4) {
241 static void b64_putc (char c, FILE * fout)
246 b64_buffer[b64_num++] = c;
250 static void encode_base64 (fgetconv_t * fc, FILE * fout, int istext)
254 b64_num = b64_linelen = 0;
256 while ((ch = fgetconv (fc)) != EOF) {
257 if (istext && ch == '\n' && ch1 != '\r')
258 b64_putc ('\r', fout);
266 static void encode_8bit (fgetconv_t * fc, FILE * fout, int istext)
270 while ((ch = fgetconv (fc)) != EOF)
275 int mutt_write_mime_header (BODY * a, FILE * f)
284 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
289 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
291 for (p = a->parameter; p; p = p->next) {
300 tmp = m_strdup(p->value);
301 encode = rfc2231_encode_string (&tmp);
302 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
304 /* Dirty hack to make messages readable by Outlook Express
305 * for the Mac: force quotes around the boundary parameter
306 * even when they aren't needed.
309 if (!ascii_strcasecmp (p->attribute, "boundary")
310 && !strcmp (buffer, tmp))
311 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
315 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
317 if (len + tmplen + 2 > 76) {
326 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
334 fprintf (f, "Content-Description: %s\n", a->description);
336 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
339 if (!(fn = a->d_filename))
345 /* Strip off the leading path... */
346 if ((t = strrchr (fn, '/')))
353 encode = rfc2231_encode_string (&tmp);
354 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
356 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
362 if (a->encoding != ENC7BIT)
363 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
365 /* Do NOT add the terminator here!!! */
366 return (ferror (f) ? -1 : 0);
369 # define write_as_text_part(a) (mutt_is_text_part(a) || mutt_is_application_pgp(a))
371 int mutt_write_mime_body (BODY * a, FILE * f)
374 char boundary[SHORT_STRING];
375 char send_charset[SHORT_STRING];
380 if (a->type == TYPEMULTIPART) {
381 /* First, find the boundary to use */
382 if (!(p = parameter_getval(a->parameter, "boundary"))) {
383 mutt_error _("No boundary parameter found! [report this error]");
387 m_strcpy(boundary, sizeof(boundary), p);
389 for (t = a->parts; t; t = t->next) {
390 fprintf (f, "\n--%s\n", boundary);
391 if (mutt_write_mime_header (t, f) == -1)
394 if (mutt_write_mime_body (t, f) == -1)
397 fprintf (f, "\n--%s--\n", boundary);
398 return (ferror (f) ? -1 : 0);
401 /* This is pretty gross, but it's the best solution for now... */
402 if (a->type == TYPEAPPLICATION && !m_strcmp(a->subtype, "pgp-encrypted")) {
403 fputs ("Version: 1\n", f);
407 if ((fpin = fopen (a->filename, "r")) == NULL) {
408 mutt_error (_("%s no longer exists!"), a->filename);
412 if (a->type == TYPETEXT && (!a->noconv))
413 fc = fgetconv_open (fpin, a->file_charset,
414 mutt_get_body_charset (send_charset,
415 sizeof (send_charset), a), 0);
417 fc = fgetconv_open (fpin, 0, 0, 0);
419 if (a->encoding == ENCQUOTEDPRINTABLE)
420 encode_quoted (fc, f, write_as_text_part (a));
421 else if (a->encoding == ENCBASE64)
422 encode_base64 (fc, f, write_as_text_part (a));
423 else if (a->type == TYPETEXT && (!a->noconv))
424 encode_8bit (fc, f, write_as_text_part (a));
426 mutt_copy_stream (fpin, f);
428 fgetconv_close (&fc);
431 return (ferror (f) ? -1 : 0);
434 #undef write_as_text_part
436 #define BOUNDARYLEN 16
437 void mutt_generate_boundary (PARAMETER ** parm)
439 char rs[BOUNDARYLEN + 1];
444 for (i = 0; i < BOUNDARYLEN; i++)
445 *p++ = __m_b64chars[lrand48() % sizeof(__m_b64chars)];
448 mutt_set_parameter ("boundary", rs, parm);
460 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
464 int whitespace = s->whitespace;
466 int linelen = s->linelen;
467 int was_cr = s->was_cr;
469 if (!d) { /* This signals EOF */
472 if (linelen > info->linemax)
473 info->linemax = linelen;
478 for (; dlen; d++, dlen--) {
491 if (linelen > info->linemax)
492 info->linemax = linelen;
507 if (linelen > info->linemax)
508 info->linemax = linelen;
513 else if (ch == '\r') {
521 else if (ch == '\t' || ch == '\f') {
525 else if (ch < 32 || ch == 127)
529 if ((ch == 'F') || (ch == 'f'))
539 if (linelen == 2 && ch != 'r')
541 else if (linelen == 3 && ch != 'o')
543 else if (linelen == 4) {
556 if (ch != ' ' && ch != '\t')
561 s->whitespace = whitespace;
563 s->linelen = linelen;
568 /* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
569 #define BUGGY_ICONV 1
572 * Find the best charset conversion of the file from fromcode into one
573 * of the tocodes. If successful, set *tocode and CONTENT *info and
574 * return the number of characters converted inexactly. If no
575 * conversion was possible, return -1.
577 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
578 * which would otherwise prevent us from knowing the number of inexact
579 * conversions. Where the candidate target charset is UTF-8 we avoid
580 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
581 * fails with some libraries.
583 * We assume that the output from iconv is never more than 4 times as
584 * long as the input for any pair of charsets we might be interested
587 static ssize_t convert_file_to (FILE * file, const char *fromcode,
588 int ncodes, const char **tocodes,
589 int *tocode, CONTENT * info)
593 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
596 ssize_t ibl, obl, ubl, ubl1, n, ret;
599 CONTENT_STATE *states;
602 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
603 if (cd1 == MUTT_ICONV_ERROR)
606 cd = p_new(iconv_t, ncodes);
607 score = p_new(ssize_t, ncodes);
608 states = p_new(CONTENT_STATE, ncodes);
609 infos = p_new(CONTENT, ncodes);
611 for (i = 0; i < ncodes; i++)
612 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
613 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
615 /* Special case for conversion to UTF-8 */
616 cd[i] = MUTT_ICONV_ERROR, score[i] = -1;
622 /* Try to fill input buffer */
623 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
626 /* Convert to UTF-8 */
628 ob = bufu, obl = sizeof (bufu);
629 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
630 assert (n == -1 || !n);
632 ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
633 assert (errno == EILSEQ ||
634 (errno == EINVAL && ib == bufi && ibl < ssizeof (bufi)));
640 /* Convert from UTF-8 */
641 for (i = 0; i < ncodes; i++)
642 if (cd[i] != MUTT_ICONV_ERROR && score[i] != -1) {
643 ub = bufu, ubl = ubl1;
644 ob = bufo, obl = sizeof (bufo);
645 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
647 assert (errno == E2BIG ||
648 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
653 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
656 else if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1)
657 /* Special case for conversion to UTF-8 */
658 update_content_info (&infos[i], &states[i], bufu, ubl1);
661 /* Save unused input */
662 memmove (bufi, ib, ibl);
663 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
670 /* Find best score */
672 for (i = 0; i < ncodes; i++) {
673 if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1) {
674 /* Special case for conversion to UTF-8 */
679 else if (cd[i] == MUTT_ICONV_ERROR || score[i] == -1)
681 else if (ret == -1 || score[i] < ret) {
689 memcpy (info, &infos[*tocode], sizeof (CONTENT));
690 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
694 for (i = 0; i < ncodes; i++)
695 if (cd[i] != MUTT_ICONV_ERROR)
707 #endif /* !HAVE_ICONV */
711 * Find the first of the fromcodes that gives a valid conversion and
712 * the best charset conversion of the file into one of the tocodes. If
713 * successful, set *fromcode and *tocode to dynamically allocated
714 * strings, set CONTENT *info, and return the number of characters
715 * converted inexactly. If no conversion was possible, return -1.
717 * Both fromcodes and tocodes may be colon-separated lists of charsets.
718 * However, if fromcode is zero then fromcodes is assumed to be the
719 * name of a single charset even if it contains a colon.
721 static ssize_t convert_file_from_to (FILE * file,
722 const char *fromcodes,
723 const char *tocodes, char **fromcode,
724 char **tocode, CONTENT * info)
732 /* Count the tocodes */
734 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
735 if ((c1 = strchr (c, ':')) == c)
741 tcode = p_new(char *, ncodes);
742 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
743 if ((c1 = strchr (c, ':')) == c)
745 tcode[i] = m_substrdup(c, c1);
750 /* Try each fromcode in turn */
751 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
752 if ((c1 = strchr (c, ':')) == c)
754 fcode = m_substrdup(c, c1);
756 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
768 /* There is only one fromcode */
769 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
778 for (i = 0; i < ncodes; i++)
787 * Analyze the contents of a file to determine which MIME encoding to use.
788 * Also set the body charset, sometimes, or not.
790 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
795 char *fromcode = NULL;
806 if (stat (fname, &sb) == -1) {
807 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
811 if (!S_ISREG (sb.st_mode)) {
812 mutt_error (_("%s isn't a regular file."), fname);
816 if ((fp = fopen (fname, "r")) == NULL) {
820 info = p_new(CONTENT, 1);
823 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
824 const char *chs = parameter_getval(b->parameter, "charset");
825 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
826 FileCharset : Charset) : Charset;
827 if (Charset && (chs || SendCharset) &&
828 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
829 &fromcode, &tocode, info) != -1) {
831 charset_canonicalize (chsbuf, sizeof (chsbuf), tocode);
832 mutt_set_parameter ("charset", chsbuf, &b->parameter);
834 b->file_charset = fromcode;
842 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
843 update_content_info (info, &state, buffer, r);
844 update_content_info (info, &state, 0, 0);
848 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
849 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
851 && !charset_is_us_ascii (Charset) ? Charset :
852 "unknown-8bit"), &b->parameter);
857 /* Given a file with path ``s'', see if there is a registered MIME type.
858 * returns the major MIME type, and copies the subtype to ``d''. First look
859 * for ~/.mime.types, then look in a system mime.types if we can find one.
860 * The longest match is used so that we can match `ps.gz' when `gz' also
864 int mutt_lookup_mime_type (BODY * att, const char *path)
868 char buf[LONG_STRING];
869 char subtype[STRING], xtype[STRING];
871 int szf, sze, cur_sze;
879 szf = m_strlen(path);
881 for (count = 0; count < 4; count++) {
883 * can't use strtok() because we use it in an inner loop below, so use
884 * a switch statement here instead.
888 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
891 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
894 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
897 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
900 goto bye; /* shouldn't happen */
903 if ((f = fopen (buf, "r")) != NULL) {
904 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
905 /* weed out any comments */
906 if ((p = strchr (buf, '#')))
909 /* remove any leading space. */
910 ct = vskipspaces(buf);
912 /* position on the next field in this line */
913 if ((p = strpbrk (ct, " \t")) == NULL)
918 /* cycle through the file extensions */
919 while ((p = strtok (p, " \t\n"))) {
921 if ((sze > cur_sze) && (szf >= sze) &&
922 (m_strcasecmp(path + szf - sze, p) == 0
923 || ascii_strcasecmp (path + szf - sze, p) == 0)
924 && (szf == sze || path[szf - sze - 1] == '.'))
926 /* get the content-type */
928 if ((p = strchr (ct, '/')) == NULL) {
929 /* malformed line, just skip it. */
934 for (q = p; *q && !ISSPACE (*q); q++);
936 m_strncpy(subtype, sizeof(subtype), p, q - p);
938 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
939 m_strcpy(xtype, sizeof(xtype), ct);
952 if (type != TYPEOTHER || *xtype != '\0') {
954 m_strreplace(&att->subtype, subtype);
955 m_strreplace(&att->xtype, xtype);
961 void mutt_message_to_7bit (BODY * a, FILE * fp)
963 char temp[_POSIX_PATH_MAX];
969 if (!a->filename && fp)
971 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
972 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
977 if (stat (a->filename, &sb) == -1) {
978 mutt_perror ("stat");
981 a->length = sb.st_size;
985 if (!(fpout = safe_fopen (temp, "w+"))) {
986 mutt_perror ("fopen");
990 fseeko (fpin, a->offset, 0);
991 a->parts = mutt_parse_messageRFC822 (fpin, a);
993 transform_to_7bit (a->parts, fpin);
995 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
996 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
998 fputs ("MIME-Version: 1.0\n", fpout);
999 mutt_write_mime_header (a->parts, fpout);
1000 fputc ('\n', fpout);
1001 mutt_write_mime_body (a->parts, fpout);
1013 a->encoding = ENC7BIT;
1014 a->d_filename = a->filename;
1015 if (a->filename && a->unlink)
1016 unlink (a->filename);
1017 a->filename = m_strdup(temp);
1019 if (stat (a->filename, &sb) == -1) {
1020 mutt_perror ("stat");
1023 a->length = sb.st_size;
1024 mutt_free_body (&a->parts);
1025 a->hdr->content = NULL;
1028 static void transform_to_7bit (BODY * a, FILE * fpin)
1030 char buff[_POSIX_PATH_MAX];
1035 for (; a; a = a->next) {
1036 if (a->type == TYPEMULTIPART) {
1037 if (a->encoding != ENC7BIT)
1038 a->encoding = ENC7BIT;
1040 transform_to_7bit (a->parts, fpin);
1042 else if (mutt_is_message_type (a->type, a->subtype)) {
1043 mutt_message_to_7bit (a, fpin);
1047 a->force_charset = 1;
1050 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
1051 mutt_perror ("fopen");
1055 mutt_decode_attachment (a, &s);
1057 a->d_filename = a->filename;
1058 a->filename = m_strdup(buff);
1060 if (stat (a->filename, &sb) == -1) {
1061 mutt_perror ("stat");
1064 a->length = sb.st_size;
1066 mutt_update_encoding (a);
1067 if (a->encoding == ENC8BIT)
1068 a->encoding = ENCQUOTEDPRINTABLE;
1069 else if (a->encoding == ENCBINARY)
1070 a->encoding = ENCBASE64;
1075 /* determine which Content-Transfer-Encoding to use */
1076 static void mutt_set_encoding (BODY * b, CONTENT * info)
1078 char send_charset[SHORT_STRING];
1080 if (b->type == TYPETEXT) {
1082 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1083 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1084 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1085 b->encoding = ENCQUOTEDPRINTABLE;
1086 else if (info->hibin)
1087 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1089 b->encoding = ENC7BIT;
1091 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1092 if (info->lobin || info->hibin) {
1093 if (option (OPTALLOW8BIT) && !info->lobin)
1094 b->encoding = ENC8BIT;
1096 mutt_message_to_7bit (b, NULL);
1099 b->encoding = ENC7BIT;
1101 else if (b->type == TYPEAPPLICATION
1102 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1103 b->encoding = ENC7BIT;
1106 if (info->lobin || info->hibin || info->binary || info->linemax > 990
1107 || info->cr || ( /* option (OPTENCODEFROM) && */ info->from))
1110 /* Determine which encoding is smaller */
1111 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1112 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1113 b->encoding = ENCBASE64;
1115 b->encoding = ENCQUOTEDPRINTABLE;
1119 b->encoding = ENC7BIT;
1123 void mutt_stamp_attachment (BODY * a)
1125 a->stamp = time (NULL);
1128 /* Get a body's character set */
1130 char *mutt_get_body_charset (char *d, ssize_t dlen, BODY * b)
1132 const char *p = NULL;
1134 if (b && b->type != TYPETEXT)
1138 p = parameter_getval(b->parameter, "charset");
1141 charset_canonicalize (d, dlen, p);
1143 m_strcpy(d, dlen, "us-ascii");
1149 /* Assumes called from send mode where BODY->filename points to actual file */
1150 void mutt_update_encoding (BODY * a)
1153 char chsbuff[STRING];
1155 /* override noconv when it's us-ascii */
1156 if (charset_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1159 if (!a->force_charset && !a->noconv)
1160 mutt_delete_parameter ("charset", &a->parameter);
1162 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1165 mutt_set_encoding (a, info);
1166 mutt_stamp_attachment (a);
1168 p_delete(&a->content);
1173 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1175 char buffer[LONG_STRING];
1178 int cmflags, chflags;
1179 int pgp = hdr->security;
1181 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1182 (hdr->security & ENCRYPT)) {
1183 if (!crypt_valid_passphrase (hdr->security))
1187 mutt_mktemp (buffer);
1188 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1191 body = mutt_new_body ();
1192 body->type = TYPEMESSAGE;
1193 body->subtype = m_strdup("rfc822");
1194 body->filename = m_strdup(buffer);
1197 body->disposition = DISPINLINE;
1200 mutt_parse_mime_message (ctx, hdr);
1205 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1206 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1207 chflags |= CH_MIME | CH_TXTPLAIN;
1208 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1209 pgp &= ~(PGPENCRYPT|SMIMEENCRYPT);
1211 else if (option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1212 if (mutt_is_multipart_encrypted (hdr->content)) {
1213 chflags |= CH_MIME | CH_NONEWLINE;
1214 cmflags = M_CM_DECODE_PGP;
1217 else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1218 chflags |= CH_MIME | CH_TXTPLAIN;
1219 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1222 else if (mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1223 chflags |= CH_MIME | CH_TXTPLAIN;
1224 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1225 pgp &= ~SMIMEENCRYPT;
1229 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1234 body->hdr = header_new();
1235 body->hdr->offset = 0;
1236 /* we don't need the user headers here */
1237 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1238 body->hdr->security = pgp;
1239 mutt_update_encoding (body);
1240 body->parts = body->hdr->content;
1247 BODY *mutt_make_file_attach (const char *path)
1252 att = mutt_new_body ();
1253 att->filename = m_strdup(path);
1255 /* Attempt to determine the appropriate content-type based on the filename
1262 mutt_lookup_mime_type (buf, sizeof (buf), xbuf, sizeof (xbuf),
1263 path)) != TYPEOTHER || *xbuf != '\0') {
1265 att->subtype = m_strdup(buf);
1266 att->xtype = m_strdup(xbuf);
1271 mutt_lookup_mime_type (att, path);
1275 if ((info = mutt_get_content_info (path, att)) == NULL) {
1276 mutt_free_body (&att);
1280 if (!att->subtype) {
1281 if (info->lobin == 0
1282 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1284 * Statistically speaking, there should be more than 10% "lobin"
1285 * chars if this is really a binary file...
1287 att->type = TYPETEXT;
1288 att->subtype = m_strdup("plain");
1291 att->type = TYPEAPPLICATION;
1292 att->subtype = m_strdup("octet-stream");
1296 mutt_update_encoding (att);
1300 static int get_toplevel_encoding (BODY * a)
1304 for (; a; a = a->next) {
1305 if (a->encoding == ENCBINARY)
1307 else if (a->encoding == ENC8BIT)
1314 BODY *mutt_make_multipart (BODY * b)
1318 new = mutt_new_body ();
1319 new->type = TYPEMULTIPART;
1320 new->subtype = m_strdup("mixed");
1321 new->encoding = get_toplevel_encoding (b);
1322 mutt_generate_boundary (&new->parameter);
1324 new->disposition = DISPINLINE;
1330 /* remove the multipart body if it exists */
1331 BODY *mutt_remove_multipart (BODY * b)
1339 mutt_free_body (&t);
1344 char *mutt_make_date (char *s, ssize_t len)
1346 time_t t = time (NULL);
1347 struct tm *l = localtime (&t);
1348 time_t tz = mutt_local_tz (t);
1352 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1353 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1354 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1355 (int) tz / 60, (int) abs (tz) % 60);
1359 /* wrapper around mutt_write_address() so we can handle very large
1360 recipient lists without needing a huge temporary buffer in memory */
1361 void mutt_write_address_list (address_t * adr, FILE * fp, int linelen,
1365 char buf[LONG_STRING];
1373 rfc822_write_address (buf, sizeof (buf), adr, display);
1374 len = m_strlen(buf);
1375 if (count && linelen + len > 74) {
1377 linelen = len + 8; /* tab is usually about 8 spaces... */
1380 if (count && adr->mailbox) {
1388 if (!adr->group && adr->next && adr->next->mailbox) {
1398 /* arbitrary number of elements to grow the array by */
1403 /* need to write the list in reverse because they are stored in reverse order
1404 * when parsed to speed up threading
1406 void mutt_write_references (string_list_t * r, FILE * f)
1408 string_list_t **ref = NULL;
1409 int refcnt = 0, refmax = 0;
1411 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1412 if (refcnt == refmax)
1413 p_realloc(&ref, refmax += REF_INC);
1417 while (refcnt-- > 0) {
1419 fputs (ref[refcnt]->data, f);
1425 /* Note: all RFC2047 encoding should be done outside of this routine, except
1426 * for the "real name." This will allow this routine to be used more than
1427 * once, if necessary.
1429 * Likewise, all IDN processing should happen outside of this routine.
1431 * mode == 1 => "lite" mode (used for edit_hdrs)
1432 * mode == 0 => normal mode. write full header + MIME headers
1433 * mode == -1 => write just the envelope info (used for postponing messages)
1435 * privacy != 0 => will omit any headers which may identify the user.
1436 * Output generated is suitable for being sent through
1437 * anonymous remailer chains.
1441 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1442 int mode, int privacy)
1444 char buffer[LONG_STRING];
1446 string_list_t *tmp = env->userhdrs;
1447 int has_agent = 0; /* user defined user-agent header field exists */
1448 list2_t* hdrs = list_from_str (EditorHeaders, " ");
1451 if (!option (OPTNEWSSEND))
1453 if (mode == 0 && !privacy)
1454 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1456 #define EDIT_HEADER(x) (mode != 1 || option(OPTXMAILTO) || (mode == 1 && list_lookup(hdrs,(list_lookup_t*) ascii_strcasecmp,x) >= 0))
1458 /* OPTUSEFROM is not consulted here so that we can still write a From:
1459 * field if the user sets it with the `my_hdr' command
1461 if (env->from && !privacy) {
1463 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1464 fprintf (fp, "From: %s\n", buffer);
1469 mutt_write_address_list (env->to, fp, 4, 0);
1473 if (!option (OPTNEWSSEND))
1475 if (EDIT_HEADER("To:"))
1476 fputs ("To:\n", fp);
1480 mutt_write_address_list (env->cc, fp, 4, 0);
1484 if (!option (OPTNEWSSEND))
1486 if (EDIT_HEADER("Cc:"))
1487 fputs ("Cc:\n", fp);
1490 if (mode != 0 || option (OPTWRITEBCC)) {
1491 fputs ("Bcc: ", fp);
1492 mutt_write_address_list (env->bcc, fp, 5, 0);
1497 if (!option (OPTNEWSSEND))
1499 if (EDIT_HEADER("Bcc:"))
1500 fputs ("Bcc:\n", fp);
1503 if (env->newsgroups)
1504 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1505 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Newsgroups:"))
1506 fputs ("Newsgroups:\n", fp);
1508 if (env->followup_to)
1509 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1510 else if (mode == 1 && option (OPTNEWSSEND) && EDIT_HEADER("Followup-To:"))
1511 fputs ("Followup-To:\n", fp);
1513 if (env->x_comment_to)
1514 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1515 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1516 EDIT_HEADER("X-Comment-To:"))
1517 fputs ("X-Comment-To:\n", fp);
1521 fprintf (fp, "Subject: %s\n", env->subject);
1522 else if (mode == 1 && EDIT_HEADER("Subject:"))
1523 fputs ("Subject:\n", fp);
1525 /* save message id if the user has set it */
1526 if (env->message_id && !privacy)
1527 fprintf (fp, "Message-ID: %s\n", env->message_id);
1529 if (env->reply_to) {
1530 fputs ("Reply-To: ", fp);
1531 mutt_write_address_list (env->reply_to, fp, 10, 0);
1533 else if (mode > 0 && EDIT_HEADER("Reply-To:"))
1534 fputs ("Reply-To:\n", fp);
1536 if (env->mail_followup_to)
1538 if (!option (OPTNEWSSEND))
1541 fputs ("Mail-Followup-To: ", fp);
1542 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1546 if (env->references) {
1547 fputs ("References:", fp);
1548 mutt_write_references (env->references, fp);
1552 /* Add the MIME headers */
1553 fputs ("MIME-Version: 1.0\n", fp);
1554 mutt_write_mime_header (attach, fp);
1557 if (env->in_reply_to) {
1558 fputs ("In-Reply-To:", fp);
1559 mutt_write_references (env->in_reply_to, fp);
1565 /* Add any user defined headers */
1566 for (; tmp; tmp = tmp->next) {
1567 if ((p = strchr (tmp->data, ':'))) {
1568 p = vskipspaces(p + 1);
1570 continue; /* don't emit empty fields. */
1572 /* check to see if the user has overridden the user-agent field */
1573 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1579 fputs (tmp->data, fp);
1584 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1587 if (OperatingSystem != NULL) {
1588 os = OperatingSystem;
1591 os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1593 /* Add a vanity header */
1594 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1597 list_del (&hdrs, (list_del_t*)xmemfree);
1599 return (ferror (fp) == 0 ? 0 : -1);
1602 static void encode_headers (string_list_t * h)
1608 for (; h; h = h->next) {
1609 if (!(p = strchr (h->data, ':')))
1613 p = vskipspaces(p + 1);
1619 rfc2047_encode_string (&tmp);
1620 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1622 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1628 const char *mutt_fqdn (short may_hide_host)
1632 if (Fqdn && Fqdn[0] != '@') {
1635 if (may_hide_host && option (OPTHIDDENHOST)) {
1636 if ((p = strchr (Fqdn, '.')))
1639 /* sanity check: don't hide the host if
1640 * the fqdn is something like detebe.org.
1643 if (!p || !(q = strchr (p, '.')))
1651 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1652 static char mutt_normalized_char(char c)
1654 return (isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.';
1657 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1659 #define APPEND_FMT(fmt, arg) \
1661 int snlen = snprintf(buf, len, fmt, arg); \
1666 #define APPEND_BYTE(c) \
1683 APPEND_BYTE(mutt_normalized_char(c));
1691 APPEND_FMT("%02d", tm->tm_mday);
1694 APPEND_FMT("%02d", tm->tm_hour);
1697 APPEND_FMT("%02d", tm->tm_mon + 1);
1700 APPEND_FMT("%02d", tm->tm_min);
1703 APPEND_FMT("%lo", (unsigned long)now);
1706 APPEND_FMT("%u", (unsigned int)getpid());
1709 APPEND_FMT("%c", MsgIdPfx);
1710 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1713 APPEND_FMT("%u", (unsigned int)rand());
1716 APPEND_FMT("%x", (unsigned int)rand());
1719 APPEND_FMT("%02d", tm->tm_sec);
1722 APPEND_FMT("%u", (unsigned int) now);
1725 APPEND_FMT("%x", (unsigned int) now);
1727 case 'Y': /* this will break in the year 10000 ;-) */
1728 APPEND_FMT("%04d", tm->tm_year + 1900);
1733 default: /* invalid formats are replaced by '.' */
1735 m_strncat(buf, len, ".", 1);
1745 char *mutt_gen_msgid (void)
1747 char buf[SHORT_STRING];
1748 char localpart[SHORT_STRING];
1749 unsigned int localpart_length;
1752 if (!(fqdn = mutt_fqdn (0)))
1753 fqdn = NONULL (Hostname);
1755 localpart_length = sizeof (buf) - m_strlen(fqdn) - 4; /* the 4 characters are '<', '@', '>' and '\0' */
1757 mutt_gen_localpart (localpart, localpart_length, MsgIdFormat);
1759 snprintf (buf, sizeof (buf), "<%s@%s>", localpart, fqdn);
1760 return (m_strdup(buf));
1763 static RETSIGTYPE alarm_handler (int sig)
1768 /* invoke sendmail in a subshell
1769 path (in) path to program to execute
1770 args (in) arguments to pass to program
1771 msg (in) temp file containing message to send
1772 tempfile (out) if sendmail is put in the background, this points
1773 to the temporary file containing the stdout of the
1776 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1782 mutt_block_signals_system ();
1785 /* we also don't want to be stopped right now */
1786 sigaddset (&set, SIGTSTP);
1787 sigprocmask (SIG_BLOCK, &set, NULL);
1789 if (SendmailWait >= 0) {
1790 char tmp[_POSIX_PATH_MAX];
1793 *tempfile = m_strdup(tmp);
1796 if ((pid = fork ()) == 0) {
1797 struct sigaction act, oldalrm;
1799 /* save parent's ID before setsid() */
1802 /* we want the delivery to continue even after the main process dies,
1803 * so we put ourselves into another session right away
1807 /* next we close all open files */
1808 #if defined(OPEN_MAX)
1809 for (fd = 0; fd < OPEN_MAX; fd++)
1811 #elif defined(_POSIX_OPEN_MAX)
1812 for (fd = 0; fd < _POSIX_OPEN_MAX; fd++)
1820 /* now the second fork() */
1821 if ((pid = fork ()) == 0) {
1822 /* "msg" will be opened as stdin */
1823 if (open (msg, O_RDONLY, 0) < 0) {
1829 if (SendmailWait >= 0) {
1830 /* *tempfile will be opened as stdout */
1831 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1834 /* redirect stderr to *tempfile too */
1839 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1841 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1848 else if (pid == -1) {
1854 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1855 * SendmailWait = 0: wait forever
1856 * SendmailWait < 0: don't wait
1858 if (SendmailWait > 0) {
1860 act.sa_handler = alarm_handler;
1862 /* need to make sure waitpid() is interrupted on SIGALRM */
1863 act.sa_flags = SA_INTERRUPT;
1867 sigemptyset (&act.sa_mask);
1868 sigaction (SIGALRM, &act, &oldalrm);
1869 alarm (SendmailWait);
1871 else if (SendmailWait < 0)
1872 _exit (0xff & EX_OK);
1874 if (waitpid (pid, &st, 0) > 0) {
1875 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1876 if (SendmailWait && st == (0xff & EX_OK)) {
1877 unlink (*tempfile); /* no longer needed */
1882 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1883 if (SendmailWait > 0) {
1889 /* reset alarm; not really needed, but... */
1891 sigaction (SIGALRM, &oldalrm, NULL);
1893 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1894 /* the parent is already dead */
1902 sigprocmask (SIG_UNBLOCK, &set, NULL);
1904 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1905 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1907 st = S_ERR; /* error */
1909 mutt_unblock_signals_system (1);
1914 static const char **
1915 add_args(const char **args, ssize_t *argslen, ssize_t *argsmax, address_t * addr)
1917 for (; addr; addr = addr->next) {
1918 /* weed out group mailboxes, since those are for display only */
1919 if (addr->mailbox && !addr->group) {
1920 if (*argslen == *argsmax)
1921 p_realloc(&args, *argsmax += 5);
1922 args[(*argslen)++] = addr->mailbox;
1928 static const char **
1929 add_option(const char **args, ssize_t *argslen, ssize_t *argsmax, const char *s)
1931 if (*argslen == *argsmax) {
1932 p_realloc(&args, *argsmax += 5);
1934 args[(*argslen)++] = s;
1938 static int mutt_invoke_sendmail (address_t * from, /* the sender */
1939 address_t * to, address_t * cc, address_t * bcc, /* recips */
1940 const char *msg, /* file containing message */
1942 { /* message contains 8bit chars */
1943 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1944 const char **args = NULL;
1945 ssize_t argslen = 0, argsmax = 0;
1949 if (option (OPTNEWSSEND)) {
1950 char cmd[LONG_STRING];
1952 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1955 i = nntp_post (msg);
1964 s = m_strdup(Sendmail);
1968 while ((ps = strtok (ps, " "))) {
1969 if (argslen == argsmax)
1970 p_realloc(&args, argsmax += 5);
1973 args[argslen++] = ps;
1975 path = m_strdup(ps);
1976 ps = strrchr (ps, '/');
1981 args[argslen++] = ps;
1988 if (!option (OPTNEWSSEND)) {
1990 if (eightbit && option (OPTUSE8BITMIME))
1991 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
1993 if (option (OPTENVFROM)) {
1994 address_t *f = NULL;
1997 else if (from && !from->next)
2000 args = add_option (args, &argslen, &argsmax, "-f");
2001 args = add_args (args, &argslen, &argsmax, f);
2005 args = add_option (args, &argslen, &argsmax, "-N");
2006 args = add_option (args, &argslen, &argsmax, DsnNotify);
2009 args = add_option (args, &argslen, &argsmax, "-R");
2010 args = add_option (args, &argslen, &argsmax, DsnReturn);
2012 args = add_option (args, &argslen, &argsmax, "--");
2013 args = add_args (args, &argslen, &argsmax, to);
2014 args = add_args (args, &argslen, &argsmax, cc);
2015 args = add_args (args, &argslen, &argsmax, bcc);
2020 if (argslen == argsmax)
2021 p_realloc(&args, ++argsmax);
2023 args[argslen++] = NULL;
2025 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2027 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2032 if (stat (childout, &st) == 0 && st.st_size > 0)
2033 mutt_do_pager (_("Output of the delivery process"), childout, 0,
2041 p_delete(&childout);
2046 if (i == (EX_OK & 0xff))
2048 else if (i == S_BKG)
2055 int mutt_invoke_mta (address_t * from, /* the sender */
2056 address_t * to, address_t * cc, address_t * bcc, /* recips */
2057 const char *msg, /* file containing message */
2059 { /* message contains 8bit chars */
2062 if (!option (OPTNEWSSEND))
2065 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
2068 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2071 /* appends string 'b' to string 'a', and returns the pointer to the new
2073 char *mutt_append_string (char *a, const char *b)
2075 ssize_t la = m_strlen(a);
2077 p_realloc(&a, la + m_strlen(b) + 1);
2078 strcpy (a + la, b); /* __STRCPY_CHECKED__ */
2082 /* returns 1 if char `c' needs to be quoted to protect from shell
2083 interpretation when executing commands in a subshell */
2084 #define INVALID_CHAR(c) (!isalnum ((unsigned char)c) && !strchr ("@.+-_,:", c))
2086 /* returns 1 if string `s' contains characters which could cause problems
2087 when used on a command line to execute a command */
2088 int mutt_needs_quote (const char *s)
2091 if (INVALID_CHAR (*s))
2098 /* Quote a string to prevent shell escapes when this string is used on the
2099 command line to send mail. */
2100 char *mutt_quote_string (const char *s)
2105 rlen = m_strlen(s) + 3;
2106 pr = r = p_new(char, rlen);
2109 if (INVALID_CHAR (*s)) {
2112 p_realloc(&r, ++rlen);
2123 /* For postponing (!final) do the necessary encodings only */
2124 void mutt_prepare_envelope (ENVELOPE * env, int final)
2126 char buffer[LONG_STRING];
2129 if (env->bcc && !(env->to || env->cc)) {
2130 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2131 * recipients if there is no To: or Cc: field, so attempt to suppress
2132 * it by using an empty To: field.
2134 env->to = address_new ();
2136 env->to->next = address_new ();
2139 rfc822_strcpy(buffer, sizeof(buffer), "undisclosed-recipients",
2142 env->to->mailbox = m_strdup(buffer);
2145 mutt_set_followup_to (env);
2147 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2148 env->message_id = mutt_gen_msgid ();
2151 /* Take care of 8-bit => 7-bit conversion. */
2152 rfc2047_encode_adrlist (env->to, "To");
2153 rfc2047_encode_adrlist (env->cc, "Cc");
2154 rfc2047_encode_adrlist (env->bcc, "Bcc");
2155 rfc2047_encode_adrlist (env->from, "From");
2156 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2157 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2161 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2164 rfc2047_encode_string (&env->subject);
2166 encode_headers (env->userhdrs);
2169 void mutt_unprepare_envelope (ENVELOPE * env)
2171 string_list_t *item;
2173 for (item = env->userhdrs; item; item = item->next)
2174 rfc2047_decode (&item->data);
2176 address_list_wipe(&env->mail_followup_to);
2178 /* back conversions */
2179 rfc2047_decode_adrlist (env->to);
2180 rfc2047_decode_adrlist (env->cc);
2181 rfc2047_decode_adrlist (env->bcc);
2182 rfc2047_decode_adrlist (env->from);
2183 rfc2047_decode_adrlist (env->reply_to);
2184 rfc2047_decode (&env->subject);
2187 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2188 const char *resent_from, address_t * env_from)
2192 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2193 MESSAGE *msg = NULL;
2196 /* Try to bounce each message out, aborting if we get any failures. */
2197 for (i = 0; i < Context->msgcount; i++)
2198 if (Context->hdrs[i]->tagged)
2200 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2205 /* If we failed to open a message, return with error */
2206 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2212 mutt_mktemp (tempfile);
2213 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2214 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2216 if (!option (OPTBOUNCEDELIVERED))
2217 ch_flags |= CH_WEED_DELIVERED;
2219 fseeko (fp, h->offset, 0);
2220 fprintf (f, "Resent-From: %s", resent_from);
2221 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2222 if (MsgIdFormat && *MsgIdFormat)
2223 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2224 fputs ("Resent-To: ", f);
2225 mutt_write_address_list (to, f, 11, 0);
2226 mutt_copy_header (fp, h, f, ch_flags, NULL);
2228 mutt_copy_bytes (fp, f, h->content->length);
2231 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2232 h->content->encoding == ENC8BIT);
2236 mx_close_message (&msg);
2241 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2244 const char *fqdn = mutt_fqdn (1);
2245 char resent_from[STRING];
2249 resent_from[0] = '\0';
2250 from = mutt_default_from ();
2253 rfc822_qualify (from, fqdn);
2255 rfc2047_encode_adrlist (from, "Resent-From");
2256 if (mutt_addrlist_to_idna (from, &err)) {
2257 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2260 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2263 unset_option (OPTNEWSSEND);
2266 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2268 address_list_wipe(&from);
2274 /* given a list of addresses, return a list of unique addresses */
2275 address_t *mutt_remove_duplicates (address_t * addr)
2277 address_t *top = addr;
2278 address_t **last = ⊤
2283 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next) {
2284 if (tmp->mailbox && addr->mailbox &&
2285 !ascii_strcasecmp (addr->mailbox, tmp->mailbox)) {
2295 address_list_wipe(&addr);
2308 static void set_noconv_flags (BODY * b, short flag)
2310 for (; b; b = b->next) {
2311 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2312 set_noconv_flags (b->parts, flag);
2313 else if (b->type == TYPETEXT && b->noconv) {
2315 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2317 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2322 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2323 int post, char *fcc)
2327 char tempfile[_POSIX_PATH_MAX];
2328 FILE *tempfp = NULL;
2332 set_noconv_flags (hdr->content, 1);
2334 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2338 /* We need to add a Content-Length field to avoid problems where a line in
2339 * the message body begins with "From "
2341 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2342 mutt_mktemp (tempfile);
2343 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2344 mutt_perror (tempfile);
2345 mx_close_mailbox (&f, NULL);
2350 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2351 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2352 mx_close_mailbox (&f, NULL);
2356 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2357 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2359 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2362 /* (postponment) if this was a reply of some sort, <msgid> contians the
2363 * Message-ID: of message replied to. Save it using a special X-Mutt-
2364 * header so it can be picked up if the message is recalled at a later
2365 * point in time. This will allow the message to be marked as replied if
2366 * the same mailbox is still open.
2369 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2371 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2372 * it can be picked up when the message is recalled
2375 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2376 fprintf (msg->fp, "Status: RO\n");
2380 /* (postponment) if the mail is to be signed or encrypted, save this info */
2381 if (post && (hdr->security & APPLICATION_PGP)) {
2382 fputs ("X-Mutt-PGP: ", msg->fp);
2383 if (hdr->security & ENCRYPT)
2384 fputc ('E', msg->fp);
2385 if (hdr->security & SIGN) {
2386 fputc ('S', msg->fp);
2387 if (PgpSignAs && *PgpSignAs)
2388 fprintf (msg->fp, "<%s>", PgpSignAs);
2390 if (hdr->security & INLINE)
2391 fputc ('I', msg->fp);
2392 fputc ('\n', msg->fp);
2395 /* (postponment) if the mail is to be signed or encrypted, save this info */
2396 if (post && (hdr->security & APPLICATION_SMIME)) {
2397 fputs ("X-Mutt-SMIME: ", msg->fp);
2398 if (hdr->security & ENCRYPT) {
2399 fputc ('E', msg->fp);
2400 if (SmimeCryptAlg && *SmimeCryptAlg)
2401 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2403 if (hdr->security & SIGN) {
2404 fputc ('S', msg->fp);
2405 if (SmimeDefaultKey && *SmimeDefaultKey)
2406 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2408 if (hdr->security & INLINE)
2409 fputc ('I', msg->fp);
2410 fputc ('\n', msg->fp);
2414 /* (postponement) if the mail is to be sent through a mixmaster
2415 * chain, save that information
2418 if (post && hdr->chain && hdr->chain) {
2421 fputs ("X-Mutt-Mix:", msg->fp);
2422 for (p = hdr->chain; p; p = p->next)
2423 fprintf (msg->fp, " %s", (char *) p->data);
2425 fputc ('\n', msg->fp);
2430 char sasha[LONG_STRING];
2433 mutt_write_mime_body (hdr->content, tempfp);
2435 /* make sure the last line ends with a newline. Emacs doesn't ensure
2436 * this will happen, and it can cause problems parsing the mailbox
2439 fseeko (tempfp, -1, 2);
2440 if (fgetc (tempfp) != '\n') {
2441 fseeko (tempfp, 0, 2);
2442 fputc ('\n', tempfp);
2446 if (ferror (tempfp)) {
2449 mx_commit_message (msg, &f); /* XXX - really? */
2450 mx_close_message (&msg);
2451 mx_close_mailbox (&f, NULL);
2455 /* count the number of lines */
2457 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2459 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2460 fprintf (msg->fp, "Lines: %d\n\n", lines);
2462 /* copy the body and clean up */
2464 r = mutt_copy_stream (tempfp, msg->fp);
2465 if (fclose (tempfp) != 0)
2467 /* if there was an error, leave the temp version */
2472 fputc ('\n', msg->fp); /* finish off the header */
2473 r = mutt_write_mime_body (hdr->content, msg->fp);
2476 if (mx_commit_message (msg, &f) != 0)
2478 mx_close_message (&msg);
2479 mx_close_mailbox (&f, NULL);
2482 set_noconv_flags (hdr->content, 0);