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