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