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