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