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