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