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