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