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