mutt stable branch with some hacks
at master 758 lines 18 kB view raw
1/* 2 * Copyright (C) 1996-2002,2012-2013 Michael R. Elkins <me@mutt.org> 3 * Copyright (C) 1999-2002,2004 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 "mutt.h" 25#include "mutt_menu.h" 26#include "mime.h" 27#include "mailbox.h" 28#include "mapping.h" 29#include "sort.h" 30#ifdef USE_IMAP 31#include "imap.h" 32#endif 33#include "mutt_crypt.h" 34 35#include <ctype.h> 36#include <unistd.h> 37#include <string.h> 38#include <sys/stat.h> 39 40static const struct mapping_t PostponeHelp[] = { 41 { N_("Exit"), OP_EXIT }, 42 { N_("Del"), OP_DELETE }, 43 { N_("Undel"), OP_UNDELETE }, 44 { N_("Help"), OP_HELP }, 45 { NULL, 0 } 46}; 47 48 49 50static short PostCount = 0; 51static CONTEXT *PostContext = NULL; 52static short UpdateNumPostponed = 0; 53 54/* Return the number of postponed messages. 55 * if force is 0, use a cached value if it is costly to get a fresh 56 * count (IMAP) - else check. 57 */ 58int mutt_num_postponed (int force) 59{ 60 struct stat st; 61 CONTEXT ctx; 62 63 static time_t LastModify = 0; 64 static char *OldPostponed = NULL; 65 66 if (UpdateNumPostponed) 67 { 68 UpdateNumPostponed = 0; 69 force = 1; 70 } 71 72 if (mutt_strcmp (Postponed, OldPostponed)) 73 { 74 FREE (&OldPostponed); 75 OldPostponed = safe_strdup (Postponed); 76 LastModify = 0; 77 force = 1; 78 } 79 80 if (!Postponed) 81 return 0; 82 83#ifdef USE_IMAP 84 /* LastModify is useless for IMAP */ 85 if (mx_is_imap (Postponed)) 86 { 87 if (force) 88 { 89 short newpc; 90 91 newpc = imap_status (Postponed, 0); 92 if (newpc >= 0) 93 { 94 PostCount = newpc; 95 dprint (3, (debugfile, "mutt_num_postponed: %d postponed IMAP messages found.\n", PostCount)); 96 } 97 else 98 dprint (3, (debugfile, "mutt_num_postponed: using old IMAP postponed count.\n")); 99 } 100 return PostCount; 101 } 102#endif 103 104 if (stat (Postponed, &st) == -1) 105 { 106 PostCount = 0; 107 LastModify = 0; 108 return (0); 109 } 110 111 if (S_ISDIR (st.st_mode)) 112 { 113 /* if we have a maildir mailbox, we need to stat the "new" dir */ 114 115 char buf[_POSIX_PATH_MAX]; 116 117 snprintf (buf, sizeof (buf), "%s/new", Postponed); 118 if (access (buf, F_OK) == 0 && stat (buf, &st) == -1) 119 { 120 PostCount = 0; 121 LastModify = 0; 122 return 0; 123 } 124 } 125 126 if (LastModify < st.st_mtime) 127 { 128 LastModify = st.st_mtime; 129 130 if (access (Postponed, R_OK | F_OK) != 0) 131 return (PostCount = 0); 132 if (mx_open_mailbox (Postponed, MUTT_NOSORT | MUTT_QUIET, &ctx) == NULL) 133 PostCount = 0; 134 else 135 PostCount = ctx.msgcount; 136 mx_fastclose_mailbox (&ctx); 137 } 138 139 return (PostCount); 140} 141 142void mutt_update_num_postponed (void) 143{ 144 UpdateNumPostponed = 1; 145} 146 147static void post_entry (char *s, size_t slen, MUTTMENU *menu, int entry) 148{ 149 CONTEXT *ctx = (CONTEXT *) menu->data; 150 151 _mutt_make_string (s, slen, NONULL (HdrFmt), ctx, ctx->hdrs[entry], 152 MUTT_FORMAT_ARROWCURSOR); 153} 154 155static HEADER *select_msg (void) 156{ 157 MUTTMENU *menu; 158 int i, done=0, r=-1; 159 char helpstr[LONG_STRING]; 160 short orig_sort; 161 162 menu = mutt_new_menu (MENU_POST); 163 menu->make_entry = post_entry; 164 menu->max = PostContext->msgcount; 165 menu->title = _("Postponed Messages"); 166 menu->data = PostContext; 167 menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_POST, PostponeHelp); 168 169 /* The postponed mailbox is setup to have sorting disabled, but the global 170 * Sort variable may indicate something different. Sorting has to be 171 * disabled while the postpone menu is being displayed. */ 172 orig_sort = Sort; 173 Sort = SORT_ORDER; 174 175 while (!done) 176 { 177 switch (i = mutt_menuLoop (menu)) 178 { 179 case OP_DELETE: 180 case OP_UNDELETE: 181 /* should deleted draft messages be saved in the trash folder? */ 182 mutt_set_flag (PostContext, PostContext->hdrs[menu->current], MUTT_DELETE, (i == OP_DELETE) ? 1 : 0); 183 PostCount = PostContext->msgcount - PostContext->deleted; 184 if (option (OPTRESOLVE) && menu->current < menu->max - 1) 185 { 186 menu->oldcurrent = menu->current; 187 menu->current++; 188 if (menu->current >= menu->top + menu->pagelen) 189 { 190 menu->top = menu->current; 191 menu->redraw = REDRAW_INDEX | REDRAW_STATUS; 192 } 193 else 194 menu->redraw |= REDRAW_MOTION_RESYNCH; 195 } 196 else 197 menu->redraw = REDRAW_CURRENT; 198 break; 199 200 case OP_GENERIC_SELECT_ENTRY: 201 r = menu->current; 202 done = 1; 203 break; 204 205 case OP_EXIT: 206 done = 1; 207 break; 208 } 209 } 210 211 Sort = orig_sort; 212 mutt_menuDestroy (&menu); 213 return (r > -1 ? PostContext->hdrs[r] : NULL); 214} 215 216/* args: 217 * ctx Context info, used when recalling a message to which 218 * we reply. 219 * hdr envelope/attachment info for recalled message 220 * cur if message was a reply, `cur' is set to the message which 221 * `hdr' is in reply to 222 * fcc fcc for the recalled message 223 * fcclen max length of fcc 224 * 225 * return vals: 226 * -1 error/no messages 227 * 0 normal exit 228 * SENDREPLY recalled message is a reply 229 */ 230int mutt_get_postponed (CONTEXT *ctx, HEADER *hdr, HEADER **cur, char *fcc, size_t fcclen) 231{ 232 HEADER *h; 233 int code = SENDPOSTPONED; 234 LIST *tmp; 235 LIST *last = NULL; 236 LIST *next; 237 const char *p; 238 int opt_delete; 239 240 if (!Postponed) 241 return (-1); 242 243 if ((PostContext = mx_open_mailbox (Postponed, MUTT_NOSORT, NULL)) == NULL) 244 { 245 PostCount = 0; 246 mutt_error _("No postponed messages."); 247 return (-1); 248 } 249 250 if (! PostContext->msgcount) 251 { 252 PostCount = 0; 253 mx_close_mailbox (PostContext, NULL); 254 FREE (&PostContext); 255 mutt_error _("No postponed messages."); 256 return (-1); 257 } 258 259 if (PostContext->msgcount == 1) 260 { 261 /* only one message, so just use that one. */ 262 h = PostContext->hdrs[0]; 263 } 264 else if ((h = select_msg ()) == NULL) 265 { 266 mx_close_mailbox (PostContext, NULL); 267 FREE (&PostContext); 268 return (-1); 269 } 270 271 if (mutt_prepare_template (NULL, PostContext, hdr, h, 0) < 0) 272 { 273 mx_fastclose_mailbox (PostContext); 274 FREE (&PostContext); 275 return (-1); 276 } 277 278 /* finished with this message, so delete it. */ 279 mutt_set_flag (PostContext, h, MUTT_DELETE, 1); 280 mutt_set_flag (PostContext, h, MUTT_PURGE, 1); 281 282 /* update the count for the status display */ 283 PostCount = PostContext->msgcount - PostContext->deleted; 284 285 /* avoid the "purge deleted messages" prompt */ 286 opt_delete = quadoption (OPT_DELETE); 287 set_quadoption (OPT_DELETE, MUTT_YES); 288 mx_close_mailbox (PostContext, NULL); 289 set_quadoption (OPT_DELETE, opt_delete); 290 291 FREE (&PostContext); 292 293 for (tmp = hdr->env->userhdrs; tmp; ) 294 { 295 if (ascii_strncasecmp ("X-Mutt-References:", tmp->data, 18) == 0) 296 { 297 if (ctx) 298 { 299 /* if a mailbox is currently open, look to see if the orignal message 300 the user attempted to reply to is in this mailbox */ 301 p = skip_email_wsp(tmp->data + 18); 302 if (!ctx->id_hash) 303 ctx->id_hash = mutt_make_id_hash (ctx); 304 *cur = hash_find (ctx->id_hash, p); 305 } 306 307 /* Remove the X-Mutt-References: header field. */ 308 next = tmp->next; 309 if (last) 310 last->next = tmp->next; 311 else 312 hdr->env->userhdrs = tmp->next; 313 tmp->next = NULL; 314 mutt_free_list (&tmp); 315 tmp = next; 316 if (*cur) 317 code |= SENDREPLY; 318 } 319 else if (ascii_strncasecmp ("X-Mutt-Fcc:", tmp->data, 11) == 0) 320 { 321 p = skip_email_wsp(tmp->data + 11); 322 strfcpy (fcc, p, fcclen); 323 mutt_pretty_mailbox (fcc, fcclen); 324 325 /* remove the X-Mutt-Fcc: header field */ 326 next = tmp->next; 327 if (last) 328 last->next = tmp->next; 329 else 330 hdr->env->userhdrs = tmp->next; 331 tmp->next = NULL; 332 mutt_free_list (&tmp); 333 tmp = next; 334 /* note that x-mutt-fcc was present. we do this because we want to add a 335 * default fcc if the header was missing, but preserve the request of the 336 * user to not make a copy if the header field is present, but empty. 337 * see http://dev.mutt.org/trac/ticket/3653 338 */ 339 code |= SENDPOSTPONEDFCC; 340 } 341 else if ((WithCrypto & APPLICATION_PGP) 342 && (mutt_strncmp ("Pgp:", tmp->data, 4) == 0 /* this is generated 343 * by old mutt versions 344 */ 345 || mutt_strncmp ("X-Mutt-PGP:", tmp->data, 11) == 0)) 346 { 347 hdr->security = mutt_parse_crypt_hdr (strchr (tmp->data, ':') + 1, 1, 348 APPLICATION_PGP); 349 hdr->security |= APPLICATION_PGP; 350 351 /* remove the pgp field */ 352 next = tmp->next; 353 if (last) 354 last->next = tmp->next; 355 else 356 hdr->env->userhdrs = tmp->next; 357 tmp->next = NULL; 358 mutt_free_list (&tmp); 359 tmp = next; 360 } 361 else if ((WithCrypto & APPLICATION_SMIME) 362 && mutt_strncmp ("X-Mutt-SMIME:", tmp->data, 13) == 0) 363 { 364 hdr->security = mutt_parse_crypt_hdr (strchr (tmp->data, ':') + 1, 1, 365 APPLICATION_SMIME); 366 hdr->security |= APPLICATION_SMIME; 367 368 /* remove the smime field */ 369 next = tmp->next; 370 if (last) 371 last->next = tmp->next; 372 else 373 hdr->env->userhdrs = tmp->next; 374 tmp->next = NULL; 375 mutt_free_list (&tmp); 376 tmp = next; 377 } 378 379#ifdef MIXMASTER 380 else if (mutt_strncmp ("X-Mutt-Mix:", tmp->data, 11) == 0) 381 { 382 char *t; 383 mutt_free_list (&hdr->chain); 384 385 t = strtok (tmp->data + 11, " \t\n"); 386 while (t) 387 { 388 hdr->chain = mutt_add_list (hdr->chain, t); 389 t = strtok (NULL, " \t\n"); 390 } 391 392 next = tmp->next; 393 if (last) 394 last->next = tmp->next; 395 else 396 hdr->env->userhdrs = tmp->next; 397 tmp->next = NULL; 398 mutt_free_list (&tmp); 399 tmp = next; 400 } 401#endif 402 403 else 404 { 405 last = tmp; 406 tmp = tmp->next; 407 } 408 } 409 410 if (option (OPTCRYPTOPPORTUNISTICENCRYPT)) 411 crypt_opportunistic_encrypt (hdr); 412 413 return (code); 414} 415 416 417 418int mutt_parse_crypt_hdr (const char *p, int set_empty_signas, int crypt_app) 419{ 420 char smime_cryptalg[LONG_STRING] = "\0"; 421 char sign_as[LONG_STRING] = "\0", *q; 422 int flags = 0; 423 424 if (!WithCrypto) 425 return 0; 426 427 p = skip_email_wsp(p); 428 for (; *p; p++) 429 { 430 431 switch (*p) 432 { 433 case 'e': 434 case 'E': 435 flags |= ENCRYPT; 436 break; 437 438 case 'o': 439 case 'O': 440 flags |= OPPENCRYPT; 441 break; 442 443 case 's': 444 case 'S': 445 flags |= SIGN; 446 q = sign_as; 447 448 if (*(p+1) == '<') 449 { 450 for (p += 2; 451 *p && *p != '>' && q < sign_as + sizeof (sign_as) - 1; 452 *q++ = *p++) 453 ; 454 455 if (*p!='>') 456 { 457 mutt_error _("Illegal crypto header"); 458 return 0; 459 } 460 } 461 462 *q = '\0'; 463 break; 464 465 /* This used to be the micalg parameter. 466 * 467 * It's no longer needed, so we just skip the parameter in order 468 * to be able to recall old messages. 469 */ 470 case 'm': 471 case 'M': 472 if(*(p+1) == '<') 473 { 474 for (p += 2; *p && *p != '>'; p++) 475 ; 476 if(*p != '>') 477 { 478 mutt_error _("Illegal crypto header"); 479 return 0; 480 } 481 } 482 483 break; 484 485 486 case 'c': 487 case 'C': 488 q = smime_cryptalg; 489 490 if(*(p+1) == '<') 491 { 492 for(p += 2; *p && *p != '>' && q < smime_cryptalg + sizeof(smime_cryptalg) - 1; 493 *q++ = *p++) 494 ; 495 496 if(*p != '>') 497 { 498 mutt_error _("Illegal S/MIME header"); 499 return 0; 500 } 501 } 502 503 *q = '\0'; 504 break; 505 506 case 'i': 507 case 'I': 508 flags |= INLINE; 509 break; 510 511 default: 512 mutt_error _("Illegal crypto header"); 513 return 0; 514 } 515 516 } 517 518 /* the cryptalg field must not be empty */ 519 if ((WithCrypto & APPLICATION_SMIME) && *smime_cryptalg) 520 mutt_str_replace (&SmimeCryptAlg, smime_cryptalg); 521 522 /* Set {Smime,Pgp}SignAs, if desired. */ 523 524 if ((WithCrypto & APPLICATION_PGP) && (crypt_app == APPLICATION_PGP) 525 && (flags & SIGN) 526 && (set_empty_signas || *sign_as)) 527 mutt_str_replace (&PgpSignAs, sign_as); 528 529 if ((WithCrypto & APPLICATION_SMIME) && (crypt_app == APPLICATION_SMIME) 530 && (flags & SIGN) 531 && (set_empty_signas || *sign_as)) 532 mutt_str_replace (&SmimeDefaultKey, sign_as); 533 534 return flags; 535} 536 537 538/* args: 539 * fp If not NULL, file containing the template 540 * ctx If fp is NULL, the context containing the header with the template 541 * newhdr The template is read into this HEADER 542 * hdr The message to recall/resend 543 * resend Set if resending (as opposed to recalling a postponed msg). 544 * Resent messages enable header weeding, and also 545 * discard any existing Message-ID and Mail-Followup-To. 546 */ 547int mutt_prepare_template (FILE *fp, CONTEXT *ctx, HEADER *newhdr, HEADER *hdr, 548 short resend) 549{ 550 MESSAGE *msg = NULL; 551 char file[_POSIX_PATH_MAX]; 552 BODY *b; 553 FILE *bfp; 554 int rv = -1; 555 STATE s; 556 int sec_type; 557 558 memset (&s, 0, sizeof (s)); 559 560 if (!fp && (msg = mx_open_message (ctx, hdr->msgno)) == NULL) 561 return (-1); 562 563 if (!fp) fp = msg->fp; 564 565 bfp = fp; 566 567 /* parse the message header and MIME structure */ 568 569 fseeko (fp, hdr->offset, 0); 570 newhdr->offset = hdr->offset; 571 /* enable header weeding for resent messages */ 572 newhdr->env = mutt_read_rfc822_header (fp, newhdr, 1, resend); 573 newhdr->content->length = hdr->content->length; 574 mutt_parse_part (fp, newhdr->content); 575 576 /* If resending a message, don't keep message_id or mail_followup_to. 577 * Otherwise, we are resuming a postponed message, and want to keep those 578 * headers if they exist. 579 */ 580 if (resend) 581 { 582 FREE (&newhdr->env->message_id); 583 FREE (&newhdr->env->mail_followup_to); 584 } 585 586 /* decrypt pgp/mime encoded messages */ 587 588 if ((WithCrypto & APPLICATION_PGP) && 589 (sec_type = mutt_is_multipart_encrypted (newhdr->content))) 590 { 591 newhdr->security |= sec_type; 592 if (!crypt_valid_passphrase (sec_type)) 593 goto err; 594 595 mutt_message _("Decrypting message..."); 596 if ((crypt_pgp_decrypt_mime (fp, &bfp, newhdr->content, &b) == -1) 597 || b == NULL) 598 { 599 err: 600 mx_close_message (ctx, &msg); 601 mutt_free_envelope (&newhdr->env); 602 mutt_free_body (&newhdr->content); 603 mutt_error _("Decryption failed."); 604 return -1; 605 } 606 607 mutt_free_body (&newhdr->content); 608 newhdr->content = b; 609 610 mutt_clear_error (); 611 } 612 613 /* 614 * remove a potential multipart/signed layer - useful when 615 * resending messages 616 */ 617 618 if (WithCrypto && mutt_is_multipart_signed (newhdr->content)) 619 { 620 newhdr->security |= SIGN; 621 if ((WithCrypto & APPLICATION_PGP) 622 && ascii_strcasecmp (mutt_get_parameter ("protocol", newhdr->content->parameter), "application/pgp-signature") == 0) 623 newhdr->security |= APPLICATION_PGP; 624 else if ((WithCrypto & APPLICATION_SMIME)) 625 newhdr->security |= APPLICATION_SMIME; 626 627 /* destroy the signature */ 628 mutt_free_body (&newhdr->content->parts->next); 629 newhdr->content = mutt_remove_multipart (newhdr->content); 630 } 631 632 633 /* 634 * We don't need no primary multipart. 635 * Note: We _do_ preserve messages! 636 * 637 * XXX - we don't handle multipart/alternative in any 638 * smart way when sending messages. However, one may 639 * consider this a feature. 640 * 641 */ 642 643 if (newhdr->content->type == TYPEMULTIPART) 644 newhdr->content = mutt_remove_multipart (newhdr->content); 645 646 s.fpin = bfp; 647 648 /* create temporary files for all attachments */ 649 for (b = newhdr->content; b; b = b->next) 650 { 651 652 /* what follows is roughly a receive-mode variant of 653 * mutt_get_tmp_attachment () from muttlib.c 654 */ 655 656 file[0] = '\0'; 657 if (b->filename) 658 { 659 strfcpy (file, b->filename, sizeof (file)); 660 b->d_filename = safe_strdup (b->filename); 661 } 662 else 663 { 664 /* avoid Content-Disposition: header with temporary filename */ 665 b->use_disp = 0; 666 } 667 668 /* set up state flags */ 669 670 s.flags = 0; 671 672 if (b->type == TYPETEXT) 673 { 674 if (!ascii_strcasecmp ("yes", mutt_get_parameter ("x-mutt-noconv", b->parameter))) 675 b->noconv = 1; 676 else 677 { 678 s.flags |= MUTT_CHARCONV; 679 b->noconv = 0; 680 } 681 682 mutt_delete_parameter ("x-mutt-noconv", &b->parameter); 683 } 684 685 mutt_adv_mktemp (file, sizeof(file)); 686 if ((s.fpout = safe_fopen (file, "w")) == NULL) 687 goto bail; 688 689 690 if ((WithCrypto & APPLICATION_PGP) && 691 ((sec_type = mutt_is_application_pgp (b)) & (ENCRYPT|SIGN))) 692 { 693 mutt_body_handler (b, &s); 694 695 newhdr->security |= sec_type; 696 697 b->type = TYPETEXT; 698 mutt_str_replace (&b->subtype, "plain"); 699 mutt_delete_parameter ("x-action", &b->parameter); 700 } 701 else if ((WithCrypto & APPLICATION_SMIME) && 702 ((sec_type = mutt_is_application_smime (b)) & (ENCRYPT|SIGN))) 703 { 704 mutt_body_handler (b, &s); 705 706 newhdr->security |= sec_type; 707 b->type = TYPETEXT; 708 mutt_str_replace (&b->subtype, "plain"); 709 } 710 else 711 mutt_decode_attachment (b, &s); 712 713 if (safe_fclose (&s.fpout) != 0) 714 goto bail; 715 716 mutt_str_replace (&b->filename, file); 717 b->unlink = 1; 718 719 mutt_stamp_attachment (b); 720 721 mutt_free_body (&b->parts); 722 if (b->hdr) b->hdr->content = NULL; /* avoid dangling pointer */ 723 } 724 725 /* Fix encryption flags. */ 726 727 /* No inline if multipart. */ 728 if (WithCrypto && (newhdr->security & INLINE) && newhdr->content->next) 729 newhdr->security &= ~INLINE; 730 731 /* Do we even support multiple mechanisms? */ 732 newhdr->security &= WithCrypto | ~(APPLICATION_PGP|APPLICATION_SMIME); 733 734 /* Theoretically, both could be set. Take the one the user wants to set by default. */ 735 if ((newhdr->security & APPLICATION_PGP) && (newhdr->security & APPLICATION_SMIME)) 736 { 737 if (option (OPTSMIMEISDEFAULT)) 738 newhdr->security &= ~APPLICATION_PGP; 739 else 740 newhdr->security &= ~APPLICATION_SMIME; 741 } 742 743 rv = 0; 744 745 bail: 746 747 /* that's it. */ 748 if (bfp != fp) safe_fclose (&bfp); 749 if (msg) mx_close_message (ctx, &msg); 750 751 if (rv == -1) 752 { 753 mutt_free_envelope (&newhdr->env); 754 mutt_free_body (&newhdr->content); 755 } 756 757 return rv; 758}