commands.c 25.3 KB
Newer Older
Thomas Roessler's avatar
Thomas Roessler committed
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>
Thomas Roessler's avatar
Thomas Roessler committed
4 5 6 7 8 9 10 11 12 13 14 15 16
 * 
 *     This program is free software; you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation; either version 2 of the License, or
 *     (at your option) any later version.
 * 
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 * 
 *     You should have received a copy of the GNU General Public License
 *     along with this program; if not, write to the Free Software
17
 *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
Thomas Roessler's avatar
Thomas Roessler committed
18 19
 */ 

20 21 22 23
#if HAVE_CONFIG_H
# include "config.h"
#endif

Thomas Roessler's avatar
Thomas Roessler committed
24 25 26 27 28 29 30 31 32
#include "mutt.h"
#include "mutt_curses.h"
#include "mutt_menu.h"
#include "mime.h"
#include "sort.h"
#include "mailbox.h"
#include "copy.h"
#include "mx.h"
#include "pager.h"
33
#include "mutt_crypt.h"
34
#include "mutt_idna.h"
35 36 37
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
Thomas Roessler's avatar
Thomas Roessler committed
38

39 40 41 42
#ifdef USE_IMAP
#include "imap.h"
#endif

Thomas Roessler's avatar
Thomas Roessler committed
43 44 45 46 47 48 49 50 51 52 53
#include "buffy.h"

#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <utime.h>

54
static const char *ExtPagerProgress = "all";
Thomas Roessler's avatar
Thomas Roessler committed
55 56 57 58

/* The folder the user last saved to.  Used by ci_save_message() */
static char LastSaveFolder[_POSIX_PATH_MAX] = "";

59
int mutt_display_message (HEADER *cur)
Thomas Roessler's avatar
Thomas Roessler committed
60 61 62
{
  char tempfile[_POSIX_PATH_MAX], buf[LONG_STRING];
  int rc = 0, builtin = 0;
Derek Martin's avatar
Derek Martin committed
63
  int cmflags = MUTT_CM_DECODE | MUTT_CM_DISPLAY | MUTT_CM_CHARCONV;
64 65
  FILE *fpout = NULL;
  FILE *fpfilterout = NULL;
Thomas Roessler's avatar
nits.  
Thomas Roessler committed
66
  pid_t filterpid = -1;
67
  int res;
Thomas Roessler's avatar
Thomas Roessler committed
68

Thomas Roessler's avatar
Thomas Roessler committed
69
  snprintf (buf, sizeof (buf), "%s/%s", TYPE (cur->content),
Thomas Roessler's avatar
Thomas Roessler committed
70 71 72
	    cur->content->subtype);

  mutt_parse_mime_message (Context, cur);
Derek Martin's avatar
Derek Martin committed
73
  mutt_message_hook (Context, cur, MUTT_MESSAGEHOOK);
Thomas Roessler's avatar
Thomas Roessler committed
74

Simon Ruderich's avatar
Simon Ruderich committed
75
  /* see if crypto is needed for this message.  if so, we should exit curses */
76
  if (WithCrypto && cur->security)
Thomas Roessler's avatar
Thomas Roessler committed
77
  {
78
    if (cur->security & ENCRYPT)
Thomas Roessler's avatar
Thomas Roessler committed
79
    {
80
      if (cur->security & APPLICATION_SMIME)
81
	crypt_smime_getkeys (cur->env);
82
      if(!crypt_valid_passphrase(cur->security))
Thomas Roessler's avatar
Thomas Roessler committed
83 84
	return 0;

Derek Martin's avatar
Derek Martin committed
85
      cmflags |= MUTT_CM_VERIFY;
Thomas Roessler's avatar
Thomas Roessler committed
86
    }
87
    else if (cur->security & SIGN)
Thomas Roessler's avatar
Thomas Roessler committed
88 89
    {
      /* find out whether or not the verify signature */
90 91
      /* L10N: Used for the $crypt_verify_sig prompt */
      if (query_quadoption (OPT_VERIFYSIG, _("Verify signature?")) == MUTT_YES)
Thomas Roessler's avatar
Thomas Roessler committed
92
      {
Derek Martin's avatar
Derek Martin committed
93
	cmflags |= MUTT_CM_VERIFY;
Thomas Roessler's avatar
Thomas Roessler committed
94 95 96
      }
    }
  }
97
  
Derek Martin's avatar
Derek Martin committed
98
  if (cmflags & MUTT_CM_VERIFY || cur->security & ENCRYPT)
99
  {
100 101 102
    if (cur->security & APPLICATION_PGP)
    {
      if (cur->env->from)
103 104 105
        crypt_pgp_invoke_getkeys (cur->env->from);
      
      crypt_invoke_message (APPLICATION_PGP);
106
    }
Thomas Roessler's avatar
Thomas Roessler committed
107

108
    if (cur->security & APPLICATION_SMIME)
109
      crypt_invoke_message (APPLICATION_SMIME);
110 111
  }

Thomas Roessler's avatar
Thomas Roessler committed
112

113
  mutt_mktemp (tempfile, sizeof (tempfile));
Thomas Roessler's avatar
Thomas Roessler committed
114 115
  if ((fpout = safe_fopen (tempfile, "w")) == NULL)
  {
116
    mutt_error _("Could not create temporary file!");
Thomas Roessler's avatar
Thomas Roessler committed
117 118 119
    return (0);
  }

120 121 122 123
  if (DisplayFilter && *DisplayFilter) 
  {
    fpfilterout = fpout;
    fpout = NULL;
124
    /* mutt_endwin (NULL); */
125 126 127 128 129
    filterpid = mutt_create_filter_fd (DisplayFilter, &fpout, NULL, NULL,
				       -1, fileno(fpfilterout), -1);
    if (filterpid < 0)
    {
      mutt_error (_("Cannot create display filter"));
130
      safe_fclose (&fpfilterout);
131 132 133 134
      unlink (tempfile);
      return 0;
    }
  }
135

136
  if (!Pager || mutt_strcmp (Pager, "builtin") == 0)
Thomas Roessler's avatar
Thomas Roessler committed
137 138 139
    builtin = 1;
  else
  {
140 141 142 143
    struct hdr_format_info hfi;
    hfi.ctx = Context;
    hfi.pager_progress = ExtPagerProgress;
    hfi.hdr = cur;
144
    mutt_make_string_info (buf, sizeof (buf), MuttIndexWindow->cols, NONULL(PagerFmt), &hfi, MUTT_FORMAT_MAKEPRINT);
Thomas Roessler's avatar
Thomas Roessler committed
145 146 147 148
    fputs (buf, fpout);
    fputs ("\n\n", fpout);
  }

149
  res = mutt_copy_message (fpout, Context, cur, cmflags,
Rocco Rutte's avatar
Rocco Rutte committed
150
       	(option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) | CH_DECODE | CH_FROM | CH_DISPLAY);
151
  if ((safe_fclose (&fpout) != 0 && errno != EPIPE) || res < 0)
Thomas Roessler's avatar
Thomas Roessler committed
152
  {
153 154
    mutt_error (_("Could not copy message"));
    if (fpfilterout != NULL)
155
    {
156
      mutt_wait_filter (filterpid);
157 158
      safe_fclose (&fpfilterout);
    }
159
    mutt_unlink (tempfile);
Thomas Roessler's avatar
Thomas Roessler committed
160 161 162
    return 0;
  }

163 164
  if (fpfilterout != NULL && mutt_wait_filter (filterpid) != 0)
    mutt_any_key_to_continue (NULL);
Thomas Roessler's avatar
Thomas Roessler committed
165

166 167 168
  safe_fclose (&fpfilterout);	/* XXX - check result? */

  
169 170 171
  if (WithCrypto)
  {
    /* update crypto information for this message */
172
    cur->security &= ~(GOODSIGN|BADSIGN);
173
    cur->security |= crypt_query (cur->content);
174
  
175 176 177 178
    /* Remove color cache for this message, in case there
       are color patterns for both ~g and ~V */
    cur->pair = 0;
  }
Thomas Roessler's avatar
Thomas Roessler committed
179

Thomas Roessler's avatar
Thomas Roessler committed
180 181 182
  if (builtin)
  {
    pager_t info;
183

184
    if (WithCrypto 
Derek Martin's avatar
Derek Martin committed
185
        && (cur->security & APPLICATION_SMIME) && (cmflags & MUTT_CM_VERIFY))
186 187 188
    {
      if (cur->security & GOODSIGN)
      {
189
	if (!crypt_smime_verify_sender(cur))
190 191 192 193
	  mutt_message ( _("S/MIME signature successfully verified."));
	else
	  mutt_error ( _("S/MIME certificate owner does not match sender."));
      }
194 195
      else if (cur->security & PARTSIGN)
	mutt_message (_("Warning: Part of this message has not been signed."));
196 197 198
      else if (cur->security & SIGN || cur->security & BADSIGN)
	mutt_error ( _("S/MIME signature could NOT be verified."));
    }
Thomas Roessler's avatar
Thomas Roessler committed
199

200
    if (WithCrypto 
Derek Martin's avatar
Derek Martin committed
201
        && (cur->security & APPLICATION_PGP) && (cmflags & MUTT_CM_VERIFY))
202 203 204 205 206
    {
      if (cur->security & GOODSIGN)
	mutt_message (_("PGP signature successfully verified."));
      else if (cur->security & PARTSIGN)
	mutt_message (_("Warning: Part of this message has not been signed."));
207
      else if (cur->security & SIGN)
208 209
	mutt_message (_("PGP signature could NOT be verified."));
    }
Thomas Roessler's avatar
Thomas Roessler committed
210

Thomas Roessler's avatar
Thomas Roessler committed
211 212 213 214
    /* Invoke the builtin pager */
    memset (&info, 0, sizeof (pager_t));
    info.hdr = cur;
    info.ctx = Context;
Derek Martin's avatar
Derek Martin committed
215
    rc = mutt_pager (NULL, tempfile, MUTT_PAGER_MESSAGE, &info);
Thomas Roessler's avatar
Thomas Roessler committed
216 217 218
  }
  else
  {
219 220
    int r;

221
    mutt_endwin (NULL);
Thomas Roessler's avatar
Thomas Roessler committed
222
    snprintf (buf, sizeof (buf), "%s %s", NONULL(Pager), tempfile);
223 224
    if ((r = mutt_system (buf)) == -1)
      mutt_error (_("Error running \"%s\"!"), buf);
Thomas Roessler's avatar
Thomas Roessler committed
225
    unlink (tempfile);
226 227
    if (!option (OPTNOCURSES))
      keypad (stdscr, TRUE);
228
    if (r != -1)
Derek Martin's avatar
Derek Martin committed
229
      mutt_set_flag (Context, cur, MUTT_READ, 1);
230
    if (r != -1 && option (OPTPROMPTAFTER))
Thomas Roessler's avatar
Thomas Roessler committed
231
    {
232
      mutt_unget_event (mutt_any_key_to_continue _("Command: "), 0);
Thomas Roessler's avatar
Thomas Roessler committed
233 234 235 236 237 238 239 240 241
      rc = km_dokey (MENU_PAGER);
    }
    else
      rc = 0;
  }

  return rc;
}

242
void ci_bounce_message (HEADER *h)
Thomas Roessler's avatar
Thomas Roessler committed
243 244
{
  char prompt[SHORT_STRING];
245
  char scratch[SHORT_STRING];
Thomas Roessler's avatar
Thomas Roessler committed
246 247
  char buf[HUGE_STRING] = { 0 };
  ADDRESS *adr = NULL;
248
  char *err = NULL;
Thomas Roessler's avatar
Thomas Roessler committed
249 250
  int rc;

251 252 253 254 255 256
 /* RfC 5322 mandates a From: header, so warn before bouncing
  * messages without one */
  if (h)
  {
    if (!h->env->from)
    {
Benno Schulenberg's avatar
Benno Schulenberg committed
257
      mutt_error _("Warning: message contains no From: header");
258 259 260 261 262 263 264 265 266
      mutt_sleep (2);
    }
  }
  else if (Context)
  {
    for (rc = 0; rc < Context->msgcount; rc++)
    {
      if (Context->hdrs[rc]->tagged && !Context->hdrs[rc]->env->from)
      {
Benno Schulenberg's avatar
Benno Schulenberg committed
267
	mutt_error _("Warning: message contains no From: header");
268 269 270 271 272 273
	mutt_sleep (2);
	break;
      }
    }
  }

274 275 276 277 278
  if(h)
    strfcpy(prompt, _("Bounce message to: "), sizeof(prompt));
  else
    strfcpy(prompt, _("Bounce tagged messages to: "), sizeof(prompt));
  
Derek Martin's avatar
Derek Martin committed
279
  rc = mutt_get_field (prompt, buf, sizeof (buf), MUTT_ALIAS);
Thomas Roessler's avatar
Thomas Roessler committed
280 281 282
  if (rc || !buf[0])
    return;

283
  if (!(adr = mutt_parse_adrlist (adr, buf)))
Thomas Roessler's avatar
Thomas Roessler committed
284
  {
285
    mutt_error _("Error parsing address!");
Thomas Roessler's avatar
Thomas Roessler committed
286 287 288 289 290
    return;
  }

  adr = mutt_expand_aliases (adr);

291
  if (mutt_addrlist_to_intl (adr, &err) < 0)
292 293 294 295 296 297 298
  {
    mutt_error (_("Bad IDN: '%s'"), err);
    FREE (&err);
    rfc822_free_address (&adr);
    return;
  }

Thomas Roessler's avatar
Thomas Roessler committed
299
  buf[0] = 0;
300
  rfc822_write_address (buf, sizeof (buf), adr, 1);
Thomas Roessler's avatar
Thomas Roessler committed
301

302
#define extra_space (15 + 7 + 2)
303
  snprintf (scratch, sizeof (scratch),
304
           (h ? _("Bounce message to %s") : _("Bounce messages to %s")), buf);
305

306
  if (mutt_strwidth (prompt) > MuttMessageWindow->cols - extra_space)
307 308
  {
    mutt_format_string (prompt, sizeof (prompt),
309
			0, MuttMessageWindow->cols-extra_space, FMT_LEFT, 0,
310
			scratch, sizeof (scratch), 0);
311
    safe_strcat (prompt, sizeof (prompt), "...?");
312 313
  }
  else
314
    snprintf (prompt, sizeof (prompt), "%s?", scratch);
315

Derek Martin's avatar
Derek Martin committed
316
  if (query_quadoption (OPT_BOUNCE, prompt) != MUTT_YES)
Thomas Roessler's avatar
Thomas Roessler committed
317 318
  {
    rfc822_free_address (&adr);
319
    mutt_window_clearline (MuttMessageWindow, 0);
320
    mutt_message (h ? _("Message not bounced.") : _("Messages not bounced."));
Thomas Roessler's avatar
Thomas Roessler committed
321 322 323
    return;
  }

324
  mutt_window_clearline (MuttMessageWindow, 0);
325
  
Paul Walker's avatar
Paul Walker committed
326
  rc = mutt_bounce_message (NULL, h, adr);
Thomas Roessler's avatar
Thomas Roessler committed
327
  rfc822_free_address (&adr);
Paul Walker's avatar
Paul Walker committed
328 329 330
  /* If no error, or background, display message. */
  if ((rc == 0) || (rc == S_BKG))
    mutt_message (h ? _("Message bounced.") : _("Messages bounced."));
Thomas Roessler's avatar
Thomas Roessler committed
331 332
}

333
static void pipe_set_flags (int decode, int print, int *cmflags, int *chflags)
Thomas Roessler's avatar
Thomas Roessler committed
334
{
335
  if (decode)
336
  {
Derek Martin's avatar
Derek Martin committed
337
    *cmflags |= MUTT_CM_DECODE | MUTT_CM_CHARCONV;
338
    *chflags |= CH_DECODE | CH_REORDER;
339 340 341
    
    if (option (OPTWEED))
    {
342
      *chflags |= CH_WEED;
Derek Martin's avatar
Derek Martin committed
343
      *cmflags |= MUTT_CM_WEED;
344 345
    }
  }
346 347
  
  if (print)
Derek Martin's avatar
Derek Martin committed
348
    *cmflags |= MUTT_CM_PRINTING;
349
  
350 351
}

352
static void pipe_msg (HEADER *h, FILE *fp, int decode, int print)
353 354 355 356
{
  int cmflags = 0;
  int chflags = CH_FROM;
  
357
  pipe_set_flags (decode, print, &cmflags, &chflags);
358

359 360 361 362 363 364
  if (WithCrypto && decode && h->security & ENCRYPT)
  {
    if(!crypt_valid_passphrase(h->security))
      return;
    endwin ();
  }
365

366
  if (decode)
Thomas Roessler's avatar
Thomas Roessler committed
367
    mutt_parse_mime_message (Context, h);
368

369
  mutt_copy_message (fp, Context, h, cmflags, chflags);
Thomas Roessler's avatar
Thomas Roessler committed
370 371 372
}


373
/* the following code is shared between printing and piping */
Thomas Roessler's avatar
Thomas Roessler committed
374

375
static int _mutt_pipe_message (HEADER *h, char *cmd,
376 377 378 379
			       int decode,
			       int print,
			       int split,
			       char *sep)
380 381 382 383 384 385
{
  
  int i, rc = 0;
  pid_t thepid;
  FILE *fpout;
  
386 387 388 389 390 391
/*   mutt_endwin (NULL); 

     is this really needed here ? 
     it makes the screen flicker on pgp and s/mime messages,
     before asking for a passphrase...
                                     Oliver Ehli */
Thomas Roessler's avatar
Thomas Roessler committed
392 393 394
  if (h)
  {

Derek Martin's avatar
Derek Martin committed
395
    mutt_message_hook (Context, h, MUTT_MESSAGEHOOK);
Thomas Roessler's avatar
Thomas Roessler committed
396

397
    if (WithCrypto && decode)
Thomas Roessler's avatar
Thomas Roessler committed
398 399
    {
      mutt_parse_mime_message (Context, h);
400
      if(h->security & ENCRYPT && !crypt_valid_passphrase(h->security))
Thomas Roessler's avatar
Thomas Roessler committed
401 402
	return 1;
    }
403
    mutt_endwin (NULL);
Thomas Roessler's avatar
Thomas Roessler committed
404

405 406 407 408 409
    if ((thepid = mutt_create_filter (cmd, &fpout, NULL, NULL)) < 0)
    {
      mutt_perror _("Can't create filter process");
      return 1;
    }
410 411

    set_option (OPTKEEPQUIET);
412
    pipe_msg (h, fpout, decode, print);
413
    safe_fclose (&fpout);
Thomas Roessler's avatar
Thomas Roessler committed
414
    rc = mutt_wait_filter (thepid);
415
    unset_option (OPTKEEPQUIET);
Thomas Roessler's avatar
Thomas Roessler committed
416 417 418 419
  }
  else
  { /* handle tagged messages */

420
    if (WithCrypto && decode)
Thomas Roessler's avatar
Thomas Roessler committed
421 422 423 424
    {
      for (i = 0; i < Context->vcount; i++)
	if(Context->hdrs[Context->v2r[i]]->tagged)
	{
Derek Martin's avatar
Derek Martin committed
425
	  mutt_message_hook (Context, Context->hdrs[Context->v2r[i]], MUTT_MESSAGEHOOK);
Thomas Roessler's avatar
Thomas Roessler committed
426
	  mutt_parse_mime_message(Context, Context->hdrs[Context->v2r[i]]);
427 428
	  if (Context->hdrs[Context->v2r[i]]->security & ENCRYPT &&
	      !crypt_valid_passphrase(Context->hdrs[Context->v2r[i]]->security))
Thomas Roessler's avatar
Thomas Roessler committed
429 430 431 432
	    return 1;
	}
    }
    
433
    if (split)
Thomas Roessler's avatar
Thomas Roessler committed
434 435 436 437 438
    {
      for (i = 0; i < Context->vcount; i++)
      {
        if (Context->hdrs[Context->v2r[i]]->tagged)
        {
Derek Martin's avatar
Derek Martin committed
439
	  mutt_message_hook (Context, Context->hdrs[Context->v2r[i]], MUTT_MESSAGEHOOK);
440
	  mutt_endwin (NULL);
441 442 443 444 445
	  if ((thepid = mutt_create_filter (cmd, &fpout, NULL, NULL)) < 0)
	  {
	    mutt_perror _("Can't create filter process");
	    return 1;
	  }
446
          set_option (OPTKEEPQUIET);
447
          pipe_msg (Context->hdrs[Context->v2r[i]], fpout, decode, print);
Thomas Roessler's avatar
Thomas Roessler committed
448
          /* add the message separator */
449
          if (sep)  fputs (sep, fpout);
450
	  safe_fclose (&fpout);
Thomas Roessler's avatar
Thomas Roessler committed
451 452
	  if (mutt_wait_filter (thepid) != 0)
	    rc = 1;
453
          unset_option (OPTKEEPQUIET);
Thomas Roessler's avatar
Thomas Roessler committed
454 455 456 457 458
        }
      }
    }
    else
    {
459
      mutt_endwin (NULL);
460 461 462 463 464
      if ((thepid = mutt_create_filter (cmd, &fpout, NULL, NULL)) < 0)
      {
	mutt_perror _("Can't create filter process");
	return 1;
      }
465
      set_option (OPTKEEPQUIET);
Thomas Roessler's avatar
Thomas Roessler committed
466 467 468 469
      for (i = 0; i < Context->vcount; i++)
      {
        if (Context->hdrs[Context->v2r[i]]->tagged)
        {
Derek Martin's avatar
Derek Martin committed
470
	  mutt_message_hook (Context, Context->hdrs[Context->v2r[i]], MUTT_MESSAGEHOOK);
471
          pipe_msg (Context->hdrs[Context->v2r[i]], fpout, decode, print);
Thomas Roessler's avatar
Thomas Roessler committed
472
          /* add the message separator */
473
          if (sep) fputs (sep, fpout);
Thomas Roessler's avatar
Thomas Roessler committed
474 475
        }
      }
476
      safe_fclose (&fpout);
Thomas Roessler's avatar
Thomas Roessler committed
477 478
      if (mutt_wait_filter (thepid) != 0)
	rc = 1;
479
      unset_option (OPTKEEPQUIET);
Thomas Roessler's avatar
Thomas Roessler committed
480 481 482 483 484
    }
  }

  if (rc || option (OPTWAITKEY))
    mutt_any_key_to_continue (NULL);
485 486 487 488 489 490 491 492
  return rc;
}

void mutt_pipe_message (HEADER *h)
{
  char buffer[LONG_STRING];

  buffer[0] = 0;
Derek Martin's avatar
Derek Martin committed
493
  if (mutt_get_field (_("Pipe to command: "), buffer, sizeof (buffer), MUTT_CMD)
494 495 496 497 498 499
      != 0 || !buffer[0])
    return;

  mutt_expand_path (buffer, sizeof (buffer));
  _mutt_pipe_message (h, buffer,
		      option (OPTPIPEDECODE),
500
		      0, 
501 502
		      option (OPTPIPESPLIT),
		      PipeSep);
Thomas Roessler's avatar
Thomas Roessler committed
503 504
}

505 506 507 508 509 510 511 512 513 514 515
void mutt_print_message (HEADER *h)
{

  if (quadoption (OPT_PRINT) && (!PrintCmd || !*PrintCmd))
  {
    mutt_message (_("No printing command has been defined."));
    return;
  }
  
  if (query_quadoption (OPT_PRINT,
			h ? _("Print message?") : _("Print tagged messages?"))
Derek Martin's avatar
Derek Martin committed
516
		  	!= MUTT_YES)
517 518 519 520
    return;

  if (_mutt_pipe_message (h, PrintCmd,
			  option (OPTPRINTDECODE),
521
			  1,
522 523 524 525 526 527 528 529 530
			  option (OPTPRINTSPLIT),
			  "\f") == 0)
    mutt_message (h ? _("Message printed") : _("Messages printed"));
  else
    mutt_message (h ? _("Message could not be printed") :
		  _("Messages could not be printed"));
}


Thomas Roessler's avatar
Thomas Roessler committed
531 532 533 534
int mutt_select_sort (int reverse)
{
  int method = Sort; /* save the current method in case of abort */

535
  switch (mutt_multi_choice (reverse ?
536
       /* L10N: The following three are the sort/reverse sort prompts.
537 538 539
        * Letters must match the order of the characters in the third
        * string.  Note that mutt now supports multiline prompts, so
        * it's okay for the translation to take up to three lines.
540
        */
541 542
	_("Rev-Sort Date/Frm/Recv/Subj/tO/Thread/Unsort/siZe/sCore/sPam/Label?: ") :
	_("Sort Date/Frm/Recv/Subj/tO/Thread/Unsort/siZe/sCore/sPam/Label?: "),
543
	_("dfrsotuzcpl")))
Thomas Roessler's avatar
Thomas Roessler committed
544
  {
545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582
  case -1: /* abort - don't resort */
    return -1;

  case 1: /* (d)ate */
    Sort = SORT_DATE;
    break;

  case 2: /* (f)rm */
    Sort = SORT_FROM;
    break;
  
  case 3: /* (r)ecv */
    Sort = SORT_RECEIVED;
    break;
  
  case 4: /* (s)ubj */
    Sort = SORT_SUBJECT;
    break;
  
  case 5: /* t(o) */
    Sort = SORT_TO;
    break;
  
  case 6: /* (t)hread */
    Sort = SORT_THREADS;
    break;
  
  case 7: /* (u)nsort */
    Sort = SORT_ORDER;
    break;
  
  case 8: /* si(z)e */
    Sort = SORT_SIZE;
    break;
  
  case 9: /* s(c)ore */ 
    Sort = SORT_SCORE;
    break;
583 584 585 586

  case 10: /* s(p)am */
    Sort = SORT_SPAM;
    break;
587 588 589 590

  case 11: /* (l)abel */
    Sort = SORT_LABEL;
    break;
Thomas Roessler's avatar
Thomas Roessler committed
591 592 593 594 595 596 597 598 599 600 601 602 603
  }
  if (reverse)
    Sort |= SORT_REVERSE;

  return (Sort != method ? 0 : -1); /* no need to resort if it's the same */
}

/* invoke a command in a subshell */
void mutt_shell_escape (void)
{
  char buf[LONG_STRING];

  buf[0] = 0;
Derek Martin's avatar
Derek Martin committed
604
  if (mutt_get_field (_("Shell command: "), buf, sizeof (buf), MUTT_CMD) == 0)
Thomas Roessler's avatar
Thomas Roessler committed
605
  {
Thomas Roessler's avatar
Thomas Roessler committed
606
    if (!buf[0] && Shell)
Thomas Roessler's avatar
Thomas Roessler committed
607
      strfcpy (buf, Shell, sizeof (buf));
Thomas Roessler's avatar
Thomas Roessler committed
608 609
    if(buf[0])
    {
610
      mutt_window_clearline (MuttMessageWindow, 0);
611
      mutt_endwin (NULL);
Thomas Roessler's avatar
Thomas Roessler committed
612 613 614 615
      fflush (stdout);
      if (mutt_system (buf) != 0 || option (OPTWAITKEY))
	mutt_any_key_to_continue (NULL);
    }
Thomas Roessler's avatar
Thomas Roessler committed
616 617 618 619 620 621 622
  }
}

/* enter a mutt command */
void mutt_enter_command (void)
{
  BUFFER err, token;
623
  char buffer[LONG_STRING];
Thomas Roessler's avatar
Thomas Roessler committed
624 625 626
  int r;

  buffer[0] = 0;
Derek Martin's avatar
Derek Martin committed
627
  if (mutt_get_field (":", buffer, sizeof (buffer), MUTT_COMMAND) != 0 || !buffer[0])
Thomas Roessler's avatar
Thomas Roessler committed
628
    return;
629
  mutt_buffer_init (&err);
630 631
  err.dsize = STRING;
  err.data = safe_malloc(err.dsize);
632
  mutt_buffer_init (&token);
Thomas Roessler's avatar
Thomas Roessler committed
633 634
  r = mutt_parse_rc_line (buffer, &token, &err);
  FREE (&token.data);
635
  if (err.data[0])
Thomas Roessler's avatar
Thomas Roessler committed
636 637 638 639 640
  {
    /* since errbuf could potentially contain printf() sequences in it,
       we must call mutt_error() in this fashion so that vsprintf()
       doesn't expect more arguments that we passed */
    if (r == 0)
641
      mutt_message ("%s", err.data);
Thomas Roessler's avatar
Thomas Roessler committed
642
    else
643
      mutt_error ("%s", err.data);
Thomas Roessler's avatar
Thomas Roessler committed
644
  }
645 646

  FREE (&err.data);
Thomas Roessler's avatar
Thomas Roessler committed
647 648
}

649
void mutt_display_address (ENVELOPE *env)
Thomas Roessler's avatar
Thomas Roessler committed
650
{
651
  char *pfx = NULL;
Thomas Roessler's avatar
Thomas Roessler committed
652
  char buf[SHORT_STRING];
653 654 655 656 657
  ADDRESS *adr = NULL;

  adr = mutt_get_address (env, &pfx);

  if (!adr) return;
658 659 660 661 662 663 664 665
  
  /* 
   * Note: We don't convert IDNA to local representation this time.
   * That is intentional, so the user has an opportunity to copy &
   * paste the on-the-wire form of the address to other, IDN-unable
   * software. 
   */
  
Thomas Roessler's avatar
Thomas Roessler committed
666
  buf[0] = 0;
667
  rfc822_write_address (buf, sizeof (buf), adr, 0);
668
  mutt_message ("%s: %s", pfx, buf);
Thomas Roessler's avatar
Thomas Roessler committed
669 670
}

671
static void set_copy_flags (HEADER *hdr, int decode, int decrypt, int *cmflags, int *chflags)
672 673
{
  *cmflags = 0;
674
  *chflags = CH_UPDATE_LEN;
675
  
676
  if (WithCrypto && !decode && decrypt && (hdr->security & ENCRYPT))
677
  {
678 679
    if ((WithCrypto & APPLICATION_PGP)
        && mutt_is_multipart_encrypted(hdr->content))
680
    {
681
      *chflags = CH_NONEWLINE | CH_XMIT | CH_MIME;
Derek Martin's avatar
Derek Martin committed
682
      *cmflags = MUTT_CM_DECODE_PGP;
683
    }
684 685
    else if ((WithCrypto & APPLICATION_PGP)
              && mutt_is_application_pgp (hdr->content) & ENCRYPT)
686
      decode = 1;
687 688
    else if ((WithCrypto & APPLICATION_SMIME)
             && mutt_is_application_smime(hdr->content) & ENCRYPT)
689 690
    {
      *chflags = CH_NONEWLINE | CH_XMIT | CH_MIME;
Derek Martin's avatar
Derek Martin committed
691
      *cmflags = MUTT_CM_DECODE_SMIME;
692 693
    }
  }
694

695
  if (decode)
696
  {
697
    *chflags = CH_XMIT | CH_MIME | CH_TXTPLAIN;
Derek Martin's avatar
Derek Martin committed
698
    *cmflags = MUTT_CM_DECODE | MUTT_CM_CHARCONV;
699

Alain Bench's avatar
Alain Bench committed
700 701 702
    if (!decrypt)	/* If decode doesn't kick in for decrypt, */
    {
      *chflags |= CH_DECODE;	/* then decode RFC 2047 headers, */
703

Alain Bench's avatar
Alain Bench committed
704 705 706
      if (option (OPTWEED))
      {
	*chflags |= CH_WEED;	/* and respect $weed. */
Derek Martin's avatar
Derek Martin committed
707
	*cmflags |= MUTT_CM_WEED;
Alain Bench's avatar
Alain Bench committed
708 709
      }
    }
710
  }
711 712
}

713
int _mutt_save_message (HEADER *h, CONTEXT *ctx, int delete, int decode, int decrypt)
714 715
{
  int cmflags, chflags;
716
  int rc;
717
  
718
  set_copy_flags (h, decode, decrypt, &cmflags, &chflags);
719

720
  if (decode || decrypt)
721
    mutt_parse_mime_message (Context, h);
722

723 724 725 726
  if ((rc = mutt_append_message (ctx, Context, h, cmflags, chflags)) != 0)
    return rc;

  if (delete)
727
  {
Derek Martin's avatar
Derek Martin committed
728
    mutt_set_flag (Context, h, MUTT_DELETE, 1);
729
    mutt_set_flag (Context, h, MUTT_PURGE, 1);
730
    if (option (OPTDELETEUNTAG))
Derek Martin's avatar
Derek Martin committed
731
      mutt_set_flag (Context, h, MUTT_TAG, 0);
732
  }
733 734
  
  return 0;
735 736
}

Thomas Roessler's avatar
Thomas Roessler committed
737
/* returns 0 if the copy/save was successful, or -1 on error/abort */
738
int mutt_save_message (HEADER *h, int delete, int decode, int decrypt)
Thomas Roessler's avatar
Thomas Roessler committed
739
{
740
  int i, need_buffy_cleanup;
741
  int need_passphrase = 0, app=0;
Thomas Roessler's avatar
Thomas Roessler committed
742 743 744 745
  char prompt[SHORT_STRING], buf[_POSIX_PATH_MAX];
  CONTEXT ctx;
  struct stat st;

746 747 748 749 750 751 752
  snprintf (prompt, sizeof (prompt),
	    decode  ? (delete ? _("Decode-save%s to mailbox") :
		       _("Decode-copy%s to mailbox")) :
	    (decrypt ? (delete ? _("Decrypt-save%s to mailbox") :
			_("Decrypt-copy%s to mailbox")) :
	     (delete ? _("Save%s to mailbox") : _("Copy%s to mailbox"))),
	    h ? "" : _(" tagged"));
753
  
754

Thomas Roessler's avatar
Thomas Roessler committed
755
  if (h)
756
  {
757 758
    if (WithCrypto)
    {
759 760
      need_passphrase = h->security & ENCRYPT;
      app = h->security;
761
    }
Derek Martin's avatar
Derek Martin committed
762
    mutt_message_hook (Context, h, MUTT_MESSAGEHOOK);
Thomas Roessler's avatar
Thomas Roessler committed
763
    mutt_default_save (buf, sizeof (buf), h);
764
  }
Thomas Roessler's avatar
Thomas Roessler committed
765 766 767 768 769 770 771 772 773 774 775 776 777
  else
  {
    /* look for the first tagged message */

    for (i = 0; i < Context->vcount; i++)
    {
      if (Context->hdrs[Context->v2r[i]]->tagged)
      {
	h = Context->hdrs[Context->v2r[i]];
	break;
      }
    }

778

Thomas Roessler's avatar
Thomas Roessler committed
779 780
    if (h)
    {
Derek Martin's avatar
Derek Martin committed
781
      mutt_message_hook (Context, h, MUTT_MESSAGEHOOK);
Thomas Roessler's avatar
Thomas Roessler committed
782
      mutt_default_save (buf, sizeof (buf), h);
783 784 785 786 787
      if (WithCrypto)
      {
        need_passphrase = h->security & ENCRYPT;
        app = h->security;
      }
Thomas Roessler's avatar
Thomas Roessler committed
788 789 790 791
      h = NULL;
    }
  }

792
  mutt_pretty_mailbox (buf, sizeof (buf));
793
  if (mutt_enter_fname (prompt, buf, sizeof (buf), 0) == -1)
Thomas Roessler's avatar
Thomas Roessler committed
794 795 796 797
    return (-1);

  if (!buf[0])
    return (-1);
798
 
Thomas Roessler's avatar
Thomas Roessler committed
799 800 801
  /* This is an undocumented feature of ELM pointed out to me by Felix von
   * Leitner <leitner@prz.fu-berlin.de>
   */
802
  if (mutt_strcmp (buf, ".") == 0)
Thomas Roessler's avatar
Thomas Roessler committed
803 804 805 806 807 808 809
    strfcpy (buf, LastSaveFolder, sizeof (buf));
  else
    strfcpy (LastSaveFolder, buf, sizeof (LastSaveFolder));

  mutt_expand_path (buf, sizeof (buf));

  /* check to make sure that this file is really the one the user wants */
810
  if (mutt_save_confirm (buf, &st) != 0)
811
    return -1;
Thomas Roessler's avatar
Thomas Roessler committed
812

813 814
  if (WithCrypto && need_passphrase && (decode || decrypt)
      && !crypt_valid_passphrase(app))
815 816
    return -1;
  
817
  mutt_message (_("Copying to %s..."), buf);
Thomas Roessler's avatar
Thomas Roessler committed
818
  
819
#ifdef USE_IMAP
Derek Martin's avatar
Derek Martin committed
820
  if (Context->magic == MUTT_IMAP && 
821 822 823 824 825 826 827 828 829 830 831 832 833 834
      !(decode || decrypt) && mx_is_imap (buf))
  {
    switch (imap_copy_messages (Context, h, buf, delete))
    {
      /* success */
      case 0: mutt_clear_error (); return 0;
      /* non-fatal error: fall through to fetch/append */
      case 1: break;
      /* fatal error, abort */
      case -1: return -1;
    }
  }
#endif

Derek Martin's avatar
Derek Martin committed
835
  if (mx_open_mailbox (buf, MUTT_APPEND, &ctx) != NULL)
Thomas Roessler's avatar
Thomas Roessler committed
836 837
  {
    if (h)
838 839 840 841 842 843 844
    {
      if (_mutt_save_message(h, &ctx, delete, decode, decrypt) != 0)
      {
        mx_close_mailbox (&ctx, NULL);
        return -1;
      }
    }
Thomas Roessler's avatar
Thomas Roessler committed
845 846 847 848 849
    else
    {
      for (i = 0; i < Context->vcount; i++)
      {
	if (Context->hdrs[Context->v2r[i]]->tagged)
850
	{
Derek Martin's avatar
Derek Martin committed
851
	  mutt_message_hook (Context, Context->hdrs[Context->v2r[i]], MUTT_MESSAGEHOOK);
852 853 854 855 856 857
	  if (_mutt_save_message(Context->hdrs[Context->v2r[i]],
			     &ctx, delete, decode, decrypt) != 0)
          {
            mx_close_mailbox (&ctx, NULL);
            return -1;
          }
858
	}
Thomas Roessler's avatar
Thomas Roessler committed
859 860 861
      }
    }

Derek Martin's avatar
Derek Martin committed
862
    need_buffy_cleanup = (ctx.magic == MUTT_MBOX || ctx.magic == MUTT_MMDF);
Thomas Roessler's avatar
Thomas Roessler committed
863

864
    mx_close_mailbox (&ctx, NULL);
Thomas Roessler's avatar
Thomas Roessler committed
865 866

    if (need_buffy_cleanup)
867
      mutt_buffy_cleanup (buf, &st);
Thomas Roessler's avatar
Thomas Roessler committed
868 869

    mutt_clear_error ();
870
    return (0);
Thomas Roessler's avatar
Thomas Roessler committed
871
  }
872 873
  
  return -1;
Thomas Roessler's avatar
Thomas Roessler committed
874 875 876 877 878
}


void mutt_version (void)
{
879
  mutt_message ("Mutt %s (%s)", MUTT_VERSION, ReleaseDate);
Thomas Roessler's avatar
Thomas Roessler committed
880
}
881

882 883 884 885 886 887 888
/*
 * Returns:
 *   1 when a structural change is made.
 *     recvattach requires this to know when to regenerate the actx.
 *   0 otherwise.
 */
int mutt_edit_content_type (HEADER *h, BODY *b, FILE *fp)
889 890
{
  char buf[LONG_STRING];
891
  char obuf[LONG_STRING];
892 893
  char tmp[STRING];
  PARAMETER *p;
894 895 896 897 898 899

  char charset[STRING];
  char *cp;

  short charset_changed = 0;
  short type_changed = 0;
900
  short structure_changed = 0;
901
  
902 903 904
  cp = mutt_get_parameter ("charset", b->parameter);
  strfcpy (charset, NONULL (cp), sizeof (charset));

905
  snprintf (buf, sizeof (buf), "%s/%s", TYPE (b), b->subtype);
906
  strfcpy (obuf, buf, sizeof (obuf));
907 908 909 910 911 912 913 914 915 916 917 918 919 920 921
  if (b->parameter)
  {
    size_t l;
    
    for (p = b->parameter; p; p = p->next)
    {
      l = strlen (buf);

      rfc822_cat (tmp, sizeof (tmp), p->value, MimeSpecials);
      snprintf (buf + l, sizeof (buf) - l, "; %s=%s", p->attribute, tmp);
    }
  }
  
  if (mutt_get_field ("Content-Type: ", buf, sizeof (buf), 0) != 0 ||
      buf[0] == 0)
922
    return 0;
923 924 925 926 927 928
  
  /* clean up previous junk */
  mutt_free_parameter (&b->parameter);
  FREE (&b->subtype);
  
  mutt_parse_content_type (buf, b);
929

930
  
931
  snprintf (tmp, sizeof (tmp), "%s/%s", TYPE (b), NONULL (b->subtype));
932 933
  type_changed = ascii_strcasecmp (tmp, obuf);
  charset_changed = ascii_strcasecmp (charset, mutt_get_parameter ("charset", b->parameter));
934 935 936 937 938

  /* if in send mode, check for conversion - current setting is default. */

  if (!h && b->type == TYPETEXT && charset_changed)
  {
939
    int r;
940 941
    snprintf (tmp, sizeof (tmp), _("Convert to %s upon sending?"),
	      mutt_get_parameter ("charset", b->parameter));
942
    if ((r = mutt_yesorno (tmp, !b->noconv)) != -1)
Derek Martin's avatar
Derek Martin committed
943
      b->noconv = (r == MUTT_NO);
944 945 946 947
  }

  /* inform the user */
  
948
  snprintf (tmp, sizeof (tmp), "%s/%s", TYPE (b), NONULL (b->subtype));
949 950
  if (type_changed)
    mutt_message (_("Content-Type changed to %s."), tmp);
951 952 953 954
  if (b->type == TYPETEXT && charset_changed)
  {
    if (type_changed)
      mutt_sleep (1);
955 956 957
    mutt_message (_("Character set changed to %s; %s."),
		  mutt_get_parameter ("charset", b->parameter),
		  b->noconv ? _("not converting") : _("converting"));
958
  }
959

960
  b->force_charset |= charset_changed ? 1 : 0;
961

962
  if (!is_multipart (b) && b->parts)
963 964
  {
    structure_changed = 1;
965
    mutt_free_body (&b->parts);
966
  }
967 968
  if (!mutt_is_message_type (b->type, b->subtype) && b->hdr)
  {
969
    structure_changed = 1;
970 971 972 973
    b->hdr->content = NULL;
    mutt_free_header (&b->hdr);
  }

974 975 976
  if (fp && !b->parts && (is_multipart (b) || mutt_is_message_type (b->type, b->subtype)))
  {
    structure_changed = 1;
977
    mutt_parse_part (fp, b);
978
  }
979
  
980
  if (WithCrypto && h)
981 982
  {
    if (h->content == b)
983
      h->security  = 0;
984

985 986
    h->security |= crypt_query (b);
  }
987 988

  return structure_changed;
989
}
990 991 992 993 994 995 996


static int _mutt_check_traditional_pgp (HEADER *h, int *redraw)
{
  MESSAGE *msg;
  int rv = 0;
  
997 998
  h->security |= PGP_TRADITIONAL_CHECKED;
  
999 1000 1001
  mutt_parse_mime_message (Context, h);
  if ((msg = mx_open_message (Context, h->msgno)) == NULL)
    return 0;
1002
  if (crypt_pgp_check_traditional (msg->fp, h->content, 0))
1003
  {
1004
    h->security = crypt_query (h->content);
1005 1006 1007 1008
    *redraw |= REDRAW_FULL;
    rv = 1;
  }
  
1009
  h->security |= PGP_TRADITIONAL_CHECKED;
Damien R.'s avatar
Damien R. committed
1010
  mx_close_message (Context, &msg);
1011 1012 1013 1014 1015 1016 1017
  return rv;
}

int mutt_check_traditional_pgp (HEADER *h, int *redraw)
{
  int i;
  int rv = 0;
1018
  if (h && !(h->security & PGP_TRADITIONAL_CHECKED))
1019 1020 1021 1022
    rv = _mutt_check_traditional_pgp (h, redraw);
  else
  {
    for (i = 0; i < Context->vcount; i++)
Thomas Roessler's avatar
Thomas Roessler committed
1023 1024
      if (Context->hdrs[Context->v2r[i]]->tagged && 
	  !(Context->hdrs[Context->v2r[i]]->security & PGP_TRADITIONAL_CHECKED))
1025 1026 1027 1028 1029 1030
	rv = _mutt_check_traditional_pgp (Context->hdrs[Context->v2r[i]], redraw)
	  || rv;
  }
  return rv;
}

1031