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