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