]> granicus.if.org Git - neomutt/blob - postpone.c
merge: light refactoring
[neomutt] / postpone.c
1 /**
2  * @file
3  * Save/restore and GUI list postponed emails
4  *
5  * @authors
6  * Copyright (C) 1996-2002,2012-2013 Michael R. Elkins <me@mutt.org>
7  * Copyright (C) 1999-2002,2004 Thomas Roessler <roessler@does-not-exist.org>
8  *
9  * @copyright
10  * This program is free software: you can redistribute it and/or modify it under
11  * the terms of the GNU General Public License as published by the Free Software
12  * Foundation, either version 2 of the License, or (at your option) any later
13  * version.
14  *
15  * This program is distributed in the hope that it will be useful, but WITHOUT
16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License along with
21  * this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23
24 /**
25  * @page postpone Save/restore and GUI list postponed emails
26  *
27  * Save/restore and GUI list postponed emails
28  */
29
30 #include "config.h"
31 #include <limits.h>
32 #include <stdbool.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <sys/stat.h>
36 #include <time.h>
37 #include <unistd.h>
38 #include "mutt/mutt.h"
39 #include "config/lib.h"
40 #include "email/lib.h"
41 #include "core/lib.h"
42 #include "mutt.h"
43 #include "context.h"
44 #include "format_flags.h"
45 #include "globals.h"
46 #include "handler.h"
47 #include "hdrline.h"
48 #include "keymap.h"
49 #include "mutt_logging.h"
50 #include "mutt_menu.h"
51 #include "mutt_thread.h"
52 #include "mutt_window.h"
53 #include "muttlib.h"
54 #include "mx.h"
55 #include "ncrypt/ncrypt.h"
56 #include "opcodes.h"
57 #include "options.h"
58 #include "protos.h"
59 #include "rfc3676.h"
60 #include "send.h"
61 #include "sendlib.h"
62 #include "sort.h"
63 #include "state.h"
64 #ifdef USE_IMAP
65 #include "imap/imap.h"
66 #endif
67
68 static const struct Mapping PostponeHelp[] = {
69   { N_("Exit"), OP_EXIT },
70   { N_("Del"), OP_DELETE },
71   { N_("Undel"), OP_UNDELETE },
72   { N_("Help"), OP_HELP },
73   { NULL, 0 },
74 };
75
76 static short PostCount = 0;
77 static bool UpdateNumPostponed = false;
78
79 /**
80  * mutt_num_postponed - Return the number of postponed messages
81  * @param m    currently selected mailbox
82  * @param force
83  * * false Use a cached value if costly to get a fresh count (IMAP)
84  * * true Force check
85  * @retval num Postponed messages
86  */
87 int mutt_num_postponed(struct Mailbox *m, bool force)
88 {
89   struct stat st;
90
91   static time_t LastModify = 0;
92   static char *OldPostponed = NULL;
93
94   if (UpdateNumPostponed)
95   {
96     UpdateNumPostponed = false;
97     force = true;
98   }
99
100   if (mutt_str_strcmp(C_Postponed, OldPostponed) != 0)
101   {
102     FREE(&OldPostponed);
103     OldPostponed = mutt_str_strdup(C_Postponed);
104     LastModify = 0;
105     force = true;
106   }
107
108   if (!C_Postponed)
109     return 0;
110
111   // We currently are in the C_Postponed mailbox so just pick the current status
112   if (m && (mutt_str_strcmp(C_Postponed, m->realpath) == 0))
113   {
114     PostCount = m->msg_count - m->msg_deleted;
115     return PostCount;
116   }
117
118 #ifdef USE_IMAP
119   /* LastModify is useless for IMAP */
120   if (imap_path_probe(C_Postponed, NULL) == MUTT_IMAP)
121   {
122     if (force)
123     {
124       short newpc;
125
126       newpc = imap_path_status(C_Postponed, false);
127       if (newpc >= 0)
128       {
129         PostCount = newpc;
130         mutt_debug(LL_DEBUG3, "%d postponed IMAP messages found\n", PostCount);
131       }
132       else
133         mutt_debug(LL_DEBUG3, "using old IMAP postponed count\n");
134     }
135     return PostCount;
136   }
137 #endif
138
139   if (stat(C_Postponed, &st) == -1)
140   {
141     PostCount = 0;
142     LastModify = 0;
143     return 0;
144   }
145
146   if (S_ISDIR(st.st_mode))
147   {
148     /* if we have a maildir mailbox, we need to stat the "new" dir */
149     struct Buffer *buf = mutt_buffer_pool_get();
150
151     mutt_buffer_printf(buf, "%s/new", C_Postponed);
152     if ((access(mutt_b2s(buf), F_OK) == 0) && (stat(mutt_b2s(buf), &st) == -1))
153     {
154       PostCount = 0;
155       LastModify = 0;
156       mutt_buffer_pool_release(&buf);
157       return 0;
158     }
159     mutt_buffer_pool_release(&buf);
160   }
161
162   if (LastModify < st.st_mtime)
163   {
164 #ifdef USE_NNTP
165     int optnews = OptNews;
166 #endif
167     LastModify = st.st_mtime;
168
169     if (access(C_Postponed, R_OK | F_OK) != 0)
170       return PostCount = 0;
171 #ifdef USE_NNTP
172     if (optnews)
173       OptNews = false;
174 #endif
175     struct Mailbox *m_post = mx_path_resolve(C_Postponed);
176     struct Context *ctx = mx_mbox_open(m_post, MUTT_NOSORT | MUTT_QUIET);
177     if (ctx)
178     {
179       PostCount = ctx->mailbox->msg_count;
180     }
181     else
182     {
183       mailbox_free(&m_post);
184       PostCount = 0;
185     }
186     mx_fastclose_mailbox(m_post);
187     ctx_free(&ctx);
188 #ifdef USE_NNTP
189     if (optnews)
190       OptNews = true;
191 #endif
192   }
193
194   return PostCount;
195 }
196
197 /**
198  * mutt_update_num_postponed - Force the update of the number of postponed messages
199  */
200 void mutt_update_num_postponed(void)
201 {
202   UpdateNumPostponed = true;
203 }
204
205 /**
206  * post_make_entry - Format a menu item for the email list - Implements Menu::menu_make_entry()
207  */
208 static void post_make_entry(char *buf, size_t buflen, struct Menu *menu, int line)
209 {
210   struct Context *ctx = menu->data;
211
212   mutt_make_string_flags(buf, buflen, menu->indexwin->cols,
213                          NONULL(C_IndexFormat), ctx, ctx->mailbox,
214                          ctx->mailbox->emails[line], MUTT_FORMAT_ARROWCURSOR);
215 }
216
217 /**
218  * select_msg - Create a Menu to select a postponed message
219  * @retval ptr Email
220  */
221 static struct Email *select_msg(struct Context *ctx)
222 {
223   int r = -1;
224   bool done = false;
225   char helpstr[1024];
226
227   struct Menu *menu = mutt_menu_new(MENU_POSTPONE);
228   menu->menu_make_entry = post_make_entry;
229   menu->max = ctx->mailbox->msg_count;
230   menu->title = _("Postponed Messages");
231   menu->data = ctx;
232   menu->help = mutt_compile_help(helpstr, sizeof(helpstr), MENU_POSTPONE, PostponeHelp);
233   mutt_menu_push_current(menu);
234
235   /* The postponed mailbox is setup to have sorting disabled, but the global
236    * C_Sort variable may indicate something different.   Sorting has to be
237    * disabled while the postpone menu is being displayed. */
238   const short orig_sort = C_Sort;
239   C_Sort = SORT_ORDER;
240
241   while (!done)
242   {
243     const int op = mutt_menu_loop(menu);
244     switch (op)
245     {
246       case OP_DELETE:
247       case OP_UNDELETE:
248         /* should deleted draft messages be saved in the trash folder? */
249         mutt_set_flag(ctx->mailbox, ctx->mailbox->emails[menu->current],
250                       MUTT_DELETE, (op == OP_DELETE));
251         PostCount = ctx->mailbox->msg_count - ctx->mailbox->msg_deleted;
252         if (C_Resolve && (menu->current < menu->max - 1))
253         {
254           menu->oldcurrent = menu->current;
255           menu->current++;
256           if (menu->current >= menu->top + menu->pagelen)
257           {
258             menu->top = menu->current;
259             menu->redraw |= REDRAW_INDEX | REDRAW_STATUS;
260           }
261           else
262             menu->redraw |= REDRAW_MOTION_RESYNC;
263         }
264         else
265           menu->redraw |= REDRAW_CURRENT;
266         break;
267
268       case OP_GENERIC_SELECT_ENTRY:
269         r = menu->current;
270         done = true;
271         break;
272
273       case OP_EXIT:
274         done = true;
275         break;
276     }
277   }
278
279   C_Sort = orig_sort;
280   mutt_menu_pop_current(menu);
281   mutt_menu_free(&menu);
282   return (r > -1) ? ctx->mailbox->emails[r] : NULL;
283 }
284
285 /**
286  * mutt_get_postponed - Recall a postponed message
287  * @param[in]  ctx     Context info, used when recalling a message to which we reply
288  * @param[in]  hdr     envelope/attachment info for recalled message
289  * @param[out] cur     if message was a reply, 'cur' is set to the message which 'hdr' is in reply to
290  * @param[in]  fcc     fcc for the recalled message
291  * @retval -1         Error/no messages
292  * @retval 0          Normal exit
293  * @retval #SEND_REPLY Recalled message is a reply
294  */
295 int mutt_get_postponed(struct Context *ctx, struct Email *hdr,
296                        struct Email **cur, struct Buffer *fcc)
297 {
298   if (!C_Postponed)
299     return -1;
300
301   struct Email *e = NULL;
302   int rc = SEND_POSTPONED;
303   const char *p = NULL;
304   struct Context *ctx_post = NULL;
305
306   struct Mailbox *m = mx_path_resolve(C_Postponed);
307   if (ctx->mailbox == m)
308     ctx_post = ctx;
309   else
310     ctx_post = mx_mbox_open(m, MUTT_NOSORT);
311
312   if (!ctx_post)
313   {
314     PostCount = 0;
315     mutt_error(_("No postponed messages"));
316     mailbox_free(&m);
317     return -1;
318   }
319
320   if (ctx_post->mailbox->msg_count == 0)
321   {
322     PostCount = 0;
323     if (ctx_post == ctx)
324       ctx_post = NULL;
325     else
326       mx_mbox_close(&ctx_post);
327     mutt_error(_("No postponed messages"));
328     return -1;
329   }
330
331   if (ctx_post->mailbox->msg_count == 1)
332   {
333     /* only one message, so just use that one. */
334     e = ctx_post->mailbox->emails[0];
335   }
336   else if (!(e = select_msg(ctx_post)))
337   {
338     if (ctx_post == ctx)
339       ctx_post = NULL;
340     else
341       mx_mbox_close(&ctx_post);
342     return -1;
343   }
344
345   if (mutt_prepare_template(NULL, ctx_post->mailbox, hdr, e, false) < 0)
346   {
347     if (ctx_post != ctx)
348     {
349       mx_fastclose_mailbox(ctx_post->mailbox);
350       FREE(&ctx_post);
351     }
352     return -1;
353   }
354
355   /* finished with this message, so delete it. */
356   mutt_set_flag(ctx_post->mailbox, e, MUTT_DELETE, true);
357   mutt_set_flag(ctx_post->mailbox, e, MUTT_PURGE, true);
358
359   /* update the count for the status display */
360   PostCount = ctx_post->mailbox->msg_count - ctx_post->mailbox->msg_deleted;
361
362   /* avoid the "purge deleted messages" prompt */
363   int opt_delete = C_Delete;
364   C_Delete = MUTT_YES;
365   if (ctx_post == ctx)
366     ctx_post = NULL;
367   else
368     mx_mbox_close(&ctx_post);
369   C_Delete = opt_delete;
370
371   struct ListNode *np = NULL, *tmp = NULL;
372   STAILQ_FOREACH_SAFE(np, &hdr->env->userhdrs, entries, tmp)
373   {
374     size_t plen = mutt_str_startswith(np->data, "X-Mutt-References:", CASE_IGNORE);
375     if (plen)
376     {
377       /* if a mailbox is currently open, look to see if the original message
378        * the user attempted to reply to is in this mailbox */
379       p = mutt_str_skip_email_wsp(np->data + plen);
380       if (!ctx->mailbox->id_hash)
381         ctx->mailbox->id_hash = mutt_make_id_hash(ctx->mailbox);
382       *cur = mutt_hash_find(ctx->mailbox->id_hash, p);
383
384       if (*cur)
385         rc |= SEND_REPLY;
386     }
387     else if ((plen = mutt_str_startswith(np->data, "X-Mutt-Fcc:", CASE_IGNORE)))
388     {
389       p = mutt_str_skip_email_wsp(np->data + plen);
390       mutt_buffer_strcpy(fcc, p);
391       mutt_buffer_pretty_mailbox(fcc);
392
393       /* note that x-mutt-fcc was present.  we do this because we want to add a
394        * default fcc if the header was missing, but preserve the request of the
395        * user to not make a copy if the header field is present, but empty.
396        * see http://dev.mutt.org/trac/ticket/3653 */
397       rc |= SEND_POSTPONED_FCC;
398     }
399     else if (((WithCrypto & APPLICATION_PGP) != 0) &&
400              /* this is generated by old neomutt versions */
401              (mutt_str_startswith(np->data, "Pgp:", CASE_MATCH) ||
402               /* this is the new way */
403               mutt_str_startswith(np->data, "X-Mutt-PGP:", CASE_MATCH)))
404     {
405       hdr->security = mutt_parse_crypt_hdr(strchr(np->data, ':') + 1, true, APPLICATION_PGP);
406       hdr->security |= APPLICATION_PGP;
407     }
408     else if (((WithCrypto & APPLICATION_SMIME) != 0) &&
409              mutt_str_startswith(np->data, "X-Mutt-SMIME:", CASE_MATCH))
410     {
411       hdr->security = mutt_parse_crypt_hdr(strchr(np->data, ':') + 1, true, APPLICATION_SMIME);
412       hdr->security |= APPLICATION_SMIME;
413     }
414 #ifdef MIXMASTER
415     else if (mutt_str_startswith(np->data, "X-Mutt-Mix:", CASE_MATCH))
416     {
417       mutt_list_free(&hdr->chain);
418
419       char *t = strtok(np->data + 11, " \t\n");
420       while (t)
421       {
422         mutt_list_insert_tail(&hdr->chain, mutt_str_strdup(t));
423         t = strtok(NULL, " \t\n");
424       }
425     }
426 #endif
427     else
428     {
429       // skip header removal
430       continue;
431     }
432
433     // remove the header
434     STAILQ_REMOVE(&hdr->env->userhdrs, np, ListNode, entries);
435     FREE(&np->data);
436     FREE(&np);
437   }
438
439   if (C_CryptOpportunisticEncrypt)
440     crypt_opportunistic_encrypt(hdr);
441
442   return rc;
443 }
444
445 /**
446  * mutt_parse_crypt_hdr - Parse a crypto header string
447  * @param p                Header string to parse
448  * @param set_empty_signas Allow an empty "Sign as"
449  * @param crypt_app App, e.g. #APPLICATION_PGP
450  * @retval num SecurityFlags, see #SecurityFlags
451  */
452 SecurityFlags mutt_parse_crypt_hdr(const char *p, bool set_empty_signas, SecurityFlags crypt_app)
453 {
454   char smime_cryptalg[1024] = { 0 };
455   char sign_as[1024] = { 0 };
456   char *q = NULL;
457   SecurityFlags flags = SEC_NO_FLAGS;
458
459   if (!WithCrypto)
460     return SEC_NO_FLAGS;
461
462   p = mutt_str_skip_email_wsp(p);
463   for (; p[0] != '\0'; p++)
464   {
465     switch (p[0])
466     {
467       case 'c':
468       case 'C':
469         q = smime_cryptalg;
470
471         if (p[1] == '<')
472         {
473           for (p += 2; (p[0] != '\0') && (p[0] != '>') &&
474                        (q < (smime_cryptalg + sizeof(smime_cryptalg) - 1));
475                *q++ = *p++)
476           {
477           }
478
479           if (p[0] != '>')
480           {
481             mutt_error(_("Illegal S/MIME header"));
482             return SEC_NO_FLAGS;
483           }
484         }
485
486         *q = '\0';
487         break;
488
489       case 'e':
490       case 'E':
491         flags |= SEC_ENCRYPT;
492         break;
493
494       case 'i':
495       case 'I':
496         flags |= SEC_INLINE;
497         break;
498
499       /* This used to be the micalg parameter.
500        *
501        * It's no longer needed, so we just skip the parameter in order
502        * to be able to recall old messages.  */
503       case 'm':
504       case 'M':
505         if (p[1] == '<')
506         {
507           for (p += 2; (p[0] != '\0') && (p[0] != '>'); p++)
508             ;
509           if (p[0] != '>')
510           {
511             mutt_error(_("Illegal crypto header"));
512             return SEC_NO_FLAGS;
513           }
514         }
515
516         break;
517
518       case 'o':
519       case 'O':
520         flags |= SEC_OPPENCRYPT;
521         break;
522
523       case 'a':
524       case 'A':
525 #ifdef USE_AUTOCRYPT
526         flags |= SEC_AUTOCRYPT;
527 #endif
528         break;
529
530       case 'z':
531       case 'Z':
532 #ifdef USE_AUTOCRYPT
533         flags |= SEC_AUTOCRYPT_OVERRIDE;
534 #endif
535         break;
536
537       case 's':
538       case 'S':
539         flags |= SEC_SIGN;
540         q = sign_as;
541
542         if (p[1] == '<')
543         {
544           for (p += 2;
545                (p[0] != '\0') && (*p != '>') && (q < (sign_as + sizeof(sign_as) - 1));
546                *q++ = *p++)
547           {
548           }
549
550           if (p[0] != '>')
551           {
552             mutt_error(_("Illegal crypto header"));
553             return SEC_NO_FLAGS;
554           }
555         }
556
557         q[0] = '\0';
558         break;
559
560       default:
561         mutt_error(_("Illegal crypto header"));
562         return SEC_NO_FLAGS;
563     }
564   }
565
566   /* the cryptalg field must not be empty */
567   if (((WithCrypto & APPLICATION_SMIME) != 0) && *smime_cryptalg)
568     mutt_str_replace(&C_SmimeEncryptWith, smime_cryptalg);
569
570   /* Set {Smime,Pgp}SignAs, if desired. */
571
572   if (((WithCrypto & APPLICATION_PGP) != 0) && (crypt_app == APPLICATION_PGP) &&
573       (flags & SEC_SIGN) && (set_empty_signas || *sign_as))
574   {
575     mutt_str_replace(&C_PgpSignAs, sign_as);
576   }
577
578   if (((WithCrypto & APPLICATION_SMIME) != 0) && (crypt_app == APPLICATION_SMIME) &&
579       (flags & SEC_SIGN) && (set_empty_signas || *sign_as))
580   {
581     mutt_str_replace(&C_SmimeSignAs, sign_as);
582   }
583
584   return flags;
585 }
586
587 /**
588  * mutt_prepare_template - Prepare a message template
589  * @param fp      If not NULL, file containing the template
590  * @param m       If fp is NULL, the Mailbox containing the header with the template
591  * @param e_new   The template is read into this Header
592  * @param e       Email to recall/resend
593  * @param resend  Set if resending (as opposed to recalling a postponed msg)
594  *                Resent messages enable header weeding, and also
595  *                discard any existing Message-ID and Mail-Followup-To
596  * @retval  0 Success
597  * @retval -1 Error
598  */
599 int mutt_prepare_template(FILE *fp, struct Mailbox *m, struct Email *e_new,
600                           struct Email *e, bool resend)
601 {
602   struct Message *msg = NULL;
603   struct Body *b = NULL;
604   FILE *fp_body = NULL;
605   int rc = -1;
606   struct State s = { 0 };
607   SecurityFlags sec_type;
608   struct Envelope *protected_headers = NULL;
609
610   if (!fp && !(msg = mx_msg_open(m, e->msgno)))
611     return -1;
612
613   if (!fp)
614     fp = msg->fp;
615
616   fp_body = fp;
617
618   /* parse the message header and MIME structure */
619
620   fseeko(fp, e->offset, SEEK_SET);
621   e_new->offset = e->offset;
622   /* enable header weeding for resent messages */
623   e_new->env = mutt_rfc822_read_header(fp, e_new, true, resend);
624   e_new->content->length = e->content->length;
625   mutt_parse_part(fp, e_new->content);
626
627   /* If resending a message, don't keep message_id or mail_followup_to.
628    * Otherwise, we are resuming a postponed message, and want to keep those
629    * headers if they exist.  */
630   if (resend)
631   {
632     FREE(&e_new->env->message_id);
633     FREE(&e_new->env->mail_followup_to);
634   }
635
636   /* decrypt pgp/mime encoded messages */
637
638   if (((WithCrypto & APPLICATION_PGP) != 0) &&
639       (sec_type = mutt_is_multipart_encrypted(e_new->content)))
640   {
641     e_new->security |= sec_type;
642     if (!crypt_valid_passphrase(sec_type))
643       goto bail;
644
645     mutt_message(_("Decrypting message..."));
646     if ((crypt_pgp_decrypt_mime(fp, &fp_body, e_new->content, &b) == -1) || !b)
647     {
648       goto bail;
649     }
650
651     mutt_body_free(&e_new->content);
652     e_new->content = b;
653
654     if (b->mime_headers)
655     {
656       protected_headers = b->mime_headers;
657       b->mime_headers = NULL;
658     }
659
660     mutt_clear_error();
661   }
662
663   /* remove a potential multipart/signed layer - useful when
664    * resending messages */
665   if ((WithCrypto != 0) && mutt_is_multipart_signed(e_new->content))
666   {
667     e_new->security |= SEC_SIGN;
668     if (((WithCrypto & APPLICATION_PGP) != 0) &&
669         (mutt_str_strcasecmp(
670              mutt_param_get(&e_new->content->parameter, "protocol"),
671              "application/pgp-signature") == 0))
672     {
673       e_new->security |= APPLICATION_PGP;
674     }
675     else if (WithCrypto & APPLICATION_SMIME)
676       e_new->security |= APPLICATION_SMIME;
677
678     /* destroy the signature */
679     mutt_body_free(&e_new->content->parts->next);
680     e_new->content = mutt_remove_multipart(e_new->content);
681
682     if (e_new->content->mime_headers)
683     {
684       mutt_env_free(&protected_headers);
685       protected_headers = e_new->content->mime_headers;
686       e_new->content->mime_headers = NULL;
687     }
688   }
689
690   /* We don't need no primary multipart.
691    * Note: We _do_ preserve messages!
692    *
693    * XXX - we don't handle multipart/alternative in any
694    * smart way when sending messages.  However, one may
695    * consider this a feature.  */
696   if (e_new->content->type == TYPE_MULTIPART)
697     e_new->content = mutt_remove_multipart(e_new->content);
698
699   s.fp_in = fp_body;
700
701   struct Buffer *file = mutt_buffer_pool_get();
702
703   /* create temporary files for all attachments */
704   for (b = e_new->content; b; b = b->next)
705   {
706     /* what follows is roughly a receive-mode variant of
707      * mutt_get_tmp_attachment () from muttlib.c */
708
709     mutt_buffer_reset(file);
710     if (b->filename)
711     {
712       mutt_buffer_strcpy(file, b->filename);
713       b->d_filename = mutt_str_strdup(b->filename);
714     }
715     else
716     {
717       /* avoid Content-Disposition: header with temporary filename */
718       b->use_disp = false;
719     }
720
721     /* set up state flags */
722
723     s.flags = 0;
724
725     if (b->type == TYPE_TEXT)
726     {
727       if (mutt_str_strcasecmp("yes",
728                               mutt_param_get(&b->parameter, "x-mutt-noconv")) == 0)
729       {
730         b->noconv = true;
731       }
732       else
733       {
734         s.flags |= MUTT_CHARCONV;
735         b->noconv = false;
736       }
737
738       mutt_param_delete(&b->parameter, "x-mutt-noconv");
739     }
740
741     mutt_adv_mktemp(file);
742     s.fp_out = mutt_file_fopen(mutt_b2s(file), "w");
743     if (!s.fp_out)
744       goto bail;
745
746     if (((WithCrypto & APPLICATION_PGP) != 0) &&
747         ((sec_type = mutt_is_application_pgp(b)) & (SEC_ENCRYPT | SEC_SIGN)))
748     {
749       if (sec_type & SEC_ENCRYPT)
750       {
751         if (!crypt_valid_passphrase(APPLICATION_PGP))
752           goto bail;
753         mutt_message(_("Decrypting message..."));
754       }
755
756       if (mutt_body_handler(b, &s) < 0)
757       {
758         mutt_error(_("Decryption failed"));
759         goto bail;
760       }
761
762       if ((b == e_new->content) && !protected_headers)
763       {
764         protected_headers = b->mime_headers;
765         b->mime_headers = NULL;
766       }
767
768       e_new->security |= sec_type;
769       b->type = TYPE_TEXT;
770       mutt_str_replace(&b->subtype, "plain");
771       mutt_param_delete(&b->parameter, "x-action");
772     }
773     else if (((WithCrypto & APPLICATION_SMIME) != 0) &&
774              ((sec_type = mutt_is_application_smime(b)) & (SEC_ENCRYPT | SEC_SIGN)))
775     {
776       if (sec_type & SEC_ENCRYPT)
777       {
778         if (!crypt_valid_passphrase(APPLICATION_SMIME))
779           goto bail;
780         crypt_smime_getkeys(e_new->env);
781         mutt_message(_("Decrypting message..."));
782       }
783
784       if (mutt_body_handler(b, &s) < 0)
785       {
786         mutt_error(_("Decryption failed"));
787         goto bail;
788       }
789
790       e_new->security |= sec_type;
791       b->type = TYPE_TEXT;
792       mutt_str_replace(&b->subtype, "plain");
793     }
794     else
795       mutt_decode_attachment(b, &s);
796
797     if (mutt_file_fclose(&s.fp_out) != 0)
798       goto bail;
799
800     mutt_str_replace(&b->filename, mutt_b2s(file));
801     b->unlink = true;
802
803     mutt_stamp_attachment(b);
804
805     mutt_body_free(&b->parts);
806     if (b->email)
807       b->email->content = NULL; /* avoid dangling pointer */
808   }
809
810   if (C_CryptProtectedHeadersRead && protected_headers && protected_headers->subject &&
811       (mutt_str_strcmp(e_new->env->subject, protected_headers->subject) != 0))
812   {
813     mutt_str_replace(&e_new->env->subject, protected_headers->subject);
814   }
815   mutt_env_free(&protected_headers);
816
817   /* Fix encryption flags. */
818
819   /* No inline if multipart. */
820   if ((WithCrypto != 0) && (e_new->security & SEC_INLINE) && e_new->content->next)
821     e_new->security &= ~SEC_INLINE;
822
823   /* Do we even support multiple mechanisms? */
824   e_new->security &= WithCrypto | ~(APPLICATION_PGP | APPLICATION_SMIME);
825
826   /* Theoretically, both could be set. Take the one the user wants to set by default. */
827   if ((e_new->security & APPLICATION_PGP) && (e_new->security & APPLICATION_SMIME))
828   {
829     if (C_SmimeIsDefault)
830       e_new->security &= ~APPLICATION_PGP;
831     else
832       e_new->security &= ~APPLICATION_SMIME;
833   }
834
835   mutt_rfc3676_space_unstuff(e_new);
836
837   rc = 0;
838
839 bail:
840
841   /* that's it. */
842   mutt_buffer_pool_release(&file);
843   if (fp_body != fp)
844     mutt_file_fclose(&fp_body);
845   if (msg)
846     mx_msg_close(m, &msg);
847
848   if (rc == -1)
849   {
850     mutt_env_free(&e_new->env);
851     mutt_body_free(&e_new->content);
852   }
853
854   return rc;
855 }