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.
10 #include <lib-lib/lib-lib.h>
13 #include <sys/utsname.h>
15 #include <lib-sys/exit.h>
16 #include <lib-sys/mutt_signal.h>
17 #include <lib-mime/mime.h>
18 #include <lib-ui/curses.h>
19 #include <lib-mx/mx.h>
21 #include <lib-crypt/crypt.h>
25 #include "recvattach.h"
29 #include "mutt_idna.h"
32 # include "mutt_libesmtp.h"
33 #endif /* USE_LIBESMTP */
36 #include <nntp/nntp.h>
39 #ifdef HAVE_SYSEXITS_H
41 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
45 static void transform_to_7bit (BODY * a, FILE * fpin);
47 static void encode_quoted (fgetconv_t * fc, FILE * fout, int istext)
50 char line[77], savechar;
52 while ((c = fgetconv (fc)) != EOF) {
53 /* Wrap the line if needed. */
54 if (linelen == 76 && ((istext && c != '\n') || !istext)) {
55 /* If the last character is "quoted", then be sure to move all three
56 * characters to the next line. Otherwise, just move the last
59 if (line[linelen - 3] == '=') {
60 line[linelen - 3] = 0;
65 line[1] = line[linelen - 2];
66 line[2] = line[linelen - 1];
70 savechar = line[linelen - 1];
71 line[linelen - 1] = '=';
80 /* Escape lines that begin with/only contain "the message separator". */
81 if (linelen == 4 && !m_strncmp("From", line, 4)) {
82 m_strcpy(line, sizeof(line), "=46rom");
85 else if (linelen == 4 && !m_strncmp("from", line, 4)) {
86 m_strcpy(line, sizeof(line), "=66rom");
89 else if (linelen == 1 && line[0] == '.') {
90 m_strcpy(line, sizeof(line), "=2E");
95 if (c == '\n' && istext) {
96 /* Check to make sure there is no trailing space on this line. */
98 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
100 sprintf (line + linelen - 1, "=%2.2X",
101 (unsigned char) line[linelen - 1]);
105 savechar = line[linelen - 1];
107 line[linelen - 1] = '=';
110 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
120 else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
121 /* Check to make sure there is enough room for the quoted character.
122 * If not, wrap to the next line.
125 line[linelen++] = '=';
131 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
135 /* Don't worry about wrapping the line here. That will happen during
136 * the next iteration when I'll also know what the next character is.
142 /* Take care of anything left in the buffer */
144 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
145 /* take care of trailing whitespace */
147 sprintf (line + linelen - 1, "=%2.2X",
148 (unsigned char) line[linelen - 1]);
150 savechar = line[linelen - 1];
151 line[linelen - 1] = '=';
155 sprintf (line, "=%2.2X", (unsigned char) savechar);
164 static char b64_buffer[3];
165 static short b64_num;
166 static short b64_linelen;
168 static void b64_flush (FILE * fout)
175 if (b64_linelen >= 72) {
180 for (i = b64_num; i < 3; i++)
181 b64_buffer[i] = '\0';
183 fputc(__m_b64chars[(b64_buffer[0] >> 2) & 0x3f], fout);
186 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
191 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
195 fputc (__m_b64chars[b64_buffer[2] & 0x3f], fout);
200 while (b64_linelen % 4) {
209 static void b64_putc (char c, FILE * fout)
214 b64_buffer[b64_num++] = c;
218 static void encode_base64 (fgetconv_t * fc, FILE * fout, int istext)
222 b64_num = b64_linelen = 0;
224 while ((ch = fgetconv (fc)) != EOF) {
225 if (istext && ch == '\n' && ch1 != '\r')
226 b64_putc ('\r', fout);
234 static void encode_8bit (fgetconv_t * fc, FILE * fout, int istext)
238 while ((ch = fgetconv (fc)) != EOF)
243 int mutt_write_mime_header (BODY * a, FILE * f)
252 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
257 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
259 for (p = a->parameter; p; p = p->next) {
268 tmp = m_strdup(p->value);
269 encode = rfc2231_encode_string (&tmp);
270 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
272 /* Dirty hack to make messages readable by Outlook Express
273 * for the Mac: force quotes around the boundary parameter
274 * even when they aren't needed.
277 if (!ascii_strcasecmp (p->attribute, "boundary")
278 && !strcmp (buffer, tmp))
279 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
283 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
285 if (len + tmplen + 2 > 76) {
294 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
302 fprintf (f, "Content-Description: %s\n", a->description);
304 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
305 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
308 if (!(fn = a->d_filename))
314 /* Strip off the leading path... */
315 if ((t = strrchr (fn, '/')))
322 encode = rfc2231_encode_string (&tmp);
323 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
325 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
331 if (a->encoding != ENC7BIT)
332 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
334 /* Do NOT add the terminator here!!! */
335 return (ferror (f) ? -1 : 0);
338 int mutt_write_mime_body (BODY * a, FILE * f)
341 char boundary[SHORT_STRING];
342 char send_charset[SHORT_STRING];
347 if (a->type == TYPEMULTIPART) {
348 /* First, find the boundary to use */
349 if (!(p = parameter_getval(a->parameter, "boundary"))) {
350 mutt_error _("No boundary parameter found! [report this error]");
354 m_strcpy(boundary, sizeof(boundary), p);
356 for (t = a->parts; t; t = t->next) {
357 fprintf (f, "\n--%s\n", boundary);
358 if (mutt_write_mime_header (t, f) == -1)
361 if (mutt_write_mime_body (t, f) == -1)
364 fprintf (f, "\n--%s--\n", boundary);
365 return (ferror (f) ? -1 : 0);
368 /* This is pretty gross, but it's the best solution for now... */
369 if (a->type == TYPEAPPLICATION && !m_strcmp(a->subtype, "pgp-encrypted")) {
370 fputs ("Version: 1\n", f);
374 if ((fpin = fopen (a->filename, "r")) == NULL) {
375 mutt_error (_("%s no longer exists!"), a->filename);
379 if (a->type == TYPETEXT && (!a->noconv))
380 fc = fgetconv_open (fpin, a->file_charset,
381 mutt_get_body_charset (send_charset,
382 sizeof (send_charset), a), 0);
384 fc = fgetconv_open (fpin, 0, 0, 0);
386 #define write_as_text_part(a) (mutt_is_text_part(a) || mutt_is_application_pgp(a))
387 if (a->encoding == ENCQUOTEDPRINTABLE)
388 encode_quoted (fc, f, write_as_text_part (a));
389 else if (a->encoding == ENCBASE64)
390 encode_base64 (fc, f, write_as_text_part (a));
391 else if (a->type == TYPETEXT && (!a->noconv))
392 encode_8bit (fc, f, write_as_text_part (a));
394 mutt_copy_stream (fpin, f);
395 #undef write_as_text_part
397 fgetconv_close (&fc);
400 return (ferror (f) ? -1 : 0);
412 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
416 int whitespace = s->whitespace;
418 int linelen = s->linelen;
419 int was_cr = s->was_cr;
421 if (!d) { /* This signals EOF */
424 if (linelen > info->linemax)
425 info->linemax = linelen;
430 for (; dlen; d++, dlen--) {
443 if (linelen > info->linemax)
444 info->linemax = linelen;
459 if (linelen > info->linemax)
460 info->linemax = linelen;
465 else if (ch == '\r') {
473 else if (ch == '\t' || ch == '\f') {
477 else if (ch < 32 || ch == 127)
481 if ((ch == 'F') || (ch == 'f'))
491 if (linelen == 2 && ch != 'r')
493 else if (linelen == 3 && ch != 'o')
495 else if (linelen == 4) {
508 if (ch != ' ' && ch != '\t')
513 s->whitespace = whitespace;
515 s->linelen = linelen;
521 * Find the best charset conversion of the file from fromcode into one
522 * of the tocodes. If successful, set *tocode and CONTENT *info and
523 * return the number of characters converted inexactly. If no
524 * conversion was possible, return -1.
526 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
527 * which would otherwise prevent us from knowing the number of inexact
528 * conversions. Where the candidate target charset is UTF-8 we avoid
529 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
530 * fails with some libraries.
532 * We assume that the output from iconv is never more than 4 times as
533 * long as the input for any pair of charsets we might be interested
536 static ssize_t convert_file_to (FILE * file, const char *fromcode,
537 int ncodes, const char **tocodes,
538 int *tocode, CONTENT * info)
542 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
545 ssize_t ibl, obl, ubl, ubl1, n, ret;
548 CONTENT_STATE *states;
551 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
552 if (cd1 == MUTT_ICONV_ERROR)
555 cd = p_new(iconv_t, ncodes);
556 score = p_new(ssize_t, ncodes);
557 states = p_new(CONTENT_STATE, ncodes);
558 infos = p_new(CONTENT, ncodes);
560 for (i = 0; i < ncodes; i++)
561 if (ascii_strcasecmp (tocodes[i], "UTF-8"))
562 cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
564 /* Special case for conversion to UTF-8 */
565 cd[i] = MUTT_ICONV_ERROR, score[i] = -1;
571 /* Try to fill input buffer */
572 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
575 /* Convert to UTF-8 */
577 ob = bufu, obl = sizeof (bufu);
578 n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
579 if (n == -1 && ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
585 /* Convert from UTF-8 */
586 for (i = 0; i < ncodes; i++)
587 if (cd[i] != MUTT_ICONV_ERROR && score[i] != -1) {
588 ub = bufu, ubl = ubl1;
589 ob = bufo, obl = sizeof (bufo);
590 n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
596 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
599 else if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1)
600 /* Special case for conversion to UTF-8 */
601 update_content_info (&infos[i], &states[i], bufu, ubl1);
604 /* Save unused input */
605 memmove (bufi, ib, ibl);
606 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
613 /* Find best score */
615 for (i = 0; i < ncodes; i++) {
616 if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1) {
617 /* Special case for conversion to UTF-8 */
622 else if (cd[i] == MUTT_ICONV_ERROR || score[i] == -1)
624 else if (ret == -1 || score[i] < ret) {
632 memcpy (info, &infos[*tocode], sizeof (CONTENT));
633 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
637 for (i = 0; i < ncodes; i++)
638 if (cd[i] != MUTT_ICONV_ERROR)
650 #endif /* !HAVE_ICONV */
654 * Find the first of the fromcodes that gives a valid conversion and
655 * the best charset conversion of the file into one of the tocodes. If
656 * successful, set *fromcode and *tocode to dynamically allocated
657 * strings, set CONTENT *info, and return the number of characters
658 * converted inexactly. If no conversion was possible, return -1.
660 * Both fromcodes and tocodes may be colon-separated lists of charsets.
661 * However, if fromcode is zero then fromcodes is assumed to be the
662 * name of a single charset even if it contains a colon.
664 static ssize_t convert_file_from_to (FILE * file,
665 const char *fromcodes,
666 const char *tocodes, char **fromcode,
667 char **tocode, CONTENT * info)
675 /* Count the tocodes */
677 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
678 if ((c1 = strchr (c, ':')) == c)
684 tcode = p_new(char *, ncodes);
685 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
686 if ((c1 = strchr (c, ':')) == c)
688 tcode[i] = m_substrdup(c, c1);
693 /* Try each fromcode in turn */
694 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
695 if ((c1 = strchr (c, ':')) == c)
697 fcode = m_substrdup(c, c1);
699 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
711 /* There is only one fromcode */
712 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
721 for (i = 0; i < ncodes; i++)
730 * Analyze the contents of a file to determine which MIME encoding to use.
731 * Also set the body charset, sometimes, or not.
733 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
738 char *fromcode = NULL;
749 if (stat (fname, &sb) == -1) {
750 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
754 if (!S_ISREG (sb.st_mode)) {
755 mutt_error (_("%s isn't a regular file."), fname);
759 if ((fp = fopen (fname, "r")) == NULL) {
763 info = p_new(CONTENT, 1);
766 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
767 const char *chs = parameter_getval(b->parameter, "charset");
768 char *fchs = b->use_disp ? ((FileCharset && *FileCharset) ?
769 FileCharset : Charset) : Charset;
770 if (Charset && (chs || SendCharset) &&
771 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
772 &fromcode, &tocode, info) != -1) {
774 charset_canonicalize (chsbuf, sizeof (chsbuf), tocode);
775 parameter_setval(&b->parameter, "charset", chsbuf);
777 b->file_charset = fromcode;
785 while ((r = fread (buffer, 1, sizeof (buffer), fp)))
786 update_content_info (info, &state, buffer, r);
787 update_content_info (info, &state, 0, 0);
791 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
792 parameter_setval(&b->parameter, "charset",
793 (!info->hibin ? "us-ascii"
794 : Charset && !charset_is_us_ascii(Charset) ? Charset : "unknown-8bit"));
799 /* Given a file with path ``s'', see if there is a registered MIME type.
800 * returns the major MIME type, and copies the subtype to ``d''. First look
801 * for ~/.mime.types, then look in a system mime.types if we can find one.
802 * The longest match is used so that we can match `ps.gz' when `gz' also
806 int mutt_lookup_mime_type (BODY * att, const char *path)
810 char buf[LONG_STRING];
811 char subtype[STRING], xtype[STRING];
813 int szf, sze, cur_sze;
821 szf = m_strlen(path);
823 for (count = 0; count < 4; count++) {
825 * can't use strtok() because we use it in an inner loop below, so use
826 * a switch statement here instead.
830 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
833 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
836 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
839 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
842 goto bye; /* shouldn't happen */
845 if ((f = fopen (buf, "r")) != NULL) {
846 while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
847 /* weed out any comments */
848 if ((p = strchr (buf, '#')))
851 /* remove any leading space. */
852 ct = vskipspaces(buf);
854 /* position on the next field in this line */
855 if ((p = strpbrk (ct, " \t")) == NULL)
860 /* cycle through the file extensions */
861 while ((p = strtok (p, " \t\n"))) {
863 if ((sze > cur_sze) && (szf >= sze) &&
864 (m_strcasecmp(path + szf - sze, p) == 0
865 || ascii_strcasecmp (path + szf - sze, p) == 0)
866 && (szf == sze || path[szf - sze - 1] == '.'))
868 /* get the content-type */
870 if ((p = strchr (ct, '/')) == NULL) {
871 /* malformed line, just skip it. */
876 for (q = p; *q && !ISSPACE (*q); q++);
878 m_strncpy(subtype, sizeof(subtype), p, q - p);
880 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
881 m_strcpy(xtype, sizeof(xtype), ct);
894 if (type != TYPEOTHER || *xtype != '\0') {
896 m_strreplace(&att->subtype, subtype);
897 m_strreplace(&att->xtype, xtype);
903 void mutt_message_to_7bit (BODY * a, FILE * fp)
905 char temp[_POSIX_PATH_MAX];
911 if (!a->filename && fp)
913 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
914 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
919 if (stat (a->filename, &sb) == -1) {
920 mutt_perror ("stat");
923 a->length = sb.st_size;
927 if (!(fpout = safe_fopen (temp, "w+"))) {
928 mutt_perror ("fopen");
932 fseeko (fpin, a->offset, 0);
933 a->parts = mutt_parse_messageRFC822 (fpin, a);
935 transform_to_7bit (a->parts, fpin);
937 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
938 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
940 fputs ("MIME-Version: 1.0\n", fpout);
941 mutt_write_mime_header (a->parts, fpout);
943 mutt_write_mime_body (a->parts, fpout);
955 a->encoding = ENC7BIT;
956 a->d_filename = a->filename;
957 if (a->filename && a->unlink)
958 unlink (a->filename);
959 a->filename = m_strdup(temp);
961 if (stat (a->filename, &sb) == -1) {
962 mutt_perror ("stat");
965 a->length = sb.st_size;
966 body_list_wipe(&a->parts);
967 a->hdr->content = NULL;
970 static void transform_to_7bit (BODY * a, FILE * fpin)
972 char buff[_POSIX_PATH_MAX];
977 for (; a; a = a->next) {
978 if (a->type == TYPEMULTIPART) {
979 if (a->encoding != ENC7BIT)
980 a->encoding = ENC7BIT;
982 transform_to_7bit (a->parts, fpin);
984 else if (mutt_is_message_type (a->type, a->subtype)) {
985 mutt_message_to_7bit (a, fpin);
989 a->force_charset = 1;
992 if ((s.fpout = safe_fopen (buff, "w")) == NULL) {
993 mutt_perror ("fopen");
997 mutt_decode_attachment (a, &s);
999 a->d_filename = a->filename;
1000 a->filename = m_strdup(buff);
1002 if (stat (a->filename, &sb) == -1) {
1003 mutt_perror ("stat");
1006 a->length = sb.st_size;
1008 mutt_update_encoding (a);
1009 if (a->encoding == ENC8BIT)
1010 a->encoding = ENCQUOTEDPRINTABLE;
1011 else if (a->encoding == ENCBINARY)
1012 a->encoding = ENCBASE64;
1017 /* determine which Content-Transfer-Encoding to use */
1018 static void mutt_set_encoding (BODY * b, CONTENT * info)
1020 char send_charset[SHORT_STRING];
1022 if (b->type == TYPETEXT) {
1024 mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1025 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1026 || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1027 b->encoding = ENCQUOTEDPRINTABLE;
1028 else if (info->hibin)
1029 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1031 b->encoding = ENC7BIT;
1033 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1034 if (info->lobin || info->hibin) {
1035 if (option (OPTALLOW8BIT) && !info->lobin)
1036 b->encoding = ENC8BIT;
1038 mutt_message_to_7bit (b, NULL);
1041 b->encoding = ENC7BIT;
1043 else if (b->type == TYPEAPPLICATION
1044 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1045 b->encoding = ENC7BIT;
1048 /* Determine which encoding is smaller */
1049 if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1050 3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1051 b->encoding = ENCBASE64;
1053 b->encoding = ENCQUOTEDPRINTABLE;
1057 void mutt_stamp_attachment (BODY * a)
1059 a->stamp = time (NULL);
1062 /* Get a body's character set */
1064 char *mutt_get_body_charset(char *d, ssize_t dlen, BODY * b)
1068 if (b && b->type != TYPETEXT)
1071 p = b ? parameter_getval(b->parameter, "charset") : NULL;
1072 charset_canonicalize(d, dlen, p);
1077 /* Assumes called from send mode where BODY->filename points to actual file */
1078 void mutt_update_encoding (BODY * a)
1081 char chsbuff[STRING];
1083 /* override noconv when it's us-ascii */
1084 if (charset_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1087 if (!a->force_charset && !a->noconv)
1088 parameter_delval(&a->parameter, "charset");
1090 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1093 mutt_set_encoding (a, info);
1094 mutt_stamp_attachment (a);
1096 p_delete(&a->content);
1101 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1103 char buffer[LONG_STRING];
1106 int cmflags, chflags;
1107 int pgp = hdr->security;
1109 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1110 (hdr->security & ENCRYPT)) {
1111 if (!crypt_valid_passphrase (hdr->security))
1115 mutt_mktemp (buffer);
1116 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1120 body->type = TYPEMESSAGE;
1121 body->subtype = m_strdup("rfc822");
1122 body->filename = m_strdup(buffer);
1125 body->disposition = DISPINLINE;
1128 mutt_parse_mime_message (ctx, hdr);
1133 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1134 if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1135 chflags |= CH_MIME | CH_TXTPLAIN;
1136 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1137 pgp &= ~(PGPENCRYPT|SMIMEENCRYPT);
1139 else if (option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1140 if (mutt_is_multipart_encrypted (hdr->content)) {
1141 chflags |= CH_MIME | CH_NONEWLINE;
1142 cmflags = M_CM_DECODE_PGP;
1145 else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1146 chflags |= CH_MIME | CH_TXTPLAIN;
1147 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1150 else if (mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1151 chflags |= CH_MIME | CH_TXTPLAIN;
1152 cmflags = M_CM_DECODE | M_CM_CHARCONV;
1153 pgp &= ~SMIMEENCRYPT;
1157 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1162 body->hdr = header_new();
1163 body->hdr->offset = 0;
1164 /* we don't need the user headers here */
1165 body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1166 body->hdr->security = pgp;
1167 mutt_update_encoding (body);
1168 body->parts = body->hdr->content;
1175 BODY *mutt_make_file_attach (const char *path)
1181 att->filename = m_strdup(path);
1183 /* Attempt to determine the appropriate content-type based on the filename
1186 mutt_lookup_mime_type (att, path);
1188 if ((info = mutt_get_content_info (path, att)) == NULL) {
1189 body_list_wipe(&att);
1193 if (!att->subtype) {
1194 if (info->lobin == 0
1195 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1197 * Statistically speaking, there should be more than 10% "lobin"
1198 * chars if this is really a binary file...
1200 att->type = TYPETEXT;
1201 att->subtype = m_strdup("plain");
1203 att->type = TYPEAPPLICATION;
1204 att->subtype = m_strdup("octet-stream");
1208 mutt_update_encoding (att);
1212 static int get_toplevel_encoding (BODY * a)
1216 for (; a; a = a->next) {
1217 if (a->encoding == ENCBINARY)
1219 else if (a->encoding == ENC8BIT)
1226 BODY *mutt_make_multipart (BODY * b)
1231 new->type = TYPEMULTIPART;
1232 new->subtype = m_strdup("mixed");
1233 new->encoding = get_toplevel_encoding (b);
1234 parameter_set_boundary(&new->parameter);
1236 new->disposition = DISPINLINE;
1242 /* remove the multipart body if it exists */
1243 BODY *mutt_remove_multipart (BODY * b)
1256 char *mutt_make_date (char *s, ssize_t len)
1258 time_t t = time (NULL);
1259 struct tm *l = localtime (&t);
1260 time_t tz = mutt_local_tz (t);
1264 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1265 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1266 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1267 (int) tz / 60, (int) abs (tz) % 60);
1271 /* wrapper around mutt_write_address() so we can handle very large
1272 recipient lists without needing a huge temporary buffer in memory */
1273 void mutt_write_address_list (address_t * adr, FILE * fp, int linelen,
1277 char buf[LONG_STRING];
1285 rfc822_write_address (buf, sizeof (buf), adr, display);
1286 len = m_strlen(buf);
1287 if (count && linelen + len > 74) {
1289 linelen = len + 8; /* tab is usually about 8 spaces... */
1292 if (count && adr->mailbox) {
1300 if (!adr->group && adr->next && adr->next->mailbox) {
1310 /* arbitrary number of elements to grow the array by */
1315 /* need to write the list in reverse because they are stored in reverse order
1316 * when parsed to speed up threading
1318 void mutt_write_references (string_list_t * r, FILE * f)
1320 string_list_t **ref = NULL;
1321 int refcnt = 0, refmax = 0;
1323 for (; (TrimRef == 0 || refcnt < TrimRef) && r; r = r->next) {
1324 if (refcnt == refmax)
1325 p_realloc(&ref, refmax += REF_INC);
1329 while (refcnt-- > 0) {
1331 fputs (ref[refcnt]->data, f);
1337 static int edit_header(int mode, const char *s)
1340 int slen = m_strlen(s);
1342 if (mode != 1 || option(OPTXMAILTO))
1345 p = skipspaces(EditorHeaders);
1347 if (!ascii_strncasecmp(p, s, slen) && p[slen - 1] == ':')
1349 p = skipspaces(p + slen);
1355 /* Note: all RFC2047 encoding should be done outside of this routine, except
1356 * for the "real name." This will allow this routine to be used more than
1357 * once, if necessary.
1359 * Likewise, all IDN processing should happen outside of this routine.
1361 * mode == 1 => "lite" mode (used for edit_hdrs)
1362 * mode == 0 => normal mode. write full header + MIME headers
1363 * mode == -1 => write just the envelope info (used for postponing messages)
1365 * privacy != 0 => will omit any headers which may identify the user.
1366 * Output generated is suitable for being sent through
1367 * anonymous remailer chains.
1371 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1372 int mode, int privacy)
1374 char buffer[LONG_STRING];
1376 string_list_t *tmp = env->userhdrs;
1377 int has_agent = 0; /* user defined user-agent header field exists */
1380 if (!option (OPTNEWSSEND))
1382 if (mode == 0 && !privacy)
1383 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1385 /* OPTUSEFROM is not consulted here so that we can still write a From:
1386 * field if the user sets it with the `my_hdr' command
1388 if (env->from && !privacy) {
1390 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1391 fprintf (fp, "From: %s\n", buffer);
1396 mutt_write_address_list (env->to, fp, 4, 0);
1400 if (!option (OPTNEWSSEND))
1402 if (edit_header(mode, "To:"))
1403 fputs ("To:\n", fp);
1407 mutt_write_address_list (env->cc, fp, 4, 0);
1411 if (!option (OPTNEWSSEND))
1413 if (edit_header(mode, "Cc:"))
1414 fputs ("Cc:\n", fp);
1417 if (mode != 0 || option (OPTWRITEBCC)) {
1418 fputs ("Bcc: ", fp);
1419 mutt_write_address_list (env->bcc, fp, 5, 0);
1424 if (!option (OPTNEWSSEND))
1426 if (edit_header(mode, "Bcc:"))
1427 fputs ("Bcc:\n", fp);
1430 if (env->newsgroups)
1431 fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1432 else if (mode == 1 && option (OPTNEWSSEND) && edit_header(mode, "Newsgroups:"))
1433 fputs ("Newsgroups:\n", fp);
1435 if (env->followup_to)
1436 fprintf (fp, "Followup-To: %s\n", env->followup_to);
1437 else if (mode == 1 && option (OPTNEWSSEND) && edit_header(mode, "Followup-To:"))
1438 fputs ("Followup-To:\n", fp);
1440 if (env->x_comment_to)
1441 fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1442 else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1443 edit_header(mode, "X-Comment-To:"))
1444 fputs ("X-Comment-To:\n", fp);
1448 fprintf (fp, "Subject: %s\n", env->subject);
1449 else if (mode == 1 && edit_header(mode, "Subject:"))
1450 fputs ("Subject:\n", fp);
1452 /* save message id if the user has set it */
1453 if (env->message_id && !privacy)
1454 fprintf (fp, "Message-ID: %s\n", env->message_id);
1456 if (env->reply_to) {
1457 fputs ("Reply-To: ", fp);
1458 mutt_write_address_list (env->reply_to, fp, 10, 0);
1460 else if (mode > 0 && edit_header(mode, "Reply-To:"))
1461 fputs ("Reply-To:\n", fp);
1463 if (env->mail_followup_to)
1465 if (!option (OPTNEWSSEND))
1468 fputs ("Mail-Followup-To: ", fp);
1469 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1473 if (env->references) {
1474 fputs ("References:", fp);
1475 mutt_write_references (env->references, fp);
1479 /* Add the MIME headers */
1480 fputs ("MIME-Version: 1.0\n", fp);
1481 mutt_write_mime_header (attach, fp);
1484 if (env->in_reply_to) {
1485 fputs ("In-Reply-To:", fp);
1486 mutt_write_references (env->in_reply_to, fp);
1490 /* Add any user defined headers */
1491 for (; tmp; tmp = tmp->next) {
1492 if ((p = strchr (tmp->data, ':'))) {
1493 p = vskipspaces(p + 1);
1495 continue; /* don't emit empty fields. */
1497 /* check to see if the user has overridden the user-agent field */
1498 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1504 fputs (tmp->data, fp);
1509 if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1512 if (OperatingSystem != NULL) {
1513 os = OperatingSystem;
1516 os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1518 /* Add a vanity header */
1519 fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1522 return (ferror (fp) == 0 ? 0 : -1);
1525 static void encode_headers (string_list_t * h)
1531 for (; h; h = h->next) {
1532 if (!(p = strchr (h->data, ':')))
1536 p = vskipspaces(p + 1);
1542 rfc2047_encode_string (&tmp);
1543 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1545 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
1551 const char *mutt_fqdn (short may_hide_host)
1555 if (Fqdn && Fqdn[0] != '@') {
1558 if (may_hide_host && option (OPTHIDDENHOST)) {
1559 if ((p = strchr (Fqdn, '.')))
1562 /* sanity check: don't hide the host if
1563 * the fqdn is something like detebe.org.
1566 if (!p || !(q = strchr (p, '.')))
1574 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1575 static char mutt_normalized_char(char c)
1577 return (isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.';
1580 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1582 #define APPEND_FMT(fmt, arg) \
1584 int snlen = snprintf(buf, len, fmt, arg); \
1589 #define APPEND_BYTE(c) \
1603 static char MsgIdPfx = 'A';
1607 APPEND_BYTE(mutt_normalized_char(c));
1615 APPEND_FMT("%02d", tm->tm_mday);
1618 APPEND_FMT("%02d", tm->tm_hour);
1621 APPEND_FMT("%02d", tm->tm_mon + 1);
1624 APPEND_FMT("%02d", tm->tm_min);
1627 APPEND_FMT("%lo", (unsigned long)now);
1630 APPEND_FMT("%u", (unsigned int)getpid());
1633 APPEND_FMT("%c", MsgIdPfx);
1634 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1637 APPEND_FMT("%u", (unsigned int)rand());
1640 APPEND_FMT("%x", (unsigned int)rand());
1643 APPEND_FMT("%02d", tm->tm_sec);
1646 APPEND_FMT("%u", (unsigned int) now);
1649 APPEND_FMT("%x", (unsigned int) now);
1651 case 'Y': /* this will break in the year 10000 ;-) */
1652 APPEND_FMT("%04d", tm->tm_year + 1900);
1657 default: /* invalid formats are replaced by '.' */
1659 m_strncat(buf, len, ".", 1);
1669 static char *mutt_gen_msgid (void)
1671 char buf[SHORT_STRING];
1672 char localpart[SHORT_STRING];
1675 if (!(fqdn = mutt_fqdn(0)))
1676 fqdn = NONULL(Hostname);
1678 mutt_gen_localpart(localpart, sizeof(localpart), MsgIdFormat);
1679 snprintf(buf, sizeof(buf), "<%s@%s>", localpart, fqdn);
1680 return m_strdup(buf);
1683 static RETSIGTYPE alarm_handler (int sig)
1688 /* invoke sendmail in a subshell
1689 path (in) path to program to execute
1690 args (in) arguments to pass to program
1691 msg (in) temp file containing message to send
1692 tempfile (out) if sendmail is put in the background, this points
1693 to the temporary file containing the stdout of the
1696 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1702 mutt_block_signals_system ();
1705 /* we also don't want to be stopped right now */
1706 sigaddset (&set, SIGTSTP);
1707 sigprocmask (SIG_BLOCK, &set, NULL);
1709 if (SendmailWait >= 0) {
1710 char tmp[_POSIX_PATH_MAX];
1713 *tempfile = m_strdup(tmp);
1716 if ((pid = fork ()) == 0) {
1717 struct sigaction act, oldalrm;
1719 /* save parent's ID before setsid() */
1722 /* we want the delivery to continue even after the main process dies,
1723 * so we put ourselves into another session right away
1727 /* next we close all open files */
1728 for (fd = 0; fd < getdtablesize(); fd++)
1731 /* now the second fork() */
1732 if ((pid = fork ()) == 0) {
1733 /* "msg" will be opened as stdin */
1734 if (open (msg, O_RDONLY, 0) < 0) {
1740 if (SendmailWait >= 0) {
1741 /* *tempfile will be opened as stdout */
1742 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1745 /* redirect stderr to *tempfile too */
1750 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
1752 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
1756 execv (path, (char**)args);
1759 else if (pid == -1) {
1765 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1766 * SendmailWait = 0: wait forever
1767 * SendmailWait < 0: don't wait
1769 if (SendmailWait > 0) {
1771 act.sa_handler = alarm_handler;
1773 /* need to make sure waitpid() is interrupted on SIGALRM */
1774 act.sa_flags = SA_INTERRUPT;
1778 sigemptyset (&act.sa_mask);
1779 sigaction (SIGALRM, &act, &oldalrm);
1780 alarm (SendmailWait);
1782 else if (SendmailWait < 0)
1783 _exit (0xff & EX_OK);
1785 if (waitpid (pid, &st, 0) > 0) {
1786 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1787 if (SendmailWait && st == (0xff & EX_OK)) {
1788 unlink (*tempfile); /* no longer needed */
1793 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1794 if (SendmailWait > 0) {
1800 /* reset alarm; not really needed, but... */
1802 sigaction (SIGALRM, &oldalrm, NULL);
1804 if (kill (ppid, 0) == -1 && errno == ESRCH) {
1805 /* the parent is already dead */
1813 sigprocmask (SIG_UNBLOCK, &set, NULL);
1815 if (pid != -1 && waitpid (pid, &st, 0) > 0)
1816 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
1818 st = S_ERR; /* error */
1820 mutt_unblock_signals_system (1);
1825 static const char **
1826 add_args(const char **args, ssize_t *argslen, ssize_t *argsmax, address_t * addr)
1828 for (; addr; addr = addr->next) {
1829 /* weed out group mailboxes, since those are for display only */
1830 if (addr->mailbox && !addr->group) {
1831 if (*argslen == *argsmax)
1832 p_realloc(&args, *argsmax += 5);
1833 args[(*argslen)++] = addr->mailbox;
1839 static const char **
1840 add_option(const char **args, ssize_t *argslen, ssize_t *argsmax, const char *s)
1842 if (*argslen == *argsmax) {
1843 p_realloc(&args, *argsmax += 5);
1845 args[(*argslen)++] = s;
1849 static int mutt_invoke_sendmail (address_t * from, /* the sender */
1850 address_t * to, address_t * cc, address_t * bcc, /* recips */
1851 const char *msg, /* file containing message */
1853 { /* message contains 8bit chars */
1854 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1855 const char **args = NULL;
1856 ssize_t argslen = 0, argsmax = 0;
1860 if (option (OPTNEWSSEND)) {
1861 char cmd[LONG_STRING];
1863 mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1866 i = nntp_post (msg);
1875 s = m_strdup(Sendmail);
1879 while ((ps = strtok (ps, " "))) {
1880 if (argslen == argsmax)
1881 p_realloc(&args, argsmax += 5);
1884 args[argslen++] = ps;
1886 path = m_strdup(ps);
1887 ps = strrchr (ps, '/');
1892 args[argslen++] = ps;
1899 if (!option (OPTNEWSSEND)) {
1901 if (eightbit && option (OPTUSE8BITMIME))
1902 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
1904 if (option (OPTENVFROM)) {
1905 address_t *f = NULL;
1908 else if (from && !from->next)
1911 args = add_option (args, &argslen, &argsmax, "-f");
1912 args = add_args (args, &argslen, &argsmax, f);
1916 args = add_option (args, &argslen, &argsmax, "-N");
1917 args = add_option (args, &argslen, &argsmax, DsnNotify);
1920 args = add_option (args, &argslen, &argsmax, "-R");
1921 args = add_option (args, &argslen, &argsmax, DsnReturn);
1923 args = add_option (args, &argslen, &argsmax, "--");
1924 args = add_args (args, &argslen, &argsmax, to);
1925 args = add_args (args, &argslen, &argsmax, cc);
1926 args = add_args (args, &argslen, &argsmax, bcc);
1931 if (argslen == argsmax)
1932 p_realloc(&args, ++argsmax);
1934 args[argslen++] = NULL;
1936 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
1938 mutt_error (_("Error sending message, child exited %d (%s)."), i,
1943 if (stat (childout, &st) == 0 && st.st_size > 0)
1944 mutt_do_pager (_("Output of the delivery process"), childout, 0,
1952 p_delete(&childout);
1957 if (i == (EX_OK & 0xff))
1959 else if (i == S_BKG)
1966 int mutt_invoke_mta (address_t * from, /* the sender */
1967 address_t * to, address_t * cc, address_t * bcc, /* recips */
1968 const char *msg, /* file containing message */
1970 { /* message contains 8bit chars */
1973 if (!option (OPTNEWSSEND))
1976 return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
1979 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
1982 /* For postponing (!final) do the necessary encodings only */
1983 void mutt_prepare_envelope (ENVELOPE * env, int final)
1985 char buffer[LONG_STRING];
1988 if (env->bcc && !(env->to || env->cc)) {
1989 /* some MTA's will put an Apparently-To: header field showing the Bcc:
1990 * recipients if there is no To: or Cc: field, so attempt to suppress
1991 * it by using an empty To: field.
1993 env->to = address_new ();
1995 env->to->next = address_new ();
1998 rfc822_strcpy(buffer, sizeof(buffer), "undisclosed-recipients",
2001 env->to->mailbox = m_strdup(buffer);
2004 mutt_set_followup_to (env);
2006 if (!env->message_id && MsgIdFormat && *MsgIdFormat)
2007 env->message_id = mutt_gen_msgid ();
2010 /* Take care of 8-bit => 7-bit conversion. */
2011 rfc2047_encode_adrlist (env->to, "To");
2012 rfc2047_encode_adrlist (env->cc, "Cc");
2013 rfc2047_encode_adrlist (env->bcc, "Bcc");
2014 rfc2047_encode_adrlist (env->from, "From");
2015 rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2016 rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2020 if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2023 rfc2047_encode_string (&env->subject);
2025 encode_headers (env->userhdrs);
2028 void mutt_unprepare_envelope (ENVELOPE * env)
2030 string_list_t *item;
2032 for (item = env->userhdrs; item; item = item->next)
2033 rfc2047_decode (&item->data);
2035 address_list_wipe(&env->mail_followup_to);
2037 /* back conversions */
2038 rfc2047_decode_adrlist (env->to);
2039 rfc2047_decode_adrlist (env->cc);
2040 rfc2047_decode_adrlist (env->bcc);
2041 rfc2047_decode_adrlist (env->from);
2042 rfc2047_decode_adrlist (env->reply_to);
2043 rfc2047_decode (&env->subject);
2046 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2047 const char *resent_from, address_t * env_from)
2051 char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2052 MESSAGE *msg = NULL;
2055 /* Try to bounce each message out, aborting if we get any failures. */
2056 for (i = 0; i < Context->msgcount; i++)
2057 if (Context->hdrs[i]->tagged)
2059 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2064 /* If we failed to open a message, return with error */
2065 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2071 mutt_mktemp (tempfile);
2072 if ((f = safe_fopen (tempfile, "w")) != NULL) {
2073 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2075 if (!option (OPTBOUNCEDELIVERED))
2076 ch_flags |= CH_WEED_DELIVERED;
2078 fseeko (fp, h->offset, 0);
2079 fprintf (f, "Resent-From: %s", resent_from);
2080 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2081 if (MsgIdFormat && *MsgIdFormat)
2082 fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2083 fputs ("Resent-To: ", f);
2084 mutt_write_address_list (to, f, 11, 0);
2085 mutt_copy_header (fp, h, f, ch_flags, NULL);
2087 mutt_copy_bytes (fp, f, h->content->length);
2090 ret = mutt_invoke_mta (env_from, to, NULL, NULL, tempfile,
2091 h->content->encoding == ENC8BIT);
2095 mx_close_message (&msg);
2100 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2103 const char *fqdn = mutt_fqdn (1);
2104 char resent_from[STRING];
2108 resent_from[0] = '\0';
2109 from = mutt_default_from ();
2112 rfc822_qualify (from, fqdn);
2114 rfc2047_encode_adrlist (from, "Resent-From");
2115 if (mutt_addrlist_to_idna (from, &err)) {
2116 mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2119 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2122 unset_option (OPTNEWSSEND);
2125 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2127 address_list_wipe(&from);
2132 static void set_noconv_flags (BODY * b, short flag)
2134 for (; b; b = b->next) {
2135 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2136 set_noconv_flags (b->parts, flag);
2137 else if (b->type == TYPETEXT && b->noconv) {
2138 parameter_setval(&b->parameter, "x-mutt-noconv", flag ? "yes" : NULL);
2143 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2144 int post, char *fcc)
2148 char tempfile[_POSIX_PATH_MAX];
2149 FILE *tempfp = NULL;
2153 set_noconv_flags (hdr->content, 1);
2155 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2159 /* We need to add a Content-Length field to avoid problems where a line in
2160 * the message body begins with "From "
2162 if (f.magic == M_MMDF || f.magic == M_MBOX) {
2163 mutt_mktemp (tempfile);
2164 if ((tempfp = safe_fopen (tempfile, "w+")) == NULL) {
2165 mutt_perror (tempfile);
2166 mx_close_mailbox (&f, NULL);
2171 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
2172 if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2173 mx_close_mailbox (&f, NULL);
2177 /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2178 * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
2180 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2183 /* (postponment) if this was a reply of some sort, <msgid> contians the
2184 * Message-ID: of message replied to. Save it using a special X-Mutt-
2185 * header so it can be picked up if the message is recalled at a later
2186 * point in time. This will allow the message to be marked as replied if
2187 * the same mailbox is still open.
2190 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2192 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2193 * it can be picked up when the message is recalled
2196 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2197 fprintf (msg->fp, "Status: RO\n");
2201 /* (postponment) if the mail is to be signed or encrypted, save this info */
2202 if (post && (hdr->security & APPLICATION_PGP)) {
2203 fputs ("X-Mutt-PGP: ", msg->fp);
2204 if (hdr->security & ENCRYPT)
2205 fputc ('E', msg->fp);
2206 if (hdr->security & SIGN) {
2207 fputc ('S', msg->fp);
2208 if (PgpSignAs && *PgpSignAs)
2209 fprintf (msg->fp, "<%s>", PgpSignAs);
2211 if (hdr->security & INLINE)
2212 fputc ('I', msg->fp);
2213 fputc ('\n', msg->fp);
2216 /* (postponment) if the mail is to be signed or encrypted, save this info */
2217 if (post && (hdr->security & APPLICATION_SMIME)) {
2218 fputs ("X-Mutt-SMIME: ", msg->fp);
2219 if (hdr->security & ENCRYPT) {
2220 fputc ('E', msg->fp);
2221 if (SmimeCryptAlg && *SmimeCryptAlg)
2222 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2224 if (hdr->security & SIGN) {
2225 fputc ('S', msg->fp);
2226 if (SmimeDefaultKey && *SmimeDefaultKey)
2227 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2229 if (hdr->security & INLINE)
2230 fputc ('I', msg->fp);
2231 fputc ('\n', msg->fp);
2235 /* (postponement) if the mail is to be sent through a mixmaster
2236 * chain, save that information
2239 if (post && hdr->chain && hdr->chain) {
2242 fputs ("X-Mutt-Mix:", msg->fp);
2243 for (p = hdr->chain; p; p = p->next)
2244 fprintf (msg->fp, " %s", (char *) p->data);
2246 fputc ('\n', msg->fp);
2251 char sasha[LONG_STRING];
2254 mutt_write_mime_body (hdr->content, tempfp);
2256 /* make sure the last line ends with a newline. Emacs doesn't ensure
2257 * this will happen, and it can cause problems parsing the mailbox
2260 fseeko (tempfp, -1, 2);
2261 if (fgetc (tempfp) != '\n') {
2262 fseeko (tempfp, 0, 2);
2263 fputc ('\n', tempfp);
2267 if (ferror (tempfp)) {
2270 mx_commit_message (msg, &f); /* XXX - really? */
2271 mx_close_message (&msg);
2272 mx_close_mailbox (&f, NULL);
2276 /* count the number of lines */
2278 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2280 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2281 fprintf (msg->fp, "Lines: %d\n\n", lines);
2283 /* copy the body and clean up */
2285 r = mutt_copy_stream (tempfp, msg->fp);
2286 if (fclose (tempfp) != 0)
2288 /* if there was an error, leave the temp version */
2293 fputc ('\n', msg->fp); /* finish off the header */
2294 r = mutt_write_mime_body (hdr->content, msg->fp);
2297 if (mx_commit_message (msg, &f) != 0)
2299 mx_close_message (&msg);
2300 mx_close_mailbox (&f, NULL);
2303 set_noconv_flags (hdr->content, 0);