mutt stable branch with some hacks
at jcs 1159 lines 30 kB view raw
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}