Have a lib-ui/lib-ui.h
[apps/madmutt.git] / sendlib.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
4  *
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.
8  */
9
10 #include <lib-lib/lib-lib.h>
11
12 #include <signal.h>
13
14 #include <lib-lua/lib-lua.h>
15 #include <lib-sys/exit.h>
16 #include <lib-sys/mutt_signal.h>
17 #include <lib-mime/mime.h>
18 #include <lib-ui/enter.h>
19 #include <lib-ui/lib-ui.h>
20 #include <lib-mx/mx.h>
21
22 #include "mutt.h"
23 #include "handler.h"
24 #include "crypt.h"
25 #include "recvattach.h"
26 #include "copy.h"
27 #include "pager.h"
28 #include "charset.h"
29 #include "mutt_idna.h"
30
31 #ifdef USE_NNTP
32 #include "nntp.h"
33 #endif
34
35 #ifdef HAVE_SYSEXITS_H
36 #include <sysexits.h>
37 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
38 #define EX_OK 0
39 #endif
40
41 static void transform_to_7bit (BODY * a, FILE * fpin);
42
43 static void encode_quoted (fgetconv_t * fc, FILE * fout, int istext)
44 {
45   int c, linelen = 0;
46   char line[77], savechar;
47
48   while ((c = fgetconv (fc)) != EOF) {
49     /* Wrap the line if needed. */
50     if (linelen == 76 && ((istext && c != '\n') || !istext)) {
51       /* If the last character is "quoted", then be sure to move all three
52        * characters to the next line.  Otherwise, just move the last
53        * character...
54        */
55       if (line[linelen - 3] == '=') {
56         line[linelen - 3] = 0;
57         fputs (line, fout);
58         fputs ("=\n", fout);
59         line[linelen] = 0;
60         line[0] = '=';
61         line[1] = line[linelen - 2];
62         line[2] = line[linelen - 1];
63         linelen = 3;
64       }
65       else {
66         savechar = line[linelen - 1];
67         line[linelen - 1] = '=';
68         line[linelen] = 0;
69         fputs (line, fout);
70         fputc ('\n', fout);
71         line[0] = savechar;
72         linelen = 1;
73       }
74     }
75
76     /* Escape lines that begin with/only contain "the message separator". */
77     if (linelen == 4 && !m_strncmp("From", line, 4)) {
78       m_strcpy(line, sizeof(line), "=46rom");
79       linelen = 6;
80     }
81     else if (linelen == 4 && !m_strncmp("from", line, 4)) {
82       m_strcpy(line, sizeof(line), "=66rom");
83       linelen = 6;
84     }
85     else if (linelen == 1 && line[0] == '.') {
86       m_strcpy(line, sizeof(line), "=2E");
87       linelen = 3;
88     }
89
90
91     if (c == '\n' && istext) {
92       /* Check to make sure there is no trailing space on this line. */
93       if (linelen > 0
94           && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
95         if (linelen < 74) {
96           sprintf (line + linelen - 1, "=%2.2X",
97                    (unsigned char) line[linelen - 1]);
98           fputs (line, fout);
99         }
100         else {
101           savechar = line[linelen - 1];
102
103           line[linelen - 1] = '=';
104           line[linelen] = 0;
105           fputs (line, fout);
106           fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
107         }
108       }
109       else {
110         line[linelen] = 0;
111         fputs (line, fout);
112       }
113       fputc ('\n', fout);
114       linelen = 0;
115     }
116     else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
117       /* Check to make sure there is enough room for the quoted character.
118        * If not, wrap to the next line.
119        */
120       if (linelen > 73) {
121         line[linelen++] = '=';
122         line[linelen] = 0;
123         fputs (line, fout);
124         fputc ('\n', fout);
125         linelen = 0;
126       }
127       sprintf (line + linelen, "=%2.2X", (unsigned char) c);
128       linelen += 3;
129     }
130     else {
131       /* Don't worry about wrapping the line here.  That will happen during
132        * the next iteration when I'll also know what the next character is.
133        */
134       line[linelen++] = c;
135     }
136   }
137
138   /* Take care of anything left in the buffer */
139   if (linelen > 0) {
140     if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
141       /* take care of trailing whitespace */
142       if (linelen < 74)
143         sprintf (line + linelen - 1, "=%2.2X",
144                  (unsigned char) line[linelen - 1]);
145       else {
146         savechar = line[linelen - 1];
147         line[linelen - 1] = '=';
148         line[linelen] = 0;
149         fputs (line, fout);
150         fputc ('\n', fout);
151         sprintf (line, "=%2.2X", (unsigned char) savechar);
152       }
153     }
154     else
155       line[linelen] = 0;
156     fputs (line, fout);
157   }
158 }
159
160 static char b64_buffer[3];
161 static short b64_num;
162 static short b64_linelen;
163
164 static void b64_flush (FILE * fout)
165 {
166   short i;
167
168   if (!b64_num)
169     return;
170
171   if (b64_linelen >= 72) {
172     fputc ('\n', fout);
173     b64_linelen = 0;
174   }
175
176   for (i = b64_num; i < 3; i++)
177     b64_buffer[i] = '\0';
178
179   fputc(__m_b64chars[(b64_buffer[0] >> 2) & 0x3f], fout);
180   b64_linelen++;
181   fputc(__m_b64chars
182          [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
183   b64_linelen++;
184
185   if (b64_num > 1) {
186     fputc (__m_b64chars
187            [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
188            fout);
189     b64_linelen++;
190     if (b64_num > 2) {
191       fputc (__m_b64chars[b64_buffer[2] & 0x3f], fout);
192       b64_linelen++;
193     }
194   }
195
196   while (b64_linelen % 4) {
197     fputc ('=', fout);
198     b64_linelen++;
199   }
200
201   b64_num = 0;
202 }
203
204
205 static void b64_putc (char c, FILE * fout)
206 {
207   if (b64_num == 3)
208     b64_flush (fout);
209
210   b64_buffer[b64_num++] = c;
211 }
212
213
214 static void encode_base64 (fgetconv_t * fc, FILE * fout, int istext)
215 {
216   int ch, ch1 = EOF;
217
218   b64_num = b64_linelen = 0;
219
220   while ((ch = fgetconv (fc)) != EOF) {
221     if (istext && ch == '\n' && ch1 != '\r')
222       b64_putc ('\r', fout);
223     b64_putc (ch, fout);
224     ch1 = ch;
225   }
226   b64_flush (fout);
227   fputc ('\n', fout);
228 }
229
230 static void encode_8bit (fgetconv_t * fc, FILE * fout,
231                          int istext __attribute__ ((unused)))
232 {
233   int ch;
234
235   while ((ch = fgetconv (fc)) != EOF)
236     fputc (ch, fout);
237 }
238
239
240 int mutt_write_mime_header (BODY * a, FILE * f)
241 {
242   char buffer[STRING];
243   char *t;
244   char *fn;
245   int len;
246   int tmplen;
247   int encode;
248
249   fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
250
251   if (a->parameter) {
252     parameter_t *p;
253
254     len = 25 + m_strlen(a->subtype);        /* approximate len. of content-type */
255
256     for (p = a->parameter; p; p = p->next) {
257       char *tmp;
258
259       if (!p->value)
260         continue;
261
262       fputc (';', f);
263
264       buffer[0] = 0;
265       tmp = m_strdup(p->value);
266       encode = rfc2231_encode_string (&tmp);
267       rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
268
269       /* Dirty hack to make messages readable by Outlook Express 
270        * for the Mac: force quotes around the boundary parameter
271        * even when they aren't needed.
272        */
273
274       if (!ascii_strcasecmp (p->attribute, "boundary")
275           && !strcmp (buffer, tmp))
276         snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
277
278       p_delete(&tmp);
279
280       tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
281
282       if (len + tmplen + 2 > 76) {
283         fputs ("\n\t", f);
284         len = tmplen + 8;
285       }
286       else {
287         fputc (' ', f);
288         len += tmplen + 1;
289       }
290
291       fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
292
293     }
294   }
295
296   fputc ('\n', f);
297
298   if (a->description)
299     fprintf (f, "Content-Description: %s\n", a->description);
300
301 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
302   fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
303
304   if (a->use_disp) {
305     if (!(fn = a->d_filename))
306       fn = a->filename;
307
308     if (fn) {
309       char *tmp;
310
311       /* Strip off the leading path... */
312       if ((t = strrchr (fn, '/')))
313         t++;
314       else
315         t = fn;
316
317       buffer[0] = 0;
318       tmp = m_strdup(t);
319       encode = rfc2231_encode_string (&tmp);
320       rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
321       p_delete(&tmp);
322       fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
323     }
324   }
325
326   fputc ('\n', f);
327
328   if (a->encoding != ENC7BIT)
329     fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
330
331   /* Do NOT add the terminator here!!! */
332   return (ferror (f) ? -1 : 0);
333 }
334
335 int mutt_write_mime_body (BODY * a, FILE * f)
336 {
337   const char *p;
338   char boundary[STRING];
339   char send_charset[STRING];
340   FILE *fpin;
341   BODY *t;
342   fgetconv_t *fc;
343
344   if (a->type == TYPEMULTIPART) {
345     /* First, find the boundary to use */
346     if (!(p = parameter_getval(a->parameter, "boundary"))) {
347       mutt_error _("No boundary parameter found! [report this error]");
348
349       return (-1);
350     }
351     m_strcpy(boundary, sizeof(boundary), p);
352
353     for (t = a->parts; t; t = t->next) {
354       fprintf (f, "\n--%s\n", boundary);
355       if (mutt_write_mime_header (t, f) == -1)
356         return -1;
357       fputc ('\n', f);
358       if (mutt_write_mime_body (t, f) == -1)
359         return -1;
360     }
361     fprintf (f, "\n--%s--\n", boundary);
362     return (ferror (f) ? -1 : 0);
363   }
364
365   /* This is pretty gross, but it's the best solution for now... */
366   if (a->type == TYPEAPPLICATION && !m_strcmp(a->subtype, "pgp-encrypted")) {
367     fputs ("Version: 1\n", f);
368     return 0;
369   }
370
371   if ((fpin = fopen (a->filename, "r")) == NULL) {
372     mutt_error (_("%s no longer exists!"), a->filename);
373     return -1;
374   }
375
376   if (a->type == TYPETEXT && (!a->noconv))
377     fc = fgetconv_open (fpin, a->file_charset,
378                         mutt_get_body_charset (send_charset,
379                                                sizeof (send_charset), a), 0);
380   else
381     fc = fgetconv_open (fpin, 0, 0, 0);
382
383 #define write_as_text_part(a)  (mutt_is_text_part(a) || mutt_is_application_pgp(a))
384   if (a->encoding == ENCQUOTEDPRINTABLE)
385     encode_quoted (fc, f, write_as_text_part (a));
386   else if (a->encoding == ENCBASE64)
387     encode_base64 (fc, f, write_as_text_part (a));
388   else if (a->type == TYPETEXT && (!a->noconv))
389     encode_8bit (fc, f, write_as_text_part (a));
390   else
391     mutt_copy_stream (fpin, f);
392 #undef write_as_text_part
393
394   fgetconv_close (&fc);
395   m_fclose(&fpin);
396
397   return (ferror (f) ? -1 : 0);
398 }
399
400 typedef struct {
401   int from;
402   int whitespace;
403   int dot;
404   int linelen;
405   int was_cr;
406 } CONTENT_STATE;
407
408
409 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
410                                  ssize_t dlen)
411 {
412   int from = s->from;
413   int whitespace = s->whitespace;
414   int dot = s->dot;
415   int linelen = s->linelen;
416   int was_cr = s->was_cr;
417
418   if (!d) {                     /* This signals EOF */
419     if (was_cr)
420       info->binary = 1;
421     if (linelen > info->linemax)
422       info->linemax = linelen;
423
424     return;
425   }
426
427   for (; dlen; d++, dlen--) {
428     char ch = *d;
429
430     if (was_cr) {
431       was_cr = 0;
432       if (ch != '\n') {
433         info->binary = 1;
434       }
435       else {
436         if (whitespace)
437           info->space = 1;
438         if (dot)
439           info->dot = 1;
440         if (linelen > info->linemax)
441           info->linemax = linelen;
442         whitespace = 0;
443         dot = 0;
444         linelen = 0;
445         continue;
446       }
447     }
448
449     linelen++;
450     if (ch == '\n') {
451       info->crlf++;
452       if (whitespace)
453         info->space = 1;
454       if (dot)
455         info->dot = 1;
456       if (linelen > info->linemax)
457         info->linemax = linelen;
458       whitespace = 0;
459       linelen = 0;
460       dot = 0;
461     }
462     else if (ch == '\r') {
463       info->crlf++;
464       info->cr = 1;
465       was_cr = 1;
466       continue;
467     }
468     else if (ch & 0x80)
469       info->hibin++;
470     else if (ch == '\t' || ch == '\f') {
471       info->ascii++;
472       whitespace++;
473     }
474     else if (ch < 32 || ch == 127)
475       info->lobin++;
476     else {
477       if (linelen == 1) {
478         if ((ch == 'F') || (ch == 'f'))
479           from = 1;
480         else
481           from = 0;
482         if (ch == '.')
483           dot = 1;
484         else
485           dot = 0;
486       }
487       else if (from) {
488         if (linelen == 2 && ch != 'r')
489           from = 0;
490         else if (linelen == 3 && ch != 'o')
491           from = 0;
492         else if (linelen == 4) {
493           if (ch == 'm')
494             info->from = 1;
495           from = 0;
496         }
497       }
498       if (ch == ' ')
499         whitespace++;
500       info->ascii++;
501     }
502
503     if (linelen > 1)
504       dot = 0;
505     if (ch != ' ' && ch != '\t')
506       whitespace = 0;
507   }
508
509   s->from = from;
510   s->whitespace = whitespace;
511   s->dot = dot;
512   s->linelen = linelen;
513   s->was_cr = was_cr;
514
515 }
516
517 /*
518  * Find the best charset conversion of the file from fromcode into one
519  * of the tocodes. If successful, set *tocode and CONTENT *info and
520  * return the number of characters converted inexactly. If no
521  * conversion was possible, return -1.
522  *
523  * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
524  * which would otherwise prevent us from knowing the number of inexact
525  * conversions. Where the candidate target charset is UTF-8 we avoid
526  * doing the second conversion because iconv_open("UTF-8", "UTF-8")
527  * fails with some libraries.
528  *
529  * We assume that the output from iconv is never more than 4 times as
530  * long as the input for any pair of charsets we might be interested
531  * in.
532  */
533 static ssize_t convert_file_to (FILE * file, const char *fromcode,
534                                int ncodes, const char **tocodes,
535                                int *tocode, CONTENT * info)
536 {
537   iconv_t cd1, *cd;
538   char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
539   const char *ib, *ub;
540   char *ob;
541   ssize_t ibl, obl, ubl, ubl1, n, ret;
542   int i;
543   CONTENT *infos;
544   CONTENT_STATE *states;
545   ssize_t *score;
546
547   cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
548   if (cd1 == MUTT_ICONV_ERROR)
549     return -1;
550
551   cd = p_new(iconv_t, ncodes);
552   score = p_new(ssize_t, ncodes);
553   states = p_new(CONTENT_STATE, ncodes);
554   infos = p_new(CONTENT, ncodes);
555
556   for (i = 0; i < ncodes; i++)
557     if (ascii_strcasecmp (tocodes[i], "UTF-8"))
558       cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
559     else
560       /* Special case for conversion to UTF-8 */
561       cd[i] = MUTT_ICONV_ERROR, score[i] = -1;
562
563   rewind (file);
564   ibl = 0;
565   for (;;) {
566
567     /* Try to fill input buffer */
568     n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
569     ibl += n;
570
571     /* Convert to UTF-8 */
572     ib = bufi;
573     ob = bufu, obl = sizeof (bufu);
574     n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
575     if (n == -1 && ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
576       ret = -1;
577       break;
578     }
579     ubl1 = ob - bufu;
580
581     /* Convert from UTF-8 */
582     for (i = 0; i < ncodes; i++)
583       if (cd[i] != MUTT_ICONV_ERROR && score[i] != -1) {
584         ub = bufu, ubl = ubl1;
585         ob = bufo, obl = sizeof (bufo);
586         n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
587         if (n == -1) {
588           score[i] = -1;
589         }
590         else {
591           score[i] += n;
592           update_content_info (&infos[i], &states[i], bufo, ob - bufo);
593         }
594       }
595       else if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1)
596         /* Special case for conversion to UTF-8 */
597         update_content_info (&infos[i], &states[i], bufu, ubl1);
598
599     if (ibl)
600       /* Save unused input */
601       memmove (bufi, ib, ibl);
602     else if (!ubl1 && ib < bufi + sizeof (bufi)) {
603       ret = 0;
604       break;
605     }
606   }
607
608   if (!ret) {
609     /* Find best score */
610     ret = -1;
611     for (i = 0; i < ncodes; i++) {
612       if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1) {
613         /* Special case for conversion to UTF-8 */
614         *tocode = i;
615         ret = 0;
616         break;
617       }
618       else if (cd[i] == MUTT_ICONV_ERROR || score[i] == -1)
619         continue;
620       else if (ret == -1 || score[i] < ret) {
621         *tocode = i;
622         ret = score[i];
623         if (!ret)
624           break;
625       }
626     }
627     if (ret != -1) {
628       memcpy (info, &infos[*tocode], sizeof (CONTENT));
629       update_content_info (info, &states[*tocode], 0, 0);       /* EOF */
630     }
631   }
632
633   for (i = 0; i < ncodes; i++)
634     if (cd[i] != MUTT_ICONV_ERROR)
635       iconv_close (cd[i]);
636
637   iconv_close (cd1);
638   p_delete(&cd);
639   p_delete(&infos);
640   p_delete(&score);
641   p_delete(&states);
642
643   return ret;
644 }
645
646 /*
647  * Find the first of the fromcodes that gives a valid conversion and
648  * the best charset conversion of the file into one of the tocodes. If
649  * successful, set *fromcode and *tocode to dynamically allocated
650  * strings, set CONTENT *info, and return the number of characters
651  * converted inexactly. If no conversion was possible, return -1.
652  *
653  * Both fromcodes and tocodes may be colon-separated lists of charsets.
654  * However, if fromcode is zero then fromcodes is assumed to be the
655  * name of a single charset even if it contains a colon.
656  */
657 static ssize_t convert_file_from_to (FILE * file,
658                                     const char *fromcodes,
659                                     const char *tocodes, char **fromcode,
660                                     char **tocode, CONTENT * info)
661 {
662   char *fcode;
663   char **tcode;
664   const char *c, *c1;
665   ssize_t ret;
666   int ncodes, i, cn;
667
668   /* Count the tocodes */
669   ncodes = 0;
670   for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
671     if ((c1 = strchr (c, ':')) == c)
672       continue;
673     ++ncodes;
674   }
675
676   /* Copy them */
677   tcode = p_new(char *, ncodes);
678   for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
679     if ((c1 = strchr (c, ':')) == c)
680       continue;
681     tcode[i] = m_substrdup(c, c1);
682   }
683
684   ret = -1;
685   if (fromcode) {
686     /* Try each fromcode in turn */
687     for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
688       if ((c1 = strchr (c, ':')) == c)
689         continue;
690       fcode = m_substrdup(c, c1);
691
692       ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
693                              &cn, info);
694       if (ret != -1) {
695         *fromcode = fcode;
696         *tocode = tcode[cn];
697         tcode[cn] = 0;
698         break;
699       }
700       p_delete(&fcode);
701     }
702   }
703   else {
704     /* There is only one fromcode */
705     ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
706                            &cn, info);
707     if (ret != -1) {
708       *tocode = tcode[cn];
709       tcode[cn] = 0;
710     }
711   }
712
713   /* Free memory */
714   for (i = 0; i < ncodes; i++)
715     p_delete(&tcode[i]);
716
717   p_delete(tcode);
718
719   return ret;
720 }
721
722 /* 
723  * Analyze the contents of a file to determine which MIME encoding to use.
724  * Also set the body charset, sometimes, or not.
725  */
726 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
727 {
728   CONTENT *info;
729   CONTENT_STATE state;
730   FILE *fp = NULL;
731   char *fromcode = NULL;
732   char *tocode = NULL;
733   char buffer[100];
734   char chsbuf[STRING];
735   ssize_t r;
736
737   struct stat sb;
738
739   if (b && !fname)
740     fname = b->filename;
741
742   if (stat (fname, &sb) == -1) {
743     mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
744     return NULL;
745   }
746
747   if (!S_ISREG (sb.st_mode)) {
748     mutt_error (_("%s isn't a regular file."), fname);
749     return NULL;
750   }
751
752   if ((fp = fopen (fname, "r")) == NULL) {
753     return (NULL);
754   }
755
756   info = p_new(CONTENT, 1);
757   p_clear(&state, 1);
758
759   if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
760     const char *chs = parameter_getval(b->parameter, "charset");
761     char *fchs = b->use_disp && !m_strisempty(mod_cset.file_charset)
762         ? FileCharset : mod_cset.charset;
763     if (mod_cset.charset && (chs || mod_cset.send_charset) &&
764         convert_file_from_to (fp, fchs, chs ? chs : mod_cset.send_charset,
765                               &fromcode, &tocode, info) != -1) {
766       if (!chs) {
767         charset_canonicalize (chsbuf, sizeof (chsbuf), tocode);
768         parameter_setval(&b->parameter, "charset", chsbuf);
769       }
770       b->file_charset = fromcode;
771       p_delete(&tocode);
772       m_fclose(&fp);
773       return info;
774     }
775   }
776
777   rewind (fp);
778   while ((r = fread (buffer, 1, sizeof (buffer), fp)))
779     update_content_info (info, &state, buffer, r);
780   update_content_info (info, &state, 0, 0);
781
782   m_fclose(&fp);
783
784   if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
785     parameter_setval(&b->parameter, "charset",
786                      (!info->hibin ? "us-ascii"
787                       : mod_cset.charset && !charset_is_us_ascii(mod_cset.charset)
788                                          ? mod_cset.charset : "unknown-8bit"));
789
790   return info;
791 }
792
793 /* Given a file with path ``s'', see if there is a registered MIME type.
794  * returns the major MIME type, and copies the subtype to ``d''.  First look
795  * for ~/.mime.types, then look in a system mime.types if we can find one.
796  * The longest match is used so that we can match `ps.gz' when `gz' also
797  * exists.
798  */
799
800 int mutt_lookup_mime_type (BODY * att, const char *path)
801 {
802   FILE *f;
803   char *p, *q, *ct;
804   char buf[LONG_STRING];
805   char subtype[STRING], xtype[STRING];
806   int count;
807   int szf, sze, cur_sze;
808   int type;
809
810   *subtype = '\0';
811   *xtype = '\0';
812   type = TYPEOTHER;
813   cur_sze = 0;
814
815   szf = m_strlen(path);
816
817   for (count = 0; count < 4; count++) {
818     /*
819      * can't use strtok() because we use it in an inner loop below, so use
820      * a switch statement here instead.
821      */
822     switch (count) {
823     case 0:
824       snprintf(buf, sizeof (buf), "%s/.mime.types", NONULL(mod_core.homedir));
825       break;
826     case 1:
827       m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
828       break;
829     case 2:
830       m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
831       break;
832     case 3:
833       m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
834       break;
835     default:
836       goto bye;                 /* shouldn't happen */
837     }
838
839     if ((f = fopen (buf, "r")) != NULL) {
840       while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
841         /* weed out any comments */
842         if ((p = strchr (buf, '#')))
843           *p = 0;
844
845         /* remove any leading space. */
846         ct = vskipspaces(buf);
847
848         /* position on the next field in this line */
849         if ((p = strpbrk (ct, " \t")) == NULL)
850           continue;
851         *p++ = 0;
852         p = vskipspaces(p);
853
854         /* cycle through the file extensions */
855         while ((p = strtok (p, " \t\n"))) {
856           sze = m_strlen(p);
857           if ((sze > cur_sze) && (szf >= sze) &&
858               (m_strcasecmp(path + szf - sze, p) == 0
859                || ascii_strcasecmp (path + szf - sze, p) == 0)
860               && (szf == sze || path[szf - sze - 1] == '.'))
861           {
862             /* get the content-type */
863
864             if ((p = strchr (ct, '/')) == NULL) {
865               /* malformed line, just skip it. */
866               break;
867             }
868             *p++ = 0;
869
870             for (q = p; *q && !ISSPACE (*q); q++);
871
872             m_strncpy(subtype, sizeof(subtype), p, q - p);
873
874             if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
875               m_strcpy(xtype, sizeof(xtype), ct);
876
877             cur_sze = sze;
878           }
879           p = NULL;
880         }
881       }
882       m_fclose(&f);
883     }
884   }
885
886 bye:
887
888   if (type != TYPEOTHER || *xtype != '\0') {
889     att->type = type;
890     m_strreplace(&att->subtype, subtype);
891     m_strreplace(&att->xtype, xtype);
892   }
893
894   return (type);
895 }
896
897 void mutt_message_to_7bit (BODY * a, FILE * fp)
898 {
899   char temp[_POSIX_PATH_MAX];
900   char *line = NULL;
901   FILE *fpin = NULL;
902   FILE *fpout = NULL;
903   struct stat sb;
904
905   if (!a->filename && fp)
906     fpin = fp;
907   else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
908     mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
909     return;
910   }
911   else {
912     a->offset = 0;
913     if (stat (a->filename, &sb) == -1) {
914       mutt_perror ("stat");
915       m_fclose(&fpin);
916     }
917     a->length = sb.st_size;
918   }
919
920   fpout = m_tempfile(temp, sizeof(temp), NONULL(mod_core.tmpdir), NULL);
921   if (!fpout) {
922     mutt_error(_("Could not create temporary file"));
923     goto cleanup;
924   }
925
926   fseeko (fpin, a->offset, 0);
927   a->parts = mutt_parse_messageRFC822 (fpin, a);
928
929   transform_to_7bit (a->parts, fpin);
930
931   mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
932                  CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
933
934   fputs ("MIME-Version: 1.0\n", fpout);
935   mutt_write_mime_header (a->parts, fpout);
936   fputc ('\n', fpout);
937   mutt_write_mime_body (a->parts, fpout);
938
939 cleanup:
940   p_delete(&line);
941
942   if (fpin && !fp)
943     m_fclose(&fpin);
944   if (fpout)
945     m_fclose(&fpout);
946   else
947     return;
948
949   a->encoding = ENC7BIT;
950   a->d_filename = a->filename;
951   if (a->filename && a->unlink)
952     unlink (a->filename);
953   a->filename = m_strdup(temp);
954   a->unlink = 1;
955   if (stat (a->filename, &sb) == -1) {
956     mutt_perror ("stat");
957     return;
958   }
959   a->length = sb.st_size;
960   body_list_wipe(&a->parts);
961   a->hdr->content = NULL;
962 }
963
964 static void transform_to_7bit (BODY * a, FILE * fpin)
965 {
966   char buff[_POSIX_PATH_MAX];
967   STATE s;
968   struct stat sb;
969
970   p_clear(&s, 1);
971   for (; a; a = a->next) {
972     if (a->type == TYPEMULTIPART) {
973       if (a->encoding != ENC7BIT)
974         a->encoding = ENC7BIT;
975
976       transform_to_7bit (a->parts, fpin);
977     }
978     else if (mutt_is_message_type(a)) {
979       mutt_message_to_7bit (a, fpin);
980     }
981     else {
982       a->noconv = 1;
983       a->force_charset = 1;
984
985       s.fpout = m_tempfile(buff, sizeof(buff), NONULL(mod_core.tmpdir), NULL);
986       if (!s.fpout) {
987         mutt_error(_("Could not create temporary file"));
988         return;
989       }
990       s.fpin = fpin;
991       mutt_decode_attachment (a, &s);
992       m_fclose(&s.fpout);
993       a->d_filename = a->filename;
994       a->filename = m_strdup(buff);
995       a->unlink = 1;
996       if (stat (a->filename, &sb) == -1) {
997         mutt_perror ("stat");
998         return;
999       }
1000       a->length = sb.st_size;
1001
1002       mutt_update_encoding (a);
1003       if (a->encoding == ENC8BIT)
1004         a->encoding = ENCQUOTEDPRINTABLE;
1005       else if (a->encoding == ENCBINARY)
1006         a->encoding = ENCBASE64;
1007     }
1008   }
1009 }
1010
1011 /* determine which Content-Transfer-Encoding to use */
1012 static void mutt_set_encoding (BODY * b, CONTENT * info)
1013 {
1014   char send_charset[STRING];
1015
1016   if (b->type == TYPETEXT) {
1017     char *chsname =
1018       mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1019     if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1020         || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1021       b->encoding = ENCQUOTEDPRINTABLE;
1022     else if (info->hibin)
1023       b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1024     else
1025       b->encoding = ENC7BIT;
1026   }
1027   else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1028     if (info->lobin || info->hibin) {
1029       if (option (OPTALLOW8BIT) && !info->lobin)
1030         b->encoding = ENC8BIT;
1031       else
1032         mutt_message_to_7bit (b, NULL);
1033     }
1034     else
1035       b->encoding = ENC7BIT;
1036   }
1037   else if (b->type == TYPEAPPLICATION
1038            && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1039     b->encoding = ENC7BIT;
1040   else
1041   {
1042     /* Determine which encoding is smaller  */
1043     if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1044         3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1045       b->encoding = ENCBASE64;
1046     else
1047       b->encoding = ENCQUOTEDPRINTABLE;
1048   }
1049 }
1050
1051 void mutt_stamp_attachment (BODY * a)
1052 {
1053   a->stamp = time (NULL);
1054 }
1055
1056 /* Get a body's character set */
1057
1058 char *mutt_get_body_charset(char *d, ssize_t dlen, BODY * b)
1059 {
1060     const char *p;
1061
1062     if (b && b->type != TYPETEXT)
1063         return NULL;
1064
1065     p = b ? parameter_getval(b->parameter, "charset") : NULL;
1066     charset_canonicalize(d, dlen, p);
1067     return d;
1068 }
1069
1070
1071 /* Assumes called from send mode where BODY->filename points to actual file */
1072 void mutt_update_encoding (BODY * a)
1073 {
1074   CONTENT *info;
1075   char chsbuff[STRING];
1076
1077   /* override noconv when it's us-ascii */
1078   if (charset_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1079     a->noconv = 0;
1080
1081   if (!a->force_charset && !a->noconv)
1082     parameter_delval(&a->parameter, "charset");
1083
1084   if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1085     return;
1086
1087   mutt_set_encoding (a, info);
1088   mutt_stamp_attachment (a);
1089
1090   p_delete(&a->content);
1091   a->content = info;
1092
1093 }
1094
1095 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1096 {
1097   char buffer[LONG_STRING];
1098   BODY *body;
1099   FILE *fp;
1100   int cmflags, chflags;
1101   int pgp = hdr->security;
1102
1103   if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1104       (hdr->security & ENCRYPT)) {
1105   }
1106
1107   fp = m_tempfile(buffer, sizeof(buffer), NONULL(mod_core.tmpdir), NULL);
1108   if (!fp)
1109     return NULL;
1110
1111   body = body_new();
1112   body->type = TYPEMESSAGE;
1113   body->subtype = m_strdup("rfc822");
1114   body->filename = m_strdup(buffer);
1115   body->unlink = 1;
1116   body->use_disp = 0;
1117   body->disposition = DISPINLINE;
1118   body->noconv = 1;
1119
1120   mutt_parse_mime_message (ctx, hdr);
1121
1122   chflags = CH_XMIT;
1123   cmflags = 0;
1124
1125   /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1126   if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1127     chflags |= CH_MIME | CH_TXTPLAIN;
1128     cmflags = M_CM_DECODE | M_CM_CHARCONV;
1129     pgp &= ~(PGPENCRYPT|SMIMEENCRYPT);
1130   }
1131   else if (option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1132     if (mutt_is_multipart_encrypted (hdr->content)) {
1133       chflags |= CH_MIME | CH_NONEWLINE;
1134       cmflags = M_CM_DECODE_PGP;
1135       pgp &= ~PGPENCRYPT;
1136     }
1137     else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1138       chflags |= CH_MIME | CH_TXTPLAIN;
1139       cmflags = M_CM_DECODE | M_CM_CHARCONV;
1140       pgp &= ~PGPENCRYPT;
1141     }
1142     else if (mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1143       chflags |= CH_MIME | CH_TXTPLAIN;
1144       cmflags = M_CM_DECODE | M_CM_CHARCONV;
1145       pgp &= ~SMIMEENCRYPT;
1146     }
1147   }
1148
1149   mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1150
1151   fflush (fp);
1152   rewind (fp);
1153
1154   body->hdr = header_new();
1155   body->hdr->offset = 0;
1156   /* we don't need the user headers here */
1157   body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1158   body->hdr->security = pgp;
1159   mutt_update_encoding (body);
1160   body->parts = body->hdr->content;
1161
1162   m_fclose(&fp);
1163
1164   return (body);
1165 }
1166
1167 BODY *mutt_make_file_attach (const char *path)
1168 {
1169   BODY *att;
1170   CONTENT *info;
1171
1172   att = body_new();
1173   att->filename = m_strdup(path);
1174
1175   /* Attempt to determine the appropriate content-type based on the filename
1176    * suffix.
1177    */
1178   mutt_lookup_mime_type (att, path);
1179
1180   if ((info = mutt_get_content_info (path, att)) == NULL) {
1181     body_list_wipe(&att);
1182     return NULL;
1183   }
1184
1185   if (!att->subtype) {
1186     if (info->lobin == 0
1187         || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1188       /*
1189        * Statistically speaking, there should be more than 10% "lobin" 
1190        * chars if this is really a binary file...
1191        */
1192       att->type = TYPETEXT;
1193       att->subtype = m_strdup("plain");
1194     } else {
1195       att->type = TYPEAPPLICATION;
1196       att->subtype = m_strdup("octet-stream");
1197     }
1198   }
1199
1200   mutt_update_encoding (att);
1201   return att;
1202 }
1203
1204 static int get_toplevel_encoding (BODY * a)
1205 {
1206     int e = ENC7BIT;
1207
1208     for (; a; a = a->next) {
1209         if (a->encoding == ENCBINARY)
1210             return ENCBINARY;
1211
1212         if (a->encoding == ENC8BIT)
1213             e = ENC8BIT;
1214     }
1215
1216     return e;
1217 }
1218
1219 BODY *mutt_make_multipart (BODY * b)
1220 {
1221   BODY *new;
1222
1223   new = body_new();
1224   new->type = TYPEMULTIPART;
1225   new->subtype = m_strdup("mixed");
1226   new->encoding = get_toplevel_encoding (b);
1227   parameter_set_boundary(&new->parameter);
1228   new->use_disp = 0;
1229   new->disposition = DISPINLINE;
1230   new->parts = b;
1231
1232   return new;
1233 }
1234
1235 /* remove the multipart body if it exists */
1236 BODY *mutt_remove_multipart (BODY * b)
1237 {
1238   BODY *t;
1239
1240   if (b->parts) {
1241     t = b;
1242     b = b->parts;
1243     t->parts = NULL;
1244     body_list_wipe(&t);
1245   }
1246   return b;
1247 }
1248
1249 char *mutt_make_date (char *s, ssize_t len)
1250 {
1251   time_t t = time (NULL);
1252   struct tm *l = localtime (&t);
1253   time_t tz = mutt_local_tz (t);
1254
1255   tz /= 60;
1256
1257   snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1258             Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1259             l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1260             (int) tz / 60, (int) abs (tz) % 60);
1261   return (s);
1262 }
1263
1264 /* wrapper around mutt_write_address() so we can handle very large
1265    recipient lists without needing a huge temporary buffer in memory */
1266 void
1267 mutt_write_address_list(address_t *addr, FILE *fp, int linelen, int display)
1268 {
1269     int first = 1;
1270
1271     while (addr) {
1272         char buf[LONG_STRING];
1273         int len = rfc822_addrcpy(buf, ssizeof(buf), addr, display);
1274
1275         if (!first) {
1276             if (linelen + len > 74) {
1277                 fputs("\n\t", fp);
1278                 linelen = 8;        /* tab is usually about 8 spaces... */
1279             } else
1280             if (addr->mailbox) {
1281                 fputc(' ', fp);
1282                 linelen++;
1283             }
1284         }
1285         first = 0;
1286
1287         linelen += len + 1;
1288         fputs(buf, fp);
1289
1290         if (!addr->group && addr->next && addr->next->mailbox) {
1291             fputc(',', fp);
1292             linelen++;
1293         }
1294
1295         addr = addr->next;
1296     }
1297     fputc ('\n', fp);
1298 }
1299
1300 /* need to write the list in reverse because they are stored in reverse order
1301  * when parsed to speed up threading
1302  */
1303 void mutt_write_references(string_list_t *r, FILE *f)
1304 {
1305     string_list_t *refs[10];
1306     int i;
1307
1308     p_clear(refs, countof(refs));
1309     for (i = 0; i < countof(refs) && r; r = r->next) {
1310         refs[i++] = r;
1311     }
1312
1313     while (i-- > 0) {
1314         fprintf(f, " %s", refs[i]->data);
1315     }
1316 }
1317
1318 static int edit_header(int mode, const char *s)
1319 {
1320     const char *p;
1321     int slen = m_strlen(s);
1322
1323     if (mode != 1 || option(OPTXMAILTO))
1324         return 1;
1325
1326     p = skipspaces(EditorHeaders);
1327     while (*p) {
1328         if (!ascii_strncasecmp(p, s, slen) && p[slen - 1] == ':')
1329             return 1;
1330         p = skipspaces(p + slen);
1331     }
1332
1333     return 0;
1334 }
1335
1336 /* Note: all RFC2047 encoding should be done outside of this routine, except
1337  * for the "real name."  This will allow this routine to be used more than
1338  * once, if necessary.
1339  * 
1340  * Likewise, all IDN processing should happen outside of this routine.
1341  *
1342  * mode == 1  => "lite" mode (used for edit_hdrs)
1343  * mode == 0  => normal mode.  write full header + MIME headers
1344  * mode == -1 => write just the envelope info (used for postponing messages)
1345  */
1346 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1347                               int mode)
1348 {
1349   char buffer[LONG_STRING];
1350   char *p;
1351   string_list_t *tmp = env->userhdrs;
1352   int has_agent = 0;            /* user defined user-agent header field exists */
1353
1354 #ifdef USE_NNTP
1355   if (!option (OPTNEWSSEND))
1356 #endif
1357     if (mode == 0)
1358       fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1359
1360   /* OPTUSEFROM is not consulted here so that we can still write a From:
1361    * field if the user sets it with the `my_hdr' command
1362    */
1363   if (env->from) {
1364     buffer[0] = 0;
1365     rfc822_addrcat(buffer, sizeof(buffer), env->from, 0);
1366     fprintf (fp, "From: %s\n", buffer);
1367   }
1368
1369   if (env->to) {
1370     fputs ("To: ", fp);
1371     mutt_write_address_list (env->to, fp, 4, 0);
1372   }
1373   else if (mode > 0)
1374 #ifdef USE_NNTP
1375     if (!option (OPTNEWSSEND))
1376 #endif
1377       if (edit_header(mode, "To:"))
1378         fputs ("To:\n", fp);
1379
1380   if (env->cc) {
1381     fputs ("Cc: ", fp);
1382     mutt_write_address_list (env->cc, fp, 4, 0);
1383   }
1384   else if (mode > 0)
1385 #ifdef USE_NNTP
1386     if (!option (OPTNEWSSEND))
1387 #endif
1388       if (edit_header(mode, "Cc:"))
1389         fputs ("Cc:\n", fp);
1390
1391   if (env->bcc) {
1392     if (mode != 0 || option (OPTWRITEBCC)) {
1393       fputs ("Bcc: ", fp);
1394       mutt_write_address_list (env->bcc, fp, 5, 0);
1395     }
1396   }
1397   else if (mode > 0)
1398 #ifdef USE_NNTP
1399     if (!option (OPTNEWSSEND))
1400 #endif
1401       if (edit_header(mode, "Bcc:"))
1402         fputs ("Bcc:\n", fp);
1403
1404 #ifdef USE_NNTP
1405   if (env->newsgroups)
1406     fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1407   else if (mode == 1 && option (OPTNEWSSEND) && edit_header(mode, "Newsgroups:"))
1408     fputs ("Newsgroups:\n", fp);
1409
1410   if (env->followup_to)
1411     fprintf (fp, "Followup-To: %s\n", env->followup_to);
1412   else if (mode == 1 && option (OPTNEWSSEND) && edit_header(mode, "Followup-To:"))
1413     fputs ("Followup-To:\n", fp);
1414 #endif
1415
1416   if (env->subject)
1417     fprintf (fp, "Subject: %s\n", env->subject);
1418   else if (mode == 1 && edit_header(mode, "Subject:"))
1419     fputs ("Subject:\n", fp);
1420
1421   /* save message id if the user has set it */
1422   if (env->message_id)
1423     fprintf (fp, "Message-ID: %s\n", env->message_id);
1424
1425   if (env->reply_to) {
1426     fputs ("Reply-To: ", fp);
1427     mutt_write_address_list (env->reply_to, fp, 10, 0);
1428   }
1429   else if (mode > 0 && edit_header(mode, "Reply-To:"))
1430     fputs ("Reply-To:\n", fp);
1431
1432   if (env->mail_followup_to)
1433 #ifdef USE_NNTP
1434     if (!option (OPTNEWSSEND))
1435 #endif
1436     {
1437       fputs ("Mail-Followup-To: ", fp);
1438       mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1439     }
1440
1441   if (mode <= 0) {
1442     if (env->references) {
1443       fputs ("References:", fp);
1444       mutt_write_references (env->references, fp);
1445       fputc ('\n', fp);
1446     }
1447
1448     /* Add the MIME headers */
1449     fputs ("MIME-Version: 1.0\n", fp);
1450     mutt_write_mime_header (attach, fp);
1451   }
1452
1453   if (env->in_reply_to) {
1454     fputs ("In-Reply-To:", fp);
1455     mutt_write_references (env->in_reply_to, fp);
1456     fputc ('\n', fp);
1457   }
1458
1459   /* Add any user defined headers */
1460   for (; tmp; tmp = tmp->next) {
1461     if ((p = strchr (tmp->data, ':'))) {
1462       p = vskipspaces(p + 1);
1463       if (!*p)
1464         continue;               /* don't emit empty fields. */
1465
1466       /* check to see if the user has overridden the user-agent field */
1467       if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1468         has_agent = 1;
1469       }
1470
1471       fputs (tmp->data, fp);
1472       fputc ('\n', fp);
1473     }
1474   }
1475
1476   if (mode == 0 && option (OPTXMAILER) && !has_agent) {
1477     if (mod_core.operating_system) {
1478       fprintf(fp, "User-Agent: %s (%s)\n", mutt_make_version(),
1479               mod_core.operating_system);
1480     } else {
1481       fprintf(fp, "User-Agent: %s\n", mutt_make_version());
1482     }
1483   }
1484
1485   return (ferror (fp) == 0 ? 0 : -1);
1486 }
1487
1488 static void encode_headers (string_list_t * h)
1489 {
1490   char *tmp;
1491   char *p;
1492   int i;
1493
1494   for (; h; h = h->next) {
1495     if (!(p = strchr (h->data, ':')))
1496       continue;
1497
1498     i = p - h->data;
1499     p = vskipspaces(p + 1);
1500     tmp = m_strdup(p);
1501
1502     if (!tmp)
1503       continue;
1504
1505     rfc2047_encode_string (&tmp);
1506     p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1507
1508     sprintf (h->data + i, ": %s", NONULL (tmp));
1509
1510     p_delete(&tmp);
1511   }
1512 }
1513
1514 const char *mutt_fqdn(short may_hide_host)
1515 {
1516   char *p = NULL, *q;
1517
1518   if (mod_core.hostname && mod_core.hostname[0] != '@') {
1519     p = mod_core.hostname;
1520
1521     if (may_hide_host && option (OPTHIDDENHOST)) {
1522       if ((p = strchr(mod_core.hostname, '.')))
1523         p++;
1524
1525       /* sanity check: don't hide the host if
1526          the fqdn is something like detebe.org.  */
1527
1528       if (!p || !(q = strchr(p, '.')))
1529         p = mod_core.hostname;
1530     }
1531   }
1532
1533   return p;
1534 }
1535
1536 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1537 {
1538 #define APPEND_FMT(fmt, arg) \
1539         if (len > 1) {                                  \
1540             int snlen = snprintf(buf, len, fmt, arg);   \
1541             buf += snlen;                               \
1542             len -= snlen;                               \
1543         }
1544
1545 #define APPEND_BYTE(c) \
1546         if (len > 1) {                                  \
1547             *buf++ = c;                                 \
1548             *buf   = '\0';                              \
1549             len--;                                      \
1550         }
1551
1552     time_t now;
1553     struct tm *tm;
1554
1555     now = time (NULL);
1556     tm = gmtime (&now);
1557
1558     while (*fmt) {
1559         static char MsgIdPfx = 'A';
1560         int c = *fmt++;
1561
1562         if (c != '%') {
1563             /* normalized character (we're stricter than RFC2822, 3.6.4) */
1564             APPEND_BYTE((isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.');
1565             continue;
1566         }
1567
1568         switch (*fmt++) {
1569           case 0:
1570             return;
1571           case 'd':
1572             APPEND_FMT("%02d", tm->tm_mday);
1573             break;
1574           case 'h':
1575             APPEND_FMT("%02d", tm->tm_hour);
1576             break;
1577           case 'm':
1578             APPEND_FMT("%02d", tm->tm_mon + 1);
1579             break;
1580           case 'M':
1581             APPEND_FMT("%02d", tm->tm_min);
1582             break;
1583           case 'O':
1584             APPEND_FMT("%lo", (unsigned long)now);
1585             break;
1586           case 'p':
1587             APPEND_FMT("%u", (unsigned int)getpid());
1588             break;
1589           case 'P':
1590             APPEND_FMT("%c", MsgIdPfx);
1591             MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1592             break;
1593           case 'r':
1594             APPEND_FMT("%u", (unsigned int)rand());
1595             break;
1596           case 'R':
1597             APPEND_FMT("%x", (unsigned int)rand());
1598             break;
1599           case 's':
1600             APPEND_FMT("%02d", tm->tm_sec);
1601             break;
1602           case 'T':
1603             APPEND_FMT("%u", (unsigned int) now);
1604             break;
1605           case 'X':
1606             APPEND_FMT("%x", (unsigned int) now);
1607             break;
1608           case 'Y':       /* this will break in the year 10000 ;-) */
1609             APPEND_FMT("%04d", tm->tm_year + 1900);
1610             break;
1611           case '%':
1612             APPEND_BYTE('%');
1613             break;
1614           default:       /* invalid formats are replaced by '.' */
1615             APPEND_BYTE('.');
1616         }
1617     }
1618
1619     *buf = '\0';
1620
1621 #undef APPEND_BYTE
1622 #undef APPEND_FMT
1623 }
1624
1625 static char *mutt_gen_msgid (void)
1626 {
1627     char buf[STRING];
1628     char localpart[STRING];
1629     const char *fqdn;
1630
1631     if (!(fqdn = mutt_fqdn(0)))
1632         fqdn = NONULL(mod_core.shorthost);
1633
1634     mutt_gen_localpart(localpart, sizeof(localpart), MsgIdFormat);
1635     snprintf(buf, sizeof(buf), "<%s@%s>", localpart, fqdn);
1636     return m_strdup(buf);
1637 }
1638
1639 static void alarm_handler (int sig __attribute__ ((unused)))
1640 {
1641   SigAlrm = 1;
1642 }
1643
1644 /* invoke sendmail in a subshell
1645    path (in)            path to program to execute
1646    args (in)            arguments to pass to program
1647    msg (in)             temp file containing message to send
1648    tempfile (out)       if sendmail is put in the background, this points
1649                         to the temporary file containing the stdout of the
1650                         child process */
1651 static int
1652 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1653 {
1654   sigset_t set;
1655   int fd, st;
1656   pid_t pid, ppid;
1657
1658   mutt_block_signals_system ();
1659
1660   sigemptyset (&set);
1661   /* we also don't want to be stopped right now */
1662   sigaddset (&set, SIGTSTP);
1663   sigprocmask (SIG_BLOCK, &set, NULL);
1664
1665   if (MTransport.sendmail_wait >= 0) {
1666     char tmp[_POSIX_PATH_MAX];
1667
1668     mutt_mktemp (tmp);
1669     *tempfile = m_strdup(tmp);
1670   }
1671
1672   if ((pid = fork ()) == 0) {
1673     struct sigaction act, oldalrm;
1674
1675     /* save parent's ID before setsid() */
1676     ppid = getppid ();
1677
1678     /* we want the delivery to continue even after the main process dies,
1679      * so we put ourselves into another session right away
1680      */
1681     setsid ();
1682
1683     /* next we close all open files */
1684     for (fd = 0; fd < getdtablesize(); fd++)
1685       close (fd);
1686
1687     /* now the second fork() */
1688     if ((pid = fork ()) == 0) {
1689       /* "msg" will be opened as stdin */
1690       if (open (msg, O_RDONLY, 0) < 0) {
1691         unlink (msg);
1692         _exit (S_ERR);
1693       }
1694       unlink (msg);
1695
1696       if (MTransport.sendmail_wait >= 0) {
1697         /* *tempfile will be opened as stdout */
1698         if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1699             0)
1700           _exit (S_ERR);
1701         /* redirect stderr to *tempfile too */
1702         if (dup (1) < 0)
1703           _exit (S_ERR);
1704       } else {
1705         if (open ("/dev/null", O_WRONLY | O_APPEND) < 0)        /* stdout */
1706           _exit (S_ERR);
1707         if (open ("/dev/null", O_RDWR | O_APPEND) < 0)  /* stderr */
1708           _exit (S_ERR);
1709       }
1710
1711       execv (path, (char**)args);
1712       _exit (S_ERR);
1713     }
1714     else if (pid == -1) {
1715       unlink (msg);
1716       p_delete(tempfile);
1717       _exit (S_ERR);
1718     }
1719
1720     /* sendmail_wait > 0: interrupt waitpid() after sendmail_wait seconds
1721      * sendmail_wait = 0: wait forever
1722      * sendmail_wait < 0: don't wait
1723      */
1724     if (MTransport.sendmail_wait > 0) {
1725       SigAlrm = 0;
1726       act.sa_handler = alarm_handler;
1727 #ifdef SA_INTERRUPT
1728       /* need to make sure waitpid() is interrupted on SIGALRM */
1729       act.sa_flags = SA_INTERRUPT;
1730 #else
1731       act.sa_flags = 0;
1732 #endif
1733       sigemptyset (&act.sa_mask);
1734       sigaction (SIGALRM, &act, &oldalrm);
1735       alarm (MTransport.sendmail_wait);
1736     }
1737     else if (MTransport.sendmail_wait < 0)
1738       _exit (0xff & EX_OK);
1739
1740     if (waitpid (pid, &st, 0) > 0) {
1741       st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1742       if (MTransport.sendmail_wait && st == (0xff & EX_OK)) {
1743         unlink (*tempfile);     /* no longer needed */
1744         p_delete(tempfile);
1745       }
1746     } else {
1747       st = (MTransport.sendmail_wait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1748       if (MTransport.sendmail_wait > 0) {
1749         unlink (*tempfile);
1750         p_delete(tempfile);
1751       }
1752     }
1753
1754     /* reset alarm; not really needed, but... */
1755     alarm (0);
1756     sigaction (SIGALRM, &oldalrm, NULL);
1757
1758     if (kill (ppid, 0) == -1 && errno == ESRCH) {
1759       /* the parent is already dead */
1760       unlink (*tempfile);
1761       p_delete(tempfile);
1762     }
1763
1764     _exit (st);
1765   }
1766
1767   sigprocmask (SIG_UNBLOCK, &set, NULL);
1768
1769   if (pid != -1 && waitpid (pid, &st, 0) > 0)
1770     st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;     /* return child status */
1771   else
1772     st = S_ERR;                 /* error */
1773
1774   mutt_unblock_signals_system (1);
1775
1776   return (st);
1777 }
1778
1779 static const char **
1780 add_args(const char **args, ssize_t *argslen, ssize_t *argsmax, address_t * addr)
1781 {
1782   for (; addr; addr = addr->next) {
1783     /* weed out group mailboxes, since those are for display only */
1784     if (addr->mailbox && !addr->group) {
1785       if (*argslen == *argsmax)
1786         p_realloc(&args, *argsmax += 5);
1787       args[(*argslen)++] = addr->mailbox;
1788     }
1789   }
1790   return (args);
1791 }
1792
1793 static const char **
1794 add_option(const char **args, ssize_t *argslen, ssize_t *argsmax, const char *s)
1795 {
1796     if (*argslen == *argsmax) {
1797         p_realloc(&args, *argsmax += 5);
1798     }
1799     args[(*argslen)++] = s;
1800     return (args);
1801 }
1802
1803 int mutt_invoke_mta(address_t *from, address_t *to, address_t *cc,
1804                     address_t *bcc, const char *msg, int eightbit)
1805 {
1806   char cmd[LONG_STRING];
1807   char *ps = NULL, *path = NULL, *childout = NULL;
1808   const char **args = NULL;
1809   ssize_t argslen = 0, argsmax = 0;
1810   int i;
1811
1812 #ifdef USE_NNTP
1813   if (option (OPTNEWSSEND)) {
1814     i = nntp_post(msg);
1815     unlink(msg);
1816     return i;
1817   } else
1818 #endif
1819   {
1820     m_strcpy(cmd, sizeof(cmd), MTransport.sendmail);
1821   }
1822
1823   ps = cmd;
1824   i = 0;
1825   while ((ps = strtok(ps, " "))) {
1826     if (argslen == argsmax)
1827       p_realloc(&args, argsmax += 5);
1828
1829     if (i)
1830       args[argslen++] = ps;
1831     else {
1832       path = m_strdup(ps);
1833       ps = strrchr (ps, '/');
1834       if (ps)
1835         ps++;
1836       else
1837         ps = path;
1838       args[argslen++] = ps;
1839     }
1840     ps = NULL;
1841     i++;
1842   }
1843
1844 #ifdef USE_NNTP
1845   if (!option (OPTNEWSSEND)) {
1846 #endif
1847     if (eightbit && MTransport.use_8bitmime)
1848       args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
1849
1850     if (MTransport.use_envelope_from) {
1851       address_t *f = MTransport.envelope_from_address;
1852       if (!f && from && !from->next)
1853         f = from;
1854       if (f) {
1855         args = add_option (args, &argslen, &argsmax, "-f");
1856         args = add_args (args, &argslen, &argsmax, f);
1857       }
1858     }
1859     if (MTransport.dsn_notify) {
1860       args = add_option (args, &argslen, &argsmax, "-N");
1861       args = add_option (args, &argslen, &argsmax, MTransport.dsn_notify);
1862     }
1863     if (MTransport.dsn_return) {
1864       args = add_option (args, &argslen, &argsmax, "-R");
1865       args = add_option (args, &argslen, &argsmax, MTransport.dsn_return);
1866     }
1867     args = add_option (args, &argslen, &argsmax, "--");
1868     args = add_args (args, &argslen, &argsmax, to);
1869     args = add_args (args, &argslen, &argsmax, cc);
1870     args = add_args (args, &argslen, &argsmax, bcc);
1871 #ifdef USE_NNTP
1872   }
1873 #endif
1874
1875   if (argslen >= argsmax)
1876     p_realloc(&args, ++argsmax);
1877
1878   args[argslen++] = NULL;
1879
1880   if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
1881     if (i != S_BKG) {
1882       mutt_error (_("Error sending message, child exited %d (%s)."), i,
1883                   m_strsysexit(i));
1884       if (childout) {
1885         struct stat st;
1886
1887         if (!stat(childout, &st) && st.st_size > 0)
1888           mutt_pager(_("Output of the delivery process"), childout, 0, NULL);
1889       }
1890     }
1891   } else {
1892     unlink (childout);
1893   }
1894
1895   p_delete(&childout);
1896   p_delete(&path);
1897   p_delete(&args);
1898
1899   if (i == (EX_OK & 0xff))
1900     i = 0;
1901   else if (i == S_BKG)
1902     i = 1;
1903   else
1904     i = -1;
1905   return (i);
1906 }
1907
1908 /* For postponing (!final) do the necessary encodings only */
1909 void mutt_prepare_envelope (ENVELOPE * env, int final)
1910 {
1911   if (final) {
1912     if (env->bcc && !(env->to || env->cc)) {
1913       /* some MTA's will put an Apparently-To: header field showing the Bcc:
1914        * recipients if there is no To: or Cc: field, so attempt to suppress
1915        * it by using an empty To: field.
1916        */
1917       env->to = address_new();
1918       env->to->group = 1;
1919       env->to->next  = address_new();
1920       env->to->mailbox = m_strdup("undisclosed-recipients");
1921     }
1922
1923     mutt_set_followup_to(env);
1924
1925     if (!env->message_id && !m_strisempty(MsgIdFormat))
1926       env->message_id = mutt_gen_msgid();
1927   }
1928
1929   /* Take care of 8-bit => 7-bit conversion. */
1930   rfc2047_encode_adrlist(env->to, "To");
1931   rfc2047_encode_adrlist(env->cc, "Cc");
1932   rfc2047_encode_adrlist(env->bcc, "Bcc");
1933   rfc2047_encode_adrlist(env->from, "From");
1934   rfc2047_encode_adrlist(env->mail_followup_to, "Mail-Followup-To");
1935   rfc2047_encode_adrlist(env->reply_to, "Reply-To");
1936
1937   if (env->subject)
1938     rfc2047_encode_string (&env->subject);
1939   encode_headers (env->userhdrs);
1940 }
1941
1942 void mutt_unprepare_envelope (ENVELOPE * env)
1943 {
1944     string_list_t *item;
1945
1946     for (item = env->userhdrs; item; item = item->next)
1947         rfc2047_decode(&item->data);
1948
1949     address_list_wipe(&env->mail_followup_to);
1950
1951     /* back conversions */
1952     rfc2047_decode_adrlist(env->to);
1953     rfc2047_decode_adrlist(env->cc);
1954     rfc2047_decode_adrlist(env->bcc);
1955     rfc2047_decode_adrlist(env->from);
1956     rfc2047_decode_adrlist(env->reply_to);
1957     rfc2047_decode(&env->subject);
1958 }
1959
1960 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
1961                                  const char *resent_from, address_t * env_from)
1962 {
1963   int i, ret = 0;
1964   FILE *f;
1965   char date[STRING], tempfile[_POSIX_PATH_MAX];
1966   MESSAGE *msg = NULL;
1967
1968   if (!h) {
1969     /* Try to bounce each message out, aborting if we get any failures. */
1970     for (i = 0; i < Context->msgcount; i++)
1971       if (Context->hdrs[i]->tagged)
1972         ret |=
1973           _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
1974                                 env_from);
1975     return ret;
1976   }
1977
1978   /* If we failed to open a message, return with error */
1979   if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
1980     return -1;
1981
1982   if (!fp)
1983     fp = msg->fp;
1984
1985   f = m_tempfile(tempfile, sizeof(tempfile), NONULL(mod_core.tmpdir), NULL);
1986   if (f) {
1987     int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
1988
1989     if (!option (OPTBOUNCEDELIVERED))
1990       ch_flags |= CH_WEED_DELIVERED;
1991
1992     fseeko (fp, h->offset, 0);
1993     fprintf (f, "Resent-From: %s", resent_from);
1994     fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
1995     if (!m_strisempty(MsgIdFormat))
1996       fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid());
1997     fputs ("Resent-To: ", f);
1998     mutt_write_address_list (to, f, 11, 0);
1999     mutt_copy_header (fp, h, f, ch_flags, NULL);
2000     fputc ('\n', f);
2001     mutt_copy_bytes (fp, f, h->content->length);
2002     m_fclose(&f);
2003
2004     ret = mutt_invoke_mta(env_from, to, NULL, NULL, tempfile,
2005                           h->content->encoding == ENC8BIT);
2006   }
2007
2008   if (msg)
2009     mx_close_message (&msg);
2010
2011   return ret;
2012 }
2013
2014 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2015 {
2016   address_t *from;
2017   char resent_from[STRING];
2018   int ret;
2019   char *err;
2020
2021   resent_from[0] = '\0';
2022   from = mutt_default_from ();
2023
2024   rfc822_qualify(from, mutt_fqdn(1));
2025
2026   rfc2047_encode_adrlist(from, "Resent-From");
2027   if (mutt_addrlist_to_idna (from, &err)) {
2028     mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2029     return -1;
2030   }
2031   rfc822_addrcat(resent_from, sizeof(resent_from), from, 0);
2032
2033 #ifdef USE_NNTP
2034   unset_option (OPTNEWSSEND);
2035 #endif
2036
2037   ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2038
2039   address_list_wipe(&from);
2040
2041   return ret;
2042 }
2043
2044 static void set_noconv_flags (BODY * b, short flag)
2045 {
2046   for (; b; b = b->next) {
2047     if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2048       set_noconv_flags (b->parts, flag);
2049     else if (b->type == TYPETEXT && b->noconv) {
2050       parameter_setval(&b->parameter, "x-mutt-noconv", flag ? "yes" : NULL);
2051     }
2052   }
2053 }
2054
2055 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2056                     int post, char *fcc)
2057 {
2058   CONTEXT f;
2059   MESSAGE *msg;
2060   char tempfile[_POSIX_PATH_MAX];
2061   FILE *tempfp = NULL;
2062   int r;
2063
2064   if (post)
2065     set_noconv_flags (hdr->content, 1);
2066
2067   if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2068     return (-1);
2069   }
2070
2071   /* We need to add a Content-Length field to avoid problems where a line in
2072    * the message body begins with "From "   
2073    */
2074   if (f.magic == M_MBOX) {
2075     tempfp = m_tempfile(tempfile, sizeof(tempfile), NONULL(mod_core.tmpdir), NULL);
2076     if (!tempfp) {
2077       mutt_error(_("Could not create temporary file"));
2078       mx_close_mailbox (&f, NULL);
2079       return -1;
2080     }
2081   }
2082
2083   hdr->read = !post;            /* make sure to put it in the `cur' directory (maildir) */
2084   if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2085     mx_close_mailbox (&f, NULL);
2086     return (-1);
2087   }
2088
2089   /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2090    * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header() 
2091    * */
2092   mutt_write_rfc822_header(msg->fp, hdr->env, hdr->content, -post);
2093
2094   /* (postponment) if this was a reply of some sort, <msgid> contians the
2095    * Message-ID: of message replied to.  Save it using a special X-Mutt-
2096    * header so it can be picked up if the message is recalled at a later
2097    * point in time.  This will allow the message to be marked as replied if
2098    * the same mailbox is still open.
2099    */
2100   if (post && msgid)
2101     fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2102
2103   /* (postponment) save the Fcc: using a special X-Mutt- header so that
2104    * it can be picked up when the message is recalled 
2105    */
2106   if (post && fcc)
2107     fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2108   fprintf (msg->fp, "Status: RO\n");
2109
2110   /* (postponment) if the mail is to be signed or encrypted, save this info */
2111   if (post && (hdr->security & APPLICATION_PGP)) {
2112     fputs ("X-Mutt-PGP: ", msg->fp);
2113     if (hdr->security & ENCRYPT)
2114       fputc ('E', msg->fp);
2115     if (hdr->security & SIGN) {
2116       fputc ('S', msg->fp);
2117       if (PgpSignAs && *PgpSignAs)
2118         fprintf (msg->fp, "<%s>", PgpSignAs);
2119     }
2120     if (hdr->security & INLINE)
2121       fputc ('I', msg->fp);
2122     fputc ('\n', msg->fp);
2123   }
2124
2125   /* (postponment) if the mail is to be signed or encrypted, save this info */
2126   if (post && (hdr->security & APPLICATION_SMIME)) {
2127     fputs ("X-Mutt-SMIME: ", msg->fp);
2128     if (hdr->security & ENCRYPT) {
2129       fputc ('E', msg->fp);
2130       if (SmimeCryptAlg && *SmimeCryptAlg)
2131         fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2132     }
2133     if (hdr->security & SIGN) {
2134       fputc ('S', msg->fp);
2135       if (SmimeDefaultKey && *SmimeDefaultKey)
2136         fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2137     }
2138     if (hdr->security & INLINE)
2139       fputc ('I', msg->fp);
2140     fputc ('\n', msg->fp);
2141   }
2142
2143   if (tempfp) {
2144     char sasha[LONG_STRING];
2145     int lines = 0;
2146
2147     mutt_write_mime_body (hdr->content, tempfp);
2148
2149     /* make sure the last line ends with a newline.  Emacs doesn't ensure
2150      * this will happen, and it can cause problems parsing the mailbox   
2151      * later.
2152      */
2153     fseeko (tempfp, -1, 2);
2154     if (fgetc (tempfp) != '\n') {
2155       fseeko (tempfp, 0, 2);
2156       fputc ('\n', tempfp);
2157     }
2158
2159     fflush (tempfp);
2160     if (ferror (tempfp)) {
2161       m_fclose(&tempfp);
2162       unlink (tempfile);
2163       mx_commit_message (msg, &f);      /* XXX - really? */
2164       mx_close_message (&msg);
2165       mx_close_mailbox (&f, NULL);
2166       return -1;
2167     }
2168
2169     /* count the number of lines */
2170     rewind (tempfp);
2171     while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2172       lines++;
2173     fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2174     fprintf (msg->fp, "Lines: %d\n\n", lines);
2175
2176     /* copy the body and clean up */
2177     rewind (tempfp);
2178     r = mutt_copy_stream (tempfp, msg->fp);
2179     if (m_fclose(&tempfp) != 0)
2180       r = -1;
2181     /* if there was an error, leave the temp version */
2182     if (!r)
2183       unlink (tempfile);
2184   } else {
2185     fputc ('\n', msg->fp);      /* finish off the header */
2186     r = mutt_write_mime_body (hdr->content, msg->fp);
2187   }
2188
2189   if (mx_commit_message (msg, &f) != 0)
2190     r = -1;
2191   mx_close_message (&msg);
2192   mx_close_mailbox (&f, NULL);
2193
2194   if (post)
2195     set_noconv_flags (hdr->content, 0);
2196
2197   return r;
2198 }