mutt stable branch with some hacks
1/*
2 * Copyright (C) 1996-2002,2009-2012 Michael R. Elkins <me@mutt.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19#define _SENDLIB_C 1
20
21#if HAVE_CONFIG_H
22# include "config.h"
23#endif
24
25#include "version.h"
26#include "mutt.h"
27#include "mutt_curses.h"
28#include "rfc2047.h"
29#include "rfc2231.h"
30#include "mx.h"
31#include "mime.h"
32#include "mailbox.h"
33#include "copy.h"
34#include "pager.h"
35#include "charset.h"
36#include "mutt_crypt.h"
37#include "mutt_idna.h"
38#include "buffy.h"
39
40#ifdef USE_AUTOCRYPT
41#include "autocrypt.h"
42#endif
43
44#include <string.h>
45#include <stdlib.h>
46#include <unistd.h>
47#include <errno.h>
48#include <ctype.h>
49#include <sys/stat.h>
50#include <signal.h>
51#include <sys/wait.h>
52#include <fcntl.h>
53
54#ifdef HAVE_SYSEXITS_H
55#include <sysexits.h>
56#else /* Make sure EX_OK is defined <philiph@pobox.com> */
57#define EX_OK 0
58#endif
59
60/* If you are debugging this file, comment out the following line. */
61#define NDEBUG
62
63#ifdef NDEBUG
64#define assert(x)
65#else
66#include <assert.h>
67#endif
68
69extern char RFC822Specials[];
70
71const char MimeSpecials[] = "@.,;:<>[]\\\"()?/= \t";
72
73const char B64Chars[64] = {
74 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
75 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
76 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
77 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
78 '8', '9', '+', '/'
79};
80
81static char MsgIdPfx = 'A';
82
83static void transform_to_7bit (BODY *a, FILE *fpin);
84
85static void encode_quoted (FGETCONV * fc, FILE *fout, int istext)
86{
87 int c, linelen = 0;
88 char line[77], savechar;
89
90 while ((c = fgetconv (fc)) != EOF)
91 {
92 /* Wrap the line if needed. */
93 if (linelen == 76 && ((istext && c != '\n') || !istext))
94 {
95 /* If the last character is "quoted", then be sure to move all three
96 * characters to the next line. Otherwise, just move the last
97 * character...
98 */
99 if (line[linelen-3] == '=')
100 {
101 line[linelen-3] = 0;
102 fputs (line, fout);
103 fputs ("=\n", fout);
104 line[linelen] = 0;
105 line[0] = '=';
106 line[1] = line[linelen-2];
107 line[2] = line[linelen-1];
108 linelen = 3;
109 }
110 else
111 {
112 savechar = line[linelen-1];
113 line[linelen-1] = '=';
114 line[linelen] = 0;
115 fputs (line, fout);
116 fputc ('\n', fout);
117 line[0] = savechar;
118 linelen = 1;
119 }
120 }
121
122 /* Escape lines that begin with/only contain "the message separator". */
123 if (linelen == 4 && !mutt_strncmp ("From", line, 4))
124 {
125 strfcpy (line, "=46rom", sizeof (line));
126 linelen = 6;
127 }
128 else if (linelen == 4 && !mutt_strncmp ("from", line, 4))
129 {
130 strfcpy (line, "=66rom", sizeof (line));
131 linelen = 6;
132 }
133 else if (linelen == 1 && line[0] == '.')
134 {
135 strfcpy (line, "=2E", sizeof (line));
136 linelen = 3;
137 }
138
139
140 if (c == '\n' && istext)
141 {
142 /* Check to make sure there is no trailing space on this line. */
143 if (linelen > 0 && (line[linelen-1] == ' ' || line[linelen-1] == '\t'))
144 {
145 if (linelen < 74)
146 {
147 sprintf (line+linelen-1, "=%2.2X", (unsigned char) line[linelen-1]);
148 fputs (line, fout);
149 }
150 else
151 {
152 int savechar = line[linelen-1];
153
154 line[linelen-1] = '=';
155 line[linelen] = 0;
156 fputs (line, fout);
157 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
158 }
159 }
160 else
161 {
162 line[linelen] = 0;
163 fputs (line, fout);
164 }
165 fputc ('\n', fout);
166 linelen = 0;
167 }
168 else if (c != 9 && (c < 32 || c > 126 || c == '='))
169 {
170 /* Check to make sure there is enough room for the quoted character.
171 * If not, wrap to the next line.
172 */
173 if (linelen > 73)
174 {
175 line[linelen++] = '=';
176 line[linelen] = 0;
177 fputs (line, fout);
178 fputc ('\n', fout);
179 linelen = 0;
180 }
181 sprintf (line+linelen,"=%2.2X", (unsigned char) c);
182 linelen += 3;
183 }
184 else
185 {
186 /* Don't worry about wrapping the line here. That will happen during
187 * the next iteration when I'll also know what the next character is.
188 */
189 line[linelen++] = c;
190 }
191 }
192
193 /* Take care of anything left in the buffer */
194 if (linelen > 0)
195 {
196 if (line[linelen-1] == ' ' || line[linelen-1] == '\t')
197 {
198 /* take care of trailing whitespace */
199 if (linelen < 74)
200 sprintf (line+linelen-1, "=%2.2X", (unsigned char) line[linelen-1]);
201 else
202 {
203 savechar = line[linelen-1];
204 line[linelen-1] = '=';
205 line[linelen] = 0;
206 fputs (line, fout);
207 fputc ('\n', fout);
208 sprintf (line, "=%2.2X", (unsigned char) savechar);
209 }
210 }
211 else
212 line[linelen] = 0;
213 fputs (line, fout);
214 }
215}
216
217static char b64_buffer[3];
218static short b64_num;
219static short b64_linelen;
220
221static void b64_flush(FILE *fout)
222{
223 short i;
224
225 if (!b64_num)
226 return;
227
228 if (b64_linelen >= 72)
229 {
230 fputc('\n', fout);
231 b64_linelen = 0;
232 }
233
234 for (i = b64_num; i < 3; i++)
235 b64_buffer[i] = '\0';
236
237 fputc(B64Chars[(b64_buffer[0] >> 2) & 0x3f], fout);
238 b64_linelen++;
239 fputc(B64Chars[((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf) ], fout);
240 b64_linelen++;
241
242 if (b64_num > 1)
243 {
244 fputc(B64Chars[((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3) ], fout);
245 b64_linelen++;
246 if (b64_num > 2)
247 {
248 fputc(B64Chars[b64_buffer[2] & 0x3f], fout);
249 b64_linelen++;
250 }
251 }
252
253 while (b64_linelen % 4)
254 {
255 fputc('=', fout);
256 b64_linelen++;
257 }
258
259 b64_num = 0;
260}
261
262
263static void b64_putc(char c, FILE *fout)
264{
265 if (b64_num == 3)
266 b64_flush(fout);
267
268 b64_buffer[b64_num++] = c;
269}
270
271
272static void encode_base64 (FGETCONV * fc, FILE *fout, int istext)
273{
274 int ch, ch1 = EOF;
275
276 b64_num = b64_linelen = 0;
277
278 while ((ch = fgetconv (fc)) != EOF)
279 {
280 if (istext && ch == '\n' && ch1 != '\r')
281 b64_putc('\r', fout);
282 b64_putc(ch, fout);
283 ch1 = ch;
284 }
285 b64_flush(fout);
286 fputc('\n', fout);
287}
288
289static void encode_8bit (FGETCONV *fc, FILE *fout, int istext)
290{
291 int ch;
292
293 while ((ch = fgetconv (fc)) != EOF)
294 fputc (ch, fout);
295}
296
297
298int mutt_write_mime_header (BODY *a, FILE *f)
299{
300 PARAMETER *p;
301 PARAMETER *param_conts, *cont;
302 char buffer[STRING];
303 char *t;
304 char *fn;
305 int len;
306 int tmplen;
307
308 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
309
310 if (a->parameter)
311 {
312 len = 25 + mutt_strlen (a->subtype); /* approximate len. of content-type */
313
314 for (p = a->parameter; p; p = p->next)
315 {
316 if (!(p->attribute && p->value))
317 continue;
318
319 param_conts = rfc2231_encode_string (p->attribute, p->value);
320 for (cont = param_conts; cont; cont = cont->next)
321 {
322 fputc (';', f);
323
324 buffer[0] = 0;
325 rfc822_cat (buffer, sizeof (buffer), cont->value, MimeSpecials);
326
327 /* Dirty hack to make messages readable by Outlook Express
328 * for the Mac: force quotes around the boundary parameter
329 * even when they aren't needed.
330 */
331 if (!ascii_strcasecmp (cont->attribute, "boundary") &&
332 !mutt_strcmp (buffer, cont->value))
333 snprintf (buffer, sizeof (buffer), "\"%s\"", cont->value);
334
335 tmplen = mutt_strlen (buffer) + mutt_strlen (cont->attribute) + 1;
336 if (len + tmplen + 2 > 76)
337 {
338 fputs ("\n\t", f);
339 len = tmplen + 1;
340 }
341 else
342 {
343 fputc (' ', f);
344 len += tmplen + 1;
345 }
346
347 fprintf (f, "%s=%s", cont->attribute, buffer);
348 }
349
350 mutt_free_parameter (¶m_conts);
351 }
352 }
353
354 fputc ('\n', f);
355
356 if (a->description)
357 fprintf(f, "Content-Description: %s\n", a->description);
358
359 if (a->disposition != DISPNONE)
360 {
361 const char *dispstr[] = {
362 "inline",
363 "attachment",
364 "form-data"
365 };
366
367 if (a->disposition < sizeof(dispstr)/sizeof(char*))
368 {
369 fprintf (f, "Content-Disposition: %s", dispstr[a->disposition]);
370 len = 21 + mutt_strlen (dispstr[a->disposition]);
371
372 if (a->use_disp)
373 {
374 if (!(fn = a->d_filename))
375 fn = a->filename;
376
377 if (fn)
378 {
379 /* Strip off the leading path... */
380 if ((t = strrchr (fn, '/')))
381 t++;
382 else
383 t = fn;
384
385 param_conts = rfc2231_encode_string ("filename", t);
386 for (cont = param_conts; cont; cont = cont->next)
387 {
388 fputc (';', f);
389 buffer[0] = 0;
390 rfc822_cat (buffer, sizeof (buffer), cont->value, MimeSpecials);
391
392 tmplen = mutt_strlen (buffer) + mutt_strlen (cont->attribute) + 1;
393 if (len + tmplen + 2 > 76)
394 {
395 fputs ("\n\t", f);
396 len = tmplen + 1;
397 }
398 else
399 {
400 fputc (' ', f);
401 len += tmplen + 1;
402 }
403
404 fprintf (f, "%s=%s", cont->attribute, buffer);
405 }
406
407 mutt_free_parameter (¶m_conts);
408 }
409 }
410
411 fputc ('\n', f);
412 }
413 else
414 {
415 dprint(1, (debugfile, "ERROR: invalid content-disposition %d\n", a->disposition));
416 }
417 }
418
419 if (a->encoding != ENC7BIT)
420 fprintf(f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
421
422 if ((option (OPTCRYPTPROTHDRSWRITE)
423#ifdef USE_AUTOCRYPT
424 || option (OPTAUTOCRYPT)
425#endif
426 ) &&
427 a->mime_headers)
428 {
429 mutt_write_rfc822_header (f, a->mime_headers, NULL, MUTT_WRITE_HEADER_MIME, 0, 0);
430 }
431
432 /* Do NOT add the terminator here!!! */
433 return (ferror (f) ? -1 : 0);
434}
435
436#define write_as_text_part(a) \
437 (mutt_is_text_part(a) || \
438 ((WithCrypto & APPLICATION_PGP) && mutt_is_application_pgp(a)))
439
440int mutt_write_mime_body (BODY *a, FILE *f)
441{
442 char *p, boundary[SHORT_STRING];
443 char send_charset[SHORT_STRING];
444 FILE *fpin;
445 BODY *t;
446 FGETCONV *fc;
447
448 if (a->type == TYPEMULTIPART)
449 {
450 /* First, find the boundary to use */
451 if (!(p = mutt_get_parameter ("boundary", a->parameter)))
452 {
453 dprint (1, (debugfile, "mutt_write_mime_body(): no boundary parameter found!\n"));
454 mutt_error _("No boundary parameter found! [report this error]");
455 return (-1);
456 }
457 strfcpy (boundary, p, sizeof (boundary));
458
459 for (t = a->parts; t ; t = t->next)
460 {
461 fprintf (f, "\n--%s\n", boundary);
462 if (mutt_write_mime_header (t, f) == -1)
463 return -1;
464 fputc ('\n', f);
465 if (mutt_write_mime_body (t, f) == -1)
466 return -1;
467 }
468 fprintf (f, "\n--%s--\n", boundary);
469 return (ferror (f) ? -1 : 0);
470 }
471
472 /* This is pretty gross, but it's the best solution for now... */
473 if ((WithCrypto & APPLICATION_PGP)
474 && a->type == TYPEAPPLICATION
475 && mutt_strcmp (a->subtype, "pgp-encrypted") == 0
476 && !a->filename)
477 {
478 fputs ("Version: 1\n", f);
479 return 0;
480 }
481
482 if ((fpin = fopen (a->filename, "r")) == NULL)
483 {
484 dprint(1,(debugfile, "write_mime_body: %s no longer exists!\n",a->filename));
485 mutt_error (_("%s no longer exists!"), a->filename);
486 return -1;
487 }
488
489 if (a->type == TYPETEXT && (!a->noconv))
490 fc = fgetconv_open (fpin, a->charset,
491 mutt_get_body_charset (send_charset, sizeof (send_charset), a),
492 0);
493 else
494 fc = fgetconv_open (fpin, 0, 0, 0);
495
496 if (a->encoding == ENCQUOTEDPRINTABLE)
497 encode_quoted (fc, f, write_as_text_part (a));
498 else if (a->encoding == ENCBASE64)
499 encode_base64 (fc, f, write_as_text_part (a));
500 else if (a->type == TYPETEXT && (!a->noconv))
501 encode_8bit (fc, f, write_as_text_part (a));
502 else
503 mutt_copy_stream (fpin, f);
504
505 fgetconv_close (&fc);
506 safe_fclose (&fpin);
507
508 return (ferror (f) ? -1 : 0);
509}
510
511#undef write_as_text_part
512
513#define BOUNDARYLEN 16
514void mutt_generate_boundary (PARAMETER **parm)
515{
516 char rs[BOUNDARYLEN + 1];
517 char *p = rs;
518 int i;
519
520 rs[BOUNDARYLEN] = 0;
521 for (i=0;i<BOUNDARYLEN;i++)
522 *p++ = B64Chars[LRAND() % sizeof (B64Chars)];
523 *p = 0;
524
525 mutt_set_parameter ("boundary", rs, parm);
526}
527
528typedef struct
529{
530 int from;
531 int whitespace;
532 int dot;
533 int linelen;
534 int was_cr;
535}
536CONTENT_STATE;
537
538
539static void update_content_info (CONTENT *info, CONTENT_STATE *s, char *d, size_t dlen)
540{
541 int from = s->from;
542 int whitespace = s->whitespace;
543 int dot = s->dot;
544 int linelen = s->linelen;
545 int was_cr = s->was_cr;
546
547 if (!d) /* This signals EOF */
548 {
549 if (was_cr)
550 info->binary = 1;
551 if (linelen > info->linemax)
552 info->linemax = linelen;
553
554 return;
555 }
556
557 for (; dlen; d++, dlen--)
558 {
559 char ch = *d;
560
561 if (was_cr)
562 {
563 was_cr = 0;
564 if (ch != '\n')
565 {
566 info->binary = 1;
567 }
568 else
569 {
570 if (whitespace) info->space = 1;
571 if (dot) info->dot = 1;
572 if (linelen > info->linemax) info->linemax = linelen;
573 whitespace = 0;
574 dot = 0;
575 linelen = 0;
576 continue;
577 }
578 }
579
580 linelen++;
581 if (ch == '\n')
582 {
583 info->crlf++;
584 if (whitespace) info->space = 1;
585 if (dot) info->dot = 1;
586 if (linelen > info->linemax) info->linemax = linelen;
587 whitespace = 0;
588 linelen = 0;
589 dot = 0;
590 }
591 else if (ch == '\r')
592 {
593 info->crlf++;
594 info->cr = 1;
595 was_cr = 1;
596 continue;
597 }
598 else if (ch & 0x80)
599 info->hibin++;
600 else if (ch == '\t' || ch == '\f')
601 {
602 info->ascii++;
603 whitespace++;
604 }
605 else if (ch == 0)
606 {
607 info->nulbin++;
608 info->lobin++;
609 }
610 else if (ch < 32 || ch == 127)
611 info->lobin++;
612 else
613 {
614 if (linelen == 1)
615 {
616 if ((ch == 'F') || (ch == 'f'))
617 from = 1;
618 else
619 from = 0;
620 if (ch == '.')
621 dot = 1;
622 else
623 dot = 0;
624 }
625 else if (from)
626 {
627 if (linelen == 2 && ch != 'r') from = 0;
628 else if (linelen == 3 && ch != 'o') from = 0;
629 else if (linelen == 4)
630 {
631 if (ch == 'm') info->from = 1;
632 from = 0;
633 }
634 }
635 if (ch == ' ') whitespace++;
636 info->ascii++;
637 }
638
639 if (linelen > 1) dot = 0;
640 if (ch != ' ' && ch != '\t') whitespace = 0;
641 }
642
643 s->from = from;
644 s->whitespace = whitespace;
645 s->dot = dot;
646 s->linelen = linelen;
647 s->was_cr = was_cr;
648
649}
650
651/* Define as 1 if iconv sometimes returns -1(EILSEQ) instead of transcribing. */
652#define BUGGY_ICONV 1
653
654/*
655 * Find the best charset conversion of the file from fromcode into one
656 * of the tocodes. If successful, set *tocode and CONTENT *info and
657 * return the number of characters converted inexactly. If no
658 * conversion was possible, return -1.
659 *
660 * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
661 * which would otherwise prevent us from knowing the number of inexact
662 * conversions. Where the candidate target charset is UTF-8 we avoid
663 * doing the second conversion because iconv_open("UTF-8", "UTF-8")
664 * fails with some libraries.
665 *
666 * We assume that the output from iconv is never more than 4 times as
667 * long as the input for any pair of charsets we might be interested
668 * in.
669 */
670static size_t convert_file_to (FILE *file, const char *fromcode,
671 int ncodes, const char **tocodes,
672 int *tocode, CONTENT *info)
673{
674#ifdef HAVE_ICONV
675 iconv_t cd1, *cd;
676 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
677 ICONV_CONST char *ib, *ub;
678 char *ob;
679 size_t ibl, obl, ubl, ubl1, n, ret;
680 int i;
681 CONTENT *infos;
682 CONTENT_STATE *states;
683 size_t *score;
684
685 cd1 = mutt_iconv_open ("utf-8", fromcode, 0);
686 if (cd1 == (iconv_t)(-1))
687 return -1;
688
689 cd = safe_calloc (ncodes, sizeof (iconv_t));
690 score = safe_calloc (ncodes, sizeof (size_t));
691 states = safe_calloc (ncodes, sizeof (CONTENT_STATE));
692 infos = safe_calloc (ncodes, sizeof (CONTENT));
693
694 for (i = 0; i < ncodes; i++)
695 if (ascii_strcasecmp (tocodes[i], "utf-8"))
696 cd[i] = mutt_iconv_open (tocodes[i], "utf-8", 0);
697 else
698 /* Special case for conversion to UTF-8 */
699 cd[i] = (iconv_t)(-1), score[i] = (size_t)(-1);
700
701 rewind (file);
702 ibl = 0;
703 for (;;)
704 {
705
706 /* Try to fill input buffer */
707 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
708 ibl += n;
709
710 /* Convert to UTF-8 */
711 ib = bufi;
712 ob = bufu, obl = sizeof (bufu);
713 n = iconv (cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
714 assert (n == (size_t)(-1) || !n || ICONV_NONTRANS);
715 if (n == (size_t)(-1) &&
716 ((errno != EINVAL && errno != E2BIG) || ib == bufi))
717 {
718 assert (errno == EILSEQ ||
719 (errno == EINVAL && ib == bufi && ibl < sizeof (bufi)));
720 ret = (size_t)(-1);
721 break;
722 }
723 ubl1 = ob - bufu;
724
725 /* Convert from UTF-8 */
726 for (i = 0; i < ncodes; i++)
727 if (cd[i] != (iconv_t)(-1) && score[i] != (size_t)(-1))
728 {
729 ub = bufu, ubl = ubl1;
730 ob = bufo, obl = sizeof (bufo);
731 n = iconv (cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
732 if (n == (size_t)(-1))
733 {
734 assert (errno == E2BIG ||
735 (BUGGY_ICONV && (errno == EILSEQ || errno == ENOENT)));
736 score[i] = (size_t)(-1);
737 }
738 else
739 {
740 score[i] += n;
741 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
742 }
743 }
744 else if (cd[i] == (iconv_t)(-1) && score[i] == (size_t)(-1))
745 /* Special case for conversion to UTF-8 */
746 update_content_info (&infos[i], &states[i], bufu, ubl1);
747
748 if (ibl)
749 /* Save unused input */
750 memmove (bufi, ib, ibl);
751 else if (!ubl1 && ib < bufi + sizeof (bufi))
752 {
753 ret = 0;
754 break;
755 }
756 }
757
758 if (!ret)
759 {
760 /* Find best score */
761 ret = (size_t)(-1);
762 for (i = 0; i < ncodes; i++)
763 {
764 if (cd[i] == (iconv_t)(-1) && score[i] == (size_t)(-1))
765 {
766 /* Special case for conversion to UTF-8 */
767 *tocode = i;
768 ret = 0;
769 break;
770 }
771 else if (cd[i] == (iconv_t)(-1) || score[i] == (size_t)(-1))
772 continue;
773 else if (ret == (size_t)(-1) || score[i] < ret)
774 {
775 *tocode = i;
776 ret = score[i];
777 if (!ret)
778 break;
779 }
780 }
781 if (ret != (size_t)(-1))
782 {
783 memcpy (info, &infos[*tocode], sizeof(CONTENT));
784 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
785 }
786 }
787
788 for (i = 0; i < ncodes; i++)
789 if (cd[i] != (iconv_t)(-1))
790 iconv_close (cd[i]);
791
792 iconv_close (cd1);
793 FREE (&cd);
794 FREE (&infos);
795 FREE (&score);
796 FREE (&states);
797
798 return ret;
799#else
800 return -1;
801#endif /* !HAVE_ICONV */
802}
803
804/*
805 * Find the first of the fromcodes that gives a valid conversion and
806 * the best charset conversion of the file into one of the tocodes. If
807 * successful, set *fromcode and *tocode to dynamically allocated
808 * strings, set CONTENT *info, and return the number of characters
809 * converted inexactly. If no conversion was possible, return -1.
810 *
811 * Both fromcodes and tocodes may be colon-separated lists of charsets.
812 * However, if fromcode is zero then fromcodes is assumed to be the
813 * name of a single charset even if it contains a colon.
814 */
815static size_t convert_file_from_to (FILE *file,
816 const char *fromcodes, const char *tocodes,
817 char **fromcode, char **tocode, CONTENT *info)
818{
819 char *fcode = NULL;
820 char **tcode;
821 const char *c, *c1;
822 size_t ret;
823 int ncodes, i, cn;
824
825 /* Count the tocodes */
826 ncodes = 0;
827 for (c = tocodes; c; c = c1 ? c1 + 1 : 0)
828 {
829 if ((c1 = strchr (c, ':')) == c)
830 continue;
831 ++ncodes;
832 }
833
834 /* Copy them */
835 tcode = safe_malloc (ncodes * sizeof (char *));
836 for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++)
837 {
838 if ((c1 = strchr (c, ':')) == c)
839 continue;
840 tcode[i] = mutt_substrdup (c, c1);
841 }
842
843 ret = (size_t)(-1);
844 if (fromcode)
845 {
846 /* Try each fromcode in turn */
847 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0)
848 {
849 if ((c1 = strchr (c, ':')) == c)
850 continue;
851 fcode = mutt_substrdup (c, c1);
852
853 ret = convert_file_to (file, fcode, ncodes, (const char **)tcode,
854 &cn, info);
855 if (ret != (size_t)(-1))
856 {
857 *fromcode = fcode;
858 *tocode = tcode[cn];
859 tcode[cn] = 0;
860 break;
861 }
862 FREE (&fcode);
863 }
864 }
865 else
866 {
867 /* There is only one fromcode */
868 ret = convert_file_to (file, fromcodes, ncodes, (const char **)tcode,
869 &cn, info);
870 if (ret != (size_t)(-1))
871 {
872 *tocode = tcode[cn];
873 tcode[cn] = 0;
874 }
875 }
876
877 /* Free memory */
878 for (i = 0; i < ncodes; i++)
879 FREE (&tcode[i]);
880
881 FREE (&tcode);
882
883 return ret;
884}
885
886/*
887 * Analyze the contents of a file to determine which MIME encoding to use.
888 * Also set the body charset, sometimes, or not.
889 */
890CONTENT *mutt_get_content_info (const char *fname, BODY *b)
891{
892 CONTENT *info;
893 CONTENT_STATE state;
894 FILE *fp = NULL;
895 char *fromcode = NULL;
896 char *tocode;
897 char buffer[100];
898 char chsbuf[STRING];
899 size_t r;
900
901 struct stat sb;
902
903 if (b && !fname) fname = b->filename;
904
905 if (stat (fname, &sb) == -1)
906 {
907 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
908 return NULL;
909 }
910
911 if (!S_ISREG(sb.st_mode))
912 {
913 mutt_error (_("%s isn't a regular file."), fname);
914 return NULL;
915 }
916
917 if ((fp = fopen (fname, "r")) == NULL)
918 {
919 dprint (1, (debugfile, "mutt_get_content_info: %s: %s (errno %d).\n",
920 fname, strerror (errno), errno));
921 return (NULL);
922 }
923
924 info = safe_calloc (1, sizeof (CONTENT));
925 memset (&state, 0, sizeof (state));
926
927 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
928 {
929 char *chs = mutt_get_parameter ("charset", b->parameter);
930 char *fchs = b->use_disp ? (AttachCharset ? AttachCharset : Charset) : Charset;
931 if (Charset && (chs || SendCharset) &&
932 convert_file_from_to (fp, fchs, chs ? chs : SendCharset,
933 &fromcode, &tocode, info) != (size_t)(-1))
934 {
935 if (!chs)
936 {
937 mutt_canonical_charset (chsbuf, sizeof (chsbuf), tocode);
938 mutt_set_parameter ("charset", chsbuf, &b->parameter);
939 }
940 FREE (&b->charset);
941 b->charset = fromcode;
942 FREE (&tocode);
943 safe_fclose (&fp);
944 return info;
945 }
946 }
947
948 rewind (fp);
949 while ((r = fread (buffer, 1, sizeof(buffer), fp)))
950 update_content_info (info, &state, buffer, r);
951 update_content_info (info, &state, 0, 0);
952
953 safe_fclose (&fp);
954
955 if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
956 mutt_set_parameter ("charset", (!info->hibin ? "us-ascii" :
957 Charset && !mutt_is_us_ascii (Charset) ? Charset : "unknown-8bit"),
958 &b->parameter);
959
960 return info;
961}
962
963/* Given a file with path ``s'', see if there is a registered MIME type.
964 * returns the major MIME type, and copies the subtype to ``d''. First look
965 * for ~/.mime.types, then look in a system mime.types if we can find one.
966 * The longest match is used so that we can match `ps.gz' when `gz' also
967 * exists.
968 */
969
970int mutt_lookup_mime_type (BODY *att, const char *path)
971{
972 FILE *f;
973 char *p, *q, *ct;
974 char buf[LONG_STRING];
975 char subtype[STRING], xtype[STRING];
976 int count;
977 int szf, sze, cur_sze;
978 int type;
979
980 *subtype = '\0';
981 *xtype = '\0';
982 type = TYPEOTHER;
983 cur_sze = 0;
984
985 szf = mutt_strlen (path);
986
987 for (count = 0 ; count < 3 ; count++)
988 {
989 /*
990 * can't use strtok() because we use it in an inner loop below, so use
991 * a switch statement here instead.
992 */
993 switch (count)
994 {
995 case 0:
996 snprintf (buf, sizeof (buf), "%s/.mime.types", NONULL(Homedir));
997 break;
998 case 1:
999 strfcpy (buf, SYSCONFDIR"/mime.types", sizeof(buf));
1000 break;
1001 case 2:
1002 strfcpy (buf, PKGDATADIR"/mime.types", sizeof (buf));
1003 break;
1004 default:
1005 dprint (1, (debugfile, "mutt_lookup_mime_type: Internal error, count = %d.\n", count));
1006 goto bye; /* shouldn't happen */
1007 }
1008
1009 if ((f = fopen (buf, "r")) != NULL)
1010 {
1011 while (fgets (buf, sizeof (buf) - 1, f) != NULL)
1012 {
1013 /* weed out any comments */
1014 if ((p = strchr (buf, '#')))
1015 *p = 0;
1016
1017 /* remove any leading space. */
1018 ct = buf;
1019 SKIPWS (ct);
1020
1021 /* position on the next field in this line */
1022 if ((p = strpbrk (ct, " \t")) == NULL)
1023 continue;
1024 *p++ = 0;
1025 SKIPWS (p);
1026
1027 /* cycle through the file extensions */
1028 while ((p = strtok (p, " \t\n")))
1029 {
1030 sze = mutt_strlen (p);
1031 if ((sze > cur_sze) && (szf >= sze) &&
1032 (mutt_strcasecmp (path + szf - sze, p) == 0 || ascii_strcasecmp (path + szf - sze, p) == 0) &&
1033 (szf == sze || path[szf - sze - 1] == '.'))
1034 {
1035 /* get the content-type */
1036
1037 if ((p = strchr (ct, '/')) == NULL)
1038 {
1039 /* malformed line, just skip it. */
1040 break;
1041 }
1042 *p++ = 0;
1043
1044 for (q = p; *q && !ISSPACE (*q); q++)
1045 ;
1046
1047 mutt_substrcpy (subtype, p, q, sizeof (subtype));
1048
1049 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
1050 strfcpy (xtype, ct, sizeof (xtype));
1051
1052 cur_sze = sze;
1053 }
1054 p = NULL;
1055 }
1056 }
1057 safe_fclose (&f);
1058 }
1059 }
1060
1061bye:
1062
1063 if (type != TYPEOTHER || *xtype != '\0')
1064 {
1065 att->type = type;
1066 mutt_str_replace (&att->subtype, subtype);
1067 mutt_str_replace (&att->xtype, xtype);
1068 }
1069
1070 return (type);
1071}
1072
1073void mutt_message_to_7bit (BODY *a, FILE *fp)
1074{
1075 BUFFER *temp = NULL;
1076 FILE *fpin = NULL;
1077 FILE *fpout = NULL;
1078 struct stat sb;
1079
1080 if (!a->filename && fp)
1081 fpin = fp;
1082 else if (!a->filename || !(fpin = fopen (a->filename, "r")))
1083 {
1084 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
1085 return;
1086 }
1087 else
1088 {
1089 a->offset = 0;
1090 if (stat (a->filename, &sb) == -1)
1091 {
1092 mutt_perror ("stat");
1093 safe_fclose (&fpin);
1094 goto cleanup;
1095 }
1096 a->length = sb.st_size;
1097 }
1098
1099 /* Avoid buffer pool due to recursion */
1100 temp = mutt_buffer_new ();
1101 mutt_buffer_mktemp (temp);
1102 if (!(fpout = safe_fopen (mutt_b2s (temp), "w+")))
1103 {
1104 mutt_perror ("fopen");
1105 goto cleanup;
1106 }
1107
1108 fseeko (fpin, a->offset, 0);
1109 a->parts = mutt_parse_messageRFC822 (fpin, a);
1110
1111 transform_to_7bit (a->parts, fpin);
1112
1113 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1114 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1115
1116 fputs ("MIME-Version: 1.0\n", fpout);
1117 mutt_write_mime_header (a->parts, fpout);
1118 fputc ('\n', fpout);
1119 mutt_write_mime_body (a->parts, fpout);
1120
1121 if (fpin != fp)
1122 safe_fclose (&fpin);
1123 safe_fclose (&fpout);
1124
1125 a->encoding = ENC7BIT;
1126 FREE (&a->d_filename);
1127 a->d_filename = a->filename;
1128 if (a->filename && a->unlink)
1129 unlink (a->filename);
1130 a->filename = safe_strdup (mutt_b2s (temp));
1131 a->unlink = 1;
1132 if (stat (a->filename, &sb) == -1)
1133 {
1134 mutt_perror ("stat");
1135 goto cleanup;
1136 }
1137 a->length = sb.st_size;
1138 mutt_free_body (&a->parts);
1139 a->hdr->content = NULL;
1140
1141cleanup:
1142 if (fpin && fpin != fp)
1143 safe_fclose (&fpin);
1144
1145 if (fpout)
1146 {
1147 safe_fclose (&fpout);
1148 mutt_unlink (mutt_b2s (temp));
1149 }
1150
1151 mutt_buffer_free (&temp);
1152}
1153
1154static void transform_to_7bit (BODY *a, FILE *fpin)
1155{
1156 BUFFER *buff;
1157 STATE s;
1158 struct stat sb;
1159
1160 memset (&s, 0, sizeof (s));
1161 for (; a; a = a->next)
1162 {
1163 if (a->type == TYPEMULTIPART)
1164 {
1165 if (a->encoding != ENC7BIT)
1166 a->encoding = ENC7BIT;
1167
1168 transform_to_7bit (a->parts, fpin);
1169 }
1170 else if (mutt_is_message_type(a->type, a->subtype))
1171 {
1172 mutt_message_to_7bit (a, fpin);
1173 }
1174 else
1175 {
1176 a->noconv = 1;
1177 a->force_charset = 1;
1178
1179 /* Because of the potential recursion in message types, we
1180 * restrict the lifetime of the buffer tightly */
1181 buff = mutt_buffer_pool_get ();
1182 mutt_buffer_mktemp (buff);
1183 if ((s.fpout = safe_fopen (mutt_b2s (buff), "w")) == NULL)
1184 {
1185 mutt_perror ("fopen");
1186 mutt_buffer_pool_release (&buff);
1187 return;
1188 }
1189 s.fpin = fpin;
1190 mutt_decode_attachment (a, &s);
1191 safe_fclose (&s.fpout);
1192 FREE (&a->d_filename);
1193 a->d_filename = a->filename;
1194 a->filename = safe_strdup (mutt_b2s (buff));
1195 mutt_buffer_pool_release (&buff);
1196 a->unlink = 1;
1197 if (stat (a->filename, &sb) == -1)
1198 {
1199 mutt_perror ("stat");
1200 return;
1201 }
1202 a->length = sb.st_size;
1203
1204 mutt_update_encoding (a);
1205 if (a->encoding == ENC8BIT)
1206 a->encoding = ENCQUOTEDPRINTABLE;
1207 else if (a->encoding == ENCBINARY)
1208 a->encoding = ENCBASE64;
1209 }
1210 }
1211}
1212
1213/* determine which Content-Transfer-Encoding to use */
1214static void mutt_set_encoding (BODY *b, CONTENT *info)
1215{
1216 char send_charset[SHORT_STRING];
1217
1218 if (b->type == TYPETEXT)
1219 {
1220 char *chsname = mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1221 if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8)) || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1222 b->encoding = ENCQUOTEDPRINTABLE;
1223 else if (info->hibin)
1224 b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1225 else
1226 b->encoding = ENC7BIT;
1227 }
1228 else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
1229 {
1230 if (info->lobin || info->hibin)
1231 {
1232 if (option (OPTALLOW8BIT) && !info->lobin)
1233 b->encoding = ENC8BIT;
1234 else
1235 mutt_message_to_7bit (b, NULL);
1236 }
1237 else
1238 b->encoding = ENC7BIT;
1239 }
1240 else if (b->type == TYPEAPPLICATION && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1241 b->encoding = ENC7BIT;
1242 else
1243 {
1244 /* Determine which encoding is smaller */
1245 if (1.33 * (float)(info->lobin+info->hibin+info->ascii) <
1246 3.0 * (float)(info->lobin + info->hibin) + (float)info->ascii)
1247 b->encoding = ENCBASE64;
1248 else
1249 b->encoding = ENCQUOTEDPRINTABLE;
1250 }
1251}
1252
1253void mutt_stamp_attachment(BODY *a)
1254{
1255 a->stamp = time(NULL);
1256}
1257
1258/* Get a body's character set */
1259
1260char *mutt_get_body_charset (char *d, size_t dlen, BODY *b)
1261{
1262 char *p = NULL;
1263
1264 if (b && b->type != TYPETEXT)
1265 return NULL;
1266
1267 if (b)
1268 p = mutt_get_parameter ("charset", b->parameter);
1269
1270 if (p)
1271 mutt_canonical_charset (d, dlen, NONULL(p));
1272 else
1273 strfcpy (d, "us-ascii", dlen);
1274
1275 return d;
1276}
1277
1278
1279/* Assumes called from send mode where BODY->filename points to actual file */
1280void mutt_update_encoding (BODY *a)
1281{
1282 CONTENT *info;
1283 char chsbuff[STRING];
1284
1285 /* override noconv when it's us-ascii */
1286 if (mutt_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1287 a->noconv = 0;
1288
1289 if (!a->force_charset && !a->noconv)
1290 mutt_delete_parameter ("charset", &a->parameter);
1291
1292 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1293 return;
1294
1295 mutt_set_encoding (a, info);
1296 mutt_stamp_attachment(a);
1297
1298 FREE (&a->content);
1299 a->content = info;
1300
1301}
1302
1303BODY *mutt_make_message_attach (CONTEXT *ctx, HEADER *hdr, int attach_msg)
1304{
1305 char buffer[LONG_STRING];
1306 BODY *body;
1307 FILE *fp;
1308 int cmflags, chflags;
1309 int pgp = WithCrypto? hdr->security : 0;
1310
1311 if (WithCrypto)
1312 {
1313 if ((option(OPTMIMEFORWDECODE) || option(OPTFORWDECRYPT)) &&
1314 (hdr->security & ENCRYPT))
1315 {
1316 if (!crypt_valid_passphrase(hdr->security))
1317 return (NULL);
1318 }
1319 }
1320
1321 mutt_mktemp (buffer, sizeof (buffer));
1322 if ((fp = safe_fopen (buffer, "w+")) == NULL)
1323 return NULL;
1324
1325 body = mutt_new_body ();
1326 body->type = TYPEMESSAGE;
1327 body->subtype = safe_strdup ("rfc822");
1328 body->filename = safe_strdup (buffer);
1329 body->unlink = 1;
1330 body->use_disp = 0;
1331 body->disposition = DISPINLINE;
1332 body->noconv = 1;
1333
1334 mutt_parse_mime_message (ctx, hdr);
1335
1336 chflags = CH_XMIT;
1337 cmflags = 0;
1338
1339 /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1340 if (!attach_msg && option (OPTMIMEFORWDECODE))
1341 {
1342 chflags |= CH_MIME | CH_TXTPLAIN;
1343 cmflags = MUTT_CM_DECODE | MUTT_CM_CHARCONV;
1344 if ((WithCrypto & APPLICATION_PGP))
1345 pgp &= ~PGPENCRYPT;
1346 if ((WithCrypto & APPLICATION_SMIME))
1347 pgp &= ~SMIMEENCRYPT;
1348 }
1349 else if (WithCrypto
1350 && option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT))
1351 {
1352 if ((WithCrypto & APPLICATION_PGP)
1353 && mutt_is_multipart_encrypted (hdr->content))
1354 {
1355 chflags |= CH_MIME | CH_NONEWLINE;
1356 cmflags = MUTT_CM_DECODE_PGP;
1357 pgp &= ~PGPENCRYPT;
1358 }
1359 else if ((WithCrypto & APPLICATION_PGP) &&
1360 ((mutt_is_application_pgp (hdr->content) & PGPENCRYPT) == PGPENCRYPT))
1361 {
1362 chflags |= CH_MIME | CH_TXTPLAIN;
1363 cmflags = MUTT_CM_DECODE | MUTT_CM_CHARCONV;
1364 pgp &= ~PGPENCRYPT;
1365 }
1366 else if ((WithCrypto & APPLICATION_SMIME) &&
1367 ((mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) == SMIMEENCRYPT))
1368 {
1369 chflags |= CH_MIME | CH_TXTPLAIN;
1370 cmflags = MUTT_CM_DECODE | MUTT_CM_CHARCONV;
1371 pgp &= ~SMIMEENCRYPT;
1372 }
1373 }
1374
1375 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1376
1377 fflush(fp);
1378 rewind(fp);
1379
1380 body->hdr = mutt_new_header();
1381 body->hdr->offset = 0;
1382 /* we don't need the user headers here */
1383 body->hdr->env = mutt_read_rfc822_header(fp, body->hdr, 0, 0);
1384 if (WithCrypto)
1385 body->hdr->security = pgp;
1386 mutt_update_encoding (body);
1387 body->parts = body->hdr->content;
1388
1389 safe_fclose (&fp);
1390
1391 return (body);
1392}
1393
1394BODY *mutt_run_send_alternative_filter (BODY *b)
1395{
1396 BUFFER *alt_file = NULL;
1397 FILE *b_fp = NULL, *alt_fp = NULL;
1398 FILE *filter_in = NULL, *filter_out = NULL, *filter_err = NULL;
1399 BODY *alternative = NULL;
1400 pid_t thepid = 0;
1401 char *mime = NULL;
1402 char *buf = NULL;
1403 size_t buflen;
1404
1405 if (!SendMultipartAltFilter)
1406 return NULL;
1407
1408 if ((b_fp = safe_fopen (b->filename, "r")) == NULL)
1409 {
1410 mutt_perror (b->filename);
1411 goto cleanup;
1412 }
1413
1414 alt_file = mutt_buffer_pool_get ();
1415 mutt_buffer_mktemp (alt_file);
1416 if ((alt_fp = safe_fopen (mutt_b2s (alt_file), "w")) == NULL)
1417 {
1418 mutt_perror (mutt_b2s (alt_file));
1419 goto cleanup;
1420 }
1421
1422 if ((thepid = mutt_create_filter (SendMultipartAltFilter, &filter_in, &filter_out, &filter_err)) < 0)
1423 {
1424 mutt_error (_("Error running \"%s\"!"), SendMultipartAltFilter);
1425 goto cleanup;
1426 }
1427
1428 mutt_copy_stream (b_fp, filter_in);
1429 safe_fclose (&b_fp);
1430 safe_fclose (&filter_in);
1431
1432 mime = mutt_read_line (NULL, &buflen, filter_out, NULL, 0);
1433 if (!mime || !strchr (mime, '/'))
1434 {
1435 /* L10N:
1436 The first line of output from $send_multipart_alternative_filter
1437 should be a mime type, e.g. text/html. This error is generated
1438 if that is missing.
1439 */
1440 mutt_error (_("Missing mime type from output of \"%s\"!"), SendMultipartAltFilter);
1441 goto cleanup;
1442 }
1443
1444 buf = mutt_read_line (NULL, &buflen, filter_out, NULL, 0);
1445 if (!buf || mutt_strlen (buf))
1446 {
1447 /* L10N:
1448 The second line of output from $send_multipart_alternative_filter
1449 should be a blank line. This error is generated if the blank line
1450 is missing.
1451 */
1452 mutt_error (_("Missing blank line separator from output of \"%s\"!"), SendMultipartAltFilter);
1453 goto cleanup;
1454 }
1455
1456 mutt_copy_stream (filter_out, alt_fp);
1457 safe_fclose (&filter_out);
1458 safe_fclose (&filter_err);
1459
1460 if (mutt_wait_filter (thepid) != 0)
1461 {
1462 mutt_error (_("Error running \"%s\"!"), SendMultipartAltFilter);
1463 thepid = 0;
1464 goto cleanup;
1465 }
1466 thepid = 0;
1467 safe_fclose (&alt_fp);
1468
1469 alternative = mutt_new_body ();
1470 alternative->filename = safe_strdup (mutt_b2s (alt_file));
1471 alternative->unlink = 1;
1472 alternative->use_disp = 0;
1473 alternative->disposition = DISPINLINE;
1474
1475 mutt_parse_content_type (mime, alternative);
1476 if (alternative->type == TYPEMULTIPART)
1477 {
1478 /* L10N:
1479 Some clever people may try to generate a multipart/mixed
1480 "alternative" using $send_multipart_alternative_filter. The
1481 actual sending for this will not work, because the data
1482 structures will not be properly generated. To preempt bug
1483 reports, this error is displayed, and the generation is blocked
1484 at the filter level.
1485 */
1486 mutt_error _("$send_multipart_alternative_filter does not support multipart type generation.");
1487 mutt_free_body (&alternative);
1488 goto cleanup;
1489 }
1490 mutt_update_encoding (alternative);
1491
1492cleanup:
1493 safe_fclose (&b_fp);
1494 if (alt_fp)
1495 {
1496 safe_fclose (&alt_fp);
1497 mutt_unlink (mutt_b2s (alt_file));
1498 }
1499 mutt_buffer_pool_release (&alt_file);
1500 safe_fclose (&filter_in);
1501 safe_fclose (&filter_out);
1502 safe_fclose (&filter_err);
1503 if (thepid > 0)
1504 mutt_wait_filter (thepid);
1505 FREE (&buf);
1506 FREE (&mime);
1507
1508 return alternative;
1509}
1510
1511static void run_mime_type_query (BODY *att)
1512{
1513 FILE *fp, *fperr;
1514 BUFFER *cmd = NULL;
1515 char *buf = NULL;
1516 size_t buflen;
1517 int dummy = 0;
1518 pid_t thepid;
1519
1520 cmd = mutt_buffer_pool_get ();
1521 mutt_expand_file_fmt (cmd, MimeTypeQueryCmd, att->filename);
1522
1523 if ((thepid = mutt_create_filter (mutt_b2s (cmd), NULL, &fp, &fperr)) < 0)
1524 {
1525 mutt_error (_("Error running \"%s\"!"), mutt_b2s (cmd));
1526 mutt_buffer_pool_release (&cmd);
1527 return;
1528 }
1529 mutt_buffer_pool_release (&cmd);
1530
1531 if ((buf = mutt_read_line (buf, &buflen, fp, &dummy, 0)) != NULL)
1532 {
1533 if (strchr(buf, '/'))
1534 mutt_parse_content_type (buf, att);
1535 FREE (&buf);
1536 }
1537
1538 safe_fclose (&fp);
1539 safe_fclose (&fperr);
1540 mutt_wait_filter (thepid);
1541}
1542
1543BODY *mutt_make_file_attach (const char *path)
1544{
1545 BODY *att;
1546 CONTENT *info;
1547
1548 att = mutt_new_body ();
1549 att->filename = safe_strdup (path);
1550
1551 if (MimeTypeQueryCmd && option (OPTMIMETYPEQUERYFIRST))
1552 run_mime_type_query (att);
1553
1554 /* Attempt to determine the appropriate content-type based on the filename
1555 * suffix.
1556 */
1557 if (!att->subtype)
1558 mutt_lookup_mime_type (att, path);
1559
1560 if (!att->subtype &&
1561 MimeTypeQueryCmd &&
1562 !option (OPTMIMETYPEQUERYFIRST))
1563 run_mime_type_query (att);
1564
1565 if ((info = mutt_get_content_info (path, att)) == NULL)
1566 {
1567 mutt_free_body (&att);
1568 return NULL;
1569 }
1570
1571 if (!att->subtype)
1572 {
1573 if ((info->nulbin == 0) &&
1574 (info->lobin == 0 || (info->lobin + info->hibin + info->ascii)/ info->lobin >= 10))
1575 {
1576 /*
1577 * Statistically speaking, there should be more than 10% "lobin"
1578 * chars if this is really a binary file...
1579 */
1580 att->type = TYPETEXT;
1581 att->subtype = safe_strdup ("plain");
1582 }
1583 else
1584 {
1585 att->type = TYPEAPPLICATION;
1586 att->subtype = safe_strdup ("octet-stream");
1587 }
1588 }
1589
1590 FREE(&info);
1591 mutt_update_encoding (att);
1592 return (att);
1593}
1594
1595static int get_toplevel_encoding (BODY *a)
1596{
1597 int e = ENC7BIT;
1598
1599 for (; a; a = a->next)
1600 {
1601 if (a->encoding == ENCBINARY)
1602 return (ENCBINARY);
1603 else if (a->encoding == ENC8BIT)
1604 e = ENC8BIT;
1605 }
1606
1607 return (e);
1608}
1609
1610/* check for duplicate boundary. return 1 if duplicate */
1611static int mutt_check_boundary (const char* boundary, BODY *b)
1612{
1613 char* p;
1614
1615 if (b->parts && mutt_check_boundary (boundary, b->parts))
1616 return 1;
1617
1618 if (b->next && mutt_check_boundary (boundary, b->next))
1619 return 1;
1620
1621 if ((p = mutt_get_parameter ("boundary", b->parameter))
1622 && !ascii_strcmp (p, boundary))
1623 return 1;
1624 return 0;
1625}
1626
1627static BODY *mutt_make_multipart (BODY *b, const char *subtype)
1628{
1629 BODY *new;
1630
1631 new = mutt_new_body ();
1632 new->type = TYPEMULTIPART;
1633 new->subtype = safe_strdup (subtype);
1634 new->encoding = get_toplevel_encoding (b);
1635 do
1636 {
1637 mutt_generate_boundary (&new->parameter);
1638 if (mutt_check_boundary (mutt_get_parameter ("boundary", new->parameter),
1639 b))
1640 mutt_delete_parameter ("boundary", &new->parameter);
1641 }
1642 while (!mutt_get_parameter ("boundary", new->parameter));
1643 new->use_disp = 0;
1644 new->disposition = DISPINLINE;
1645 new->parts = b;
1646
1647 return new;
1648}
1649
1650/* remove the multipart body if it exists */
1651BODY *mutt_remove_multipart (BODY *b)
1652{
1653 BODY *t;
1654
1655 if (b->parts)
1656 {
1657 t = b;
1658 b = b->parts;
1659 t->parts = NULL;
1660 mutt_free_body (&t);
1661 }
1662 return b;
1663}
1664
1665BODY *mutt_make_multipart_mixed (BODY *b)
1666{
1667 return mutt_make_multipart (b, "mixed");
1668}
1669
1670/* remove the multipart/mixed body if it exists */
1671BODY *mutt_remove_multipart_mixed (BODY *b)
1672{
1673 if ((b->type == TYPEMULTIPART) &&
1674 !ascii_strcasecmp (b->subtype, "mixed"))
1675 return mutt_remove_multipart (b);
1676
1677 return b;
1678}
1679
1680BODY *mutt_make_multipart_alternative (BODY *b, BODY *alternative)
1681{
1682 BODY *attachments, *mp;
1683
1684 attachments = b->next;
1685
1686 b->next = alternative;
1687 mp = mutt_make_multipart (b, "alternative");
1688
1689 mp->next = attachments;
1690
1691 return mp;
1692}
1693
1694BODY *mutt_remove_multipart_alternative (BODY *b)
1695{
1696 BODY *attachments;
1697
1698 if ((b->type != TYPEMULTIPART) ||
1699 ascii_strcasecmp (b->subtype, "alternative"))
1700 return b;
1701
1702 attachments = b->next;
1703 b->next = NULL;
1704
1705 b = mutt_remove_multipart (b);
1706
1707 mutt_free_body (&b->next);
1708 b->next = attachments;
1709
1710 return b;
1711}
1712
1713char *mutt_make_date (char *s, size_t len)
1714{
1715 time_t t = time (NULL);
1716 struct tm *l = localtime (&t);
1717 time_t tz = mutt_local_tz (t);
1718
1719 tz /= 60;
1720
1721 snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1722 Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1723 l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1724 (int) tz / 60, (int) abs ((int) tz) % 60);
1725 return (s);
1726}
1727
1728/* wrapper around mutt_write_address() so we can handle very large
1729 recipient lists without needing a huge temporary buffer in memory */
1730void mutt_write_address_list (ADDRESS *adr, FILE *fp, int linelen, int display)
1731{
1732 ADDRESS *tmp;
1733 char buf[LONG_STRING];
1734 int count = 0;
1735 int len;
1736
1737 while (adr)
1738 {
1739 tmp = adr->next;
1740 adr->next = NULL;
1741 buf[0] = 0;
1742 rfc822_write_address (buf, sizeof (buf), adr, display);
1743 len = mutt_strlen (buf);
1744 if (count && linelen + len > 74)
1745 {
1746 fputs ("\n\t", fp);
1747 linelen = len + 8; /* tab is usually about 8 spaces... */
1748 }
1749 else
1750 {
1751 if (count && adr->mailbox)
1752 {
1753 fputc (' ', fp);
1754 linelen++;
1755 }
1756 linelen += len;
1757 }
1758 fputs (buf, fp);
1759 adr->next = tmp;
1760 if (!adr->group && adr->next && adr->next->mailbox)
1761 {
1762 linelen++;
1763 fputc (',', fp);
1764 }
1765 adr = adr->next;
1766 count++;
1767 }
1768 fputc ('\n', fp);
1769}
1770
1771/* arbitrary number of elements to grow the array by */
1772#define REF_INC 16
1773
1774/* need to write the list in reverse because they are stored in reverse order
1775 * when parsed to speed up threading
1776 */
1777void mutt_write_references (LIST *r, FILE *f, int trim)
1778{
1779 LIST **ref = NULL;
1780 int refcnt = 0, refmax = 0;
1781
1782 for ( ; (trim == 0 || refcnt < trim) && r ; r = r->next)
1783 {
1784 if (refcnt == refmax)
1785 safe_realloc (&ref, (refmax += REF_INC) * sizeof (LIST *));
1786 ref[refcnt++] = r;
1787 }
1788
1789 while (refcnt-- > 0)
1790 {
1791 fputc (' ', f);
1792 fputs (ref[refcnt]->data, f);
1793 if (refcnt >= 1)
1794 fputc ('\n', f);
1795 }
1796
1797 FREE (&ref);
1798}
1799
1800static const char *find_word (const char *src)
1801{
1802 const char *p = src;
1803
1804 while (p && *p && strchr (" \t\n", *p))
1805 p++;
1806 while (p && *p && !strchr (" \t\n", *p))
1807 p++;
1808 return p;
1809}
1810
1811/* like wcwidth(), but gets const char* not wchar_t* */
1812static int my_width (const char *str, int col, int flags)
1813{
1814 wchar_t wc;
1815 int l, w = 0, nl = 0;
1816 const char *p = str;
1817
1818 while (p && *p)
1819 {
1820 if (mbtowc (&wc, p, MB_CUR_MAX) >= 0)
1821 {
1822 l = wcwidth (wc);
1823 if (l < 0)
1824 l = 1;
1825 /* correctly calc tab stop, even for sending as the
1826 * line should look pretty on the receiving end */
1827 if (wc == L'\t' || (nl && wc == L' '))
1828 {
1829 nl = 0;
1830 l = 8 - (col % 8);
1831 }
1832 /* track newlines for display-case: if we have a space
1833 * after a newline, assume 8 spaces as for display we
1834 * always tab-fold */
1835 else if ((flags & CH_DISPLAY) && wc == '\n')
1836 nl = 1;
1837 }
1838 else
1839 l = 1;
1840 w += l;
1841 p++;
1842 }
1843 return w;
1844}
1845
1846static int print_val (FILE *fp, const char *pfx, const char *value,
1847 int flags, size_t col)
1848{
1849 while (value && *value)
1850 {
1851 if (fputc (*value, fp) == EOF)
1852 return -1;
1853 /* corner-case: break words longer than 998 chars by force,
1854 * mandated by RfC5322 */
1855 if (!(flags & CH_DISPLAY) && ++col >= 998)
1856 {
1857 if (fputs ("\n ", fp) < 0)
1858 return -1;
1859 col = 1;
1860 }
1861 if (*value == '\n')
1862 {
1863 if (*(value + 1) && pfx && *pfx && fputs (pfx, fp) == EOF)
1864 return -1;
1865 /* for display, turn folding spaces into folding tabs */
1866 if ((flags & CH_DISPLAY) && (*(value + 1) == ' ' || *(value + 1) == '\t'))
1867 {
1868 value++;
1869 while (*value && (*value == ' ' || *value == '\t'))
1870 value++;
1871 if (fputc ('\t', fp) == EOF)
1872 return -1;
1873 continue;
1874 }
1875 }
1876 value++;
1877 }
1878 return 0;
1879}
1880
1881static int fold_one_header (FILE *fp, const char *tag, const char *value,
1882 const char *pfx, int wraplen, int flags)
1883{
1884 const char *p = value, *next, *sp;
1885 char buf[HUGE_STRING] = "";
1886 int first = 1, enc, col = 0, w, l = 0, fold;
1887
1888 dprint(4,(debugfile,"mwoh: pfx=[%s], tag=[%s], flags=%d value=[%s]\n",
1889 NONULL (pfx), tag, flags, value));
1890
1891 if (tag && *tag && fprintf (fp, "%s%s: ", NONULL (pfx), tag) < 0)
1892 return -1;
1893 col = mutt_strlen (tag) + (tag && *tag ? 2 : 0) + mutt_strlen (pfx);
1894
1895 while (p && *p)
1896 {
1897 fold = 0;
1898
1899 /* find the next word and place it in `buf'. it may start with
1900 * whitespace we can fold before */
1901 next = find_word (p);
1902 l = MIN(sizeof (buf) - 1, next - p);
1903 memcpy (buf, p, l);
1904 buf[l] = 0;
1905
1906 /* determine width: character cells for display, bytes for sending
1907 * (we get pure ascii only) */
1908 w = my_width (buf, col, flags);
1909 enc = mutt_strncmp (buf, "=?", 2) == 0;
1910
1911 dprint(5,(debugfile,"mwoh: word=[%s], col=%d, w=%d, next=[0x0%x]\n",
1912 buf, col, w, *next));
1913
1914 /* insert a folding \n before the current word's lwsp except for
1915 * header name, first word on a line (word longer than wrap width)
1916 * and encoded words */
1917 if (!first && !enc && col && col + w >= wraplen)
1918 {
1919 col = mutt_strlen (pfx);
1920 fold = 1;
1921 if (fprintf (fp, "\n%s", NONULL(pfx)) <= 0)
1922 return -1;
1923 }
1924
1925 /* print the actual word; for display, ignore leading ws for word
1926 * and fold with tab for readability */
1927 if ((flags & CH_DISPLAY) && fold)
1928 {
1929 char *p = buf;
1930 while (*p && (*p == ' ' || *p == '\t'))
1931 {
1932 p++;
1933 col--;
1934 }
1935 if (fputc ('\t', fp) == EOF)
1936 return -1;
1937 if (print_val (fp, pfx, p, flags, col) < 0)
1938 return -1;
1939 col += 8;
1940 }
1941 else if (print_val (fp, pfx, buf, flags, col) < 0)
1942 return -1;
1943 col += w;
1944
1945 /* if the current word ends in \n, ignore all its trailing spaces
1946 * and reset column; this prevents us from putting only spaces (or
1947 * even none) on a line if the trailing spaces are located at our
1948 * current line width
1949 * XXX this covers ASCII space only, for display we probably
1950 * XXX want something like iswspace() here */
1951 sp = next;
1952 while (*sp && (*sp == ' ' || *sp == '\t'))
1953 sp++;
1954 if (*sp == '\n')
1955 {
1956 next = sp;
1957 col = 0;
1958 }
1959
1960 p = next;
1961 first = 0;
1962 }
1963
1964 /* if we have printed something but didn't \n-terminate it, do it
1965 * except the last word we printed ended in \n already */
1966 if (col && (l == 0 || buf[l - 1] != '\n'))
1967 if (putc ('\n', fp) == EOF)
1968 return -1;
1969
1970 return 0;
1971}
1972
1973static char *unfold_header (char *s)
1974{
1975 char *p = s, *q = s;
1976
1977 while (p && *p)
1978 {
1979 /* remove CRLF prior to FWSP, turn \t into ' ' */
1980 if (*p == '\r' && *(p + 1) && *(p + 1) == '\n' && *(p + 2) &&
1981 (*(p + 2) == ' ' || *(p + 2) == '\t'))
1982 {
1983 *q++ = ' ';
1984 p += 3;
1985 continue;
1986 }
1987 /* remove LF prior to FWSP, turn \t into ' ' */
1988 else if (*p == '\n' && *(p + 1) && (*(p + 1) == ' ' || *(p + 1) == '\t'))
1989 {
1990 *q++ = ' ';
1991 p += 2;
1992 continue;
1993 }
1994 *q++ = *p++;
1995 }
1996 if (q)
1997 *q = 0;
1998
1999 return s;
2000}
2001
2002static int write_one_header (FILE *fp, int pfxw, int max, int wraplen,
2003 const char *pfx, const char *start, const char *end,
2004 int flags)
2005{
2006 char *tagbuf, *valbuf, *t;
2007 int is_from = ((end - start) > 5 &&
2008 ascii_strncasecmp (start, "from ", 5) == 0);
2009
2010 /* only pass through folding machinery if necessary for sending,
2011 never wrap From_ headers on sending */
2012 if (!(flags & CH_DISPLAY) && (pfxw + max <= wraplen || is_from))
2013 {
2014 valbuf = mutt_substrdup (start, end);
2015 dprint(4,(debugfile,"mwoh: buf[%s%s] short enough, "
2016 "max width = %d <= %d\n",
2017 NONULL(pfx), valbuf, max, wraplen));
2018 if (pfx && *pfx)
2019 if (fputs (pfx, fp) == EOF)
2020 return -1;
2021 if (!(t = strchr (valbuf, ':')))
2022 {
2023 dprint (1, (debugfile, "mwoh: warning: header not in "
2024 "'key: value' format!\n"));
2025 return 0;
2026 }
2027 if (print_val (fp, pfx, valbuf, flags, mutt_strlen (pfx)) < 0)
2028 {
2029 FREE(&valbuf);
2030 return -1;
2031 }
2032 FREE(&valbuf);
2033 }
2034 else
2035 {
2036 t = strchr (start, ':');
2037 if (!t || t > end)
2038 {
2039 dprint (1, (debugfile, "mwoh: warning: header not in "
2040 "'key: value' format!\n"));
2041 return 0;
2042 }
2043 if (is_from)
2044 {
2045 tagbuf = NULL;
2046 valbuf = mutt_substrdup (start, end);
2047 }
2048 else
2049 {
2050 tagbuf = mutt_substrdup (start, t);
2051 /* skip over the colon separating the header field name and value */
2052 ++t;
2053
2054 /* skip over any leading whitespace (WSP, as defined in RFC5322)
2055 * NOTE: skip_email_wsp() does the wrong thing here.
2056 * See tickets 3609 and 3716. */
2057 while (*t == ' ' || *t == '\t')
2058 t++;
2059
2060 valbuf = mutt_substrdup (t, end);
2061 }
2062 dprint(4,(debugfile,"mwoh: buf[%s%s] too long, "
2063 "max width = %d > %d\n",
2064 NONULL(pfx), valbuf, max, wraplen));
2065 if (fold_one_header (fp, tagbuf, valbuf, pfx, wraplen, flags) < 0)
2066 return -1;
2067 FREE (&tagbuf);
2068 FREE (&valbuf);
2069 }
2070 return 0;
2071}
2072
2073/* split several headers into individual ones and call write_one_header
2074 * for each one */
2075int mutt_write_one_header (FILE *fp, const char *tag, const char *value,
2076 const char *pfx, int wraplen, int flags)
2077{
2078 char *p = (char *)value, *last, *line;
2079 int max = 0, w, rc = -1;
2080 int pfxw = mutt_strwidth (pfx);
2081 char *v = safe_strdup (value);
2082
2083 if (!(flags & CH_DISPLAY) || option (OPTWEED))
2084 v = unfold_header (v);
2085
2086 /* when not displaying, use sane wrap value */
2087 if (!(flags & CH_DISPLAY))
2088 {
2089 if (WrapHeaders < 78 || WrapHeaders > 998)
2090 wraplen = 78;
2091 else
2092 wraplen = WrapHeaders;
2093 }
2094 else if (wraplen <= 0 || wraplen > MuttIndexWindow->cols)
2095 wraplen = MuttIndexWindow->cols;
2096
2097 if (tag)
2098 {
2099 /* if header is short enough, simply print it */
2100 if (!(flags & CH_DISPLAY) && mutt_strwidth (tag) + 2 + pfxw +
2101 mutt_strwidth (v) <= wraplen)
2102 {
2103 dprint(4,(debugfile,"mwoh: buf[%s%s: %s] is short enough\n",
2104 NONULL(pfx), tag, v));
2105 if (fprintf (fp, "%s%s: %s\n", NONULL(pfx), tag, v) <= 0)
2106 goto out;
2107 rc = 0;
2108 goto out;
2109 }
2110 else
2111 {
2112 rc = fold_one_header (fp, tag, v, pfx, wraplen, flags);
2113 goto out;
2114 }
2115 }
2116
2117 p = last = line = (char *)v;
2118 while (p && *p)
2119 {
2120 p = strchr (p, '\n');
2121
2122 /* find maximum line width in current header */
2123 if (p)
2124 *p = 0;
2125 if ((w = my_width (line, 0, flags)) > max)
2126 max = w;
2127 if (p)
2128 *p = '\n';
2129
2130 if (!p)
2131 break;
2132
2133 line = ++p;
2134 if (*p != ' ' && *p != '\t')
2135 {
2136 if (write_one_header (fp, pfxw, max, wraplen, pfx, last, p, flags) < 0)
2137 goto out;
2138 last = p;
2139 max = 0;
2140 }
2141 }
2142
2143 if (last && *last)
2144 if (write_one_header (fp, pfxw, max, wraplen, pfx, last, p, flags) < 0)
2145 goto out;
2146
2147 rc = 0;
2148
2149out:
2150 FREE (&v);
2151 return rc;
2152}
2153
2154
2155/* Note: all RFC2047 encoding should be done outside of this routine, except
2156 * for the "real name." This will allow this routine to be used more than
2157 * once, if necessary.
2158 *
2159 * Likewise, all IDN processing should happen outside of this routine.
2160 *
2161 * mode == MUTT_WRITE_HEADER_EDITHDRS => "lite" mode (used for edit_hdrs)
2162 * mode == MUTT_WRITE_HEADER_NORMAL => normal mode. write full header + MIME headers
2163 * mode == MUTT_WRITE_HEADER_FCC => fcc mode, like normal mode but for Bcc header
2164 * mode == MUTT_WRITE_HEADER_POSTPONE => write just the envelope info
2165 * mode == MUTT_WRITE_HEADER_MIME => for writing protected headers
2166 *
2167 * privacy != 0 => will omit any headers which may identify the user.
2168 * Output generated is suitable for being sent through
2169 * anonymous remailer chains.
2170 *
2171 * hide_protected_subject: replaces the Subject header with
2172 * $crypt_protected_headers_subject in NORMAL or POSTPONE mode.
2173 *
2174 */
2175int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach,
2176 mutt_write_header_mode mode, int privacy,
2177 int hide_protected_subject)
2178{
2179 char buffer[LONG_STRING];
2180 char *p, *q;
2181 LIST *tmp = env->userhdrs;
2182 int has_agent = 0; /* user defined user-agent header field exists */
2183
2184 if ((mode == MUTT_WRITE_HEADER_NORMAL || mode == MUTT_WRITE_HEADER_FCC) &&
2185 !privacy)
2186 fputs (mutt_make_date (buffer, sizeof(buffer)), fp);
2187
2188 /* OPTUSEFROM is not consulted here so that we can still write a From:
2189 * field if the user sets it with the `my_hdr' command
2190 */
2191 if (env->from && !privacy)
2192 {
2193 buffer[0] = 0;
2194 rfc822_write_address (buffer, sizeof (buffer), env->from, 0);
2195 fprintf (fp, "From: %s\n", buffer);
2196 }
2197
2198 if (env->sender && !privacy)
2199 {
2200 buffer[0] = 0;
2201 rfc822_write_address (buffer, sizeof (buffer), env->sender, 0);
2202 fprintf (fp, "Sender: %s\n", buffer);
2203 }
2204
2205 if (env->to)
2206 {
2207 fputs ("To: ", fp);
2208 mutt_write_address_list (env->to, fp, 4, 0);
2209 }
2210 else if (mode == MUTT_WRITE_HEADER_EDITHDRS)
2211 fputs ("To: \n", fp);
2212
2213 if (env->cc)
2214 {
2215 fputs ("Cc: ", fp);
2216 mutt_write_address_list (env->cc, fp, 4, 0);
2217 }
2218 else if (mode == MUTT_WRITE_HEADER_EDITHDRS)
2219 fputs ("Cc: \n", fp);
2220
2221 if (env->bcc)
2222 {
2223 if (mode == MUTT_WRITE_HEADER_POSTPONE ||
2224 mode == MUTT_WRITE_HEADER_EDITHDRS ||
2225 mode == MUTT_WRITE_HEADER_FCC ||
2226 (mode == MUTT_WRITE_HEADER_NORMAL && option(OPTWRITEBCC)))
2227 {
2228 fputs ("Bcc: ", fp);
2229 mutt_write_address_list (env->bcc, fp, 5, 0);
2230 }
2231 }
2232 else if (mode == MUTT_WRITE_HEADER_EDITHDRS)
2233 fputs ("Bcc: \n", fp);
2234
2235 if (env->subject)
2236 {
2237 if (hide_protected_subject &&
2238 (mode == MUTT_WRITE_HEADER_NORMAL ||
2239 mode == MUTT_WRITE_HEADER_FCC ||
2240 mode == MUTT_WRITE_HEADER_POSTPONE))
2241 mutt_write_one_header (fp, "Subject", ProtHdrSubject, NULL, 0, 0);
2242 else
2243 mutt_write_one_header (fp, "Subject", env->subject, NULL, 0, 0);
2244 }
2245 else if (mode == MUTT_WRITE_HEADER_EDITHDRS)
2246 fputs ("Subject: \n", fp);
2247
2248 /* save message id if the user has set it */
2249 if (env->message_id && !privacy)
2250 fprintf (fp, "Message-ID: %s\n", env->message_id);
2251
2252 if (env->reply_to)
2253 {
2254 fputs ("Reply-To: ", fp);
2255 mutt_write_address_list (env->reply_to, fp, 10, 0);
2256 }
2257 else if (mode == MUTT_WRITE_HEADER_EDITHDRS)
2258 fputs ("Reply-To: \n", fp);
2259
2260 if (env->mail_followup_to)
2261 {
2262 fputs ("Mail-Followup-To: ", fp);
2263 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
2264 }
2265
2266 if (mode == MUTT_WRITE_HEADER_NORMAL ||
2267 mode == MUTT_WRITE_HEADER_FCC ||
2268 mode == MUTT_WRITE_HEADER_POSTPONE)
2269 {
2270 if (env->references)
2271 {
2272 fputs ("References:", fp);
2273 mutt_write_references (env->references, fp, 10);
2274 fputc('\n', fp);
2275 }
2276
2277 /* Add the MIME headers */
2278 fputs ("MIME-Version: 1.0\n", fp);
2279 mutt_write_mime_header (attach, fp);
2280 }
2281
2282 if (env->in_reply_to)
2283 {
2284 fputs ("In-Reply-To:", fp);
2285 mutt_write_references (env->in_reply_to, fp, 0);
2286 fputc ('\n', fp);
2287 }
2288
2289#ifdef USE_AUTOCRYPT
2290 if (option (OPTAUTOCRYPT))
2291 {
2292 if (mode == MUTT_WRITE_HEADER_NORMAL || mode == MUTT_WRITE_HEADER_FCC)
2293 mutt_autocrypt_write_autocrypt_header (env, fp);
2294 if (mode == MUTT_WRITE_HEADER_MIME)
2295 mutt_autocrypt_write_gossip_headers (env, fp);
2296 }
2297#endif
2298
2299 /* Add any user defined headers */
2300 for (; tmp; tmp = tmp->next)
2301 {
2302 if ((p = strchr (tmp->data, ':')))
2303 {
2304 q = p;
2305
2306 *p = '\0';
2307
2308 p = skip_email_wsp(p + 1);
2309 if (!*p)
2310 {
2311 *q = ':';
2312 continue; /* don't emit empty fields. */
2313 }
2314
2315 /* check to see if the user has overridden the user-agent field */
2316 if (!ascii_strncasecmp ("user-agent", tmp->data, 10))
2317 {
2318 has_agent = 1;
2319 if (privacy)
2320 {
2321 *q = ':';
2322 continue;
2323 }
2324 }
2325
2326 mutt_write_one_header (fp, tmp->data, p, NULL, 0, 0);
2327 *q = ':';
2328 }
2329 }
2330
2331 if ((mode == MUTT_WRITE_HEADER_NORMAL || mode == MUTT_WRITE_HEADER_FCC) &&
2332 !privacy &&
2333 option (OPTXMAILER) && !has_agent)
2334 {
2335 /* Add a vanity header */
2336 fprintf (fp, "User-Agent: Mutt/%s (%s)\n", MUTT_VERSION, ReleaseDate);
2337 }
2338
2339 return (ferror (fp) == 0 ? 0 : -1);
2340}
2341
2342static void encode_headers (LIST *h)
2343{
2344 char *tmp;
2345 char *p;
2346 int i;
2347
2348 for (; h; h = h->next)
2349 {
2350 if (!(p = strchr (h->data, ':')))
2351 continue;
2352
2353 i = p - h->data;
2354 p = skip_email_wsp(p + 1);
2355 tmp = safe_strdup (p);
2356
2357 if (!tmp)
2358 continue;
2359
2360 rfc2047_encode_string (&tmp);
2361 safe_realloc (&h->data, mutt_strlen (h->data) + 2 + mutt_strlen (tmp) + 1);
2362
2363 sprintf (h->data + i, ": %s", NONULL (tmp)); /* __SPRINTF_CHECKED__ */
2364
2365 FREE (&tmp);
2366 }
2367}
2368
2369const char *mutt_fqdn(short may_hide_host)
2370{
2371 char *p = NULL;
2372
2373 if (Fqdn && Fqdn[0] != '@')
2374 {
2375 p = Fqdn;
2376
2377 if (may_hide_host && option(OPTHIDDENHOST))
2378 {
2379 if ((p = strchr(Fqdn, '.')))
2380 p++;
2381
2382 /* sanity check: don't hide the host if
2383 * the fqdn is something like detebe.org.
2384 */
2385
2386 if (!p || !strchr(p, '.'))
2387 p = Fqdn;
2388 }
2389 }
2390
2391 return p;
2392}
2393
2394char *mutt_gen_msgid (void)
2395{
2396 char buf[SHORT_STRING];
2397 time_t now;
2398 struct tm *tm;
2399 const char *fqdn;
2400
2401 now = time (NULL);
2402 tm = gmtime (&now);
2403 if (!(fqdn = mutt_fqdn(0)))
2404 fqdn = NONULL(Hostname);
2405
2406 snprintf (buf, sizeof (buf), "<%d%02d%02d%02d%02d%02d.G%c%u@%s>",
2407 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour,
2408 tm->tm_min, tm->tm_sec, MsgIdPfx, (unsigned int)getpid (), fqdn);
2409 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
2410 return (safe_strdup (buf));
2411}
2412
2413static void alarm_handler (int sig)
2414{
2415 SigAlrm = 1;
2416}
2417
2418/* invoke sendmail in a subshell
2419 path (in) path to program to execute
2420 args (in) arguments to pass to program
2421 msg (in) temp file containing message to send
2422 tempfile (out) if sendmail is put in the background, this points
2423 to the temporary file containing the stdout of the
2424 child process. If it is NULL, stderr and stdout
2425 are not redirected. */
2426static int
2427send_msg (const char *path, char **args, const char *msg, char **tempfile)
2428{
2429 sigset_t set;
2430 int fd, st;
2431 pid_t pid, ppid;
2432
2433 mutt_block_signals_system ();
2434
2435 sigemptyset (&set);
2436 /* we also don't want to be stopped right now */
2437 sigaddset (&set, SIGTSTP);
2438 sigprocmask (SIG_BLOCK, &set, NULL);
2439
2440 if (SendmailWait >= 0 && tempfile)
2441 {
2442 BUFFER *tmp;
2443
2444 tmp = mutt_buffer_pool_get ();
2445 mutt_buffer_mktemp (tmp);
2446 *tempfile = safe_strdup (mutt_b2s (tmp));
2447 mutt_buffer_pool_release (&tmp);
2448 }
2449
2450 if ((pid = fork ()) == 0)
2451 {
2452 struct sigaction act, oldalrm;
2453
2454 /* save parent's ID before setsid() */
2455 ppid = getppid ();
2456
2457 /* we want the delivery to continue even after the main process dies,
2458 * so we put ourselves into another session right away
2459 */
2460 setsid ();
2461
2462 /* next we close all open files */
2463 close (0);
2464#if defined(OPEN_MAX)
2465 for (fd = tempfile ? 1 : 3; fd < OPEN_MAX; fd++)
2466 close (fd);
2467#elif defined(_POSIX_OPEN_MAX)
2468 for (fd = tempfile ? 1 : 3; fd < _POSIX_OPEN_MAX; fd++)
2469 close (fd);
2470#else
2471 if (tempfile)
2472 {
2473 close (1);
2474 close (2);
2475 }
2476#endif
2477
2478 /* now the second fork() */
2479 if ((pid = fork ()) == 0)
2480 {
2481 /* "msg" will be opened as stdin */
2482 if (open (msg, O_RDONLY, 0) < 0)
2483 {
2484 unlink (msg);
2485 _exit (S_ERR);
2486 }
2487 unlink (msg);
2488
2489 if (SendmailWait >= 0 && tempfile && *tempfile)
2490 {
2491 /* *tempfile will be opened as stdout */
2492 if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) < 0)
2493 _exit (S_ERR);
2494 /* redirect stderr to *tempfile too */
2495 if (dup (1) < 0)
2496 _exit (S_ERR);
2497 }
2498 else if (tempfile)
2499 {
2500 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
2501 _exit (S_ERR);
2502 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
2503 _exit (S_ERR);
2504 }
2505
2506 /* execvpe is a glibc extension */
2507 /* execvpe (path, args, mutt_envlist ()); */
2508 execvp (path, args);
2509 _exit (S_ERR);
2510 }
2511 else if (pid == -1)
2512 {
2513 unlink (msg);
2514 if (tempfile)
2515 FREE (tempfile); /* __FREE_CHECKED__ */
2516 _exit (S_ERR);
2517 }
2518
2519 /* SendmailWait > 0: interrupt waitpid() after SendmailWait seconds
2520 * SendmailWait = 0: wait forever
2521 * SendmailWait < 0: don't wait
2522 */
2523 if (SendmailWait > 0)
2524 {
2525 SigAlrm = 0;
2526 act.sa_handler = alarm_handler;
2527#ifdef SA_INTERRUPT
2528 /* need to make sure waitpid() is interrupted on SIGALRM */
2529 act.sa_flags = SA_INTERRUPT;
2530#else
2531 act.sa_flags = 0;
2532#endif
2533 sigemptyset (&act.sa_mask);
2534 sigaction (SIGALRM, &act, &oldalrm);
2535 alarm (SendmailWait);
2536 }
2537 else if (SendmailWait < 0)
2538 _exit (0xff & EX_OK);
2539
2540 if (waitpid (pid, &st, 0) > 0)
2541 {
2542 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
2543 if (SendmailWait && st == (0xff & EX_OK) && tempfile && *tempfile)
2544 {
2545 unlink (*tempfile); /* no longer needed */
2546 FREE (tempfile); /* __FREE_CHECKED__ */
2547 }
2548 }
2549 else
2550 {
2551 st = (SendmailWait > 0 && errno == EINTR && SigAlrm) ?
2552 S_BKG : S_ERR;
2553 if (SendmailWait > 0 && tempfile && *tempfile)
2554 {
2555 unlink (*tempfile);
2556 FREE (tempfile); /* __FREE_CHECKED__ */
2557 }
2558 }
2559
2560 /* reset alarm; not really needed, but... */
2561 alarm (0);
2562 sigaction (SIGALRM, &oldalrm, NULL);
2563
2564 if (kill (ppid, 0) == -1 && errno == ESRCH && tempfile && *tempfile)
2565 {
2566 /* the parent is already dead */
2567 unlink (*tempfile);
2568 FREE (tempfile); /* __FREE_CHECKED__ */
2569 }
2570
2571 _exit (st);
2572 }
2573
2574 sigprocmask (SIG_UNBLOCK, &set, NULL);
2575
2576 if (pid != -1 && waitpid (pid, &st, 0) > 0)
2577 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
2578 else
2579 st = S_ERR; /* error */
2580
2581 mutt_unblock_signals_system (1);
2582
2583 return (st);
2584}
2585
2586static char **
2587add_args (char **args, size_t *argslen, size_t *argsmax, ADDRESS *addr)
2588{
2589 for (; addr; addr = addr->next)
2590 {
2591 /* weed out group mailboxes, since those are for display only */
2592 if (addr->mailbox && !addr->group)
2593 {
2594 if (*argslen == *argsmax)
2595 safe_realloc (&args, (*argsmax += 5) * sizeof (char *));
2596 args[(*argslen)++] = addr->mailbox;
2597 }
2598 }
2599 return (args);
2600}
2601
2602static char **
2603add_option (char **args, size_t *argslen, size_t *argsmax, char *s)
2604{
2605 if (*argslen == *argsmax)
2606 safe_realloc (&args, (*argsmax += 5) * sizeof (char *));
2607 args[(*argslen)++] = s;
2608 return (args);
2609}
2610
2611int
2612mutt_invoke_sendmail (ADDRESS *from, /* the sender */
2613 ADDRESS *to, ADDRESS *cc, ADDRESS *bcc, /* recips */
2614 const char *msg, /* file containing message */
2615 int eightbit) /* message contains 8bit chars */
2616{
2617 char *ps = NULL, *path = NULL, *s = safe_strdup (Sendmail), *childout = NULL;
2618 char **args = NULL;
2619 size_t argslen = 0, argsmax = 0;
2620 char **extra_args = NULL;
2621 size_t extra_argslen = 0, extra_argsmax = 0;
2622 int i;
2623
2624 /* ensure that $sendmail is set to avoid a crash. http://dev.mutt.org/trac/ticket/3548 */
2625 if (!s)
2626 {
2627 mutt_error(_("$sendmail must be set in order to send mail."));
2628 return -1;
2629 }
2630
2631 ps = s;
2632 i = 0;
2633 while ((ps = strtok (ps, " ")))
2634 {
2635 if (argslen == argsmax)
2636 safe_realloc (&args, sizeof (char *) * (argsmax += 5));
2637
2638 if (i)
2639 {
2640 if (!mutt_strcmp (ps, "--"))
2641 break;
2642 args[argslen++] = ps;
2643 }
2644 else
2645 {
2646 path = safe_strdup (ps);
2647 ps = strrchr (ps, '/');
2648 if (ps)
2649 ps++;
2650 else
2651 ps = path;
2652 args[argslen++] = ps;
2653 }
2654 ps = NULL;
2655 i++;
2656 }
2657
2658 /* If Sendmail contained a "--", we save the recipients to append to
2659 * args after other possible options added below. */
2660 if (ps)
2661 {
2662 ps = NULL;
2663 while ((ps = strtok (ps, " ")))
2664 {
2665 if (extra_argslen == extra_argsmax)
2666 safe_realloc (&extra_args, sizeof (char *) * (extra_argsmax += 5));
2667
2668 extra_args[extra_argslen++] = ps;
2669 ps = NULL;
2670 }
2671 }
2672
2673 if (eightbit && option (OPTUSE8BITMIME))
2674 args = add_option (args, &argslen, &argsmax, "-B8BITMIME");
2675
2676 if (option (OPTENVFROM))
2677 {
2678 if (EnvFrom)
2679 {
2680 args = add_option (args, &argslen, &argsmax, "-f");
2681 args = add_args (args, &argslen, &argsmax, EnvFrom);
2682 }
2683 else if (from && !from->next)
2684 {
2685 args = add_option (args, &argslen, &argsmax, "-f");
2686 args = add_args (args, &argslen, &argsmax, from);
2687 }
2688 }
2689
2690 if (DsnNotify)
2691 {
2692 args = add_option (args, &argslen, &argsmax, "-N");
2693 args = add_option (args, &argslen, &argsmax, DsnNotify);
2694 }
2695 if (DsnReturn)
2696 {
2697 args = add_option (args, &argslen, &argsmax, "-R");
2698 args = add_option (args, &argslen, &argsmax, DsnReturn);
2699 }
2700 args = add_option (args, &argslen, &argsmax, "--");
2701 for (i = 0; i < extra_argslen; i++)
2702 args = add_option (args, &argslen, &argsmax, extra_args[i]);
2703 args = add_args (args, &argslen, &argsmax, to);
2704 args = add_args (args, &argslen, &argsmax, cc);
2705 args = add_args (args, &argslen, &argsmax, bcc);
2706
2707 if (argslen == argsmax)
2708 safe_realloc (&args, sizeof (char *) * (++argsmax));
2709
2710 args[argslen++] = NULL;
2711
2712 /* Some user's $sendmail command uses gpg for password decryption,
2713 * and is set up to prompt using ncurses pinentry. If we
2714 * mutt_endwin() it leaves other users staring at a blank screen.
2715 * So instead, just force a hard redraw on the next refresh. */
2716 if (!option (OPTNOCURSES))
2717 mutt_need_hard_redraw ();
2718
2719 if ((i = send_msg (path, args, msg, option(OPTNOCURSES) ? NULL : &childout)) != (EX_OK & 0xff))
2720 {
2721 if (i != S_BKG)
2722 {
2723 const char *e;
2724
2725 e = mutt_strsysexit (i);
2726 mutt_error (_("Error sending message, child exited %d (%s)."), i, NONULL (e));
2727 if (childout)
2728 {
2729 struct stat st;
2730
2731 if (stat (childout, &st) == 0 && st.st_size > 0)
2732 mutt_do_pager (_("Output of the delivery process"), childout, 0, NULL);
2733 }
2734 }
2735 }
2736 else if (childout)
2737 unlink (childout);
2738
2739 FREE (&childout);
2740 FREE (&path);
2741 FREE (&s);
2742 FREE (&args);
2743 FREE (&extra_args);
2744
2745 if (i == (EX_OK & 0xff))
2746 i = 0;
2747 else if (i == S_BKG)
2748 i = 1;
2749 else
2750 i = -1;
2751 return (i);
2752}
2753
2754/* For postponing (!final) do the necessary encodings only */
2755void mutt_prepare_envelope (ENVELOPE *env, int final)
2756{
2757 char buffer[LONG_STRING];
2758
2759 if (final)
2760 {
2761 if (env->bcc && !(env->to || env->cc))
2762 {
2763 /* some MTA's will put an Apparently-To: header field showing the Bcc:
2764 * recipients if there is no To: or Cc: field, so attempt to suppress
2765 * it by using an empty To: field.
2766 */
2767 env->to = rfc822_new_address ();
2768 env->to->group = 1;
2769 env->to->next = rfc822_new_address ();
2770
2771 buffer[0] = 0;
2772 rfc822_cat (buffer, sizeof (buffer), "undisclosed-recipients",
2773 RFC822Specials);
2774
2775 env->to->mailbox = safe_strdup (buffer);
2776 }
2777
2778 mutt_set_followup_to (env);
2779
2780 if (!env->message_id)
2781 env->message_id = mutt_gen_msgid ();
2782 }
2783
2784 /* Take care of 8-bit => 7-bit conversion. */
2785 rfc2047_encode_envelope (env);
2786 encode_headers (env->userhdrs);
2787}
2788
2789void mutt_unprepare_envelope (ENVELOPE *env)
2790{
2791 LIST *item;
2792
2793 for (item = env->userhdrs; item; item = item->next)
2794 rfc2047_decode (&item->data);
2795
2796 rfc822_free_address (&env->mail_followup_to);
2797
2798 /* back conversions */
2799 rfc2047_decode_envelope (env);
2800}
2801
2802static int _mutt_bounce_message (FILE *fp, HEADER *h, ADDRESS *to, const char *resent_from,
2803 ADDRESS *env_from)
2804{
2805 int i, ret = 0;
2806 FILE *f;
2807 char date[SHORT_STRING];
2808 BUFFER *tempfile;
2809 MESSAGE *msg = NULL;
2810
2811 if (!h)
2812 {
2813 /* Try to bounce each message out, aborting if we get any failures. */
2814 for (i=0; i<Context->msgcount; i++)
2815 if (Context->hdrs[i]->tagged)
2816 ret |= _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from, env_from);
2817 return ret;
2818 }
2819
2820 /* If we failed to open a message, return with error */
2821 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2822 return -1;
2823
2824 if (!fp) fp = msg->fp;
2825
2826 tempfile = mutt_buffer_pool_get ();
2827 mutt_buffer_mktemp (tempfile);
2828 if ((f = safe_fopen (mutt_b2s (tempfile), "w")) != NULL)
2829 {
2830 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2831 char* msgid_str;
2832
2833 if (!option (OPTBOUNCEDELIVERED))
2834 ch_flags |= CH_WEED_DELIVERED;
2835
2836 fseeko (fp, h->offset, 0);
2837 fprintf (f, "Resent-From: %s", resent_from);
2838 fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof(date)));
2839 msgid_str = mutt_gen_msgid();
2840 fprintf (f, "Resent-Message-ID: %s\n", msgid_str);
2841 fputs ("Resent-To: ", f);
2842 mutt_write_address_list (to, f, 11, 0);
2843 mutt_copy_header (fp, h, f, ch_flags, NULL);
2844 fputc ('\n', f);
2845 mutt_copy_bytes (fp, f, h->content->length);
2846 safe_fclose (&f);
2847 FREE (&msgid_str);
2848
2849#if USE_SMTP
2850 if (SmtpUrl)
2851 ret = mutt_smtp_send (env_from, to, NULL, NULL, mutt_b2s (tempfile),
2852 h->content->encoding == ENC8BIT);
2853 else
2854#endif /* USE_SMTP */
2855 ret = mutt_invoke_sendmail (env_from, to, NULL, NULL, mutt_b2s (tempfile),
2856 h->content->encoding == ENC8BIT);
2857 }
2858
2859 mutt_buffer_pool_release (&tempfile);
2860
2861 if (msg)
2862 mx_close_message (Context, &msg);
2863
2864 return ret;
2865}
2866
2867int mutt_bounce_message (FILE *fp, HEADER *h, ADDRESS *to)
2868{
2869 ADDRESS *from, *resent_to;
2870 const char *fqdn = mutt_fqdn (1);
2871 char resent_from[STRING];
2872 int ret;
2873 char *err = NULL;
2874
2875 resent_from[0] = '\0';
2876 from = mutt_default_from ();
2877
2878 /*
2879 * mutt_default_from() does not use $realname if the real name is not set
2880 * in $from, so we add it here. The reason it is not added in
2881 * mutt_default_from() is that during normal sending, we execute
2882 * send-hooks and set the realname last so that it can be changed based
2883 * upon message criteria.
2884 */
2885 if (! from->personal)
2886 from->personal = safe_strdup(Realname);
2887
2888 if (fqdn)
2889 rfc822_qualify (from, fqdn);
2890
2891 rfc2047_encode_adrlist (from, "Resent-From");
2892 if (mutt_addrlist_to_intl (from, &err))
2893 {
2894 mutt_error (_("Bad IDN %s while preparing resent-from."),
2895 err);
2896 FREE (&err);
2897 rfc822_free_address (&from);
2898 return -1;
2899 }
2900 rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
2901
2902 /*
2903 * prepare recipient list. idna conversion appears to happen before this
2904 * function is called, since the user receives confirmation of the address
2905 * list being bounced to.
2906 */
2907 resent_to = rfc822_cpy_adr(to, 0);
2908 rfc2047_encode_adrlist(resent_to, "Resent-To");
2909
2910 ret = _mutt_bounce_message (fp, h, resent_to, resent_from, from);
2911
2912 rfc822_free_address (&resent_to);
2913 rfc822_free_address (&from);
2914
2915 return ret;
2916}
2917
2918
2919/* given a list of addresses, return a list of unique addresses */
2920ADDRESS *mutt_remove_duplicates (ADDRESS *addr)
2921{
2922 ADDRESS *top = addr;
2923 ADDRESS **last = ⊤
2924 ADDRESS *tmp;
2925 int dup;
2926
2927 while (addr)
2928 {
2929 for (tmp = top, dup = 0; tmp && tmp != addr; tmp = tmp->next)
2930 {
2931 if (tmp->mailbox && addr->mailbox &&
2932 !ascii_strcasecmp (addr->mailbox, tmp->mailbox))
2933 {
2934 dup = 1;
2935 break;
2936 }
2937 }
2938
2939 if (dup)
2940 {
2941 dprint (2, (debugfile, "mutt_remove_duplicates: Removing %s\n",
2942 addr->mailbox));
2943
2944 *last = addr->next;
2945
2946 addr->next = NULL;
2947 rfc822_free_address(&addr);
2948
2949 addr = *last;
2950 }
2951 else
2952 {
2953 last = &addr->next;
2954 addr = addr->next;
2955 }
2956 }
2957
2958 return (top);
2959}
2960
2961static void set_noconv_flags (BODY *b, short flag)
2962{
2963 for (; b; b = b->next)
2964 {
2965 if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2966 set_noconv_flags (b->parts, flag);
2967 else if (b->type == TYPETEXT && b->noconv)
2968 {
2969 if (flag)
2970 mutt_set_parameter ("x-mutt-noconv", "yes", &b->parameter);
2971 else
2972 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
2973 }
2974 }
2975}
2976
2977int mutt_write_fcc (const char *path, HEADER *hdr, const char *msgid, int post, const char *fcc)
2978{
2979 CONTEXT f;
2980 MESSAGE *msg;
2981 BUFFER *tempfile = NULL;
2982 FILE *tempfp = NULL;
2983 int r = -1, need_buffy_cleanup = 0;
2984 struct stat st;
2985 char buf[SHORT_STRING];
2986 int onm_flags;
2987
2988 if (post)
2989 set_noconv_flags (hdr->content, 1);
2990
2991 if (mx_open_mailbox (path, MUTT_APPEND | MUTT_QUIET, &f) == NULL)
2992 {
2993 dprint (1, (debugfile, "mutt_write_fcc(): unable to open mailbox %s in append-mode, aborting.\n",
2994 path));
2995 goto cleanup;
2996 }
2997
2998 /* We need to add a Content-Length field to avoid problems where a line in
2999 * the message body begins with "From "
3000 */
3001 if (f.magic == MUTT_MMDF || f.magic == MUTT_MBOX)
3002 {
3003 tempfile = mutt_buffer_pool_get ();
3004 mutt_buffer_mktemp (tempfile);
3005 if ((tempfp = safe_fopen (mutt_b2s (tempfile), "w+")) == NULL)
3006 {
3007 mutt_perror (mutt_b2s (tempfile));
3008 mx_close_mailbox (&f, NULL);
3009 goto cleanup;
3010 }
3011 /* remember new mail status before appending message */
3012 need_buffy_cleanup = 1;
3013 stat (path, &st);
3014 }
3015
3016 hdr->read = !post; /* make sure to put it in the `cur' directory (maildir) */
3017 onm_flags = MUTT_ADD_FROM;
3018 if (post)
3019 onm_flags |= MUTT_SET_DRAFT;
3020 if ((msg = mx_open_new_message (&f, hdr, onm_flags)) == NULL)
3021 {
3022 mx_close_mailbox (&f, NULL);
3023 goto cleanup;
3024 }
3025
3026 /* post == 1 => postpone message.
3027 * post == 0 => fcc mode.
3028 * */
3029 mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content,
3030 post ? MUTT_WRITE_HEADER_POSTPONE : MUTT_WRITE_HEADER_FCC,
3031 0,
3032 option (OPTCRYPTPROTHDRSREAD) &&
3033 mutt_should_hide_protected_subject (hdr));
3034
3035 /* (postponment) if this was a reply of some sort, <msgid> contains the
3036 * Message-ID: of message replied to. Save it using a special X-Mutt-
3037 * header so it can be picked up if the message is recalled at a later
3038 * point in time. This will allow the message to be marked as replied if
3039 * the same mailbox is still open.
3040 */
3041 if (post && msgid)
3042 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
3043
3044 /* (postponment) save the Fcc: using a special X-Mutt- header so that
3045 * it can be picked up when the message is recalled
3046 */
3047 if (post && fcc)
3048 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
3049
3050 if (f.magic == MUTT_MMDF || f.magic == MUTT_MBOX)
3051 fprintf (msg->fp, "Status: RO\n");
3052
3053 /* mutt_write_rfc822_header() only writes out a Date: header with
3054 * mode == 0, i.e. _not_ postponment; so write out one ourself */
3055 if (post)
3056 fprintf (msg->fp, "%s", mutt_make_date (buf, sizeof (buf)));
3057
3058 /* (postponment) if the mail is to be signed or encrypted, save this info */
3059 if ((WithCrypto & APPLICATION_PGP)
3060 && post && (hdr->security & APPLICATION_PGP))
3061 {
3062 fputs ("X-Mutt-PGP: ", msg->fp);
3063 if (hdr->security & ENCRYPT)
3064 fputc ('E', msg->fp);
3065 if (hdr->security & OPPENCRYPT)
3066 fputc ('O', msg->fp);
3067 if (hdr->security & SIGN)
3068 {
3069 fputc ('S', msg->fp);
3070 if (PgpSignAs)
3071 fprintf (msg->fp, "<%s>", PgpSignAs);
3072 }
3073 if (hdr->security & INLINE)
3074 fputc ('I', msg->fp);
3075#ifdef USE_AUTOCRYPT
3076 if (hdr->security & AUTOCRYPT)
3077 fputc ('A', msg->fp);
3078 if (hdr->security & AUTOCRYPT_OVERRIDE)
3079 fputc ('Z', msg->fp);
3080#endif
3081 fputc ('\n', msg->fp);
3082 }
3083
3084 /* (postponment) if the mail is to be signed or encrypted, save this info */
3085 if ((WithCrypto & APPLICATION_SMIME)
3086 && post && (hdr->security & APPLICATION_SMIME))
3087 {
3088 fputs ("X-Mutt-SMIME: ", msg->fp);
3089 if (hdr->security & ENCRYPT)
3090 {
3091 fputc ('E', msg->fp);
3092 if (SmimeCryptAlg)
3093 fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
3094 }
3095 if (hdr->security & OPPENCRYPT)
3096 fputc ('O', msg->fp);
3097 if (hdr->security & SIGN)
3098 {
3099 fputc ('S', msg->fp);
3100 if (SmimeSignAs)
3101 fprintf (msg->fp, "<%s>", SmimeSignAs);
3102 }
3103 if (hdr->security & INLINE)
3104 fputc ('I', msg->fp);
3105 fputc ('\n', msg->fp);
3106 }
3107
3108#ifdef MIXMASTER
3109 /* (postponement) if the mail is to be sent through a mixmaster
3110 * chain, save that information
3111 */
3112
3113 if (post && hdr->chain && hdr->chain)
3114 {
3115 LIST *p;
3116
3117 fputs ("X-Mutt-Mix:", msg->fp);
3118 for (p = hdr->chain; p; p = p->next)
3119 fprintf (msg->fp, " %s", (char *) p->data);
3120
3121 fputc ('\n', msg->fp);
3122 }
3123#endif
3124
3125 if (tempfp)
3126 {
3127 char sasha[LONG_STRING];
3128 int lines = 0;
3129
3130 mutt_write_mime_body (hdr->content, tempfp);
3131
3132 /* make sure the last line ends with a newline. Emacs doesn't ensure
3133 * this will happen, and it can cause problems parsing the mailbox
3134 * later.
3135 */
3136 fseek (tempfp, -1, 2);
3137 if (fgetc (tempfp) != '\n')
3138 {
3139 fseek (tempfp, 0, 2);
3140 fputc ('\n', tempfp);
3141 }
3142
3143 fflush (tempfp);
3144 if (ferror (tempfp))
3145 {
3146 dprint (1, (debugfile, "mutt_write_fcc(): %s: write failed.\n", mutt_b2s (tempfile)));
3147 safe_fclose (&tempfp);
3148 unlink (mutt_b2s (tempfile));
3149 mx_commit_message (msg, &f); /* XXX - really? */
3150 mx_close_message (&f, &msg);
3151 mx_close_mailbox (&f, NULL);
3152 goto cleanup;
3153 }
3154
3155 /* count the number of lines */
3156 rewind (tempfp);
3157 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
3158 lines++;
3159 fprintf (msg->fp, "Content-Length: " OFF_T_FMT "\n", (LOFF_T) ftello (tempfp));
3160 fprintf (msg->fp, "Lines: %d\n\n", lines);
3161
3162 /* copy the body and clean up */
3163 rewind (tempfp);
3164 r = mutt_copy_stream (tempfp, msg->fp);
3165 if (safe_fclose (&tempfp) != 0)
3166 r = -1;
3167 /* if there was an error, leave the temp version */
3168 if (!r)
3169 unlink (mutt_b2s (tempfile));
3170 }
3171 else
3172 {
3173 fputc ('\n', msg->fp); /* finish off the header */
3174 r = mutt_write_mime_body (hdr->content, msg->fp);
3175 }
3176
3177 if (mx_commit_message (msg, &f) != 0)
3178 r = -1;
3179 mx_close_message (&f, &msg);
3180 mx_close_mailbox (&f, NULL);
3181
3182 if (!post && need_buffy_cleanup)
3183 mutt_buffy_cleanup (path, &st);
3184
3185 if (post)
3186 set_noconv_flags (hdr->content, 0);
3187
3188cleanup:
3189 if (tempfp)
3190 {
3191 safe_fclose (&tempfp);
3192 unlink (mutt_b2s (tempfile));
3193 }
3194 mutt_buffer_pool_release (&tempfile);
3195
3196 return r;
3197}