remove antiquated cruft: please, who use MMDF's nowadays ?!
[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/curses.h>
19 #include <lib-mx/mx.h>
20
21 #include "mutt.h"
22 #include "handler.h"
23 #include "crypt.h"
24 #include "recvattach.h"
25 #include "copy.h"
26 #include "pager.h"
27 #include "charset.h"
28 #include "mutt_idna.h"
29 #include "send_smtp.h"
30
31 #ifdef USE_NNTP
32 #include <nntp/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 #ifdef HAVE_ICONV
538   iconv_t cd1, *cd;
539   char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
540   const char *ib, *ub;
541   char *ob;
542   ssize_t ibl, obl, ubl, ubl1, n, ret;
543   int i;
544   CONTENT *infos;
545   CONTENT_STATE *states;
546   ssize_t *score;
547
548   cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
549   if (cd1 == MUTT_ICONV_ERROR)
550     return -1;
551
552   cd = p_new(iconv_t, ncodes);
553   score = p_new(ssize_t, ncodes);
554   states = p_new(CONTENT_STATE, ncodes);
555   infos = p_new(CONTENT, ncodes);
556
557   for (i = 0; i < ncodes; i++)
558     if (ascii_strcasecmp (tocodes[i], "UTF-8"))
559       cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
560     else
561       /* Special case for conversion to UTF-8 */
562       cd[i] = MUTT_ICONV_ERROR, score[i] = -1;
563
564   rewind (file);
565   ibl = 0;
566   for (;;) {
567
568     /* Try to fill input buffer */
569     n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
570     ibl += n;
571
572     /* Convert to UTF-8 */
573     ib = bufi;
574     ob = bufu, obl = sizeof (bufu);
575     n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
576     if (n == -1 && ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
577       ret = -1;
578       break;
579     }
580     ubl1 = ob - bufu;
581
582     /* Convert from UTF-8 */
583     for (i = 0; i < ncodes; i++)
584       if (cd[i] != MUTT_ICONV_ERROR && score[i] != -1) {
585         ub = bufu, ubl = ubl1;
586         ob = bufo, obl = sizeof (bufo);
587         n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
588         if (n == -1) {
589           score[i] = -1;
590         }
591         else {
592           score[i] += n;
593           update_content_info (&infos[i], &states[i], bufo, ob - bufo);
594         }
595       }
596       else if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1)
597         /* Special case for conversion to UTF-8 */
598         update_content_info (&infos[i], &states[i], bufu, ubl1);
599
600     if (ibl)
601       /* Save unused input */
602       memmove (bufi, ib, ibl);
603     else if (!ubl1 && ib < bufi + sizeof (bufi)) {
604       ret = 0;
605       break;
606     }
607   }
608
609   if (!ret) {
610     /* Find best score */
611     ret = -1;
612     for (i = 0; i < ncodes; i++) {
613       if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1) {
614         /* Special case for conversion to UTF-8 */
615         *tocode = i;
616         ret = 0;
617         break;
618       }
619       else if (cd[i] == MUTT_ICONV_ERROR || score[i] == -1)
620         continue;
621       else if (ret == -1 || score[i] < ret) {
622         *tocode = i;
623         ret = score[i];
624         if (!ret)
625           break;
626       }
627     }
628     if (ret != -1) {
629       memcpy (info, &infos[*tocode], sizeof (CONTENT));
630       update_content_info (info, &states[*tocode], 0, 0);       /* EOF */
631     }
632   }
633
634   for (i = 0; i < ncodes; i++)
635     if (cd[i] != MUTT_ICONV_ERROR)
636       iconv_close (cd[i]);
637
638   iconv_close (cd1);
639   p_delete(&cd);
640   p_delete(&infos);
641   p_delete(&score);
642   p_delete(&states);
643
644   return ret;
645 #else
646   return -1;
647 #endif /* !HAVE_ICONV */
648 }
649
650 /*
651  * Find the first of the fromcodes that gives a valid conversion and
652  * the best charset conversion of the file into one of the tocodes. If
653  * successful, set *fromcode and *tocode to dynamically allocated
654  * strings, set CONTENT *info, and return the number of characters
655  * converted inexactly. If no conversion was possible, return -1.
656  *
657  * Both fromcodes and tocodes may be colon-separated lists of charsets.
658  * However, if fromcode is zero then fromcodes is assumed to be the
659  * name of a single charset even if it contains a colon.
660  */
661 static ssize_t convert_file_from_to (FILE * file,
662                                     const char *fromcodes,
663                                     const char *tocodes, char **fromcode,
664                                     char **tocode, CONTENT * info)
665 {
666   char *fcode;
667   char **tcode;
668   const char *c, *c1;
669   ssize_t ret;
670   int ncodes, i, cn;
671
672   /* Count the tocodes */
673   ncodes = 0;
674   for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
675     if ((c1 = strchr (c, ':')) == c)
676       continue;
677     ++ncodes;
678   }
679
680   /* Copy them */
681   tcode = p_new(char *, ncodes);
682   for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
683     if ((c1 = strchr (c, ':')) == c)
684       continue;
685     tcode[i] = m_substrdup(c, c1);
686   }
687
688   ret = -1;
689   if (fromcode) {
690     /* Try each fromcode in turn */
691     for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
692       if ((c1 = strchr (c, ':')) == c)
693         continue;
694       fcode = m_substrdup(c, c1);
695
696       ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
697                              &cn, info);
698       if (ret != -1) {
699         *fromcode = fcode;
700         *tocode = tcode[cn];
701         tcode[cn] = 0;
702         break;
703       }
704       p_delete(&fcode);
705     }
706   }
707   else {
708     /* There is only one fromcode */
709     ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
710                            &cn, info);
711     if (ret != -1) {
712       *tocode = tcode[cn];
713       tcode[cn] = 0;
714     }
715   }
716
717   /* Free memory */
718   for (i = 0; i < ncodes; i++)
719     p_delete(&tcode[i]);
720
721   p_delete(tcode);
722
723   return ret;
724 }
725
726 /* 
727  * Analyze the contents of a file to determine which MIME encoding to use.
728  * Also set the body charset, sometimes, or not.
729  */
730 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
731 {
732   CONTENT *info;
733   CONTENT_STATE state;
734   FILE *fp = NULL;
735   char *fromcode = NULL;
736   char *tocode = NULL;
737   char buffer[100];
738   char chsbuf[STRING];
739   ssize_t r;
740
741   struct stat sb;
742
743   if (b && !fname)
744     fname = b->filename;
745
746   if (stat (fname, &sb) == -1) {
747     mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
748     return NULL;
749   }
750
751   if (!S_ISREG (sb.st_mode)) {
752     mutt_error (_("%s isn't a regular file."), fname);
753     return NULL;
754   }
755
756   if ((fp = fopen (fname, "r")) == NULL) {
757     return (NULL);
758   }
759
760   info = p_new(CONTENT, 1);
761   p_clear(&state, 1);
762
763   if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
764     const char *chs = parameter_getval(b->parameter, "charset");
765     char *fchs = b->use_disp && !m_strisempty(MCharset.file_charset)
766         ? FileCharset : MCharset.charset;
767     if (MCharset.charset && (chs || MCharset.send_charset) &&
768         convert_file_from_to (fp, fchs, chs ? chs : MCharset.send_charset,
769                               &fromcode, &tocode, info) != -1) {
770       if (!chs) {
771         charset_canonicalize (chsbuf, sizeof (chsbuf), tocode);
772         parameter_setval(&b->parameter, "charset", chsbuf);
773       }
774       b->file_charset = fromcode;
775       p_delete(&tocode);
776       m_fclose(&fp);
777       return info;
778     }
779   }
780
781   rewind (fp);
782   while ((r = fread (buffer, 1, sizeof (buffer), fp)))
783     update_content_info (info, &state, buffer, r);
784   update_content_info (info, &state, 0, 0);
785
786   m_fclose(&fp);
787
788   if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
789     parameter_setval(&b->parameter, "charset",
790                      (!info->hibin ? "us-ascii"
791                       : MCharset.charset && !charset_is_us_ascii(MCharset.charset)
792                                          ? MCharset.charset : "unknown-8bit"));
793
794   return info;
795 }
796
797 /* Given a file with path ``s'', see if there is a registered MIME type.
798  * returns the major MIME type, and copies the subtype to ``d''.  First look
799  * for ~/.mime.types, then look in a system mime.types if we can find one.
800  * The longest match is used so that we can match `ps.gz' when `gz' also
801  * exists.
802  */
803
804 int mutt_lookup_mime_type (BODY * att, const char *path)
805 {
806   FILE *f;
807   char *p, *q, *ct;
808   char buf[LONG_STRING];
809   char subtype[STRING], xtype[STRING];
810   int count;
811   int szf, sze, cur_sze;
812   int type;
813
814   *subtype = '\0';
815   *xtype = '\0';
816   type = TYPEOTHER;
817   cur_sze = 0;
818
819   szf = m_strlen(path);
820
821   for (count = 0; count < 4; count++) {
822     /*
823      * can't use strtok() because we use it in an inner loop below, so use
824      * a switch statement here instead.
825      */
826     switch (count) {
827     case 0:
828       snprintf(buf, sizeof (buf), "%s/.mime.types", NONULL(MCore.homedir));
829       break;
830     case 1:
831       m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
832       break;
833     case 2:
834       m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
835       break;
836     case 3:
837       m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
838       break;
839     default:
840       goto bye;                 /* shouldn't happen */
841     }
842
843     if ((f = fopen (buf, "r")) != NULL) {
844       while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
845         /* weed out any comments */
846         if ((p = strchr (buf, '#')))
847           *p = 0;
848
849         /* remove any leading space. */
850         ct = vskipspaces(buf);
851
852         /* position on the next field in this line */
853         if ((p = strpbrk (ct, " \t")) == NULL)
854           continue;
855         *p++ = 0;
856         p = vskipspaces(p);
857
858         /* cycle through the file extensions */
859         while ((p = strtok (p, " \t\n"))) {
860           sze = m_strlen(p);
861           if ((sze > cur_sze) && (szf >= sze) &&
862               (m_strcasecmp(path + szf - sze, p) == 0
863                || ascii_strcasecmp (path + szf - sze, p) == 0)
864               && (szf == sze || path[szf - sze - 1] == '.'))
865           {
866             /* get the content-type */
867
868             if ((p = strchr (ct, '/')) == NULL) {
869               /* malformed line, just skip it. */
870               break;
871             }
872             *p++ = 0;
873
874             for (q = p; *q && !ISSPACE (*q); q++);
875
876             m_strncpy(subtype, sizeof(subtype), p, q - p);
877
878             if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
879               m_strcpy(xtype, sizeof(xtype), ct);
880
881             cur_sze = sze;
882           }
883           p = NULL;
884         }
885       }
886       m_fclose(&f);
887     }
888   }
889
890 bye:
891
892   if (type != TYPEOTHER || *xtype != '\0') {
893     att->type = type;
894     m_strreplace(&att->subtype, subtype);
895     m_strreplace(&att->xtype, xtype);
896   }
897
898   return (type);
899 }
900
901 void mutt_message_to_7bit (BODY * a, FILE * fp)
902 {
903   char temp[_POSIX_PATH_MAX];
904   char *line = NULL;
905   FILE *fpin = NULL;
906   FILE *fpout = NULL;
907   struct stat sb;
908
909   if (!a->filename && fp)
910     fpin = fp;
911   else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
912     mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
913     return;
914   }
915   else {
916     a->offset = 0;
917     if (stat (a->filename, &sb) == -1) {
918       mutt_perror ("stat");
919       m_fclose(&fpin);
920     }
921     a->length = sb.st_size;
922   }
923
924   fpout = m_tempfile(temp, sizeof(temp), NONULL(MCore.tmpdir), NULL);
925   if (!fpout) {
926     mutt_error(_("Could not create temporary file"));
927     goto cleanup;
928   }
929
930   fseeko (fpin, a->offset, 0);
931   a->parts = mutt_parse_messageRFC822 (fpin, a);
932
933   transform_to_7bit (a->parts, fpin);
934
935   mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
936                  CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
937
938   fputs ("MIME-Version: 1.0\n", fpout);
939   mutt_write_mime_header (a->parts, fpout);
940   fputc ('\n', fpout);
941   mutt_write_mime_body (a->parts, fpout);
942
943 cleanup:
944   p_delete(&line);
945
946   if (fpin && !fp)
947     m_fclose(&fpin);
948   if (fpout)
949     m_fclose(&fpout);
950   else
951     return;
952
953   a->encoding = ENC7BIT;
954   a->d_filename = a->filename;
955   if (a->filename && a->unlink)
956     unlink (a->filename);
957   a->filename = m_strdup(temp);
958   a->unlink = 1;
959   if (stat (a->filename, &sb) == -1) {
960     mutt_perror ("stat");
961     return;
962   }
963   a->length = sb.st_size;
964   body_list_wipe(&a->parts);
965   a->hdr->content = NULL;
966 }
967
968 static void transform_to_7bit (BODY * a, FILE * fpin)
969 {
970   char buff[_POSIX_PATH_MAX];
971   STATE s;
972   struct stat sb;
973
974   p_clear(&s, 1);
975   for (; a; a = a->next) {
976     if (a->type == TYPEMULTIPART) {
977       if (a->encoding != ENC7BIT)
978         a->encoding = ENC7BIT;
979
980       transform_to_7bit (a->parts, fpin);
981     }
982     else if (mutt_is_message_type(a)) {
983       mutt_message_to_7bit (a, fpin);
984     }
985     else {
986       a->noconv = 1;
987       a->force_charset = 1;
988
989       s.fpout = m_tempfile(buff, sizeof(buff), NONULL(MCore.tmpdir), NULL);
990       if (!s.fpout) {
991         mutt_error(_("Could not create temporary file"));
992         return;
993       }
994       s.fpin = fpin;
995       mutt_decode_attachment (a, &s);
996       m_fclose(&s.fpout);
997       a->d_filename = a->filename;
998       a->filename = m_strdup(buff);
999       a->unlink = 1;
1000       if (stat (a->filename, &sb) == -1) {
1001         mutt_perror ("stat");
1002         return;
1003       }
1004       a->length = sb.st_size;
1005
1006       mutt_update_encoding (a);
1007       if (a->encoding == ENC8BIT)
1008         a->encoding = ENCQUOTEDPRINTABLE;
1009       else if (a->encoding == ENCBINARY)
1010         a->encoding = ENCBASE64;
1011     }
1012   }
1013 }
1014
1015 /* determine which Content-Transfer-Encoding to use */
1016 static void mutt_set_encoding (BODY * b, CONTENT * info)
1017 {
1018   char send_charset[STRING];
1019
1020   if (b->type == TYPETEXT) {
1021     char *chsname =
1022       mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1023     if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1024         || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1025       b->encoding = ENCQUOTEDPRINTABLE;
1026     else if (info->hibin)
1027       b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1028     else
1029       b->encoding = ENC7BIT;
1030   }
1031   else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1032     if (info->lobin || info->hibin) {
1033       if (option (OPTALLOW8BIT) && !info->lobin)
1034         b->encoding = ENC8BIT;
1035       else
1036         mutt_message_to_7bit (b, NULL);
1037     }
1038     else
1039       b->encoding = ENC7BIT;
1040   }
1041   else if (b->type == TYPEAPPLICATION
1042            && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1043     b->encoding = ENC7BIT;
1044   else
1045   {
1046     /* Determine which encoding is smaller  */
1047     if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1048         3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1049       b->encoding = ENCBASE64;
1050     else
1051       b->encoding = ENCQUOTEDPRINTABLE;
1052   }
1053 }
1054
1055 void mutt_stamp_attachment (BODY * a)
1056 {
1057   a->stamp = time (NULL);
1058 }
1059
1060 /* Get a body's character set */
1061
1062 char *mutt_get_body_charset(char *d, ssize_t dlen, BODY * b)
1063 {
1064     const char *p;
1065
1066     if (b && b->type != TYPETEXT)
1067         return NULL;
1068
1069     p = b ? parameter_getval(b->parameter, "charset") : NULL;
1070     charset_canonicalize(d, dlen, p);
1071     return d;
1072 }
1073
1074
1075 /* Assumes called from send mode where BODY->filename points to actual file */
1076 void mutt_update_encoding (BODY * a)
1077 {
1078   CONTENT *info;
1079   char chsbuff[STRING];
1080
1081   /* override noconv when it's us-ascii */
1082   if (charset_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1083     a->noconv = 0;
1084
1085   if (!a->force_charset && !a->noconv)
1086     parameter_delval(&a->parameter, "charset");
1087
1088   if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1089     return;
1090
1091   mutt_set_encoding (a, info);
1092   mutt_stamp_attachment (a);
1093
1094   p_delete(&a->content);
1095   a->content = info;
1096
1097 }
1098
1099 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1100 {
1101   char buffer[LONG_STRING];
1102   BODY *body;
1103   FILE *fp;
1104   int cmflags, chflags;
1105   int pgp = hdr->security;
1106
1107   if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1108       (hdr->security & ENCRYPT)) {
1109   }
1110
1111   fp = m_tempfile(buffer, sizeof(buffer), NONULL(MCore.tmpdir), NULL);
1112   if (!fp)
1113     return NULL;
1114
1115   body = body_new();
1116   body->type = TYPEMESSAGE;
1117   body->subtype = m_strdup("rfc822");
1118   body->filename = m_strdup(buffer);
1119   body->unlink = 1;
1120   body->use_disp = 0;
1121   body->disposition = DISPINLINE;
1122   body->noconv = 1;
1123
1124   mutt_parse_mime_message (ctx, hdr);
1125
1126   chflags = CH_XMIT;
1127   cmflags = 0;
1128
1129   /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1130   if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1131     chflags |= CH_MIME | CH_TXTPLAIN;
1132     cmflags = M_CM_DECODE | M_CM_CHARCONV;
1133     pgp &= ~(PGPENCRYPT|SMIMEENCRYPT);
1134   }
1135   else if (option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1136     if (mutt_is_multipart_encrypted (hdr->content)) {
1137       chflags |= CH_MIME | CH_NONEWLINE;
1138       cmflags = M_CM_DECODE_PGP;
1139       pgp &= ~PGPENCRYPT;
1140     }
1141     else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1142       chflags |= CH_MIME | CH_TXTPLAIN;
1143       cmflags = M_CM_DECODE | M_CM_CHARCONV;
1144       pgp &= ~PGPENCRYPT;
1145     }
1146     else if (mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1147       chflags |= CH_MIME | CH_TXTPLAIN;
1148       cmflags = M_CM_DECODE | M_CM_CHARCONV;
1149       pgp &= ~SMIMEENCRYPT;
1150     }
1151   }
1152
1153   mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1154
1155   fflush (fp);
1156   rewind (fp);
1157
1158   body->hdr = header_new();
1159   body->hdr->offset = 0;
1160   /* we don't need the user headers here */
1161   body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1162   body->hdr->security = pgp;
1163   mutt_update_encoding (body);
1164   body->parts = body->hdr->content;
1165
1166   m_fclose(&fp);
1167
1168   return (body);
1169 }
1170
1171 BODY *mutt_make_file_attach (const char *path)
1172 {
1173   BODY *att;
1174   CONTENT *info;
1175
1176   att = body_new();
1177   att->filename = m_strdup(path);
1178
1179   /* Attempt to determine the appropriate content-type based on the filename
1180    * suffix.
1181    */
1182   mutt_lookup_mime_type (att, path);
1183
1184   if ((info = mutt_get_content_info (path, att)) == NULL) {
1185     body_list_wipe(&att);
1186     return NULL;
1187   }
1188
1189   if (!att->subtype) {
1190     if (info->lobin == 0
1191         || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1192       /*
1193        * Statistically speaking, there should be more than 10% "lobin" 
1194        * chars if this is really a binary file...
1195        */
1196       att->type = TYPETEXT;
1197       att->subtype = m_strdup("plain");
1198     } else {
1199       att->type = TYPEAPPLICATION;
1200       att->subtype = m_strdup("octet-stream");
1201     }
1202   }
1203
1204   mutt_update_encoding (att);
1205   return att;
1206 }
1207
1208 static int get_toplevel_encoding (BODY * a)
1209 {
1210     int e = ENC7BIT;
1211
1212     for (; a; a = a->next) {
1213         if (a->encoding == ENCBINARY)
1214             return ENCBINARY;
1215
1216         if (a->encoding == ENC8BIT)
1217             e = ENC8BIT;
1218     }
1219
1220     return e;
1221 }
1222
1223 BODY *mutt_make_multipart (BODY * b)
1224 {
1225   BODY *new;
1226
1227   new = body_new();
1228   new->type = TYPEMULTIPART;
1229   new->subtype = m_strdup("mixed");
1230   new->encoding = get_toplevel_encoding (b);
1231   parameter_set_boundary(&new->parameter);
1232   new->use_disp = 0;
1233   new->disposition = DISPINLINE;
1234   new->parts = b;
1235
1236   return new;
1237 }
1238
1239 /* remove the multipart body if it exists */
1240 BODY *mutt_remove_multipart (BODY * b)
1241 {
1242   BODY *t;
1243
1244   if (b->parts) {
1245     t = b;
1246     b = b->parts;
1247     t->parts = NULL;
1248     body_list_wipe(&t);
1249   }
1250   return b;
1251 }
1252
1253 char *mutt_make_date (char *s, ssize_t len)
1254 {
1255   time_t t = time (NULL);
1256   struct tm *l = localtime (&t);
1257   time_t tz = mutt_local_tz (t);
1258
1259   tz /= 60;
1260
1261   snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1262             Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1263             l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1264             (int) tz / 60, (int) abs (tz) % 60);
1265   return (s);
1266 }
1267
1268 /* wrapper around mutt_write_address() so we can handle very large
1269    recipient lists without needing a huge temporary buffer in memory */
1270 void
1271 mutt_write_address_list(address_t *addr, FILE *fp, int linelen, int display)
1272 {
1273     int first = 1;
1274
1275     while (addr) {
1276         char buf[LONG_STRING];
1277         int len = rfc822_addrcpy(buf, ssizeof(buf), addr, display);
1278
1279         if (!first) {
1280             if (linelen + len > 74) {
1281                 fputs("\n\t", fp);
1282                 linelen = 8;        /* tab is usually about 8 spaces... */
1283             } else
1284             if (addr->mailbox) {
1285                 fputc(' ', fp);
1286                 linelen++;
1287             }
1288         }
1289         first = 0;
1290
1291         linelen += len + 1;
1292         fputs(buf, fp);
1293
1294         if (!addr->group && addr->next && addr->next->mailbox) {
1295             fputc(',', fp);
1296             linelen++;
1297         }
1298
1299         addr = addr->next;
1300     }
1301     fputc ('\n', fp);
1302 }
1303
1304 /* need to write the list in reverse because they are stored in reverse order
1305  * when parsed to speed up threading
1306  */
1307 void mutt_write_references(string_list_t *r, FILE *f)
1308 {
1309     string_list_t *refs[10];
1310     int i;
1311
1312     p_clear(refs, countof(refs));
1313     for (i = 0; i < countof(refs) && r; r = r->next) {
1314         refs[i++] = r;
1315     }
1316
1317     while (i-- > 0) {
1318         fprintf(f, " %s", refs[i]->data);
1319     }
1320 }
1321
1322 static int edit_header(int mode, const char *s)
1323 {
1324     const char *p;
1325     int slen = m_strlen(s);
1326
1327     if (mode != 1 || option(OPTXMAILTO))
1328         return 1;
1329
1330     p = skipspaces(EditorHeaders);
1331     while (*p) {
1332         if (!ascii_strncasecmp(p, s, slen) && p[slen - 1] == ':')
1333             return 1;
1334         p = skipspaces(p + slen);
1335     }
1336
1337     return 0;
1338 }
1339
1340 /* Note: all RFC2047 encoding should be done outside of this routine, except
1341  * for the "real name."  This will allow this routine to be used more than
1342  * once, if necessary.
1343  * 
1344  * Likewise, all IDN processing should happen outside of this routine.
1345  *
1346  * mode == 1  => "lite" mode (used for edit_hdrs)
1347  * mode == 0  => normal mode.  write full header + MIME headers
1348  * mode == -1 => write just the envelope info (used for postponing messages)
1349  * 
1350  * privacy != 0 => will omit any headers which may identify the user.
1351  *               Output generated is suitable for being sent through
1352  *               anonymous remailer chains.
1353  *
1354  */
1355 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1356                               int mode, int privacy)
1357 {
1358   char buffer[LONG_STRING];
1359   char *p;
1360   string_list_t *tmp = env->userhdrs;
1361   int has_agent = 0;            /* user defined user-agent header field exists */
1362
1363 #ifdef USE_NNTP
1364   if (!option (OPTNEWSSEND))
1365 #endif
1366     if (mode == 0 && !privacy)
1367       fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1368
1369   /* OPTUSEFROM is not consulted here so that we can still write a From:
1370    * field if the user sets it with the `my_hdr' command
1371    */
1372   if (env->from && !privacy) {
1373     buffer[0] = 0;
1374     rfc822_addrcat(buffer, sizeof(buffer), env->from, 0);
1375     fprintf (fp, "From: %s\n", buffer);
1376   }
1377
1378   if (env->to) {
1379     fputs ("To: ", fp);
1380     mutt_write_address_list (env->to, fp, 4, 0);
1381   }
1382   else if (mode > 0)
1383 #ifdef USE_NNTP
1384     if (!option (OPTNEWSSEND))
1385 #endif
1386       if (edit_header(mode, "To:"))
1387         fputs ("To:\n", fp);
1388
1389   if (env->cc) {
1390     fputs ("Cc: ", fp);
1391     mutt_write_address_list (env->cc, fp, 4, 0);
1392   }
1393   else if (mode > 0)
1394 #ifdef USE_NNTP
1395     if (!option (OPTNEWSSEND))
1396 #endif
1397       if (edit_header(mode, "Cc:"))
1398         fputs ("Cc:\n", fp);
1399
1400   if (env->bcc) {
1401     if (mode != 0 || option (OPTWRITEBCC)) {
1402       fputs ("Bcc: ", fp);
1403       mutt_write_address_list (env->bcc, fp, 5, 0);
1404     }
1405   }
1406   else if (mode > 0)
1407 #ifdef USE_NNTP
1408     if (!option (OPTNEWSSEND))
1409 #endif
1410       if (edit_header(mode, "Bcc:"))
1411         fputs ("Bcc:\n", fp);
1412
1413 #ifdef USE_NNTP
1414   if (env->newsgroups)
1415     fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1416   else if (mode == 1 && option (OPTNEWSSEND) && edit_header(mode, "Newsgroups:"))
1417     fputs ("Newsgroups:\n", fp);
1418
1419   if (env->followup_to)
1420     fprintf (fp, "Followup-To: %s\n", env->followup_to);
1421   else if (mode == 1 && option (OPTNEWSSEND) && edit_header(mode, "Followup-To:"))
1422     fputs ("Followup-To:\n", fp);
1423
1424   if (env->x_comment_to)
1425     fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1426   else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1427            edit_header(mode, "X-Comment-To:"))
1428     fputs ("X-Comment-To:\n", fp);
1429 #endif
1430
1431   if (env->subject)
1432     fprintf (fp, "Subject: %s\n", env->subject);
1433   else if (mode == 1 && edit_header(mode, "Subject:"))
1434     fputs ("Subject:\n", fp);
1435
1436   /* save message id if the user has set it */
1437   if (env->message_id && !privacy)
1438     fprintf (fp, "Message-ID: %s\n", env->message_id);
1439
1440   if (env->reply_to) {
1441     fputs ("Reply-To: ", fp);
1442     mutt_write_address_list (env->reply_to, fp, 10, 0);
1443   }
1444   else if (mode > 0 && edit_header(mode, "Reply-To:"))
1445     fputs ("Reply-To:\n", fp);
1446
1447   if (env->mail_followup_to)
1448 #ifdef USE_NNTP
1449     if (!option (OPTNEWSSEND))
1450 #endif
1451     {
1452       fputs ("Mail-Followup-To: ", fp);
1453       mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1454     }
1455
1456   if (mode <= 0) {
1457     if (env->references) {
1458       fputs ("References:", fp);
1459       mutt_write_references (env->references, fp);
1460       fputc ('\n', fp);
1461     }
1462
1463     /* Add the MIME headers */
1464     fputs ("MIME-Version: 1.0\n", fp);
1465     mutt_write_mime_header (attach, fp);
1466   }
1467
1468   if (env->in_reply_to) {
1469     fputs ("In-Reply-To:", fp);
1470     mutt_write_references (env->in_reply_to, fp);
1471     fputc ('\n', fp);
1472   }
1473
1474   /* Add any user defined headers */
1475   for (; tmp; tmp = tmp->next) {
1476     if ((p = strchr (tmp->data, ':'))) {
1477       p = vskipspaces(p + 1);
1478       if (!*p)
1479         continue;               /* don't emit empty fields. */
1480
1481       /* check to see if the user has overridden the user-agent field */
1482       if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1483         has_agent = 1;
1484         if (privacy)
1485           continue;
1486       }
1487
1488       fputs (tmp->data, fp);
1489       fputc ('\n', fp);
1490     }
1491   }
1492
1493   if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1494     if (MCore.operating_system) {
1495       fprintf(fp, "User-Agent: %s (%s)\n", mutt_make_version(),
1496               MCore.operating_system);
1497     } else {
1498       fprintf(fp, "User-Agent: %s\n", mutt_make_version());
1499     }
1500   }
1501
1502   return (ferror (fp) == 0 ? 0 : -1);
1503 }
1504
1505 static void encode_headers (string_list_t * h)
1506 {
1507   char *tmp;
1508   char *p;
1509   int i;
1510
1511   for (; h; h = h->next) {
1512     if (!(p = strchr (h->data, ':')))
1513       continue;
1514
1515     i = p - h->data;
1516     p = vskipspaces(p + 1);
1517     tmp = m_strdup(p);
1518
1519     if (!tmp)
1520       continue;
1521
1522     rfc2047_encode_string (&tmp);
1523     p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1524
1525     sprintf (h->data + i, ": %s", NONULL (tmp));
1526
1527     p_delete(&tmp);
1528   }
1529 }
1530
1531 const char *mutt_fqdn(short may_hide_host)
1532 {
1533   char *p = NULL, *q;
1534
1535   if (MCore.hostname && MCore.hostname[0] != '@') {
1536     p = MCore.hostname;
1537
1538     if (may_hide_host && option (OPTHIDDENHOST)) {
1539       if ((p = strchr(MCore.hostname, '.')))
1540         p++;
1541
1542       /* sanity check: don't hide the host if
1543          the fqdn is something like detebe.org.  */
1544
1545       if (!p || !(q = strchr(p, '.')))
1546         p = MCore.hostname;
1547     }
1548   }
1549
1550   return p;
1551 }
1552
1553 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1554 {
1555 #define APPEND_FMT(fmt, arg) \
1556         if (len > 1) {                                  \
1557             int snlen = snprintf(buf, len, fmt, arg);   \
1558             buf += snlen;                               \
1559             len -= snlen;                               \
1560         }
1561
1562 #define APPEND_BYTE(c) \
1563         if (len > 1) {                                  \
1564             *buf++ = c;                                 \
1565             *buf   = '\0';                              \
1566             len--;                                      \
1567         }
1568
1569     time_t now;
1570     struct tm *tm;
1571
1572     now = time (NULL);
1573     tm = gmtime (&now);
1574
1575     while (*fmt) {
1576         static char MsgIdPfx = 'A';
1577         int c = *fmt++;
1578
1579         if (c != '%') {
1580             /* normalized character (we're stricter than RFC2822, 3.6.4) */
1581             APPEND_BYTE((isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.');
1582             continue;
1583         }
1584
1585         switch (*fmt++) {
1586           case 0:
1587             return;
1588           case 'd':
1589             APPEND_FMT("%02d", tm->tm_mday);
1590             break;
1591           case 'h':
1592             APPEND_FMT("%02d", tm->tm_hour);
1593             break;
1594           case 'm':
1595             APPEND_FMT("%02d", tm->tm_mon + 1);
1596             break;
1597           case 'M':
1598             APPEND_FMT("%02d", tm->tm_min);
1599             break;
1600           case 'O':
1601             APPEND_FMT("%lo", (unsigned long)now);
1602             break;
1603           case 'p':
1604             APPEND_FMT("%u", (unsigned int)getpid());
1605             break;
1606           case 'P':
1607             APPEND_FMT("%c", MsgIdPfx);
1608             MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1609             break;
1610           case 'r':
1611             APPEND_FMT("%u", (unsigned int)rand());
1612             break;
1613           case 'R':
1614             APPEND_FMT("%x", (unsigned int)rand());
1615             break;
1616           case 's':
1617             APPEND_FMT("%02d", tm->tm_sec);
1618             break;
1619           case 'T':
1620             APPEND_FMT("%u", (unsigned int) now);
1621             break;
1622           case 'X':
1623             APPEND_FMT("%x", (unsigned int) now);
1624             break;
1625           case 'Y':       /* this will break in the year 10000 ;-) */
1626             APPEND_FMT("%04d", tm->tm_year + 1900);
1627             break;
1628           case '%':
1629             APPEND_BYTE('%');
1630             break;
1631           default:       /* invalid formats are replaced by '.' */
1632             APPEND_BYTE('.');
1633         }
1634     }
1635
1636     *buf = '\0';
1637
1638 #undef APPEND_BYTE
1639 #undef APPEND_FMT
1640 }
1641
1642 static char *mutt_gen_msgid (void)
1643 {
1644     char buf[STRING];
1645     char localpart[STRING];
1646     const char *fqdn;
1647
1648     if (!(fqdn = mutt_fqdn(0)))
1649         fqdn = NONULL(MCore.shorthost);
1650
1651     mutt_gen_localpart(localpart, sizeof(localpart), MsgIdFormat);
1652     snprintf(buf, sizeof(buf), "<%s@%s>", localpart, fqdn);
1653     return m_strdup(buf);
1654 }
1655
1656 static RETSIGTYPE alarm_handler (int sig __attribute__ ((unused)))
1657 {
1658   SigAlrm = 1;
1659 }
1660
1661 /* invoke sendmail in a subshell
1662    path (in)            path to program to execute
1663    args (in)            arguments to pass to program
1664    msg (in)             temp file containing message to send
1665    tempfile (out)       if sendmail is put in the background, this points
1666                         to the temporary file containing the stdout of the
1667                         child process */
1668 static int
1669 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1670 {
1671   sigset_t set;
1672   int fd, st;
1673   pid_t pid, ppid;
1674
1675   mutt_block_signals_system ();
1676
1677   sigemptyset (&set);
1678   /* we also don't want to be stopped right now */
1679   sigaddset (&set, SIGTSTP);
1680   sigprocmask (SIG_BLOCK, &set, NULL);
1681
1682   if (MTransport.sendmail_wait >= 0) {
1683     char tmp[_POSIX_PATH_MAX];
1684
1685     mutt_mktemp (tmp);
1686     *tempfile = m_strdup(tmp);
1687   }
1688
1689   if ((pid = fork ()) == 0) {
1690     struct sigaction act, oldalrm;
1691
1692     /* save parent's ID before setsid() */
1693     ppid = getppid ();
1694
1695     /* we want the delivery to continue even after the main process dies,
1696      * so we put ourselves into another session right away
1697      */
1698     setsid ();
1699
1700     /* next we close all open files */
1701     for (fd = 0; fd < getdtablesize(); fd++)
1702       close (fd);
1703
1704     /* now the second fork() */
1705     if ((pid = fork ()) == 0) {
1706       /* "msg" will be opened as stdin */
1707       if (open (msg, O_RDONLY, 0) < 0) {
1708         unlink (msg);
1709         _exit (S_ERR);
1710       }
1711       unlink (msg);
1712
1713       if (MTransport.sendmail_wait >= 0) {
1714         /* *tempfile will be opened as stdout */
1715         if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1716             0)
1717           _exit (S_ERR);
1718         /* redirect stderr to *tempfile too */
1719         if (dup (1) < 0)
1720           _exit (S_ERR);
1721       } else {
1722         if (open ("/dev/null", O_WRONLY | O_APPEND) < 0)        /* stdout */
1723           _exit (S_ERR);
1724         if (open ("/dev/null", O_RDWR | O_APPEND) < 0)  /* stderr */
1725           _exit (S_ERR);
1726       }
1727
1728       execv (path, (char**)args);
1729       _exit (S_ERR);
1730     }
1731     else if (pid == -1) {
1732       unlink (msg);
1733       p_delete(tempfile);
1734       _exit (S_ERR);
1735     }
1736
1737     /* sendmail_wait > 0: interrupt waitpid() after sendmail_wait seconds
1738      * sendmail_wait = 0: wait forever
1739      * sendmail_wait < 0: don't wait
1740      */
1741     if (MTransport.sendmail_wait > 0) {
1742       SigAlrm = 0;
1743       act.sa_handler = alarm_handler;
1744 #ifdef SA_INTERRUPT
1745       /* need to make sure waitpid() is interrupted on SIGALRM */
1746       act.sa_flags = SA_INTERRUPT;
1747 #else
1748       act.sa_flags = 0;
1749 #endif
1750       sigemptyset (&act.sa_mask);
1751       sigaction (SIGALRM, &act, &oldalrm);
1752       alarm (MTransport.sendmail_wait);
1753     }
1754     else if (MTransport.sendmail_wait < 0)
1755       _exit (0xff & EX_OK);
1756
1757     if (waitpid (pid, &st, 0) > 0) {
1758       st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1759       if (MTransport.sendmail_wait && st == (0xff & EX_OK)) {
1760         unlink (*tempfile);     /* no longer needed */
1761         p_delete(tempfile);
1762       }
1763     } else {
1764       st = (MTransport.sendmail_wait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1765       if (MTransport.sendmail_wait > 0) {
1766         unlink (*tempfile);
1767         p_delete(tempfile);
1768       }
1769     }
1770
1771     /* reset alarm; not really needed, but... */
1772     alarm (0);
1773     sigaction (SIGALRM, &oldalrm, NULL);
1774
1775     if (kill (ppid, 0) == -1 && errno == ESRCH) {
1776       /* the parent is already dead */
1777       unlink (*tempfile);
1778       p_delete(tempfile);
1779     }
1780
1781     _exit (st);
1782   }
1783
1784   sigprocmask (SIG_UNBLOCK, &set, NULL);
1785
1786   if (pid != -1 && waitpid (pid, &st, 0) > 0)
1787     st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;     /* return child status */
1788   else
1789     st = S_ERR;                 /* error */
1790
1791   mutt_unblock_signals_system (1);
1792
1793   return (st);
1794 }
1795
1796 static const char **
1797 add_args(const char **args, ssize_t *argslen, ssize_t *argsmax, address_t * addr)
1798 {
1799   for (; addr; addr = addr->next) {
1800     /* weed out group mailboxes, since those are for display only */
1801     if (addr->mailbox && !addr->group) {
1802       if (*argslen == *argsmax)
1803         p_realloc(&args, *argsmax += 5);
1804       args[(*argslen)++] = addr->mailbox;
1805     }
1806   }
1807   return (args);
1808 }
1809
1810 static const char **
1811 add_option(const char **args, ssize_t *argslen, ssize_t *argsmax, const char *s)
1812 {
1813     if (*argslen == *argsmax) {
1814         p_realloc(&args, *argsmax += 5);
1815     }
1816     args[(*argslen)++] = s;
1817     return (args);
1818 }
1819
1820 static int mutt_invoke_sendmail (address_t * from,        /* the sender */
1821                                  address_t * to, address_t * cc, address_t * bcc,     /* recips */
1822                                  const char *msg,       /* file containing message */
1823                                  int eightbit)
1824 {                               /* message contains 8bit chars */
1825   char cmd[LONG_STRING];
1826   char *ps = NULL, *path = NULL, *childout = NULL;
1827   const char **args = NULL;
1828   ssize_t argslen = 0, argsmax = 0;
1829   int i;
1830
1831 #ifdef USE_NNTP
1832   if (option (OPTNEWSSEND)) {
1833     m_strformat(cmd, sizeof(cmd), 0, Inews, nntp_format_str, 0, 0);
1834     if (m_strisempty(cmd)) {
1835       i = nntp_post (msg);
1836       unlink (msg);
1837       return i;
1838     }
1839   } else
1840 #endif
1841   {
1842     m_strcpy(cmd, sizeof(cmd), MTransport.sendmail);
1843   }
1844
1845   ps = cmd;
1846   i = 0;
1847   while ((ps = strtok(ps, " "))) {
1848     if (argslen == argsmax)
1849       p_realloc(&args, argsmax += 5);
1850
1851     if (i)
1852       args[argslen++] = ps;
1853     else {
1854       path = m_strdup(ps);
1855       ps = strrchr (ps, '/');
1856       if (ps)
1857         ps++;
1858       else
1859         ps = path;
1860       args[argslen++] = ps;
1861     }
1862     ps = NULL;
1863     i++;
1864   }
1865
1866 #ifdef USE_NNTP
1867   if (!option (OPTNEWSSEND)) {
1868 #endif
1869     if (eightbit && MTransport.use_8bitmime)
1870       args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
1871
1872     if (MTransport.use_envelope_from) {
1873       address_t *f = MTransport.envelope_from_address;
1874       if (!f && from && !from->next)
1875         f = from;
1876       if (f) {
1877         args = add_option (args, &argslen, &argsmax, "-f");
1878         args = add_args (args, &argslen, &argsmax, f);
1879       }
1880     }
1881     if (MTransport.dsn_notify) {
1882       args = add_option (args, &argslen, &argsmax, "-N");
1883       args = add_option (args, &argslen, &argsmax, MTransport.dsn_notify);
1884     }
1885     if (MTransport.dsn_return) {
1886       args = add_option (args, &argslen, &argsmax, "-R");
1887       args = add_option (args, &argslen, &argsmax, MTransport.dsn_return);
1888     }
1889     args = add_option (args, &argslen, &argsmax, "--");
1890     args = add_args (args, &argslen, &argsmax, to);
1891     args = add_args (args, &argslen, &argsmax, cc);
1892     args = add_args (args, &argslen, &argsmax, bcc);
1893 #ifdef USE_NNTP
1894   }
1895 #endif
1896
1897   if (argslen >= argsmax)
1898     p_realloc(&args, ++argsmax);
1899
1900   args[argslen++] = NULL;
1901
1902   if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
1903     if (i != S_BKG) {
1904       mutt_error (_("Error sending message, child exited %d (%s)."), i,
1905                   m_strsysexit(i));
1906       if (childout) {
1907         struct stat st;
1908
1909         if (!stat(childout, &st) && st.st_size > 0)
1910           mutt_pager(_("Output of the delivery process"), childout, 0, NULL);
1911       }
1912     }
1913   } else {
1914     unlink (childout);
1915   }
1916
1917   p_delete(&childout);
1918   p_delete(&path);
1919   p_delete(&args);
1920
1921   if (i == (EX_OK & 0xff))
1922     i = 0;
1923   else if (i == S_BKG)
1924     i = 1;
1925   else
1926     i = -1;
1927   return (i);
1928 }
1929
1930 int mutt_invoke_mta (address_t * from,    /* the sender */
1931                      address_t * to, address_t * cc, address_t * bcc, /* recips */
1932                      const char *msg,   /* file containing message */
1933                      int eightbit)
1934 {                               /* message contains 8bit chars */
1935 #ifdef USE_LIBESMTP
1936 #ifdef USE_NNTP
1937   if (!option (OPTNEWSSEND))
1938 #endif
1939     if (SmtpHost)
1940       return send_smtp_invoke (from, to, cc, bcc, msg, eightbit);
1941 #endif
1942
1943   return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
1944 }
1945
1946 /* For postponing (!final) do the necessary encodings only */
1947 void mutt_prepare_envelope (ENVELOPE * env, int final)
1948 {
1949   if (final) {
1950     if (env->bcc && !(env->to || env->cc)) {
1951       /* some MTA's will put an Apparently-To: header field showing the Bcc:
1952        * recipients if there is no To: or Cc: field, so attempt to suppress
1953        * it by using an empty To: field.
1954        */
1955       env->to = address_new();
1956       env->to->group = 1;
1957       env->to->next  = address_new();
1958       env->to->mailbox = m_strdup("undisclosed-recipients");
1959     }
1960
1961     mutt_set_followup_to(env);
1962
1963     if (!env->message_id && !m_strisempty(MsgIdFormat))
1964       env->message_id = mutt_gen_msgid();
1965   }
1966
1967   /* Take care of 8-bit => 7-bit conversion. */
1968   rfc2047_encode_adrlist(env->to, "To");
1969   rfc2047_encode_adrlist(env->cc, "Cc");
1970   rfc2047_encode_adrlist(env->bcc, "Bcc");
1971   rfc2047_encode_adrlist(env->from, "From");
1972   rfc2047_encode_adrlist(env->mail_followup_to, "Mail-Followup-To");
1973   rfc2047_encode_adrlist(env->reply_to, "Reply-To");
1974
1975   if (env->subject)
1976     rfc2047_encode_string (&env->subject);
1977   encode_headers (env->userhdrs);
1978 }
1979
1980 void mutt_unprepare_envelope (ENVELOPE * env)
1981 {
1982     string_list_t *item;
1983
1984     for (item = env->userhdrs; item; item = item->next)
1985         rfc2047_decode(&item->data);
1986
1987     address_list_wipe(&env->mail_followup_to);
1988
1989     /* back conversions */
1990     rfc2047_decode_adrlist(env->to);
1991     rfc2047_decode_adrlist(env->cc);
1992     rfc2047_decode_adrlist(env->bcc);
1993     rfc2047_decode_adrlist(env->from);
1994     rfc2047_decode_adrlist(env->reply_to);
1995     rfc2047_decode(&env->subject);
1996 }
1997
1998 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
1999                                  const char *resent_from, address_t * env_from)
2000 {
2001   int i, ret = 0;
2002   FILE *f;
2003   char date[STRING], tempfile[_POSIX_PATH_MAX];
2004   MESSAGE *msg = NULL;
2005
2006   if (!h) {
2007     /* Try to bounce each message out, aborting if we get any failures. */
2008     for (i = 0; i < Context->msgcount; i++)
2009       if (Context->hdrs[i]->tagged)
2010         ret |=
2011           _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2012                                 env_from);
2013     return ret;
2014   }
2015
2016   /* If we failed to open a message, return with error */
2017   if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2018     return -1;
2019
2020   if (!fp)
2021     fp = msg->fp;
2022
2023   f = m_tempfile(tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL);
2024   if (f) {
2025     int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2026
2027     if (!option (OPTBOUNCEDELIVERED))
2028       ch_flags |= CH_WEED_DELIVERED;
2029
2030     fseeko (fp, h->offset, 0);
2031     fprintf (f, "Resent-From: %s", resent_from);
2032     fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2033     if (!m_strisempty(MsgIdFormat))
2034       fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid());
2035     fputs ("Resent-To: ", f);
2036     mutt_write_address_list (to, f, 11, 0);
2037     mutt_copy_header (fp, h, f, ch_flags, NULL);
2038     fputc ('\n', f);
2039     mutt_copy_bytes (fp, f, h->content->length);
2040     m_fclose(&f);
2041
2042     ret = mutt_invoke_mta(env_from, to, NULL, NULL, tempfile,
2043                           h->content->encoding == ENC8BIT);
2044   }
2045
2046   if (msg)
2047     mx_close_message (&msg);
2048
2049   return ret;
2050 }
2051
2052 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2053 {
2054   address_t *from;
2055   char resent_from[STRING];
2056   int ret;
2057   char *err;
2058
2059   resent_from[0] = '\0';
2060   from = mutt_default_from ();
2061
2062   rfc822_qualify(from, mutt_fqdn(1));
2063
2064   rfc2047_encode_adrlist(from, "Resent-From");
2065   if (mutt_addrlist_to_idna (from, &err)) {
2066     mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2067     return -1;
2068   }
2069   rfc822_addrcat(resent_from, sizeof(resent_from), from, 0);
2070
2071 #ifdef USE_NNTP
2072   unset_option (OPTNEWSSEND);
2073 #endif
2074
2075   ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2076
2077   address_list_wipe(&from);
2078
2079   return ret;
2080 }
2081
2082 static void set_noconv_flags (BODY * b, short flag)
2083 {
2084   for (; b; b = b->next) {
2085     if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2086       set_noconv_flags (b->parts, flag);
2087     else if (b->type == TYPETEXT && b->noconv) {
2088       parameter_setval(&b->parameter, "x-mutt-noconv", flag ? "yes" : NULL);
2089     }
2090   }
2091 }
2092
2093 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2094                     int post, char *fcc)
2095 {
2096   CONTEXT f;
2097   MESSAGE *msg;
2098   char tempfile[_POSIX_PATH_MAX];
2099   FILE *tempfp = NULL;
2100   int r;
2101
2102   if (post)
2103     set_noconv_flags (hdr->content, 1);
2104
2105   if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2106     return (-1);
2107   }
2108
2109   /* We need to add a Content-Length field to avoid problems where a line in
2110    * the message body begins with "From "   
2111    */
2112   if (f.magic == M_MBOX) {
2113     tempfp = m_tempfile(tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL);
2114     if (!tempfp) {
2115       mutt_error(_("Could not create temporary file"));
2116       mx_close_mailbox (&f, NULL);
2117       return -1;
2118     }
2119   }
2120
2121   hdr->read = !post;            /* make sure to put it in the `cur' directory (maildir) */
2122   if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2123     mx_close_mailbox (&f, NULL);
2124     return (-1);
2125   }
2126
2127   /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2128    * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header() 
2129    * */
2130   mutt_write_rfc822_header(msg->fp, hdr->env, hdr->content, -post, 0);
2131
2132   /* (postponment) if this was a reply of some sort, <msgid> contians the
2133    * Message-ID: of message replied to.  Save it using a special X-Mutt-
2134    * header so it can be picked up if the message is recalled at a later
2135    * point in time.  This will allow the message to be marked as replied if
2136    * the same mailbox is still open.
2137    */
2138   if (post && msgid)
2139     fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2140
2141   /* (postponment) save the Fcc: using a special X-Mutt- header so that
2142    * it can be picked up when the message is recalled 
2143    */
2144   if (post && fcc)
2145     fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2146   fprintf (msg->fp, "Status: RO\n");
2147
2148   /* (postponment) if the mail is to be signed or encrypted, save this info */
2149   if (post && (hdr->security & APPLICATION_PGP)) {
2150     fputs ("X-Mutt-PGP: ", msg->fp);
2151     if (hdr->security & ENCRYPT)
2152       fputc ('E', msg->fp);
2153     if (hdr->security & SIGN) {
2154       fputc ('S', msg->fp);
2155       if (PgpSignAs && *PgpSignAs)
2156         fprintf (msg->fp, "<%s>", PgpSignAs);
2157     }
2158     if (hdr->security & INLINE)
2159       fputc ('I', msg->fp);
2160     fputc ('\n', msg->fp);
2161   }
2162
2163   /* (postponment) if the mail is to be signed or encrypted, save this info */
2164   if (post && (hdr->security & APPLICATION_SMIME)) {
2165     fputs ("X-Mutt-SMIME: ", msg->fp);
2166     if (hdr->security & ENCRYPT) {
2167       fputc ('E', msg->fp);
2168       if (SmimeCryptAlg && *SmimeCryptAlg)
2169         fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2170     }
2171     if (hdr->security & SIGN) {
2172       fputc ('S', msg->fp);
2173       if (SmimeDefaultKey && *SmimeDefaultKey)
2174         fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2175     }
2176     if (hdr->security & INLINE)
2177       fputc ('I', msg->fp);
2178     fputc ('\n', msg->fp);
2179   }
2180
2181   /* (postponement) if the mail is to be sent through a mixmaster 
2182    * chain, save that information
2183    */
2184   if (post && hdr->chain && hdr->chain) {
2185     string_list_t *p;
2186
2187     fputs ("X-Mutt-Mix:", msg->fp);
2188     for (p = hdr->chain; p; p = p->next)
2189       fprintf (msg->fp, " %s", (char *) p->data);
2190
2191     fputc ('\n', msg->fp);
2192   }
2193
2194   if (tempfp) {
2195     char sasha[LONG_STRING];
2196     int lines = 0;
2197
2198     mutt_write_mime_body (hdr->content, tempfp);
2199
2200     /* make sure the last line ends with a newline.  Emacs doesn't ensure
2201      * this will happen, and it can cause problems parsing the mailbox   
2202      * later.
2203      */
2204     fseeko (tempfp, -1, 2);
2205     if (fgetc (tempfp) != '\n') {
2206       fseeko (tempfp, 0, 2);
2207       fputc ('\n', tempfp);
2208     }
2209
2210     fflush (tempfp);
2211     if (ferror (tempfp)) {
2212       m_fclose(&tempfp);
2213       unlink (tempfile);
2214       mx_commit_message (msg, &f);      /* XXX - really? */
2215       mx_close_message (&msg);
2216       mx_close_mailbox (&f, NULL);
2217       return -1;
2218     }
2219
2220     /* count the number of lines */
2221     rewind (tempfp);
2222     while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2223       lines++;
2224     fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2225     fprintf (msg->fp, "Lines: %d\n\n", lines);
2226
2227     /* copy the body and clean up */
2228     rewind (tempfp);
2229     r = mutt_copy_stream (tempfp, msg->fp);
2230     if (m_fclose(&tempfp) != 0)
2231       r = -1;
2232     /* if there was an error, leave the temp version */
2233     if (!r)
2234       unlink (tempfile);
2235   } else {
2236     fputc ('\n', msg->fp);      /* finish off the header */
2237     r = mutt_write_mime_body (hdr->content, msg->fp);
2238   }
2239
2240   if (mx_commit_message (msg, &f) != 0)
2241     r = -1;
2242   mx_close_message (&msg);
2243   mx_close_mailbox (&f, NULL);
2244
2245   if (post)
2246     set_noconv_flags (hdr->content, 0);
2247
2248   return r;
2249 }