lots of simplifications.
[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-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[SHORT_STRING];
343   char send_charset[SHORT_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 ? ((FileCharset && *FileCharset) ?
770                                 FileCharset : Charset) : Charset;
771     if (Charset && (chs || SendCharset) &&
772         convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
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                       : Charset && !charset_is_us_ascii(Charset) ? Charset : "unknown-8bit"));
796
797   return info;
798 }
799
800 /* Given a file with path ``s'', see if there is a registered MIME type.
801  * returns the major MIME type, and copies the subtype to ``d''.  First look
802  * for ~/.mime.types, then look in a system mime.types if we can find one.
803  * The longest match is used so that we can match `ps.gz' when `gz' also
804  * exists.
805  */
806
807 int mutt_lookup_mime_type (BODY * att, const char *path)
808 {
809   FILE *f;
810   char *p, *q, *ct;
811   char buf[LONG_STRING];
812   char subtype[STRING], xtype[STRING];
813   int count;
814   int szf, sze, cur_sze;
815   int type;
816
817   *subtype = '\0';
818   *xtype = '\0';
819   type = TYPEOTHER;
820   cur_sze = 0;
821
822   szf = m_strlen(path);
823
824   for (count = 0; count < 4; count++) {
825     /*
826      * can't use strtok() because we use it in an inner loop below, so use
827      * a switch statement here instead.
828      */
829     switch (count) {
830     case 0:
831       snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL (Homedir));
832       break;
833     case 1:
834       m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
835       break;
836     case 2:
837       m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
838       break;
839     case 3:
840       m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
841       break;
842     default:
843       goto bye;                 /* shouldn't happen */
844     }
845
846     if ((f = fopen (buf, "r")) != NULL) {
847       while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
848         /* weed out any comments */
849         if ((p = strchr (buf, '#')))
850           *p = 0;
851
852         /* remove any leading space. */
853         ct = vskipspaces(buf);
854
855         /* position on the next field in this line */
856         if ((p = strpbrk (ct, " \t")) == NULL)
857           continue;
858         *p++ = 0;
859         p = vskipspaces(p);
860
861         /* cycle through the file extensions */
862         while ((p = strtok (p, " \t\n"))) {
863           sze = m_strlen(p);
864           if ((sze > cur_sze) && (szf >= sze) &&
865               (m_strcasecmp(path + szf - sze, p) == 0
866                || ascii_strcasecmp (path + szf - sze, p) == 0)
867               && (szf == sze || path[szf - sze - 1] == '.'))
868           {
869             /* get the content-type */
870
871             if ((p = strchr (ct, '/')) == NULL) {
872               /* malformed line, just skip it. */
873               break;
874             }
875             *p++ = 0;
876
877             for (q = p; *q && !ISSPACE (*q); q++);
878
879             m_strncpy(subtype, sizeof(subtype), p, q - p);
880
881             if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
882               m_strcpy(xtype, sizeof(xtype), ct);
883
884             cur_sze = sze;
885           }
886           p = NULL;
887         }
888       }
889       m_fclose(&f);
890     }
891   }
892
893 bye:
894
895   if (type != TYPEOTHER || *xtype != '\0') {
896     att->type = type;
897     m_strreplace(&att->subtype, subtype);
898     m_strreplace(&att->xtype, xtype);
899   }
900
901   return (type);
902 }
903
904 void mutt_message_to_7bit (BODY * a, FILE * fp)
905 {
906   char temp[_POSIX_PATH_MAX];
907   char *line = NULL;
908   FILE *fpin = NULL;
909   FILE *fpout = NULL;
910   struct stat sb;
911
912   if (!a->filename && fp)
913     fpin = fp;
914   else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
915     mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
916     return;
917   }
918   else {
919     a->offset = 0;
920     if (stat (a->filename, &sb) == -1) {
921       mutt_perror ("stat");
922       m_fclose(&fpin);
923     }
924     a->length = sb.st_size;
925   }
926
927   fpout = m_tempfile(temp, sizeof(temp), NONULL(Tempdir), NULL);
928   if (!fpout) {
929     mutt_error(_("Could not create temporary file"));
930     goto cleanup;
931   }
932
933   fseeko (fpin, a->offset, 0);
934   a->parts = mutt_parse_messageRFC822 (fpin, a);
935
936   transform_to_7bit (a->parts, fpin);
937
938   mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
939                  CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
940
941   fputs ("MIME-Version: 1.0\n", fpout);
942   mutt_write_mime_header (a->parts, fpout);
943   fputc ('\n', fpout);
944   mutt_write_mime_body (a->parts, fpout);
945
946 cleanup:
947   p_delete(&line);
948
949   if (fpin && !fp)
950     m_fclose(&fpin);
951   if (fpout)
952     m_fclose(&fpout);
953   else
954     return;
955
956   a->encoding = ENC7BIT;
957   a->d_filename = a->filename;
958   if (a->filename && a->unlink)
959     unlink (a->filename);
960   a->filename = m_strdup(temp);
961   a->unlink = 1;
962   if (stat (a->filename, &sb) == -1) {
963     mutt_perror ("stat");
964     return;
965   }
966   a->length = sb.st_size;
967   body_list_wipe(&a->parts);
968   a->hdr->content = NULL;
969 }
970
971 static void transform_to_7bit (BODY * a, FILE * fpin)
972 {
973   char buff[_POSIX_PATH_MAX];
974   STATE s;
975   struct stat sb;
976
977   p_clear(&s, 1);
978   for (; a; a = a->next) {
979     if (a->type == TYPEMULTIPART) {
980       if (a->encoding != ENC7BIT)
981         a->encoding = ENC7BIT;
982
983       transform_to_7bit (a->parts, fpin);
984     }
985     else if (mutt_is_message_type (a->type, a->subtype)) {
986       mutt_message_to_7bit (a, fpin);
987     }
988     else {
989       a->noconv = 1;
990       a->force_charset = 1;
991
992       s.fpout = m_tempfile(buff, sizeof(buff), NONULL(Tempdir), NULL);
993       if (!s.fpout) {
994         mutt_error(_("Could not create temporary file"));
995         return;
996       }
997       s.fpin = fpin;
998       mutt_decode_attachment (a, &s);
999       m_fclose(&s.fpout);
1000       a->d_filename = a->filename;
1001       a->filename = m_strdup(buff);
1002       a->unlink = 1;
1003       if (stat (a->filename, &sb) == -1) {
1004         mutt_perror ("stat");
1005         return;
1006       }
1007       a->length = sb.st_size;
1008
1009       mutt_update_encoding (a);
1010       if (a->encoding == ENC8BIT)
1011         a->encoding = ENCQUOTEDPRINTABLE;
1012       else if (a->encoding == ENCBINARY)
1013         a->encoding = ENCBASE64;
1014     }
1015   }
1016 }
1017
1018 /* determine which Content-Transfer-Encoding to use */
1019 static void mutt_set_encoding (BODY * b, CONTENT * info)
1020 {
1021   char send_charset[SHORT_STRING];
1022
1023   if (b->type == TYPETEXT) {
1024     char *chsname =
1025       mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1026     if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1027         || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1028       b->encoding = ENCQUOTEDPRINTABLE;
1029     else if (info->hibin)
1030       b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1031     else
1032       b->encoding = ENC7BIT;
1033   }
1034   else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1035     if (info->lobin || info->hibin) {
1036       if (option (OPTALLOW8BIT) && !info->lobin)
1037         b->encoding = ENC8BIT;
1038       else
1039         mutt_message_to_7bit (b, NULL);
1040     }
1041     else
1042       b->encoding = ENC7BIT;
1043   }
1044   else if (b->type == TYPEAPPLICATION
1045            && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1046     b->encoding = ENC7BIT;
1047   else
1048   {
1049     /* Determine which encoding is smaller  */
1050     if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1051         3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1052       b->encoding = ENCBASE64;
1053     else
1054       b->encoding = ENCQUOTEDPRINTABLE;
1055   }
1056 }
1057
1058 void mutt_stamp_attachment (BODY * a)
1059 {
1060   a->stamp = time (NULL);
1061 }
1062
1063 /* Get a body's character set */
1064
1065 char *mutt_get_body_charset(char *d, ssize_t dlen, BODY * b)
1066 {
1067     const char *p;
1068
1069     if (b && b->type != TYPETEXT)
1070         return NULL;
1071
1072     p = b ? parameter_getval(b->parameter, "charset") : NULL;
1073     charset_canonicalize(d, dlen, p);
1074     return d;
1075 }
1076
1077
1078 /* Assumes called from send mode where BODY->filename points to actual file */
1079 void mutt_update_encoding (BODY * a)
1080 {
1081   CONTENT *info;
1082   char chsbuff[STRING];
1083
1084   /* override noconv when it's us-ascii */
1085   if (charset_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1086     a->noconv = 0;
1087
1088   if (!a->force_charset && !a->noconv)
1089     parameter_delval(&a->parameter, "charset");
1090
1091   if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1092     return;
1093
1094   mutt_set_encoding (a, info);
1095   mutt_stamp_attachment (a);
1096
1097   p_delete(&a->content);
1098   a->content = info;
1099
1100 }
1101
1102 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1103 {
1104   char buffer[LONG_STRING];
1105   BODY *body;
1106   FILE *fp;
1107   int cmflags, chflags;
1108   int pgp = hdr->security;
1109
1110   if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1111       (hdr->security & ENCRYPT)) {
1112     if (!crypt_valid_passphrase (hdr->security))
1113       return (NULL);
1114   }
1115
1116   fp = m_tempfile(buffer, sizeof(buffer), NONULL(Tempdir), NULL);
1117   if (!fp)
1118     return NULL;
1119
1120   body = body_new();
1121   body->type = TYPEMESSAGE;
1122   body->subtype = m_strdup("rfc822");
1123   body->filename = m_strdup(buffer);
1124   body->unlink = 1;
1125   body->use_disp = 0;
1126   body->disposition = DISPINLINE;
1127   body->noconv = 1;
1128
1129   mutt_parse_mime_message (ctx, hdr);
1130
1131   chflags = CH_XMIT;
1132   cmflags = 0;
1133
1134   /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1135   if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1136     chflags |= CH_MIME | CH_TXTPLAIN;
1137     cmflags = M_CM_DECODE | M_CM_CHARCONV;
1138     pgp &= ~(PGPENCRYPT|SMIMEENCRYPT);
1139   }
1140   else if (option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1141     if (mutt_is_multipart_encrypted (hdr->content)) {
1142       chflags |= CH_MIME | CH_NONEWLINE;
1143       cmflags = M_CM_DECODE_PGP;
1144       pgp &= ~PGPENCRYPT;
1145     }
1146     else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1147       chflags |= CH_MIME | CH_TXTPLAIN;
1148       cmflags = M_CM_DECODE | M_CM_CHARCONV;
1149       pgp &= ~PGPENCRYPT;
1150     }
1151     else if (mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1152       chflags |= CH_MIME | CH_TXTPLAIN;
1153       cmflags = M_CM_DECODE | M_CM_CHARCONV;
1154       pgp &= ~SMIMEENCRYPT;
1155     }
1156   }
1157
1158   mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1159
1160   fflush (fp);
1161   rewind (fp);
1162
1163   body->hdr = header_new();
1164   body->hdr->offset = 0;
1165   /* we don't need the user headers here */
1166   body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1167   body->hdr->security = pgp;
1168   mutt_update_encoding (body);
1169   body->parts = body->hdr->content;
1170
1171   m_fclose(&fp);
1172
1173   return (body);
1174 }
1175
1176 BODY *mutt_make_file_attach (const char *path)
1177 {
1178   BODY *att;
1179   CONTENT *info;
1180
1181   att = body_new();
1182   att->filename = m_strdup(path);
1183
1184   /* Attempt to determine the appropriate content-type based on the filename
1185    * suffix.
1186    */
1187   mutt_lookup_mime_type (att, path);
1188
1189   if ((info = mutt_get_content_info (path, att)) == NULL) {
1190     body_list_wipe(&att);
1191     return NULL;
1192   }
1193
1194   if (!att->subtype) {
1195     if (info->lobin == 0
1196         || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1197       /*
1198        * Statistically speaking, there should be more than 10% "lobin" 
1199        * chars if this is really a binary file...
1200        */
1201       att->type = TYPETEXT;
1202       att->subtype = m_strdup("plain");
1203     } else {
1204       att->type = TYPEAPPLICATION;
1205       att->subtype = m_strdup("octet-stream");
1206     }
1207   }
1208
1209   mutt_update_encoding (att);
1210   return att;
1211 }
1212
1213 static int get_toplevel_encoding (BODY * a)
1214 {
1215   int e = ENC7BIT;
1216
1217   for (; a; a = a->next) {
1218     if (a->encoding == ENCBINARY)
1219       return (ENCBINARY);
1220     else if (a->encoding == ENC8BIT)
1221       e = ENC8BIT;
1222   }
1223
1224   return (e);
1225 }
1226
1227 BODY *mutt_make_multipart (BODY * b)
1228 {
1229   BODY *new;
1230
1231   new = body_new();
1232   new->type = TYPEMULTIPART;
1233   new->subtype = m_strdup("mixed");
1234   new->encoding = get_toplevel_encoding (b);
1235   parameter_set_boundary(&new->parameter);
1236   new->use_disp = 0;
1237   new->disposition = DISPINLINE;
1238   new->parts = b;
1239
1240   return new;
1241 }
1242
1243 /* remove the multipart body if it exists */
1244 BODY *mutt_remove_multipart (BODY * b)
1245 {
1246   BODY *t;
1247
1248   if (b->parts) {
1249     t = b;
1250     b = b->parts;
1251     t->parts = NULL;
1252     body_list_wipe(&t);
1253   }
1254   return b;
1255 }
1256
1257 char *mutt_make_date (char *s, ssize_t len)
1258 {
1259   time_t t = time (NULL);
1260   struct tm *l = localtime (&t);
1261   time_t tz = mutt_local_tz (t);
1262
1263   tz /= 60;
1264
1265   snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1266             Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1267             l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1268             (int) tz / 60, (int) abs (tz) % 60);
1269   return (s);
1270 }
1271
1272 /* wrapper around mutt_write_address() so we can handle very large
1273    recipient lists without needing a huge temporary buffer in memory */
1274 void mutt_write_address_list (address_t * adr, FILE * fp, int linelen,
1275                               int display)
1276 {
1277   address_t *tmp;
1278   char buf[LONG_STRING];
1279   int count = 0;
1280   int len;
1281
1282   while (adr) {
1283     tmp = adr->next;
1284     adr->next = NULL;
1285     buf[0] = 0;
1286     rfc822_write_address (buf, sizeof (buf), adr, display);
1287     len = m_strlen(buf);
1288     if (count && linelen + len > 74) {
1289       fputs ("\n\t", fp);
1290       linelen = len + 8;        /* tab is usually about 8 spaces... */
1291     }
1292     else {
1293       if (count && adr->mailbox) {
1294         fputc (' ', fp);
1295         linelen++;
1296       }
1297       linelen += len;
1298     }
1299     fputs (buf, fp);
1300     adr->next = tmp;
1301     if (!adr->group && adr->next && adr->next->mailbox) {
1302       linelen++;
1303       fputc (',', fp);
1304     }
1305     adr = adr->next;
1306     count++;
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
1363 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1364                               int mode, int privacy)
1365 {
1366   char buffer[LONG_STRING];
1367   char *p;
1368   string_list_t *tmp = env->userhdrs;
1369   int has_agent = 0;            /* user defined user-agent header field exists */
1370
1371 #ifdef USE_NNTP
1372   if (!option (OPTNEWSSEND))
1373 #endif
1374     if (mode == 0 && !privacy)
1375       fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1376
1377   /* OPTUSEFROM is not consulted here so that we can still write a From:
1378    * field if the user sets it with the `my_hdr' command
1379    */
1380   if (env->from && !privacy) {
1381     buffer[0] = 0;
1382     rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
1383     fprintf (fp, "From: %s\n", buffer);
1384   }
1385
1386   if (env->to) {
1387     fputs ("To: ", fp);
1388     mutt_write_address_list (env->to, fp, 4, 0);
1389   }
1390   else if (mode > 0)
1391 #ifdef USE_NNTP
1392     if (!option (OPTNEWSSEND))
1393 #endif
1394       if (edit_header(mode, "To:"))
1395         fputs ("To:\n", fp);
1396
1397   if (env->cc) {
1398     fputs ("Cc: ", fp);
1399     mutt_write_address_list (env->cc, fp, 4, 0);
1400   }
1401   else if (mode > 0)
1402 #ifdef USE_NNTP
1403     if (!option (OPTNEWSSEND))
1404 #endif
1405       if (edit_header(mode, "Cc:"))
1406         fputs ("Cc:\n", fp);
1407
1408   if (env->bcc) {
1409     if (mode != 0 || option (OPTWRITEBCC)) {
1410       fputs ("Bcc: ", fp);
1411       mutt_write_address_list (env->bcc, fp, 5, 0);
1412     }
1413   }
1414   else if (mode > 0)
1415 #ifdef USE_NNTP
1416     if (!option (OPTNEWSSEND))
1417 #endif
1418       if (edit_header(mode, "Bcc:"))
1419         fputs ("Bcc:\n", fp);
1420
1421 #ifdef USE_NNTP
1422   if (env->newsgroups)
1423     fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1424   else if (mode == 1 && option (OPTNEWSSEND) && edit_header(mode, "Newsgroups:"))
1425     fputs ("Newsgroups:\n", fp);
1426
1427   if (env->followup_to)
1428     fprintf (fp, "Followup-To: %s\n", env->followup_to);
1429   else if (mode == 1 && option (OPTNEWSSEND) && edit_header(mode, "Followup-To:"))
1430     fputs ("Followup-To:\n", fp);
1431
1432   if (env->x_comment_to)
1433     fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
1434   else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO) &&
1435            edit_header(mode, "X-Comment-To:"))
1436     fputs ("X-Comment-To:\n", fp);
1437 #endif
1438
1439   if (env->subject)
1440     fprintf (fp, "Subject: %s\n", env->subject);
1441   else if (mode == 1 && edit_header(mode, "Subject:"))
1442     fputs ("Subject:\n", fp);
1443
1444   /* save message id if the user has set it */
1445   if (env->message_id && !privacy)
1446     fprintf (fp, "Message-ID: %s\n", env->message_id);
1447
1448   if (env->reply_to) {
1449     fputs ("Reply-To: ", fp);
1450     mutt_write_address_list (env->reply_to, fp, 10, 0);
1451   }
1452   else if (mode > 0 && edit_header(mode, "Reply-To:"))
1453     fputs ("Reply-To:\n", fp);
1454
1455   if (env->mail_followup_to)
1456 #ifdef USE_NNTP
1457     if (!option (OPTNEWSSEND))
1458 #endif
1459     {
1460       fputs ("Mail-Followup-To: ", fp);
1461       mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1462     }
1463
1464   if (mode <= 0) {
1465     if (env->references) {
1466       fputs ("References:", fp);
1467       mutt_write_references (env->references, fp);
1468       fputc ('\n', fp);
1469     }
1470
1471     /* Add the MIME headers */
1472     fputs ("MIME-Version: 1.0\n", fp);
1473     mutt_write_mime_header (attach, fp);
1474   }
1475
1476   if (env->in_reply_to) {
1477     fputs ("In-Reply-To:", fp);
1478     mutt_write_references (env->in_reply_to, fp);
1479     fputc ('\n', fp);
1480   }
1481
1482   /* Add any user defined headers */
1483   for (; tmp; tmp = tmp->next) {
1484     if ((p = strchr (tmp->data, ':'))) {
1485       p = vskipspaces(p + 1);
1486       if (!*p)
1487         continue;               /* don't emit empty fields. */
1488
1489       /* check to see if the user has overridden the user-agent field */
1490       if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1491         has_agent = 1;
1492         if (privacy)
1493           continue;
1494       }
1495
1496       fputs (tmp->data, fp);
1497       fputc ('\n', fp);
1498     }
1499   }
1500
1501   if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1502     const char *os;
1503
1504     if (OperatingSystem != NULL) {
1505       os = OperatingSystem;
1506     } else {
1507       struct utsname un;
1508       os = (uname(&un) == -1) ? "UNIX" : un.sysname;
1509     }
1510     /* Add a vanity header */
1511     fprintf (fp, "User-Agent: %s (%s)\n", mutt_make_version (0), os);
1512   }
1513
1514   return (ferror (fp) == 0 ? 0 : -1);
1515 }
1516
1517 static void encode_headers (string_list_t * h)
1518 {
1519   char *tmp;
1520   char *p;
1521   int i;
1522
1523   for (; h; h = h->next) {
1524     if (!(p = strchr (h->data, ':')))
1525       continue;
1526
1527     i = p - h->data;
1528     p = vskipspaces(p + 1);
1529     tmp = m_strdup(p);
1530
1531     if (!tmp)
1532       continue;
1533
1534     rfc2047_encode_string (&tmp);
1535     p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1536
1537     sprintf (h->data + i, ": %s", NONULL (tmp));        /* __SPRINTF_CHECKED__ */
1538
1539     p_delete(&tmp);
1540   }
1541 }
1542
1543 const char *mutt_fqdn (short may_hide_host)
1544 {
1545   char *p = NULL, *q;
1546
1547   if (Fqdn && Fqdn[0] != '@') {
1548     p = Fqdn;
1549
1550     if (may_hide_host && option (OPTHIDDENHOST)) {
1551       if ((p = strchr (Fqdn, '.')))
1552         p++;
1553
1554       /* sanity check: don't hide the host if
1555        * the fqdn is something like detebe.org.
1556        */
1557
1558       if (!p || !(q = strchr (p, '.')))
1559         p = Fqdn;
1560     }
1561   }
1562
1563   return p;
1564 }
1565
1566 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1567 {
1568 #define APPEND_FMT(fmt, arg) \
1569         if (len > 1) {                                  \
1570             int snlen = snprintf(buf, len, fmt, arg);   \
1571             buf += snlen;                               \
1572             len -= snlen;                               \
1573         }
1574
1575 #define APPEND_BYTE(c) \
1576         if (len > 1) {                                  \
1577             *buf++ = c;                                 \
1578             *buf   = '\0';                              \
1579             len--;                                      \
1580         }
1581
1582     time_t now;
1583     struct tm *tm;
1584
1585     now = time (NULL);
1586     tm = gmtime (&now);
1587
1588     while (*fmt) {
1589         static char MsgIdPfx = 'A';
1590         int c = *fmt++;
1591
1592         if (c != '%') {
1593             /* normalized character (we're stricter than RFC2822, 3.6.4) */
1594             APPEND_BYTE((isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.');
1595             continue;
1596         }
1597
1598         switch (*fmt++) {
1599           case 0:
1600             return;
1601           case 'd':
1602             APPEND_FMT("%02d", tm->tm_mday);
1603             break;
1604           case 'h':
1605             APPEND_FMT("%02d", tm->tm_hour);
1606             break;
1607           case 'm':
1608             APPEND_FMT("%02d", tm->tm_mon + 1);
1609             break;
1610           case 'M':
1611             APPEND_FMT("%02d", tm->tm_min);
1612             break;
1613           case 'O':
1614             APPEND_FMT("%lo", (unsigned long)now);
1615             break;
1616           case 'p':
1617             APPEND_FMT("%u", (unsigned int)getpid());
1618             break;
1619           case 'P':
1620             APPEND_FMT("%c", MsgIdPfx);
1621             MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1622             break;
1623           case 'r':
1624             APPEND_FMT("%u", (unsigned int)rand());
1625             break;
1626           case 'R':
1627             APPEND_FMT("%x", (unsigned int)rand());
1628             break;
1629           case 's':
1630             APPEND_FMT("%02d", tm->tm_sec);
1631             break;
1632           case 'T':
1633             APPEND_FMT("%u", (unsigned int) now);
1634             break;
1635           case 'X':
1636             APPEND_FMT("%x", (unsigned int) now);
1637             break;
1638           case 'Y':       /* this will break in the year 10000 ;-) */
1639             APPEND_FMT("%04d", tm->tm_year + 1900);
1640             break;
1641           case '%':
1642             APPEND_BYTE('%');
1643             break;
1644           default:       /* invalid formats are replaced by '.' */
1645             APPEND_BYTE('.');
1646         }
1647     }
1648
1649     *buf = '\0';
1650
1651 #undef APPEND_BYTE
1652 #undef APPEND_FMT
1653 }
1654
1655 static char *mutt_gen_msgid (void)
1656 {
1657     char buf[SHORT_STRING];
1658     char localpart[SHORT_STRING];
1659     const char *fqdn;
1660
1661     if (!(fqdn = mutt_fqdn(0)))
1662         fqdn = NONULL(Hostname);
1663
1664     mutt_gen_localpart(localpart, sizeof(localpart), MsgIdFormat);
1665     snprintf(buf, sizeof(buf), "<%s@%s>", localpart, fqdn);
1666     return m_strdup(buf);
1667 }
1668
1669 static RETSIGTYPE alarm_handler (int sig __attribute__ ((unused)))
1670 {
1671   SigAlrm = 1;
1672 }
1673
1674 /* invoke sendmail in a subshell
1675    path (in)            path to program to execute
1676    args (in)            arguments to pass to program
1677    msg (in)             temp file containing message to send
1678    tempfile (out)       if sendmail is put in the background, this points
1679                         to the temporary file containing the stdout of the
1680                         child process */
1681 static int
1682 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
1683 {
1684   sigset_t set;
1685   int fd, st;
1686   pid_t pid, ppid;
1687
1688   mutt_block_signals_system ();
1689
1690   sigemptyset (&set);
1691   /* we also don't want to be stopped right now */
1692   sigaddset (&set, SIGTSTP);
1693   sigprocmask (SIG_BLOCK, &set, NULL);
1694
1695   if (SendmailWait >= 0) {
1696     char tmp[_POSIX_PATH_MAX];
1697
1698     mutt_mktemp (tmp);
1699     *tempfile = m_strdup(tmp);
1700   }
1701
1702   if ((pid = fork ()) == 0) {
1703     struct sigaction act, oldalrm;
1704
1705     /* save parent's ID before setsid() */
1706     ppid = getppid ();
1707
1708     /* we want the delivery to continue even after the main process dies,
1709      * so we put ourselves into another session right away
1710      */
1711     setsid ();
1712
1713     /* next we close all open files */
1714     for (fd = 0; fd < getdtablesize(); fd++)
1715       close (fd);
1716
1717     /* now the second fork() */
1718     if ((pid = fork ()) == 0) {
1719       /* "msg" will be opened as stdin */
1720       if (open (msg, O_RDONLY, 0) < 0) {
1721         unlink (msg);
1722         _exit (S_ERR);
1723       }
1724       unlink (msg);
1725
1726       if (SendmailWait >= 0) {
1727         /* *tempfile will be opened as stdout */
1728         if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
1729             0)
1730           _exit (S_ERR);
1731         /* redirect stderr to *tempfile too */
1732         if (dup (1) < 0)
1733           _exit (S_ERR);
1734       }
1735       else {
1736         if (open ("/dev/null", O_WRONLY | O_APPEND) < 0)        /* stdout */
1737           _exit (S_ERR);
1738         if (open ("/dev/null", O_RDWR | O_APPEND) < 0)  /* stderr */
1739           _exit (S_ERR);
1740       }
1741
1742       execv (path, (char**)args);
1743       _exit (S_ERR);
1744     }
1745     else if (pid == -1) {
1746       unlink (msg);
1747       p_delete(tempfile);
1748       _exit (S_ERR);
1749     }
1750
1751     /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
1752      * SendmailWait = 0: wait forever
1753      * SendmailWait < 0: don't wait
1754      */
1755     if (SendmailWait > 0) {
1756       SigAlrm = 0;
1757       act.sa_handler = alarm_handler;
1758 #ifdef SA_INTERRUPT
1759       /* need to make sure waitpid() is interrupted on SIGALRM */
1760       act.sa_flags = SA_INTERRUPT;
1761 #else
1762       act.sa_flags = 0;
1763 #endif
1764       sigemptyset (&act.sa_mask);
1765       sigaction (SIGALRM, &act, &oldalrm);
1766       alarm (SendmailWait);
1767     }
1768     else if (SendmailWait < 0)
1769       _exit (0xff & EX_OK);
1770
1771     if (waitpid (pid, &st, 0) > 0) {
1772       st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
1773       if (SendmailWait && st == (0xff & EX_OK)) {
1774         unlink (*tempfile);     /* no longer needed */
1775         p_delete(tempfile);
1776       }
1777     }
1778     else {
1779       st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
1780       if (SendmailWait > 0) {
1781         unlink (*tempfile);
1782         p_delete(tempfile);
1783       }
1784     }
1785
1786     /* reset alarm; not really needed, but... */
1787     alarm (0);
1788     sigaction (SIGALRM, &oldalrm, NULL);
1789
1790     if (kill (ppid, 0) == -1 && errno == ESRCH) {
1791       /* the parent is already dead */
1792       unlink (*tempfile);
1793       p_delete(tempfile);
1794     }
1795
1796     _exit (st);
1797   }
1798
1799   sigprocmask (SIG_UNBLOCK, &set, NULL);
1800
1801   if (pid != -1 && waitpid (pid, &st, 0) > 0)
1802     st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;     /* return child status */
1803   else
1804     st = S_ERR;                 /* error */
1805
1806   mutt_unblock_signals_system (1);
1807
1808   return (st);
1809 }
1810
1811 static const char **
1812 add_args(const char **args, ssize_t *argslen, ssize_t *argsmax, address_t * addr)
1813 {
1814   for (; addr; addr = addr->next) {
1815     /* weed out group mailboxes, since those are for display only */
1816     if (addr->mailbox && !addr->group) {
1817       if (*argslen == *argsmax)
1818         p_realloc(&args, *argsmax += 5);
1819       args[(*argslen)++] = addr->mailbox;
1820     }
1821   }
1822   return (args);
1823 }
1824
1825 static const char **
1826 add_option(const char **args, ssize_t *argslen, ssize_t *argsmax, const char *s)
1827 {
1828     if (*argslen == *argsmax) {
1829         p_realloc(&args, *argsmax += 5);
1830     }
1831     args[(*argslen)++] = s;
1832     return (args);
1833 }
1834
1835 static int mutt_invoke_sendmail (address_t * from,        /* the sender */
1836                                  address_t * to, address_t * cc, address_t * bcc,     /* recips */
1837                                  const char *msg,       /* file containing message */
1838                                  int eightbit)
1839 {                               /* message contains 8bit chars */
1840   char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
1841   const char **args = NULL;
1842   ssize_t argslen = 0, argsmax = 0;
1843   int i;
1844
1845 #ifdef USE_NNTP
1846   if (option (OPTNEWSSEND)) {
1847     char cmd[LONG_STRING];
1848
1849     mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0,
1850                        0);
1851     if (m_strisempty(cmd)) {
1852       i = nntp_post (msg);
1853       unlink (msg);
1854       return i;
1855     }
1856
1857     s = m_strdup(cmd);
1858   }
1859   else
1860 #endif
1861     s = m_strdup(Sendmail);
1862
1863   ps = s;
1864   i = 0;
1865   while ((ps = strtok (ps, " "))) {
1866     if (argslen == argsmax)
1867       p_realloc(&args, argsmax += 5);
1868
1869     if (i)
1870       args[argslen++] = ps;
1871     else {
1872       path = m_strdup(ps);
1873       ps = strrchr (ps, '/');
1874       if (ps)
1875         ps++;
1876       else
1877         ps = path;
1878       args[argslen++] = ps;
1879     }
1880     ps = NULL;
1881     i++;
1882   }
1883
1884 #ifdef USE_NNTP
1885   if (!option (OPTNEWSSEND)) {
1886 #endif
1887     if (eightbit && option (OPTUSE8BITMIME))
1888       args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
1889
1890     if (option (OPTENVFROM)) {
1891       address_t *f = NULL;
1892       if (EnvFrom)
1893         f = EnvFrom;
1894       else if (from && !from->next)
1895         f = from;
1896       if (f) {
1897         args = add_option (args, &argslen, &argsmax, "-f");
1898         args = add_args (args, &argslen, &argsmax, f);
1899       }
1900     }
1901     if (DsnNotify) {
1902       args = add_option (args, &argslen, &argsmax, "-N");
1903       args = add_option (args, &argslen, &argsmax, DsnNotify);
1904     }
1905     if (DsnReturn) {
1906       args = add_option (args, &argslen, &argsmax, "-R");
1907       args = add_option (args, &argslen, &argsmax, DsnReturn);
1908     }
1909     args = add_option (args, &argslen, &argsmax, "--");
1910     args = add_args (args, &argslen, &argsmax, to);
1911     args = add_args (args, &argslen, &argsmax, cc);
1912     args = add_args (args, &argslen, &argsmax, bcc);
1913 #ifdef USE_NNTP
1914   }
1915 #endif
1916
1917   if (argslen == argsmax)
1918     p_realloc(&args, ++argsmax);
1919
1920   args[argslen++] = NULL;
1921
1922   if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
1923     if (i != S_BKG) {
1924       mutt_error (_("Error sending message, child exited %d (%s)."), i,
1925                   m_strsysexit(i));
1926       if (childout) {
1927         struct stat st;
1928
1929         if (stat (childout, &st) == 0 && st.st_size > 0)
1930           mutt_do_pager (_("Output of the delivery process"), childout, 0,
1931                          NULL);
1932       }
1933     }
1934   }
1935   else
1936     unlink (childout);
1937
1938   p_delete(&childout);
1939   p_delete(&path);
1940   p_delete(&s);
1941   p_delete(&args);
1942
1943   if (i == (EX_OK & 0xff))
1944     i = 0;
1945   else if (i == S_BKG)
1946     i = 1;
1947   else
1948     i = -1;
1949   return (i);
1950 }
1951
1952 int mutt_invoke_mta (address_t * from,    /* the sender */
1953                      address_t * to, address_t * cc, address_t * bcc, /* recips */
1954                      const char *msg,   /* file containing message */
1955                      int eightbit)
1956 {                               /* message contains 8bit chars */
1957 #ifdef USE_LIBESMTP
1958 #ifdef USE_NNTP
1959   if (!option (OPTNEWSSEND))
1960 #endif
1961     if (SmtpHost)
1962       return mutt_libesmtp_invoke (from, to, cc, bcc, msg, eightbit);
1963 #endif
1964
1965   return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
1966 }
1967
1968 /* For postponing (!final) do the necessary encodings only */
1969 void mutt_prepare_envelope (ENVELOPE * env, int final)
1970 {
1971   char buffer[LONG_STRING];
1972
1973   if (final) {
1974     if (env->bcc && !(env->to || env->cc)) {
1975       /* some MTA's will put an Apparently-To: header field showing the Bcc:
1976        * recipients if there is no To: or Cc: field, so attempt to suppress
1977        * it by using an empty To: field.
1978        */
1979       env->to = address_new ();
1980       env->to->group = 1;
1981       env->to->next = address_new ();
1982
1983       buffer[0] = 0;
1984       rfc822_strcpy(buffer, sizeof(buffer), "undisclosed-recipients",
1985                     RFC822Specials);
1986
1987       env->to->mailbox = m_strdup(buffer);
1988     }
1989
1990     mutt_set_followup_to (env);
1991
1992     if (!env->message_id && MsgIdFormat && *MsgIdFormat)
1993       env->message_id = mutt_gen_msgid ();
1994   }
1995
1996   /* Take care of 8-bit => 7-bit conversion. */
1997   rfc2047_encode_adrlist (env->to, "To");
1998   rfc2047_encode_adrlist (env->cc, "Cc");
1999   rfc2047_encode_adrlist (env->bcc, "Bcc");
2000   rfc2047_encode_adrlist (env->from, "From");
2001   rfc2047_encode_adrlist (env->mail_followup_to, "Mail-Followup-To");
2002   rfc2047_encode_adrlist (env->reply_to, "Reply-To");
2003
2004   if (env->subject)
2005 #ifdef USE_NNTP
2006     if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
2007 #endif
2008     {
2009       rfc2047_encode_string (&env->subject);
2010     }
2011   encode_headers (env->userhdrs);
2012 }
2013
2014 void mutt_unprepare_envelope (ENVELOPE * env)
2015 {
2016   string_list_t *item;
2017
2018   for (item = env->userhdrs; item; item = item->next)
2019     rfc2047_decode (&item->data);
2020
2021   address_list_wipe(&env->mail_followup_to);
2022
2023   /* back conversions */
2024   rfc2047_decode_adrlist (env->to);
2025   rfc2047_decode_adrlist (env->cc);
2026   rfc2047_decode_adrlist (env->bcc);
2027   rfc2047_decode_adrlist (env->from);
2028   rfc2047_decode_adrlist (env->reply_to);
2029   rfc2047_decode (&env->subject);
2030 }
2031
2032 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2033                                  const char *resent_from, address_t * env_from)
2034 {
2035   int i, ret = 0;
2036   FILE *f;
2037   char date[SHORT_STRING], tempfile[_POSIX_PATH_MAX];
2038   MESSAGE *msg = NULL;
2039
2040   if (!h) {
2041     /* Try to bounce each message out, aborting if we get any failures. */
2042     for (i = 0; i < Context->msgcount; i++)
2043       if (Context->hdrs[i]->tagged)
2044         ret |=
2045           _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2046                                 env_from);
2047     return ret;
2048   }
2049
2050   /* If we failed to open a message, return with error */
2051   if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2052     return -1;
2053
2054   if (!fp)
2055     fp = msg->fp;
2056
2057   f = m_tempfile(tempfile, sizeof(tempfile), NONULL(Tempdir), NULL);
2058   if (f) {
2059     int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2060
2061     if (!option (OPTBOUNCEDELIVERED))
2062       ch_flags |= CH_WEED_DELIVERED;
2063
2064     fseeko (fp, h->offset, 0);
2065     fprintf (f, "Resent-From: %s", resent_from);
2066     fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2067     if (MsgIdFormat && *MsgIdFormat)
2068       fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid ());
2069     fputs ("Resent-To: ", f);
2070     mutt_write_address_list (to, f, 11, 0);
2071     mutt_copy_header (fp, h, f, ch_flags, NULL);
2072     fputc ('\n', f);
2073     mutt_copy_bytes (fp, f, h->content->length);
2074     m_fclose(&f);
2075
2076     ret = mutt_invoke_mta(env_from, to, NULL, NULL, tempfile,
2077                           h->content->encoding == ENC8BIT);
2078   }
2079
2080   if (msg)
2081     mx_close_message (&msg);
2082
2083   return ret;
2084 }
2085
2086 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2087 {
2088   address_t *from;
2089   const char *fqdn = mutt_fqdn (1);
2090   char resent_from[STRING];
2091   int ret;
2092   char *err;
2093
2094   resent_from[0] = '\0';
2095   from = mutt_default_from ();
2096
2097   if (fqdn)
2098     rfc822_qualify (from, fqdn);
2099
2100   rfc2047_encode_adrlist (from, "Resent-From");
2101   if (mutt_addrlist_to_idna (from, &err)) {
2102     mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2103     return -1;
2104   }
2105   rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2106
2107 #ifdef USE_NNTP
2108   unset_option (OPTNEWSSEND);
2109 #endif
2110
2111   ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2112
2113   address_list_wipe(&from);
2114
2115   return ret;
2116 }
2117
2118 static void set_noconv_flags (BODY * b, short flag)
2119 {
2120   for (; b; b = b->next) {
2121     if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2122       set_noconv_flags (b->parts, flag);
2123     else if (b->type == TYPETEXT && b->noconv) {
2124       parameter_setval(&b->parameter, "x-mutt-noconv", flag ? "yes" : NULL);
2125     }
2126   }
2127 }
2128
2129 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2130                     int post, char *fcc)
2131 {
2132   CONTEXT f;
2133   MESSAGE *msg;
2134   char tempfile[_POSIX_PATH_MAX];
2135   FILE *tempfp = NULL;
2136   int r;
2137
2138   if (post)
2139     set_noconv_flags (hdr->content, 1);
2140
2141   if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2142     return (-1);
2143   }
2144
2145   /* We need to add a Content-Length field to avoid problems where a line in
2146    * the message body begins with "From "   
2147    */
2148   if (f.magic == M_MMDF || f.magic == M_MBOX) {
2149     tempfp = m_tempfile(tempfile, sizeof(tempfile), NONULL(Tempdir), NULL);
2150     if (!tempfp) {
2151       mutt_error(_("Could not create temporary file"));
2152       mx_close_mailbox (&f, NULL);
2153       return -1;
2154     }
2155   }
2156
2157   hdr->read = !post;            /* make sure to put it in the `cur' directory (maildir) */
2158   if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2159     mx_close_mailbox (&f, NULL);
2160     return (-1);
2161   }
2162
2163   /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2164    * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header() 
2165    * */
2166   mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0,
2167                             0);
2168
2169   /* (postponment) if this was a reply of some sort, <msgid> contians the
2170    * Message-ID: of message replied to.  Save it using a special X-Mutt-
2171    * header so it can be picked up if the message is recalled at a later
2172    * point in time.  This will allow the message to be marked as replied if
2173    * the same mailbox is still open.
2174    */
2175   if (post && msgid)
2176     fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2177
2178   /* (postponment) save the Fcc: using a special X-Mutt- header so that
2179    * it can be picked up when the message is recalled 
2180    */
2181   if (post && fcc)
2182     fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2183   fprintf (msg->fp, "Status: RO\n");
2184
2185
2186
2187   /* (postponment) if the mail is to be signed or encrypted, save this info */
2188   if (post && (hdr->security & APPLICATION_PGP)) {
2189     fputs ("X-Mutt-PGP: ", msg->fp);
2190     if (hdr->security & ENCRYPT)
2191       fputc ('E', msg->fp);
2192     if (hdr->security & SIGN) {
2193       fputc ('S', msg->fp);
2194       if (PgpSignAs && *PgpSignAs)
2195         fprintf (msg->fp, "<%s>", PgpSignAs);
2196     }
2197     if (hdr->security & INLINE)
2198       fputc ('I', msg->fp);
2199     fputc ('\n', msg->fp);
2200   }
2201
2202   /* (postponment) if the mail is to be signed or encrypted, save this info */
2203   if (post && (hdr->security & APPLICATION_SMIME)) {
2204     fputs ("X-Mutt-SMIME: ", msg->fp);
2205     if (hdr->security & ENCRYPT) {
2206       fputc ('E', msg->fp);
2207       if (SmimeCryptAlg && *SmimeCryptAlg)
2208         fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2209     }
2210     if (hdr->security & SIGN) {
2211       fputc ('S', msg->fp);
2212       if (SmimeDefaultKey && *SmimeDefaultKey)
2213         fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2214     }
2215     if (hdr->security & INLINE)
2216       fputc ('I', msg->fp);
2217     fputc ('\n', msg->fp);
2218   }
2219
2220   /* (postponement) if the mail is to be sent through a mixmaster 
2221    * chain, save that information
2222    */
2223   if (post && hdr->chain && hdr->chain) {
2224     string_list_t *p;
2225
2226     fputs ("X-Mutt-Mix:", msg->fp);
2227     for (p = hdr->chain; p; p = p->next)
2228       fprintf (msg->fp, " %s", (char *) p->data);
2229
2230     fputc ('\n', msg->fp);
2231   }
2232
2233   if (tempfp) {
2234     char sasha[LONG_STRING];
2235     int lines = 0;
2236
2237     mutt_write_mime_body (hdr->content, tempfp);
2238
2239     /* make sure the last line ends with a newline.  Emacs doesn't ensure
2240      * this will happen, and it can cause problems parsing the mailbox   
2241      * later.
2242      */
2243     fseeko (tempfp, -1, 2);
2244     if (fgetc (tempfp) != '\n') {
2245       fseeko (tempfp, 0, 2);
2246       fputc ('\n', tempfp);
2247     }
2248
2249     fflush (tempfp);
2250     if (ferror (tempfp)) {
2251       m_fclose(&tempfp);
2252       unlink (tempfile);
2253       mx_commit_message (msg, &f);      /* XXX - really? */
2254       mx_close_message (&msg);
2255       mx_close_mailbox (&f, NULL);
2256       return -1;
2257     }
2258
2259     /* count the number of lines */
2260     rewind (tempfp);
2261     while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2262       lines++;
2263     fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2264     fprintf (msg->fp, "Lines: %d\n\n", lines);
2265
2266     /* copy the body and clean up */
2267     rewind (tempfp);
2268     r = mutt_copy_stream (tempfp, msg->fp);
2269     if (m_fclose(&tempfp) != 0)
2270       r = -1;
2271     /* if there was an error, leave the temp version */
2272     if (!r)
2273       unlink (tempfile);
2274   }
2275   else {
2276     fputc ('\n', msg->fp);      /* finish off the header */
2277     r = mutt_write_mime_body (hdr->content, msg->fp);
2278   }
2279
2280   if (mx_commit_message (msg, &f) != 0)
2281     r = -1;
2282   mx_close_message (&msg);
2283   mx_close_mailbox (&f, NULL);
2284
2285   if (post)
2286     set_noconv_flags (hdr->content, 0);
2287
2288   return r;
2289 }