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