mutt stable branch with some hacks
1/*
2 * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
3 * Copyright (C) 2000-2004,2006 Thomas Roessler <roessler@does-not-exist.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20#if HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "version.h"
25#include "mutt.h"
26#include "mutt_curses.h"
27#include "mutt_menu.h"
28#include "mime.h"
29#include "sort.h"
30#include "mailbox.h"
31#include "copy.h"
32#include "mx.h"
33#include "pager.h"
34#include "mutt_crypt.h"
35#include "mutt_idna.h"
36#include <sys/types.h>
37#include <sys/stat.h>
38#include <fcntl.h>
39
40#ifdef USE_IMAP
41#include "imap.h"
42#endif
43
44#ifdef USE_AUTOCRYPT
45#include "autocrypt/autocrypt.h"
46#endif
47
48#include "buffy.h"
49
50#include <errno.h>
51#include <unistd.h>
52#include <stdlib.h>
53#include <string.h>
54#include <sys/wait.h>
55#include <sys/stat.h>
56#include <sys/types.h>
57#include <utime.h>
58
59static const char *ExtPagerProgress = "all";
60
61/* The folder the user last saved to. Used by ci_save_message() */
62static char LastSaveFolder[_POSIX_PATH_MAX] = "";
63
64static void process_protected_headers (HEADER *cur)
65{
66 ENVELOPE *prot_headers = NULL;
67 regmatch_t pmatch[1];
68
69 if (!option (OPTCRYPTPROTHDRSREAD)
70#ifdef USE_AUTOCRYPT
71 && !option (OPTAUTOCRYPT)
72#endif
73 )
74 return;
75
76 /* Grab protected headers to update in the index */
77 if (cur->security & SIGN)
78 {
79 /* Don't update on a bad signature.
80 *
81 * This is a simplification. It's possible the headers are in the
82 * encrypted part of a nested encrypt/signed. But properly handling that
83 * case would require more complexity in the decryption handlers, which
84 * I'm not sure is worth it. */
85 if (!(cur->security & GOODSIGN))
86 return;
87
88 if (mutt_is_multipart_signed (cur->content) &&
89 cur->content->parts)
90 {
91 prot_headers = cur->content->parts->mime_headers;
92 }
93 else if ((WithCrypto & APPLICATION_SMIME) &&
94 mutt_is_application_smime (cur->content))
95 {
96 prot_headers = cur->content->mime_headers;
97 }
98 }
99 if (!prot_headers && (cur->security & ENCRYPT))
100 {
101 if ((WithCrypto & APPLICATION_PGP) &&
102 (mutt_is_valid_multipart_pgp_encrypted (cur->content) ||
103 mutt_is_malformed_multipart_pgp_encrypted (cur->content)))
104 {
105 prot_headers = cur->content->mime_headers;
106 }
107 else if ((WithCrypto & APPLICATION_SMIME) &&
108 mutt_is_application_smime (cur->content))
109 {
110 prot_headers = cur->content->mime_headers;
111 }
112 }
113
114 /* Update protected headers in the index and header cache. */
115 if (option (OPTCRYPTPROTHDRSREAD) &&
116 prot_headers &&
117 prot_headers->subject &&
118 mutt_strcmp (cur->env->subject, prot_headers->subject))
119 {
120 if (Context->subj_hash && cur->env->real_subj)
121 hash_delete (Context->subj_hash, cur->env->real_subj, cur, NULL);
122
123 mutt_str_replace (&cur->env->subject, prot_headers->subject);
124 FREE (&cur->env->disp_subj);
125 if (regexec (ReplyRegexp.rx, cur->env->subject, 1, pmatch, 0) == 0)
126 cur->env->real_subj = cur->env->subject + pmatch[0].rm_eo;
127 else
128 cur->env->real_subj = cur->env->subject;
129
130 if (Context->subj_hash)
131 hash_insert (Context->subj_hash, cur->env->real_subj, cur);
132
133 mx_save_to_header_cache (Context, cur);
134
135 /* Also persist back to the message headers if this is set */
136 if (option (OPTCRYPTPROTHDRSSAVE))
137 {
138 cur->env->changed |= MUTT_ENV_CHANGED_SUBJECT;
139 cur->changed = 1;
140 Context->changed = 1;
141 }
142 }
143
144#ifdef USE_AUTOCRYPT
145 if (option (OPTAUTOCRYPT) &&
146 (cur->security & ENCRYPT) &&
147 prot_headers &&
148 prot_headers->autocrypt_gossip)
149 {
150 mutt_autocrypt_process_gossip_header (cur, prot_headers);
151 }
152#endif
153}
154
155int mutt_display_message (HEADER *cur)
156{
157 BUFFER *tempfile = NULL;
158 int rc = 0, builtin = 0;
159 int cmflags = MUTT_CM_DECODE | MUTT_CM_DISPLAY | MUTT_CM_CHARCONV;
160 FILE *fpout = NULL;
161 FILE *fpfilterout = NULL;
162 pid_t filterpid = -1;
163 int res;
164
165 mutt_parse_mime_message (Context, cur);
166 mutt_message_hook (Context, cur, MUTT_MESSAGEHOOK);
167
168 /* see if crypto is needed for this message. if so, we should exit curses */
169 if (WithCrypto && cur->security)
170 {
171 if (cur->security & ENCRYPT)
172 {
173 if (cur->security & APPLICATION_SMIME)
174 crypt_smime_getkeys (cur->env);
175 if (!crypt_valid_passphrase(cur->security))
176 goto cleanup;
177
178 cmflags |= MUTT_CM_VERIFY;
179 }
180 else if (cur->security & SIGN)
181 {
182 /* find out whether or not the verify signature */
183 /* L10N: Used for the $crypt_verify_sig prompt */
184 if (query_quadoption (OPT_VERIFYSIG, _("Verify signature?")) == MUTT_YES)
185 {
186 cmflags |= MUTT_CM_VERIFY;
187 }
188 }
189 }
190
191 if (cmflags & MUTT_CM_VERIFY || cur->security & ENCRYPT)
192 {
193 if (cur->security & APPLICATION_PGP)
194 {
195 if (cur->env->from)
196 crypt_pgp_invoke_getkeys (cur->env->from);
197
198 crypt_invoke_message (APPLICATION_PGP);
199 }
200
201 if (cur->security & APPLICATION_SMIME)
202 crypt_invoke_message (APPLICATION_SMIME);
203 }
204
205
206 tempfile = mutt_buffer_pool_get ();
207 mutt_buffer_mktemp (tempfile);
208 if ((fpout = safe_fopen (mutt_b2s (tempfile), "w")) == NULL)
209 {
210 mutt_error _("Could not create temporary file!");
211 goto cleanup;
212 }
213
214 if (DisplayFilter)
215 {
216 fpfilterout = fpout;
217 fpout = NULL;
218 /* mutt_endwin (NULL); */
219 filterpid = mutt_create_filter_fd (DisplayFilter, &fpout, NULL, NULL,
220 -1, fileno(fpfilterout), -1);
221 if (filterpid < 0)
222 {
223 mutt_error (_("Cannot create display filter"));
224 safe_fclose (&fpfilterout);
225 unlink (mutt_b2s (tempfile));
226 goto cleanup;
227 }
228 }
229
230 if (!Pager || mutt_strcmp (Pager, "builtin") == 0)
231 builtin = 1;
232 else
233 {
234 char buf[LONG_STRING];
235 struct hdr_format_info hfi;
236
237 hfi.ctx = Context;
238 hfi.pager_progress = ExtPagerProgress;
239 hfi.hdr = cur;
240 mutt_make_string_info (buf, sizeof (buf), MuttIndexWindow->cols, NONULL(PagerFmt), &hfi, 0);
241 fputs (buf, fpout);
242 fputs ("\n\n", fpout);
243 }
244
245 res = mutt_copy_message (fpout, Context, cur, cmflags,
246 (option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) |
247 CH_DECODE | CH_FROM | CH_DISPLAY);
248 if ((safe_fclose (&fpout) != 0 && errno != EPIPE) || res < 0)
249 {
250 mutt_error (_("Could not copy message"));
251 if (fpfilterout != NULL)
252 {
253 mutt_wait_filter (filterpid);
254 safe_fclose (&fpfilterout);
255 }
256 mutt_unlink (mutt_b2s (tempfile));
257 goto cleanup;
258 }
259
260 if (fpfilterout != NULL && mutt_wait_filter (filterpid) != 0)
261 mutt_any_key_to_continue (NULL);
262
263 safe_fclose (&fpfilterout); /* XXX - check result? */
264
265 if (WithCrypto)
266 {
267 /* update crypto information for this message */
268 cur->security &= ~(GOODSIGN|BADSIGN);
269 cur->security |= crypt_query (cur->content);
270
271 /* Remove color cache for this message, in case there
272 are color patterns for both ~g and ~V */
273 cur->pair = 0;
274
275 /* Process protected headers and autocrypt gossip headers */
276 process_protected_headers (cur);
277 }
278
279 if (builtin)
280 {
281 pager_t info;
282
283 if (WithCrypto
284 && (cur->security & APPLICATION_SMIME) && (cmflags & MUTT_CM_VERIFY))
285 {
286 if (cur->security & GOODSIGN)
287 {
288 if (!crypt_smime_verify_sender(cur))
289 mutt_message ( _("S/MIME signature successfully verified."));
290 else
291 mutt_error ( _("S/MIME certificate owner does not match sender."));
292 }
293 else if (cur->security & PARTSIGN)
294 mutt_message (_("Warning: Part of this message has not been signed."));
295 else if (cur->security & SIGN || cur->security & BADSIGN)
296 mutt_error ( _("S/MIME signature could NOT be verified."));
297 }
298
299 if (WithCrypto
300 && (cur->security & APPLICATION_PGP) && (cmflags & MUTT_CM_VERIFY))
301 {
302 if (cur->security & GOODSIGN)
303 mutt_message (_("PGP signature successfully verified."));
304 else if (cur->security & PARTSIGN)
305 mutt_message (_("Warning: Part of this message has not been signed."));
306 else if (cur->security & SIGN)
307 mutt_message (_("PGP signature could NOT be verified."));
308 }
309
310 /* Invoke the builtin pager */
311 memset (&info, 0, sizeof (pager_t));
312 info.hdr = cur;
313 info.ctx = Context;
314 rc = mutt_pager (NULL, mutt_b2s (tempfile), MUTT_PAGER_MESSAGE, &info);
315 }
316 else
317 {
318 int r;
319 BUFFER *cmd = NULL;
320
321 mutt_endwin (NULL);
322
323 cmd = mutt_buffer_pool_get ();
324 mutt_buffer_printf (cmd, "%s %s", NONULL(Pager), mutt_b2s (tempfile));
325 if ((r = mutt_system (mutt_b2s (cmd))) == -1)
326 mutt_error (_("Error running \"%s\"!"), mutt_b2s (cmd));
327 unlink (mutt_b2s (tempfile));
328 mutt_buffer_pool_release (&cmd);
329
330 if (!option (OPTNOCURSES))
331 keypad (stdscr, TRUE);
332 if (r != -1)
333 mutt_set_flag (Context, cur, MUTT_READ, 1);
334 if (r != -1 && option (OPTPROMPTAFTER))
335 {
336 mutt_unget_event (mutt_any_key_to_continue _("Command: "), 0);
337 rc = km_dokey (MENU_PAGER);
338 }
339 else
340 rc = 0;
341 }
342
343cleanup:
344 mutt_buffer_pool_release (&tempfile);
345 return rc;
346}
347
348void ci_bounce_message (HEADER *h)
349{
350 char prompt[SHORT_STRING+1];
351 char scratch[SHORT_STRING];
352 char buf[HUGE_STRING] = { 0 };
353 ADDRESS *adr = NULL;
354 char *err = NULL;
355 int rc;
356
357 /* RfC 5322 mandates a From: header, so warn before bouncing
358 * messages without one */
359 if (h)
360 {
361 if (!h->env->from)
362 {
363 mutt_error _("Warning: message contains no From: header");
364 mutt_sleep (2);
365 }
366 }
367 else if (Context)
368 {
369 for (rc = 0; rc < Context->msgcount; rc++)
370 {
371 if (Context->hdrs[rc]->tagged && !Context->hdrs[rc]->env->from)
372 {
373 mutt_error _("Warning: message contains no From: header");
374 mutt_sleep (2);
375 break;
376 }
377 }
378 }
379
380 if (h)
381 strfcpy(prompt, _("Bounce message to: "), sizeof(prompt));
382 else
383 strfcpy(prompt, _("Bounce tagged messages to: "), sizeof(prompt));
384
385 rc = mutt_get_field (prompt, buf, sizeof (buf), MUTT_ALIAS);
386 if (rc || !buf[0])
387 return;
388
389 if (!(adr = mutt_parse_adrlist (adr, buf)))
390 {
391 mutt_error _("Error parsing address!");
392 return;
393 }
394
395 adr = mutt_expand_aliases (adr);
396
397 if (mutt_addrlist_to_intl (adr, &err) < 0)
398 {
399 mutt_error (_("Bad IDN: '%s'"), err);
400 FREE (&err);
401 rfc822_free_address (&adr);
402 return;
403 }
404
405 buf[0] = 0;
406 rfc822_write_address (buf, sizeof (buf), adr, 1);
407
408#define extra_space (15 + 7 + 2)
409 snprintf (scratch, sizeof (scratch),
410 (h ? _("Bounce message to %s") : _("Bounce messages to %s")), buf);
411
412 if (mutt_strwidth (prompt) > MuttMessageWindow->cols - extra_space)
413 {
414 mutt_format_string (prompt, sizeof (prompt),
415 0, MuttMessageWindow->cols-extra_space, FMT_LEFT, 0,
416 scratch, sizeof (scratch), 0);
417 safe_strcat (prompt, sizeof (prompt), "...?");
418 }
419 else
420 snprintf (prompt, sizeof (prompt), "%s?", scratch);
421
422 if (query_quadoption (OPT_BOUNCE, prompt) != MUTT_YES)
423 {
424 rfc822_free_address (&adr);
425 mutt_window_clearline (MuttMessageWindow, 0);
426 mutt_message (h ? _("Message not bounced.") : _("Messages not bounced."));
427 return;
428 }
429
430 mutt_window_clearline (MuttMessageWindow, 0);
431
432 rc = mutt_bounce_message (NULL, h, adr);
433 rfc822_free_address (&adr);
434 /* If no error, or background, display message. */
435 if ((rc == 0) || (rc == S_BKG))
436 mutt_message (h ? _("Message bounced.") : _("Messages bounced."));
437}
438
439static void pipe_set_flags (int decode, int print, int *cmflags, int *chflags)
440{
441 if (decode)
442 {
443 *chflags |= CH_DECODE | CH_REORDER;
444 *cmflags |= MUTT_CM_DECODE | MUTT_CM_CHARCONV;
445
446 if (option (OPTWEED))
447 {
448 *chflags |= CH_WEED;
449 *cmflags |= MUTT_CM_WEED;
450 }
451
452 /* Just as with copy-decode, we need to update the
453 * mime fields to avoid confusing programs that may
454 * process the email. However, we don't want to force
455 * those fields to appear in printouts. */
456 if (!print)
457 *chflags |= CH_MIME | CH_TXTPLAIN;
458 }
459
460 if (print)
461 *cmflags |= MUTT_CM_PRINTING;
462
463}
464
465static void pipe_msg (HEADER *h, FILE *fp, int decode, int print)
466{
467 int cmflags = 0;
468 int chflags = CH_FROM;
469
470 pipe_set_flags (decode, print, &cmflags, &chflags);
471
472 if (WithCrypto && decode && h->security & ENCRYPT)
473 {
474 if (!crypt_valid_passphrase(h->security))
475 return;
476 endwin ();
477 }
478
479 if (decode)
480 mutt_parse_mime_message (Context, h);
481
482 mutt_copy_message (fp, Context, h, cmflags, chflags);
483}
484
485
486/* the following code is shared between printing and piping */
487
488static int _mutt_pipe_message (HEADER *h, char *cmd,
489 int decode,
490 int print,
491 int split,
492 char *sep)
493{
494
495 int i, rc = 0;
496 pid_t thepid;
497 FILE *fpout;
498
499/* mutt_endwin (NULL);
500
501 is this really needed here ?
502 it makes the screen flicker on pgp and s/mime messages,
503 before asking for a passphrase...
504 Oliver Ehli */
505 if (h)
506 {
507
508 mutt_message_hook (Context, h, MUTT_MESSAGEHOOK);
509
510 if (WithCrypto && decode)
511 {
512 mutt_parse_mime_message (Context, h);
513 if (h->security & ENCRYPT && !crypt_valid_passphrase(h->security))
514 return 1;
515 }
516 mutt_endwin (NULL);
517
518 if ((thepid = mutt_create_filter (cmd, &fpout, NULL, NULL)) < 0)
519 {
520 mutt_perror _("Can't create filter process");
521 return 1;
522 }
523
524 set_option (OPTKEEPQUIET);
525 pipe_msg (h, fpout, decode, print);
526 safe_fclose (&fpout);
527 rc = mutt_wait_filter (thepid);
528 unset_option (OPTKEEPQUIET);
529 }
530 else
531 { /* handle tagged messages */
532
533 if (WithCrypto && decode)
534 {
535 for (i = 0; i < Context->vcount; i++)
536 if (Context->hdrs[Context->v2r[i]]->tagged)
537 {
538 mutt_message_hook (Context, Context->hdrs[Context->v2r[i]], MUTT_MESSAGEHOOK);
539 mutt_parse_mime_message(Context, Context->hdrs[Context->v2r[i]]);
540 if (Context->hdrs[Context->v2r[i]]->security & ENCRYPT &&
541 !crypt_valid_passphrase(Context->hdrs[Context->v2r[i]]->security))
542 return 1;
543 }
544 }
545
546 if (split)
547 {
548 for (i = 0; i < Context->vcount; i++)
549 {
550 if (Context->hdrs[Context->v2r[i]]->tagged)
551 {
552 mutt_message_hook (Context, Context->hdrs[Context->v2r[i]], MUTT_MESSAGEHOOK);
553 mutt_endwin (NULL);
554 if ((thepid = mutt_create_filter (cmd, &fpout, NULL, NULL)) < 0)
555 {
556 mutt_perror _("Can't create filter process");
557 return 1;
558 }
559 set_option (OPTKEEPQUIET);
560 pipe_msg (Context->hdrs[Context->v2r[i]], fpout, decode, print);
561 /* add the message separator */
562 if (sep) fputs (sep, fpout);
563 safe_fclose (&fpout);
564 if (mutt_wait_filter (thepid) != 0)
565 rc = 1;
566 unset_option (OPTKEEPQUIET);
567 }
568 }
569 }
570 else
571 {
572 mutt_endwin (NULL);
573 if ((thepid = mutt_create_filter (cmd, &fpout, NULL, NULL)) < 0)
574 {
575 mutt_perror _("Can't create filter process");
576 return 1;
577 }
578 set_option (OPTKEEPQUIET);
579 for (i = 0; i < Context->vcount; i++)
580 {
581 if (Context->hdrs[Context->v2r[i]]->tagged)
582 {
583 mutt_message_hook (Context, Context->hdrs[Context->v2r[i]], MUTT_MESSAGEHOOK);
584 pipe_msg (Context->hdrs[Context->v2r[i]], fpout, decode, print);
585 /* add the message separator */
586 if (sep) fputs (sep, fpout);
587 }
588 }
589 safe_fclose (&fpout);
590 if (mutt_wait_filter (thepid) != 0)
591 rc = 1;
592 unset_option (OPTKEEPQUIET);
593 }
594 }
595
596 if (rc || option (OPTWAITKEY))
597 mutt_any_key_to_continue (NULL);
598 return rc;
599}
600
601void mutt_pipe_message (HEADER *h)
602{
603 char buffer[LONG_STRING];
604
605 buffer[0] = 0;
606 if (mutt_get_field (_("Pipe to command: "), buffer, sizeof (buffer), MUTT_CMD)
607 != 0 || !buffer[0])
608 return;
609
610 mutt_expand_path (buffer, sizeof (buffer));
611 _mutt_pipe_message (h, buffer,
612 option (OPTPIPEDECODE),
613 0,
614 option (OPTPIPESPLIT),
615 PipeSep);
616}
617
618void mutt_print_message (HEADER *h)
619{
620
621 if (quadoption (OPT_PRINT) && !PrintCmd)
622 {
623 mutt_message (_("No printing command has been defined."));
624 return;
625 }
626
627 if (query_quadoption (OPT_PRINT,
628 h ? _("Print message?") : _("Print tagged messages?"))
629 != MUTT_YES)
630 return;
631
632 if (_mutt_pipe_message (h, PrintCmd,
633 option (OPTPRINTDECODE),
634 1,
635 option (OPTPRINTSPLIT),
636 "\f") == 0)
637 mutt_message (h ? _("Message printed") : _("Messages printed"));
638 else
639 mutt_message (h ? _("Message could not be printed") :
640 _("Messages could not be printed"));
641}
642
643
644int mutt_select_sort (int reverse)
645{
646 int method = Sort; /* save the current method in case of abort */
647
648 switch (mutt_multi_choice (reverse ?
649 /* L10N: The following three are the sort/reverse sort prompts.
650 * Letters must match the order of the characters in the third
651 * string. Note that mutt now supports multiline prompts, so
652 * it's okay for the translation to take up to three lines.
653 */
654 _("Rev-Sort Date/Frm/Recv/Subj/tO/Thread/Unsort/siZe/sCore/sPam/Label?: ") :
655 _("Sort Date/Frm/Recv/Subj/tO/Thread/Unsort/siZe/sCore/sPam/Label?: "),
656 _("dfrsotuzcpl")))
657 {
658 case -1: /* abort - don't resort */
659 return -1;
660
661 case 1: /* (d)ate */
662 Sort = SORT_DATE;
663 break;
664
665 case 2: /* (f)rm */
666 Sort = SORT_FROM;
667 break;
668
669 case 3: /* (r)ecv */
670 Sort = SORT_RECEIVED;
671 break;
672
673 case 4: /* (s)ubj */
674 Sort = SORT_SUBJECT;
675 break;
676
677 case 5: /* t(o) */
678 Sort = SORT_TO;
679 break;
680
681 case 6: /* (t)hread */
682 Sort = SORT_THREADS;
683 break;
684
685 case 7: /* (u)nsort */
686 Sort = SORT_ORDER;
687 break;
688
689 case 8: /* si(z)e */
690 Sort = SORT_SIZE;
691 break;
692
693 case 9: /* s(c)ore */
694 Sort = SORT_SCORE;
695 break;
696
697 case 10: /* s(p)am */
698 Sort = SORT_SPAM;
699 break;
700
701 case 11: /* (l)abel */
702 Sort = SORT_LABEL;
703 break;
704 }
705 if (reverse)
706 Sort |= SORT_REVERSE;
707
708 return (Sort != method ? 0 : -1); /* no need to resort if it's the same */
709}
710
711/* invoke a command in a subshell */
712void mutt_shell_escape (void)
713{
714 char buf[LONG_STRING];
715
716 buf[0] = 0;
717 if (mutt_get_field (_("Shell command: "), buf, sizeof (buf), MUTT_CMD) == 0)
718 {
719 if (!buf[0] && Shell)
720 strfcpy (buf, Shell, sizeof (buf));
721 if (buf[0])
722 {
723 mutt_window_clearline (MuttMessageWindow, 0);
724 mutt_endwin (NULL);
725 fflush (stdout);
726 if (mutt_system (buf) != 0 || option (OPTWAITKEY))
727 mutt_any_key_to_continue (NULL);
728 }
729 }
730}
731
732/* enter a mutt command */
733void mutt_enter_command (void)
734{
735 BUFFER err, token;
736 char buffer[LONG_STRING];
737 int r;
738
739 buffer[0] = 0;
740 if (mutt_get_field (":", buffer, sizeof (buffer), MUTT_COMMAND) != 0 || !buffer[0])
741 return;
742 mutt_buffer_init (&err);
743 err.dsize = STRING;
744 err.data = safe_malloc(err.dsize);
745 mutt_buffer_init (&token);
746 r = mutt_parse_rc_line (buffer, &token, &err);
747 FREE (&token.data);
748 if (err.data[0])
749 {
750 /* since errbuf could potentially contain printf() sequences in it,
751 we must call mutt_error() in this fashion so that vsprintf()
752 doesn't expect more arguments that we passed */
753 if (r == 0)
754 mutt_message ("%s", err.data);
755 else
756 mutt_error ("%s", err.data);
757 }
758
759 FREE (&err.data);
760}
761
762void mutt_display_address (ENVELOPE *env)
763{
764 char *pfx = NULL;
765 char buf[SHORT_STRING];
766 ADDRESS *adr = NULL;
767
768 adr = mutt_get_address (env, &pfx);
769
770 if (!adr) return;
771
772 /*
773 * Note: We don't convert IDNA to local representation this time.
774 * That is intentional, so the user has an opportunity to copy &
775 * paste the on-the-wire form of the address to other, IDN-unable
776 * software.
777 */
778
779 buf[0] = 0;
780 rfc822_write_address (buf, sizeof (buf), adr, 0);
781 mutt_message ("%s: %s", pfx, buf);
782}
783
784static void set_copy_flags (HEADER *hdr, int decode, int decrypt, int *cmflags, int *chflags)
785{
786 *cmflags = 0;
787 *chflags = CH_UPDATE_LEN;
788
789 if (WithCrypto && !decode && decrypt && (hdr->security & ENCRYPT))
790 {
791 if ((WithCrypto & APPLICATION_PGP)
792 && mutt_is_multipart_encrypted(hdr->content))
793 {
794 *chflags = CH_NONEWLINE | CH_XMIT | CH_MIME;
795 *cmflags = MUTT_CM_DECODE_PGP;
796 }
797 else if ((WithCrypto & APPLICATION_PGP)
798 && mutt_is_application_pgp (hdr->content) & ENCRYPT)
799 decode = 1;
800 else if ((WithCrypto & APPLICATION_SMIME)
801 && mutt_is_application_smime(hdr->content) & ENCRYPT)
802 {
803 *chflags = CH_NONEWLINE | CH_XMIT | CH_MIME;
804 *cmflags = MUTT_CM_DECODE_SMIME;
805 }
806 }
807
808 if (decode)
809 {
810 *chflags = CH_XMIT | CH_MIME | CH_TXTPLAIN;
811 *cmflags = MUTT_CM_DECODE | MUTT_CM_CHARCONV;
812
813 if (!decrypt) /* If decode doesn't kick in for decrypt, */
814 {
815 *chflags |= CH_DECODE; /* then decode RFC 2047 headers, */
816
817 if (option (OPTWEED))
818 {
819 *chflags |= CH_WEED; /* and respect $weed. */
820 *cmflags |= MUTT_CM_WEED;
821 }
822 }
823 }
824}
825
826int _mutt_save_message (HEADER *h, CONTEXT *ctx, int delete, int decode, int decrypt)
827{
828 int cmflags, chflags;
829 int rc;
830
831 set_copy_flags (h, decode, decrypt, &cmflags, &chflags);
832
833 if (decode || decrypt)
834 mutt_parse_mime_message (Context, h);
835
836 if ((rc = mutt_append_message (ctx, Context, h, cmflags, chflags)) != 0)
837 return rc;
838
839 if (delete)
840 {
841 mutt_set_flag (Context, h, MUTT_DELETE, 1);
842 mutt_set_flag (Context, h, MUTT_PURGE, 1);
843 if (option (OPTDELETEUNTAG))
844 mutt_set_flag (Context, h, MUTT_TAG, 0);
845 }
846
847 return 0;
848}
849
850/* returns 0 if the copy/save was successful, or -1 on error/abort */
851int mutt_save_message (HEADER *h, int delete, int decode, int decrypt)
852{
853 int i, need_buffy_cleanup;
854 int need_passphrase = 0, app=0;
855 int rc = -1;
856 char prompt[SHORT_STRING];
857 BUFFER *buf = NULL;
858 CONTEXT ctx;
859 struct stat st;
860
861 buf = mutt_buffer_pool_get ();
862
863 snprintf (prompt, sizeof (prompt),
864 decode ? (delete ? _("Decode-save%s to mailbox") :
865 _("Decode-copy%s to mailbox")) :
866 (decrypt ? (delete ? _("Decrypt-save%s to mailbox") :
867 _("Decrypt-copy%s to mailbox")) :
868 (delete ? _("Save%s to mailbox") : _("Copy%s to mailbox"))),
869 h ? "" : _(" tagged"));
870
871
872 if (h)
873 {
874 if (WithCrypto)
875 {
876 need_passphrase = h->security & ENCRYPT;
877 app = h->security;
878 }
879 mutt_message_hook (Context, h, MUTT_MESSAGEHOOK);
880 mutt_default_save (buf->data, buf->dsize, h);
881 mutt_buffer_fix_dptr (buf);
882 }
883 else
884 {
885 /* look for the first tagged message */
886
887 for (i = 0; i < Context->vcount; i++)
888 {
889 if (Context->hdrs[Context->v2r[i]]->tagged)
890 {
891 h = Context->hdrs[Context->v2r[i]];
892 break;
893 }
894 }
895
896
897 if (h)
898 {
899 mutt_message_hook (Context, h, MUTT_MESSAGEHOOK);
900 mutt_default_save (buf->data, buf->dsize, h);
901 mutt_buffer_fix_dptr (buf);
902 if (WithCrypto)
903 {
904 need_passphrase = h->security & ENCRYPT;
905 app = h->security;
906 }
907 h = NULL;
908 }
909 }
910
911 mutt_buffer_pretty_mailbox (buf);
912 if (mutt_buffer_enter_fname (prompt, buf, 0) == -1)
913 goto cleanup;
914 if (!mutt_buffer_len (buf))
915 goto cleanup;
916
917 /* This is an undocumented feature of ELM pointed out to me by Felix von
918 * Leitner <leitner@prz.fu-berlin.de>
919 */
920 if (mutt_strcmp (mutt_b2s (buf), ".") == 0)
921 mutt_buffer_strcpy (buf, LastSaveFolder);
922 else
923 strfcpy (LastSaveFolder, mutt_b2s (buf), sizeof (LastSaveFolder));
924
925 mutt_buffer_expand_path (buf);
926
927 /* check to make sure that this file is really the one the user wants */
928 if (mutt_save_confirm (mutt_b2s (buf), &st) != 0)
929 goto cleanup;
930
931 if (WithCrypto && need_passphrase && (decode || decrypt)
932 && !crypt_valid_passphrase(app))
933 goto cleanup;
934
935 mutt_message (_("Copying to %s..."), mutt_b2s (buf));
936
937#ifdef USE_IMAP
938 if (Context->magic == MUTT_IMAP &&
939 !(decode || decrypt) && mx_is_imap (mutt_b2s (buf)))
940 {
941 switch (imap_copy_messages (Context, h, mutt_b2s (buf), delete))
942 {
943 /* success */
944 case 0:
945 mutt_clear_error ();
946 rc = 0;
947 goto cleanup;
948 /* non-fatal error: fall through to fetch/append */
949 case 1:
950 break;
951 /* fatal error, abort */
952 case -1:
953 goto cleanup;
954 }
955 }
956#endif
957
958 if (mx_open_mailbox (mutt_b2s (buf), MUTT_APPEND, &ctx) != NULL)
959 {
960 if (h)
961 {
962 if (_mutt_save_message(h, &ctx, delete, decode, decrypt) != 0)
963 {
964 mx_close_mailbox (&ctx, NULL);
965 goto cleanup;
966 }
967 }
968 else
969 {
970 for (i = 0; i < Context->vcount; i++)
971 {
972 if (Context->hdrs[Context->v2r[i]]->tagged)
973 {
974 mutt_message_hook (Context, Context->hdrs[Context->v2r[i]], MUTT_MESSAGEHOOK);
975 if (_mutt_save_message(Context->hdrs[Context->v2r[i]],
976 &ctx, delete, decode, decrypt) != 0)
977 {
978 mx_close_mailbox (&ctx, NULL);
979 goto cleanup;
980 }
981 }
982 }
983 }
984
985 need_buffy_cleanup = (ctx.magic == MUTT_MBOX || ctx.magic == MUTT_MMDF);
986
987 mx_close_mailbox (&ctx, NULL);
988
989 if (need_buffy_cleanup)
990 mutt_buffy_cleanup (mutt_b2s (buf), &st);
991
992 mutt_clear_error ();
993 rc = 0;
994 }
995
996cleanup:
997 mutt_buffer_pool_release (&buf);
998 return rc;
999}
1000
1001
1002void mutt_version (void)
1003{
1004 mutt_message ("Mutt %s (%s)", MUTT_VERSION, ReleaseDate);
1005}
1006
1007/*
1008 * Returns:
1009 * 1 when a structural change is made.
1010 * recvattach requires this to know when to regenerate the actx.
1011 * 0 otherwise.
1012 */
1013int mutt_edit_content_type (HEADER *h, BODY *b, FILE *fp)
1014{
1015 char buf[LONG_STRING];
1016 char obuf[LONG_STRING];
1017 char tmp[STRING];
1018 PARAMETER *p;
1019
1020 char charset[STRING];
1021 char *cp;
1022
1023 short charset_changed = 0;
1024 short type_changed = 0;
1025 short structure_changed = 0;
1026
1027 cp = mutt_get_parameter ("charset", b->parameter);
1028 strfcpy (charset, NONULL (cp), sizeof (charset));
1029
1030 snprintf (buf, sizeof (buf), "%s/%s", TYPE (b), b->subtype);
1031 strfcpy (obuf, buf, sizeof (obuf));
1032 if (b->parameter)
1033 {
1034 size_t l;
1035
1036 for (p = b->parameter; p; p = p->next)
1037 {
1038 l = strlen (buf);
1039
1040 rfc822_cat (tmp, sizeof (tmp), p->value, MimeSpecials);
1041 snprintf (buf + l, sizeof (buf) - l, "; %s=%s", p->attribute, tmp);
1042 }
1043 }
1044
1045 if (mutt_get_field ("Content-Type: ", buf, sizeof (buf), 0) != 0 ||
1046 buf[0] == 0)
1047 return 0;
1048
1049 /* clean up previous junk */
1050 mutt_free_parameter (&b->parameter);
1051 FREE (&b->subtype);
1052
1053 mutt_parse_content_type (buf, b);
1054
1055
1056 snprintf (tmp, sizeof (tmp), "%s/%s", TYPE (b), NONULL (b->subtype));
1057 type_changed = ascii_strcasecmp (tmp, obuf);
1058 charset_changed = ascii_strcasecmp (charset, mutt_get_parameter ("charset", b->parameter));
1059
1060 /* if in send mode, check for conversion - current setting is default. */
1061
1062 if (!h && b->type == TYPETEXT && charset_changed)
1063 {
1064 int r;
1065 snprintf (tmp, sizeof (tmp), _("Convert to %s upon sending?"),
1066 mutt_get_parameter ("charset", b->parameter));
1067 if ((r = mutt_yesorno (tmp, !b->noconv)) != -1)
1068 b->noconv = (r == MUTT_NO);
1069 }
1070
1071 /* inform the user */
1072
1073 snprintf (tmp, sizeof (tmp), "%s/%s", TYPE (b), NONULL (b->subtype));
1074 if (type_changed)
1075 mutt_message (_("Content-Type changed to %s."), tmp);
1076 if (b->type == TYPETEXT && charset_changed)
1077 {
1078 if (type_changed)
1079 mutt_sleep (1);
1080 mutt_message (_("Character set changed to %s; %s."),
1081 mutt_get_parameter ("charset", b->parameter),
1082 b->noconv ? _("not converting") : _("converting"));
1083 }
1084
1085 b->force_charset |= charset_changed ? 1 : 0;
1086
1087 if (!is_multipart (b) && b->parts)
1088 {
1089 structure_changed = 1;
1090 mutt_free_body (&b->parts);
1091 }
1092 if (!mutt_is_message_type (b->type, b->subtype) && b->hdr)
1093 {
1094 structure_changed = 1;
1095 b->hdr->content = NULL;
1096 mutt_free_header (&b->hdr);
1097 }
1098
1099 if (fp && !b->parts && (is_multipart (b) || mutt_is_message_type (b->type, b->subtype)))
1100 {
1101 structure_changed = 1;
1102 mutt_parse_part (fp, b);
1103 }
1104
1105 if (WithCrypto && h)
1106 {
1107 if (h->content == b)
1108 h->security = 0;
1109
1110 h->security |= crypt_query (b);
1111 }
1112
1113 return structure_changed;
1114}
1115
1116
1117static int _mutt_check_traditional_pgp (HEADER *h, int *redraw)
1118{
1119 MESSAGE *msg;
1120 int rv = 0;
1121
1122 h->security |= PGP_TRADITIONAL_CHECKED;
1123
1124 mutt_parse_mime_message (Context, h);
1125 if ((msg = mx_open_message (Context, h->msgno)) == NULL)
1126 return 0;
1127 if (crypt_pgp_check_traditional (msg->fp, h->content, 0))
1128 {
1129 h->security = crypt_query (h->content);
1130 *redraw |= REDRAW_FULL;
1131 rv = 1;
1132 }
1133
1134 h->security |= PGP_TRADITIONAL_CHECKED;
1135 mx_close_message (Context, &msg);
1136 return rv;
1137}
1138
1139int mutt_check_traditional_pgp (HEADER *h, int *redraw)
1140{
1141 int i;
1142 int rv = 0;
1143 if (h && !(h->security & PGP_TRADITIONAL_CHECKED))
1144 rv = _mutt_check_traditional_pgp (h, redraw);
1145 else
1146 {
1147 for (i = 0; i < Context->vcount; i++)
1148 if (Context->hdrs[Context->v2r[i]]->tagged &&
1149 !(Context->hdrs[Context->v2r[i]]->security & PGP_TRADITIONAL_CHECKED))
1150 rv = _mutt_check_traditional_pgp (Context->hdrs[Context->v2r[i]], redraw)
1151 || rv;
1152 }
1153 return rv;
1154}
1155
1156void mutt_check_stats (void)
1157{
1158 mutt_buffy_check (MUTT_BUFFY_CHECK_FORCE | MUTT_BUFFY_CHECK_FORCE_STATS);
1159}