b53fb3d14a0e8160864033d31917d37cfbff6c64
[apps/madmutt.git] / commands.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
4  * Copyright (C) 2000 Thomas Roessler <roessler@does-not-exist.org>
5  *
6  * This file is part of mutt-ng, see http://www.muttng.org/.
7  * It's licensed under the GNU General Public License,
8  * please see the file GPL in the top level source directory.
9  */
10
11 #if HAVE_CONFIG_H
12 # include "config.h"
13 #endif
14
15 #include <errno.h>
16 #include <unistd.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <sys/wait.h>
20 #include <sys/stat.h>
21 #include <sys/types.h>
22 #include <utime.h>
23
24 #include <lib-lib/mem.h>
25 #include <lib-lib/file.h>
26 #include <lib-lib/macros.h>
27 #include <lib-lib/ascii.h>
28 #include <lib-lib/debug.h>
29
30 #include <lib-mime/mime.h>
31
32 #include <lib-sys/exit.h>
33 #include <lib-sys/unix.h>
34
35 #include <lib-ui/curses.h>
36 #include <lib-ui/enter.h>
37 #include <lib-ui/menu.h>
38
39 #include "mutt.h"
40 #include "alias.h"
41 #include "recvattach.h"
42 #include "sort.h"
43 #include "copy.h"
44 #include "mx.h"
45 #include "pager.h"
46 #include <lib-crypt/crypt.h>
47 #include "mutt_idna.h"
48 #include "rfc1524.h"
49 #include <sys/types.h>
50 #include <sys/stat.h>
51 #include <fcntl.h>
52
53 #include <imap/imap.h>
54
55 #ifdef BUFFY_SIZE
56 #include "buffy.h"
57 #endif
58
59 /* The folder the user last saved to.  Used by ci_save_message() */
60 static char LastSaveFolder[_POSIX_PATH_MAX] = "";
61
62 int mutt_display_message (HEADER * cur)
63 {
64   char tempfile[_POSIX_PATH_MAX], buf[LONG_STRING];
65   int rc = 0, builtin = 0;
66   int cmflags = M_CM_DECODE | M_CM_DISPLAY | M_CM_CHARCONV;
67   FILE *fpout = NULL;
68   FILE *fpfilterout = NULL;
69   MESSAGE *msg = NULL;
70   pid_t filterpid = -1;
71   int res = 0;
72
73   snprintf (buf, sizeof (buf), "%s/%s", TYPE (cur->content),
74             cur->content->subtype);
75
76   mutt_parse_mime_message (Context, cur);
77   mutt_message_hook (Context, cur, M_MESSAGEHOOK);
78
79   mutt_mktemp (tempfile);
80   if ((fpout = safe_fopen (tempfile, "w")) == NULL) {
81     mutt_error _("Could not create temporary file!");
82
83     return (0);
84   }
85
86   if (DisplayFilter && *DisplayFilter) {
87     fpfilterout = fpout;
88     fpout = NULL;
89     /* mutt_endwin (NULL); */
90     filterpid = mutt_create_filter_fd (DisplayFilter, &fpout, NULL, NULL,
91                                        -1, fileno (fpfilterout), -1);
92     if (filterpid < 0) {
93       mutt_error (_("Cannot create display filter"));
94       safe_fclose (&fpfilterout);
95       unlink (tempfile);
96       return 0;
97     }
98   }
99
100   if (!Pager || m_strcmp(Pager, "builtin") == 0)
101     builtin = 1;
102   else {
103     mutt_make_string (buf, sizeof (buf), NONULL (PagerFmt), Context, cur);
104     fputs (buf, fpout);
105     fputs ("\n\n", fpout);
106   }
107
108   msg = mx_open_message (Context, cur->msgno);
109   if (msg == NULL) res = -1;
110
111   if (res != -1) {
112     /* see if crytpo is needed for this message.  if so, we should exit curses */
113     if (cur->security) {
114       if (cur->security & ENCRYPT) {
115         if (cur->security & APPLICATION_SMIME)
116           crypt_smime_getkeys (cur->env);
117         if (!crypt_valid_passphrase (cur->security))
118           return 0;
119
120         cmflags |= M_CM_VERIFY;
121       }
122       else if (cur->security & SIGN) {
123         /* find out whether or not the verify signature */
124         if (query_quadoption (OPT_VERIFYSIG, _("Verify PGP signature?")) ==
125             M_YES) {
126           cmflags |= M_CM_VERIFY;
127         }
128       }
129     }
130
131     if (cmflags & M_CM_VERIFY || cur->security & ENCRYPT) {
132       if (cur->security & APPLICATION_PGP) {
133         if (cur->env->from)
134           crypt_pgp_invoke_getkeys (cur->env->from);
135
136         crypt_invoke_message (APPLICATION_PGP);
137       }
138
139       if (cur->security & APPLICATION_SMIME)
140         crypt_invoke_message (APPLICATION_SMIME);
141     }
142
143     res = _mutt_copy_message (fpout, msg->fp, cur, cur->content, cmflags,
144                              (option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) |
145                              CH_DECODE | CH_FROM);
146     if (res == 0 && (ferror(fpout) || feof(fpout))) {
147       debug_print (1, ("_mutt_copy_message failed to detect EOF!\n"));
148       res = -1;
149     }
150
151     mx_close_message (&msg);
152   }
153
154   if ((safe_fclose (&fpout) != 0 && errno != EPIPE) || res == -1) {
155     mutt_error (_("Could not copy message"));
156     if (fpfilterout != NULL) {
157       mutt_wait_filter (filterpid);
158       safe_fclose (&fpfilterout);
159     }
160 #if 0
161     /* this is maybe just plain wrong but it makes the pager display
162      * what we have; i.e. for the crypto stuff we only get
163      * 'Could not copy message' for invalid passphrases, no PGP output
164      * not nothing; so just display what we have...
165      * - pdmef
166      */
167     mutt_unlink (tempfile);
168     return 0;
169 #endif
170   }
171
172   if (fpfilterout != NULL && mutt_wait_filter (filterpid) != 0)
173     mutt_any_key_to_continue (NULL);
174
175   safe_fclose (&fpfilterout);   /* XXX - check result? */
176
177
178   /* update crypto information for this message */
179   cur->security &= ~(GOODSIGN|BADSIGN);
180   cur->security |= crypt_query (cur->content);
181
182   /* Remove color cache for this message, in case there
183      are color patterns for both ~g and ~V */
184   cur->pair = 0;
185
186   if (builtin) {
187     pager_t info;
188
189     if ((cur->security & APPLICATION_SMIME) && (cmflags & M_CM_VERIFY)) {
190       if (cur->security & GOODSIGN) {
191         if (!crypt_smime_verify_sender (cur))
192           mutt_message (_("S/MIME signature successfully verified."));
193         else
194           mutt_error (_("S/MIME certificate owner does not match sender."));
195       }
196       else if (cur->security & PARTSIGN)
197         mutt_message (_
198                       ("Warning: Part of this message has not been signed."));
199       else if (cur->security & SIGN || cur->security & BADSIGN)
200         mutt_error (_("S/MIME signature could NOT be verified."));
201     }
202
203     if ((cur->security & APPLICATION_PGP) && (cmflags & M_CM_VERIFY)) {
204       if (cur->security & GOODSIGN)
205         mutt_message (_("PGP signature successfully verified."));
206       else if (cur->security & PARTSIGN)
207         mutt_message (_
208                       ("Warning: Part of this message has not been signed."));
209       else if (cur->security & SIGN)
210         mutt_message (_("PGP signature could NOT be verified."));
211     }
212
213     /* Invoke the builtin pager */
214     p_clear(&info, 1);
215     info.hdr = cur;
216     info.ctx = Context;
217     rc = mutt_pager (NULL, tempfile, M_PAGER_MESSAGE, &info);
218   }
219   else {
220     int r;
221
222     mutt_endwin (NULL);
223     snprintf (buf, sizeof (buf), "%s %s", NONULL (Pager), tempfile);
224     if ((r = mutt_system (buf)) == -1)
225       mutt_error (_("Error running \"%s\"!"), buf);
226     unlink (tempfile);
227     keypad (stdscr, TRUE);
228     if (r != -1)
229       mutt_set_flag (Context, cur, M_READ, 1);
230     if (r != -1 && option (OPTPROMPTAFTER)) {
231       mutt_ungetch (mutt_any_key_to_continue _("Command: "), 0);
232       rc = km_dokey (MENU_PAGER);
233     }
234     else
235       rc = 0;
236   }
237
238   return rc;
239 }
240
241 void ci_bounce_message (HEADER * h, int *redraw)
242 {
243   char prompt[SHORT_STRING];
244   char buf[HUGE_STRING] = { 0 };
245   address_t *adr = NULL;
246   const char *err = NULL;
247   int rc;
248
249   if (h)
250     m_strcpy(prompt, sizeof(prompt), _("Bounce message to: "));
251   else
252     m_strcpy(prompt, sizeof(prompt), _("Bounce tagged messages to: "));
253
254   rc = mutt_get_field (prompt, buf, sizeof (buf), M_ALIAS);
255
256   if (option (OPTNEEDREDRAW)) {
257     unset_option (OPTNEEDREDRAW);
258     *redraw = REDRAW_FULL;
259   }
260
261   if (rc || !buf[0])
262     return;
263
264   if (!(adr = rfc822_parse_adrlist (adr, buf))) {
265     mutt_error _("Error parsing address!");
266
267     return;
268   }
269
270   adr = mutt_expand_aliases (adr);
271
272   if (mutt_addrlist_to_idna (adr, &err) < 0) {
273     mutt_error (_("Bad IDN: '%s'"), err);
274     p_delete(&err);
275     address_list_wipe(&adr);
276     return;
277   }
278
279   buf[0] = 0;
280   rfc822_write_address (buf, sizeof (buf), adr, 1);
281
282 #define extra_space (15 + 7 + 2)
283   snprintf (prompt, sizeof (prompt),
284             (h ? _("Bounce message to %s") : _("Bounce messages to %s")),
285             buf);
286
287   if (mutt_strwidth (prompt) > COLS - extra_space) {
288     mutt_format_string(prompt, sizeof(prompt), 0, COLS - extra_space, 0, 0,
289                        prompt, sizeof(prompt), 0);
290     m_strcat(prompt, sizeof(prompt), "...?");
291   } else {
292     m_strcat(prompt, sizeof(prompt), "?");
293   }
294
295   if (query_quadoption (OPT_BOUNCE, prompt) != M_YES) {
296     address_list_wipe(&adr);
297     CLEARLINE (LINES - 1);
298     mutt_message (h ? _("Message not bounced.") : _("Messages not bounced."));
299     return;
300   }
301
302   CLEARLINE (LINES - 1);
303
304   rc = mutt_bounce_message (NULL, h, adr);
305   address_list_wipe(&adr);
306   /* If no error, or background, display message. */
307   if ((rc == 0) || (rc == S_BKG))
308     mutt_message (h ? _("Message bounced.") : _("Messages bounced."));
309 }
310
311 static void pipe_set_flags (int decode, int print, int *cmflags, int *chflags)
312 {
313   if (decode) {
314     *cmflags |= M_CM_DECODE | M_CM_CHARCONV;
315     *chflags |= CH_DECODE | CH_REORDER;
316
317     if (option (OPTWEED)) {
318       *chflags |= CH_WEED;
319       *cmflags |= M_CM_WEED;
320     }
321   }
322
323   if (print)
324     *cmflags |= M_CM_PRINTING;
325
326 }
327
328 static void pipe_msg (HEADER * h, FILE * fp, int decode, int print)
329 {
330   int cmflags = 0;
331   int chflags = CH_FROM;
332
333   pipe_set_flags (decode, print, &cmflags, &chflags);
334
335   if (decode && h->security & ENCRYPT) {
336     if (!crypt_valid_passphrase (h->security))
337       return;
338     endwin ();
339   }
340
341   if (decode)
342     mutt_parse_mime_message (Context, h);
343
344   mutt_copy_message (fp, Context, h, cmflags, chflags);
345 }
346
347
348 /* the following code is shared between printing and piping */
349
350 static int _mutt_pipe_message(HEADER * h, char *cmd, int decode, int print,
351                               int split, const char *sep)
352 {
353
354   int i, rc = 0;
355   pid_t thepid;
356   FILE *fpout;
357
358 /*   mutt_endwin (NULL); 
359
360      is this really needed here ? 
361      it makes the screen flicker on pgp and s/mime messages,
362      before asking for a passphrase...
363                                      Oliver Ehli */
364   if (h) {
365
366     mutt_message_hook (Context, h, M_MESSAGEHOOK);
367
368     if (decode) {
369       mutt_parse_mime_message (Context, h);
370       if (h->security & ENCRYPT && !crypt_valid_passphrase (h->security))
371         return 1;
372     }
373     mutt_endwin (NULL);
374
375     if ((thepid = mutt_create_filter (cmd, &fpout, NULL, NULL)) < 0) {
376       mutt_perror (_("Can't create filter process"));
377
378       return 1;
379     }
380
381     pipe_msg (h, fpout, decode, print);
382     fclose (fpout);
383     rc = mutt_wait_filter (thepid);
384   }
385   else {                        /* handle tagged messages */
386
387     if (decode) {
388       for (i = 0; i < Context->vcount; i++)
389         if (Context->hdrs[Context->v2r[i]]->tagged) {
390           mutt_message_hook (Context, Context->hdrs[Context->v2r[i]],
391                              M_MESSAGEHOOK);
392           mutt_parse_mime_message (Context, Context->hdrs[Context->v2r[i]]);
393           if (Context->hdrs[Context->v2r[i]]->security & ENCRYPT &&
394               !crypt_valid_passphrase (Context->hdrs[Context->v2r[i]]->
395                                        security))
396             return 1;
397         }
398     }
399
400     if (split) {
401       for (i = 0; i < Context->vcount; i++) {
402         if (Context->hdrs[Context->v2r[i]]->tagged) {
403           mutt_message_hook (Context, Context->hdrs[Context->v2r[i]],
404                              M_MESSAGEHOOK);
405           mutt_endwin (NULL);
406           if ((thepid = mutt_create_filter (cmd, &fpout, NULL, NULL)) < 0) {
407             mutt_perror (_("Can't create filter process"));
408
409             return 1;
410           }
411           pipe_msg (Context->hdrs[Context->v2r[i]], fpout, decode, print);
412           /* add the message separator */
413           if (sep)
414             fputs (sep, fpout);
415           safe_fclose (&fpout);
416           if (mutt_wait_filter (thepid) != 0)
417             rc = 1;
418         }
419       }
420     }
421     else {
422       mutt_endwin (NULL);
423       if ((thepid = mutt_create_filter (cmd, &fpout, NULL, NULL)) < 0) {
424         mutt_perror (_("Can't create filter process"));
425
426         return 1;
427       }
428       for (i = 0; i < Context->vcount; i++) {
429         if (Context->hdrs[Context->v2r[i]]->tagged) {
430           mutt_message_hook (Context, Context->hdrs[Context->v2r[i]],
431                              M_MESSAGEHOOK);
432           pipe_msg (Context->hdrs[Context->v2r[i]], fpout, decode, print);
433           /* add the message separator */
434           if (sep)
435             fputs (sep, fpout);
436         }
437       }
438       safe_fclose (&fpout);
439       if (mutt_wait_filter (thepid) != 0)
440         rc = 1;
441     }
442   }
443
444   if (rc || option (OPTWAITKEY))
445     mutt_any_key_to_continue (NULL);
446   return rc;
447 }
448
449 void mutt_pipe_message (HEADER * h)
450 {
451   char buffer[LONG_STRING];
452
453   buffer[0] = 0;
454   if (mutt_get_field (_("Pipe to command: "), buffer, sizeof (buffer), M_CMD)
455       != 0 || !buffer[0])
456     return;
457
458   mutt_expand_path (buffer, sizeof (buffer));
459   _mutt_pipe_message (h, buffer,
460                       option (OPTPIPEDECODE),
461                       0, option (OPTPIPESPLIT), PipeSep);
462 }
463
464 void mutt_print_message (HEADER * h)
465 {
466
467   if (quadoption (OPT_PRINT) && (!PrintCmd || !*PrintCmd)) {
468     mutt_message (_("No printing command has been defined."));
469     return;
470   }
471
472   if (query_quadoption (OPT_PRINT,
473                         h ? _("Print message?") : _("Print tagged messages?"))
474       != M_YES)
475     return;
476
477   if (_mutt_pipe_message (h, PrintCmd,
478                           option (OPTPRINTDECODE),
479                           1, option (OPTPRINTSPLIT), "\f") == 0)
480     mutt_message (h ? _("Message printed") : _("Messages printed"));
481   else
482     mutt_message (h ? _("Message could not be printed") :
483                   _("Messages could not be printed"));
484 }
485
486
487 int mutt_select_sort (int reverse)
488 {
489   int method = Sort;            /* save the current method in case of abort */
490
491   switch (mutt_multi_choice (reverse ?
492                              _
493                              ("Rev-Sort (d)ate/(f)rm/(r)ecv/(s)ubj/t(o)/(t)hread/(u)nsort/si(z)e/s(c)ore/s(p)am?: ")
494                              :
495                              _
496                              ("Sort (d)ate/(f)rm/(r)ecv/(s)ubj/t(o)/(t)hread/(u)nsort/si(z)e/s(c)ore/s(p)am?: "),
497                              _("dfrsotuzcp"))) {
498   case -1:                     /* abort - don't resort */
499     return -1;
500
501   case 1:                      /* (d)ate */
502     Sort = SORT_DATE;
503     break;
504
505   case 2:                      /* (f)rm */
506     Sort = SORT_FROM;
507     break;
508
509   case 3:                      /* (r)ecv */
510     Sort = SORT_RECEIVED;
511     break;
512
513   case 4:                      /* (s)ubj */
514     Sort = SORT_SUBJECT;
515     break;
516
517   case 5:                      /* t(o) */
518     Sort = SORT_TO;
519     break;
520
521   case 6:                      /* (t)hread */
522     Sort = SORT_THREADS;
523     break;
524
525   case 7:                      /* (u)nsort */
526     Sort = SORT_ORDER;
527     break;
528
529   case 8:                      /* si(z)e */
530     Sort = SORT_SIZE;
531     break;
532
533   case 9:                      /* s(c)ore */
534     Sort = SORT_SCORE;
535     break;
536
537   case 10:                     /* s(p)am */
538     Sort = SORT_SPAM;
539     break;
540   }
541   if (reverse)
542     Sort |= SORT_REVERSE;
543
544   return (Sort != method ? 0 : -1);     /* no need to resort if it's the same */
545 }
546
547 /* invoke a command in a subshell */
548 void mutt_shell_escape (void)
549 {
550   char buf[LONG_STRING];
551
552   buf[0] = 0;
553   if (mutt_get_field (_("Shell command: "), buf, sizeof (buf), M_CMD) == 0) {
554     if (!buf[0] && Shell)
555       m_strcpy(buf, sizeof(buf), Shell);
556     if (buf[0]) {
557       CLEARLINE (LINES - 1);
558       mutt_endwin (NULL);
559       fflush (stdout);
560       if (mutt_system (buf) != 0 || option (OPTWAITKEY))
561         mutt_any_key_to_continue (NULL);
562     }
563   }
564 }
565
566 /* enter a mutt command */
567 void mutt_enter_command (void)
568 {
569   BUFFER err, token;
570   char buffer[LONG_STRING], errbuf[SHORT_STRING];
571   int r;
572
573   buffer[0] = 0;
574   if (mutt_get_field (":", buffer, sizeof (buffer), M_COMMAND) != 0
575       || !buffer[0])
576     return;
577   err.data = errbuf;
578   err.dsize = sizeof (errbuf);
579   p_clear(&token, 1);
580   r = mutt_parse_rc_line (buffer, &token, &err);
581   p_delete(&token.data);
582   if (errbuf[0]) {
583     /* since errbuf could potentially contain printf() sequences in it,
584        we must call mutt_error() in this fashion so that vsprintf()
585        doesn't expect more arguments that we passed */
586     if (r == 0)
587       mutt_message ("%s", errbuf);
588     else
589       mutt_error ("%s", errbuf);
590   }
591 }
592
593 void mutt_display_address (ENVELOPE * env)
594 {
595   const char *pfx = NULL;
596   char buf[SHORT_STRING];
597   address_t *adr = NULL;
598
599   adr = mutt_get_address(env, &pfx);
600
601   if (!adr)
602     return;
603
604   /* 
605    * Note: We don't convert IDNA to local representation this time.
606    * That is intentional, so the user has an opportunity to copy &
607    * paste the on-the-wire form of the address to other, IDN-unable
608    * software. 
609    */
610
611   buf[0] = 0;
612   rfc822_write_address (buf, sizeof (buf), adr, 0);
613   mutt_message ("%s: %s", pfx, buf);
614 }
615
616 static void set_copy_flags (HEADER * hdr, int decode, int decrypt,
617                             int *cmflags, int *chflags)
618 {
619   *cmflags = 0;
620   *chflags = CH_UPDATE_LEN;
621
622   if (!decode && decrypt && (hdr->security & ENCRYPT)) {
623     if (mutt_is_multipart_encrypted (hdr->content)) {
624       *chflags = CH_NONEWLINE | CH_XMIT | CH_MIME;
625       *cmflags = M_CM_DECODE_PGP;
626     }
627     else if (mutt_is_application_pgp (hdr->content) & ENCRYPT)
628       decode = 1;
629     else if (mutt_is_application_smime (hdr->content) & ENCRYPT) {
630       *chflags = CH_NONEWLINE | CH_XMIT | CH_MIME;
631       *cmflags = M_CM_DECODE_SMIME;
632     }
633   }
634
635   if (decode) {
636     *chflags = CH_XMIT | CH_MIME | CH_TXTPLAIN;
637     *cmflags = M_CM_DECODE | M_CM_CHARCONV;
638
639     if (!decrypt) {             /* If decode doesn't kick in for decrypt, */
640       *chflags |= CH_DECODE;    /* then decode RFC 2047 headers, */
641
642       if (option (OPTWEED)) {
643         *chflags |= CH_WEED;    /* and respect $weed. */
644         *cmflags |= M_CM_WEED;
645       }
646     }
647   }
648 }
649
650 int _mutt_save_message (HEADER * h, CONTEXT * ctx, int delete, int decode,
651                          int decrypt) {
652   int cmflags, chflags;
653   int rc;
654
655   set_copy_flags (h, decode, decrypt, &cmflags, &chflags);
656
657   if (decode || decrypt)
658     mutt_parse_mime_message (Context, h);
659
660   if ((rc = mutt_append_message (ctx, Context, h, cmflags, chflags)) != 0)
661     return rc;
662
663   if (delete) {
664     mutt_set_flag (Context, h, M_DELETE, 1);
665     if (option (OPTDELETEUNTAG))
666       mutt_set_flag (Context, h, M_TAG, 0);
667     mutt_set_flag (Context, h, M_APPENDED, 1);
668   }
669   return (0);
670 }
671
672 /* returns 0 if the copy/save was successful, or -1 on error/abort */
673 int mutt_save_message (HEADER * h, int delete,
674                        int decode, int decrypt, int *redraw) {
675   int i, need_buffy_cleanup;
676   int need_passphrase = 0, app = 0;
677   char prompt[SHORT_STRING], buf[_POSIX_PATH_MAX];
678   CONTEXT ctx;
679   struct stat st;
680
681 #ifdef BUFFY_SIZE
682   BUFFY *tmp = NULL;
683 #else
684   struct utimbuf ut;
685 #endif
686
687   *redraw = 0;
688
689
690   snprintf (prompt, sizeof (prompt),
691             decode ? (delete ? _("Decode-save%s to mailbox") :
692                       _("Decode-copy%s to mailbox")) :
693             (decrypt ? (delete ? _("Decrypt-save%s to mailbox") :
694                         _("Decrypt-copy%s to mailbox")) :
695              (delete ? _("Save%s to mailbox") : _("Copy%s to mailbox"))),
696             h ? "" : _(" tagged"));
697
698
699   if (h) {
700     need_passphrase = h->security & ENCRYPT;
701     app = h->security;
702     mutt_message_hook (Context, h, M_MESSAGEHOOK);
703     mutt_default_save (buf, sizeof (buf), h);
704   }
705   else {
706     /* look for the first tagged message */
707
708     for (i = 0; i < Context->vcount; i++) {
709       if (Context->hdrs[Context->v2r[i]]->tagged) {
710         h = Context->hdrs[Context->v2r[i]];
711         break;
712       }
713     }
714
715
716     if (h) {
717       mutt_message_hook (Context, h, M_MESSAGEHOOK);
718       mutt_default_save (buf, sizeof (buf), h);
719       need_passphrase = h->security & ENCRYPT;
720       app = h->security;
721       h = NULL;
722     }
723   }
724
725   mutt_pretty_mailbox (buf);
726   if (mutt_enter_fname (prompt, buf, sizeof (buf), redraw, 0) == -1)
727     return (-1);
728
729   if (*redraw != REDRAW_FULL) {
730     if (!h)
731       *redraw = REDRAW_INDEX | REDRAW_STATUS;
732     else
733       *redraw = REDRAW_STATUS;
734   }
735
736   if (!buf[0])
737     return (-1);
738
739   /* This is an undocumented feature of ELM pointed out to me by Felix von
740    * Leitner <leitner@prz.fu-berlin.de>
741    */
742   if (m_strcmp(buf, ".") == 0)
743     m_strcpy(buf, sizeof(buf), LastSaveFolder);
744   else
745     m_strcpy(LastSaveFolder, sizeof(LastSaveFolder), buf);
746
747   mutt_expand_path (buf, sizeof (buf));
748
749   /* check to make sure that this file is really the one the user wants */
750   if (mutt_save_confirm (buf, &st) != 0)
751     return -1;
752
753   if (need_passphrase && (decode || decrypt)
754       && !crypt_valid_passphrase (app))
755     return -1;
756
757   mutt_message (_("Copying to %s..."), buf);
758
759   if (Context->magic == M_IMAP && !(decode || decrypt) && mx_get_magic (buf) == M_IMAP) {
760     switch (imap_copy_messages (Context, h, buf, delete)) {
761       /* success */
762     case 0:
763       mutt_clear_error ();
764       return 0;
765       /* non-fatal error: fall through to fetch/append */
766     case 1:
767       break;
768       /* fatal error, abort */
769     case -1:
770       return -1;
771     }
772   }
773
774   if (mx_open_mailbox (buf, M_APPEND, &ctx) != NULL) {
775     if (h) {
776       if (_mutt_save_message (h, &ctx, delete, decode, decrypt) != 0) {
777         mx_close_mailbox (&ctx, NULL);
778         return (-1);
779       }
780     } else {
781       for (i = 0; i < Context->vcount; i++) {
782         if (Context->hdrs[Context->v2r[i]]->tagged) {
783           mutt_message_hook (Context, Context->hdrs[Context->v2r[i]],
784                              M_MESSAGEHOOK);
785           if (_mutt_save_message (Context->hdrs[Context->v2r[i]], &ctx, delete,
786                                   decode, decrypt) != 0) {
787             mx_close_mailbox (&ctx, NULL);
788             return (-1);
789           }
790         }
791       }
792     }
793
794     need_buffy_cleanup = (ctx.magic == M_MBOX || ctx.magic == M_MMDF);
795
796     mx_close_mailbox (&ctx, NULL);
797
798     if (need_buffy_cleanup) {
799 #ifdef BUFFY_SIZE
800       tmp = buffy_find_mailbox (buf);
801       if (tmp && tmp->new <= 0)
802         buffy_update_mailbox (tmp);
803 #else
804       /* fix up the times so buffy won't get confused */
805       if (st.st_mtime > st.st_atime) {
806         ut.actime = st.st_atime;
807         ut.modtime = time (NULL);
808         utime (buf, &ut);
809       }
810       else
811         utime (buf, NULL);
812 #endif
813     }
814
815     mutt_clear_error ();
816     return (0);
817   }
818
819   return -1;
820 }
821
822 void mutt_version (void)
823 {
824   mutt_message (mutt_make_version (1));
825 }
826
827 void mutt_edit_content_type (HEADER * h, BODY * b, FILE * fp)
828 {
829   char buf[LONG_STRING];
830   char obuf[LONG_STRING];
831   char tmp[STRING];
832   PARAMETER *p;
833
834   char charset[STRING];
835   char *cp;
836
837   short charset_changed = 0;
838   short type_changed = 0;
839
840   cp = mutt_get_parameter ("charset", b->parameter);
841   m_strcpy(charset, sizeof(charset), NONULL(cp));
842
843   snprintf (buf, sizeof (buf), "%s/%s", TYPE (b), b->subtype);
844   m_strcpy(obuf, sizeof(obuf), buf);
845   if (b->parameter) {
846     ssize_t l;
847
848     for (p = b->parameter; p; p = p->next) {
849       l = m_strlen(buf);
850
851       rfc822_strcpy(tmp, sizeof(tmp), p->value, MimeSpecials);
852       snprintf (buf + l, sizeof (buf) - l, "; %s=%s", p->attribute, tmp);
853     }
854   }
855
856   if (mutt_get_field ("Content-Type: ", buf, sizeof (buf), 0) != 0 ||
857       buf[0] == 0)
858     return;
859
860   /* clean up previous junk */
861   parameter_list_wipe(&b->parameter);
862   p_delete(&b->subtype);
863
864   mutt_parse_content_type (buf, b);
865
866
867   snprintf (tmp, sizeof (tmp), "%s/%s", TYPE (b), NONULL (b->subtype));
868   type_changed = ascii_strcasecmp (tmp, obuf);
869   charset_changed =
870     ascii_strcasecmp (charset, mutt_get_parameter ("charset", b->parameter));
871
872   /* if in send mode, check for conversion - current setting is default. */
873
874   if (!h && b->type == TYPETEXT && charset_changed) {
875     int r;
876
877     snprintf (tmp, sizeof (tmp), _("Convert to %s upon sending?"),
878               mutt_get_parameter ("charset", b->parameter));
879     if ((r = mutt_yesorno (tmp, !b->noconv)) != -1)
880       b->noconv = (r == M_NO);
881   }
882
883   /* inform the user */
884
885   snprintf (tmp, sizeof (tmp), "%s/%s", TYPE (b), NONULL (b->subtype));
886   if (type_changed)
887     mutt_message (_("Content-Type changed to %s."), tmp);
888   if (b->type == TYPETEXT && charset_changed) {
889     if (type_changed)
890       mutt_sleep (1);
891     mutt_message (_("Character set changed to %s; %s."),
892                   mutt_get_parameter ("charset", b->parameter),
893                   b->noconv ? _("not converting") : _("converting"));
894   }
895
896   b->force_charset |= charset_changed ? 1 : 0;
897
898   if (!is_multipart(b) && b->parts)
899     mutt_free_body (&b->parts);
900   if (!mutt_is_message_type (b->type, b->subtype) && b->hdr) {
901     b->hdr->content = NULL;
902     header_delete(&b->hdr);
903   }
904
905   if (fp && (is_multipart(b) || mutt_is_message_type(b->type, b->subtype)))
906     mutt_parse_part (fp, b);
907
908   if (h) {
909     if (h->content == b)
910       h->security = 0;
911
912     h->security |= crypt_query (b);
913   }
914 }
915
916
917 static int _mutt_check_traditional_pgp (HEADER * h, int *redraw)
918 {
919   MESSAGE *msg;
920   int rv = 0;
921
922   h->security |= PGP_TRADITIONAL_CHECKED;
923
924   mutt_parse_mime_message (Context, h);
925   if ((msg = mx_open_message (Context, h->msgno)) == NULL)
926     return 0;
927   if (crypt_pgp_check_traditional (msg->fp, h->content, 0)) {
928     h->security = crypt_query (h->content);
929     *redraw |= REDRAW_FULL;
930     rv = 1;
931   }
932
933   h->security |= PGP_TRADITIONAL_CHECKED;
934   mx_close_message (&msg);
935   return rv;
936 }
937
938 int mutt_check_traditional_pgp (HEADER * h, int *redraw)
939 {
940   int i;
941   int rv = 0;
942
943   if (h && !(h->security & PGP_TRADITIONAL_CHECKED))
944     rv = _mutt_check_traditional_pgp (h, redraw);
945   else {
946     for (i = 0; i < Context->vcount; i++)
947       if (Context->hdrs[Context->v2r[i]]->tagged &&
948           !(Context->hdrs[Context->v2r[i]]->
949             security & PGP_TRADITIONAL_CHECKED))
950         rv =
951           _mutt_check_traditional_pgp (Context->hdrs[Context->v2r[i]], redraw)
952           || rv;
953   }
954   return rv;
955 }