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