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