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