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