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