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