]> granicus.if.org Git - neomutt/blob - postpone.c
Convert mutt_attach_reply() to use buffer pool
[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       mailbox_free(&m_post);
180       PostCount = 0;
181     }
182     else
183       PostCount = ctx->mailbox->msg_count;
184     mx_fastclose_mailbox(m_post);
185     ctx_free(&ctx);
186 #ifdef USE_NNTP
187     if (optnews)
188       OptNews = true;
189 #endif
190   }
191
192   return PostCount;
193 }
194
195 /**
196  * mutt_update_num_postponed - Force the update of the number of postponed messages
197  */
198 void mutt_update_num_postponed(void)
199 {
200   UpdateNumPostponed = true;
201 }
202
203 /**
204  * post_make_entry - Format a menu item for the email list - Implements Menu::menu_make_entry()
205  */
206 static void post_make_entry(char *buf, size_t buflen, struct Menu *menu, int line)
207 {
208   struct Context *ctx = menu->data;
209
210   mutt_make_string_flags(buf, buflen, menu->indexwin->cols,
211                          NONULL(C_IndexFormat), ctx, ctx->mailbox,
212                          ctx->mailbox->emails[line], MUTT_FORMAT_ARROWCURSOR);
213 }
214
215 /**
216  * select_msg - Create a Menu to select a postponed message
217  * @retval ptr Email
218  */
219 static struct Email *select_msg(struct Context *ctx)
220 {
221   int r = -1;
222   bool done = false;
223   char helpstr[1024];
224
225   struct Menu *menu = mutt_menu_new(MENU_POSTPONE);
226   menu->menu_make_entry = post_make_entry;
227   menu->max = ctx->mailbox->msg_count;
228   menu->title = _("Postponed Messages");
229   menu->data = ctx;
230   menu->help = mutt_compile_help(helpstr, sizeof(helpstr), MENU_POSTPONE, PostponeHelp);
231   mutt_menu_push_current(menu);
232
233   /* The postponed mailbox is setup to have sorting disabled, but the global
234    * C_Sort variable may indicate something different.   Sorting has to be
235    * disabled while the postpone menu is being displayed. */
236   const short orig_sort = C_Sort;
237   C_Sort = SORT_ORDER;
238
239   while (!done)
240   {
241     const int op = mutt_menu_loop(menu);
242     switch (op)
243     {
244       case OP_DELETE:
245       case OP_UNDELETE:
246         /* should deleted draft messages be saved in the trash folder? */
247         mutt_set_flag(ctx->mailbox, ctx->mailbox->emails[menu->current],
248                       MUTT_DELETE, (op == OP_DELETE));
249         PostCount = ctx->mailbox->msg_count - ctx->mailbox->msg_deleted;
250         if (C_Resolve && (menu->current < menu->max - 1))
251         {
252           menu->oldcurrent = menu->current;
253           menu->current++;
254           if (menu->current >= menu->top + menu->pagelen)
255           {
256             menu->top = menu->current;
257             menu->redraw |= REDRAW_INDEX | REDRAW_STATUS;
258           }
259           else
260             menu->redraw |= REDRAW_MOTION_RESYNC;
261         }
262         else
263           menu->redraw |= REDRAW_CURRENT;
264         break;
265
266       case OP_GENERIC_SELECT_ENTRY:
267         r = menu->current;
268         done = true;
269         break;
270
271       case OP_EXIT:
272         done = true;
273         break;
274     }
275   }
276
277   C_Sort = orig_sort;
278   mutt_menu_pop_current(menu);
279   mutt_menu_free(&menu);
280   return (r > -1) ? ctx->mailbox->emails[r] : NULL;
281 }
282
283 /**
284  * mutt_get_postponed - Recall a postponed message
285  * @param[in]  ctx     Context info, used when recalling a message to which we reply
286  * @param[in]  hdr     envelope/attachment info for recalled message
287  * @param[out] cur     if message was a reply, 'cur' is set to the message which 'hdr' is in reply to
288  * @param[in]  fcc     fcc for the recalled message
289  * @param[in]  fcclen  max length of fcc
290  * @retval -1         Error/no messages
291  * @retval 0          Normal exit
292  * @retval #SEND_REPLY Recalled message is a reply
293  */
294 int mutt_get_postponed(struct Context *ctx, struct Email *hdr,
295                        struct Email **cur, char *fcc, size_t fcclen)
296 {
297   if (!C_Postponed)
298     return -1;
299
300   struct Email *e = NULL;
301   int rc = SEND_POSTPONED;
302   const char *p = NULL;
303   struct Context *ctx_post = NULL;
304
305   struct Mailbox *m = mx_path_resolve(C_Postponed);
306   if (ctx->mailbox == m)
307     ctx_post = ctx;
308   else
309     ctx_post = mx_mbox_open(m, MUTT_NOSORT);
310
311   if (!ctx_post)
312   {
313     PostCount = 0;
314     mutt_error(_("No postponed messages"));
315     mailbox_free(&m);
316     return -1;
317   }
318
319   if (ctx_post->mailbox->msg_count == 0)
320   {
321     PostCount = 0;
322     if (ctx_post == ctx)
323       ctx_post = NULL;
324     else
325       mx_mbox_close(&ctx_post);
326     mutt_error(_("No postponed messages"));
327     return -1;
328   }
329
330   if (ctx_post->mailbox->msg_count == 1)
331   {
332     /* only one message, so just use that one. */
333     e = ctx_post->mailbox->emails[0];
334   }
335   else if (!(e = select_msg(ctx_post)))
336   {
337     if (ctx_post == ctx)
338       ctx_post = NULL;
339     else
340       mx_mbox_close(&ctx_post);
341     return -1;
342   }
343
344   if (mutt_prepare_template(NULL, ctx_post->mailbox, hdr, e, false) < 0)
345   {
346     if (ctx_post != ctx)
347     {
348       mx_fastclose_mailbox(ctx_post->mailbox);
349       FREE(&ctx_post);
350     }
351     return -1;
352   }
353
354   /* finished with this message, so delete it. */
355   mutt_set_flag(ctx_post->mailbox, e, MUTT_DELETE, true);
356   mutt_set_flag(ctx_post->mailbox, e, MUTT_PURGE, true);
357
358   /* update the count for the status display */
359   PostCount = ctx_post->mailbox->msg_count - ctx_post->mailbox->msg_deleted;
360
361   /* avoid the "purge deleted messages" prompt */
362   int opt_delete = C_Delete;
363   C_Delete = MUTT_YES;
364   if (ctx_post == ctx)
365     ctx_post = NULL;
366   else
367     mx_mbox_close(&ctx_post);
368   C_Delete = opt_delete;
369
370   struct ListNode *np = NULL, *tmp = NULL;
371   STAILQ_FOREACH_SAFE(np, &hdr->env->userhdrs, entries, tmp)
372   {
373     size_t plen = mutt_str_startswith(np->data, "X-Mutt-References:", CASE_IGNORE);
374     if (plen)
375     {
376       /* if a mailbox is currently open, look to see if the original message
377        * the user attempted to reply to is in this mailbox */
378       p = mutt_str_skip_email_wsp(np->data + plen);
379       if (!ctx->mailbox->id_hash)
380         ctx->mailbox->id_hash = mutt_make_id_hash(ctx->mailbox);
381       *cur = mutt_hash_find(ctx->mailbox->id_hash, p);
382
383       if (*cur)
384         rc |= SEND_REPLY;
385     }
386     else if ((plen = mutt_str_startswith(np->data, "X-Mutt-Fcc:", CASE_IGNORE)))
387     {
388       p = mutt_str_skip_email_wsp(np->data + plen);
389       mutt_str_strfcpy(fcc, p, fcclen);
390       mutt_pretty_mailbox(fcc, fcclen);
391
392       /* note that x-mutt-fcc was present.  we do this because we want to add a
393        * default fcc if the header was missing, but preserve the request of the
394        * user to not make a copy if the header field is present, but empty.
395        * see http://dev.mutt.org/trac/ticket/3653 */
396       rc |= SEND_POSTPONED_FCC;
397     }
398     else if (((WithCrypto & APPLICATION_PGP) != 0) &&
399              /* this is generated by old neomutt versions */
400              (mutt_str_startswith(np->data, "Pgp:", CASE_MATCH) ||
401               /* this is the new way */
402               mutt_str_startswith(np->data, "X-Mutt-PGP:", CASE_MATCH)))
403     {
404       hdr->security = mutt_parse_crypt_hdr(strchr(np->data, ':') + 1, true, APPLICATION_PGP);
405       hdr->security |= APPLICATION_PGP;
406     }
407     else if (((WithCrypto & APPLICATION_SMIME) != 0) &&
408              mutt_str_startswith(np->data, "X-Mutt-SMIME:", CASE_MATCH))
409     {
410       hdr->security = mutt_parse_crypt_hdr(strchr(np->data, ':') + 1, true, APPLICATION_SMIME);
411       hdr->security |= APPLICATION_SMIME;
412     }
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
428     else
429     {
430       // skip header removal
431       continue;
432     }
433
434     // remove the header
435     STAILQ_REMOVE(&hdr->env->userhdrs, np, ListNode, entries);
436     FREE(&np->data);
437     FREE(&np);
438   }
439
440   if (C_CryptOpportunisticEncrypt)
441     crypt_opportunistic_encrypt(hdr);
442
443   return rc;
444 }
445
446 /**
447  * mutt_parse_crypt_hdr - Parse a crypto header string
448  * @param p                Header string to parse
449  * @param set_empty_signas Allow an empty "Sign as"
450  * @param crypt_app App, e.g. #APPLICATION_PGP
451  * @retval num SecurityFlags, see #SecurityFlags
452  */
453 SecurityFlags mutt_parse_crypt_hdr(const char *p, bool set_empty_signas, SecurityFlags crypt_app)
454 {
455   char smime_cryptalg[1024] = { 0 };
456   char sign_as[1024] = { 0 };
457   char *q = NULL;
458   SecurityFlags flags = SEC_NO_FLAGS;
459
460   if (!WithCrypto)
461     return SEC_NO_FLAGS;
462
463   p = mutt_str_skip_email_wsp(p);
464   for (; p[0] != '\0'; p++)
465   {
466     switch (p[0])
467     {
468       case 'c':
469       case 'C':
470         q = smime_cryptalg;
471
472         if (p[1] == '<')
473         {
474           for (p += 2; (p[0] != '\0') && (p[0] != '>') &&
475                        (q < (smime_cryptalg + sizeof(smime_cryptalg) - 1));
476                *q++ = *p++)
477           {
478           }
479
480           if (p[0] != '>')
481           {
482             mutt_error(_("Illegal S/MIME header"));
483             return SEC_NO_FLAGS;
484           }
485         }
486
487         *q = '\0';
488         break;
489
490       case 'e':
491       case 'E':
492         flags |= SEC_ENCRYPT;
493         break;
494
495       case 'i':
496       case 'I':
497         flags |= SEC_INLINE;
498         break;
499
500       /* This used to be the micalg parameter.
501        *
502        * It's no longer needed, so we just skip the parameter in order
503        * to be able to recall old messages.  */
504       case 'm':
505       case 'M':
506         if (p[1] == '<')
507         {
508           for (p += 2; (p[0] != '\0') && (p[0] != '>'); p++)
509             ;
510           if (p[0] != '>')
511           {
512             mutt_error(_("Illegal crypto header"));
513             return SEC_NO_FLAGS;
514           }
515         }
516
517         break;
518
519       case 'o':
520       case 'O':
521         flags |= SEC_OPPENCRYPT;
522         break;
523
524       case 'a':
525       case 'A':
526 #ifdef USE_AUTOCRYPT
527         flags |= SEC_AUTOCRYPT;
528 #endif
529         break;
530
531       case 'z':
532       case 'Z':
533 #ifdef USE_AUTOCRYPT
534         flags |= SEC_AUTOCRYPT_OVERRIDE;
535 #endif
536         break;
537
538       case 's':
539       case 'S':
540         flags |= SEC_SIGN;
541         q = sign_as;
542
543         if (p[1] == '<')
544         {
545           for (p += 2;
546                (p[0] != '\0') && (*p != '>') && (q < (sign_as + sizeof(sign_as) - 1));
547                *q++ = *p++)
548           {
549           }
550
551           if (p[0] != '>')
552           {
553             mutt_error(_("Illegal crypto header"));
554             return SEC_NO_FLAGS;
555           }
556         }
557
558         q[0] = '\0';
559         break;
560
561       default:
562         mutt_error(_("Illegal crypto header"));
563         return SEC_NO_FLAGS;
564     }
565   }
566
567   /* the cryptalg field must not be empty */
568   if (((WithCrypto & APPLICATION_SMIME) != 0) && *smime_cryptalg)
569     mutt_str_replace(&C_SmimeEncryptWith, smime_cryptalg);
570
571   /* Set {Smime,Pgp}SignAs, if desired. */
572
573   if (((WithCrypto & APPLICATION_PGP) != 0) && (crypt_app == APPLICATION_PGP) &&
574       (flags & SEC_SIGN) && (set_empty_signas || *sign_as))
575   {
576     mutt_str_replace(&C_PgpSignAs, sign_as);
577   }
578
579   if (((WithCrypto & APPLICATION_SMIME) != 0) && (crypt_app == APPLICATION_SMIME) &&
580       (flags & SEC_SIGN) && (set_empty_signas || *sign_as))
581   {
582     mutt_str_replace(&C_SmimeSignAs, sign_as);
583   }
584
585   return flags;
586 }
587
588 /**
589  * mutt_prepare_template - Prepare a message template
590  * @param fp      If not NULL, file containing the template
591  * @param m       If fp is NULL, the Mailbox containing the header with the template
592  * @param e_new   The template is read into this Header
593  * @param e       Email to recall/resend
594  * @param resend  Set if resending (as opposed to recalling a postponed msg)
595  *                Resent messages enable header weeding, and also
596  *                discard any existing Message-ID and Mail-Followup-To
597  * @retval  0 Success
598  * @retval -1 Error
599  */
600 int mutt_prepare_template(FILE *fp, struct Mailbox *m, struct Email *e_new,
601                           struct Email *e, bool resend)
602 {
603   struct Message *msg = NULL;
604   struct Body *b = NULL;
605   FILE *fp_body = NULL;
606   int rc = -1;
607   struct State s = { 0 };
608   SecurityFlags sec_type;
609   struct Envelope *protected_headers = NULL;
610
611   if (!fp && !(msg = mx_msg_open(m, e->msgno)))
612     return -1;
613
614   if (!fp)
615     fp = msg->fp;
616
617   fp_body = fp;
618
619   /* parse the message header and MIME structure */
620
621   fseeko(fp, e->offset, SEEK_SET);
622   e_new->offset = e->offset;
623   /* enable header weeding for resent messages */
624   e_new->env = mutt_rfc822_read_header(fp, e_new, true, resend);
625   e_new->content->length = e->content->length;
626   mutt_parse_part(fp, e_new->content);
627
628   /* If resending a message, don't keep message_id or mail_followup_to.
629    * Otherwise, we are resuming a postponed message, and want to keep those
630    * headers if they exist.  */
631   if (resend)
632   {
633     FREE(&e_new->env->message_id);
634     FREE(&e_new->env->mail_followup_to);
635   }
636
637   /* decrypt pgp/mime encoded messages */
638
639   if (((WithCrypto & APPLICATION_PGP) != 0) &&
640       (sec_type = mutt_is_multipart_encrypted(e_new->content)))
641   {
642     e_new->security |= sec_type;
643     if (!crypt_valid_passphrase(sec_type))
644       goto bail;
645
646     mutt_message(_("Decrypting message..."));
647     if ((crypt_pgp_decrypt_mime(fp, &fp_body, e_new->content, &b) == -1) || !b)
648     {
649       goto bail;
650     }
651
652     mutt_body_free(&e_new->content);
653     e_new->content = b;
654
655     if (b->mime_headers)
656     {
657       protected_headers = b->mime_headers;
658       b->mime_headers = NULL;
659     }
660
661     mutt_clear_error();
662   }
663
664   /* remove a potential multipart/signed layer - useful when
665    * resending messages */
666   if ((WithCrypto != 0) && mutt_is_multipart_signed(e_new->content))
667   {
668     e_new->security |= SEC_SIGN;
669     if (((WithCrypto & APPLICATION_PGP) != 0) &&
670         (mutt_str_strcasecmp(
671              mutt_param_get(&e_new->content->parameter, "protocol"),
672              "application/pgp-signature") == 0))
673     {
674       e_new->security |= APPLICATION_PGP;
675     }
676     else if (WithCrypto & APPLICATION_SMIME)
677       e_new->security |= APPLICATION_SMIME;
678
679     /* destroy the signature */
680     mutt_body_free(&e_new->content->parts->next);
681     e_new->content = mutt_remove_multipart(e_new->content);
682
683     if (e_new->content->mime_headers)
684     {
685       mutt_env_free(&protected_headers);
686       protected_headers = e_new->content->mime_headers;
687       e_new->content->mime_headers = NULL;
688     }
689   }
690
691   /* We don't need no primary multipart.
692    * Note: We _do_ preserve messages!
693    *
694    * XXX - we don't handle multipart/alternative in any
695    * smart way when sending messages.  However, one may
696    * consider this a feature.  */
697   if (e_new->content->type == TYPE_MULTIPART)
698     e_new->content = mutt_remove_multipart(e_new->content);
699
700   s.fp_in = fp_body;
701
702   struct Buffer *file = mutt_buffer_pool_get();
703
704   /* create temporary files for all attachments */
705   for (b = e_new->content; b; b = b->next)
706   {
707     /* what follows is roughly a receive-mode variant of
708      * mutt_get_tmp_attachment () from muttlib.c */
709
710     mutt_buffer_reset(file);
711     if (b->filename)
712     {
713       mutt_buffer_strcpy(file, b->filename);
714       b->d_filename = mutt_str_strdup(b->filename);
715     }
716     else
717     {
718       /* avoid Content-Disposition: header with temporary filename */
719       b->use_disp = false;
720     }
721
722     /* set up state flags */
723
724     s.flags = 0;
725
726     if (b->type == TYPE_TEXT)
727     {
728       if (mutt_str_strcasecmp("yes",
729                               mutt_param_get(&b->parameter, "x-mutt-noconv")) == 0)
730       {
731         b->noconv = true;
732       }
733       else
734       {
735         s.flags |= MUTT_CHARCONV;
736         b->noconv = false;
737       }
738
739       mutt_param_delete(&b->parameter, "x-mutt-noconv");
740     }
741
742     mutt_adv_mktemp(file);
743     s.fp_out = mutt_file_fopen(mutt_b2s(file), "w");
744     if (!s.fp_out)
745       goto bail;
746
747     if (((WithCrypto & APPLICATION_PGP) != 0) &&
748         ((sec_type = mutt_is_application_pgp(b)) & (SEC_ENCRYPT | SEC_SIGN)))
749     {
750       if (sec_type & SEC_ENCRYPT)
751       {
752         if (!crypt_valid_passphrase(APPLICATION_PGP))
753           goto bail;
754         mutt_message(_("Decrypting message..."));
755       }
756
757       if (mutt_body_handler(b, &s) < 0)
758       {
759         mutt_error(_("Decryption failed"));
760         goto bail;
761       }
762
763       if ((b == e_new->content) && !protected_headers)
764       {
765         protected_headers = b->mime_headers;
766         b->mime_headers = NULL;
767       }
768
769       e_new->security |= sec_type;
770       b->type = TYPE_TEXT;
771       mutt_str_replace(&b->subtype, "plain");
772       mutt_param_delete(&b->parameter, "x-action");
773     }
774     else if (((WithCrypto & APPLICATION_SMIME) != 0) &&
775              ((sec_type = mutt_is_application_smime(b)) & (SEC_ENCRYPT | SEC_SIGN)))
776     {
777       if (sec_type & SEC_ENCRYPT)
778       {
779         if (!crypt_valid_passphrase(APPLICATION_SMIME))
780           goto bail;
781         crypt_smime_getkeys(e_new->env);
782         mutt_message(_("Decrypting message..."));
783       }
784
785       if (mutt_body_handler(b, &s) < 0)
786       {
787         mutt_error(_("Decryption failed"));
788         goto bail;
789       }
790
791       e_new->security |= sec_type;
792       b->type = TYPE_TEXT;
793       mutt_str_replace(&b->subtype, "plain");
794     }
795     else
796       mutt_decode_attachment(b, &s);
797
798     if (mutt_file_fclose(&s.fp_out) != 0)
799       goto bail;
800
801     mutt_str_replace(&b->filename, mutt_b2s(file));
802     b->unlink = true;
803
804     mutt_stamp_attachment(b);
805
806     mutt_body_free(&b->parts);
807     if (b->email)
808       b->email->content = NULL; /* avoid dangling pointer */
809   }
810
811   if (C_CryptProtectedHeadersRead && protected_headers && protected_headers->subject &&
812       (mutt_str_strcmp(e_new->env->subject, protected_headers->subject) != 0))
813   {
814     mutt_str_replace(&e_new->env->subject, protected_headers->subject);
815   }
816   mutt_env_free(&protected_headers);
817
818   /* Fix encryption flags. */
819
820   /* No inline if multipart. */
821   if ((WithCrypto != 0) && (e_new->security & SEC_INLINE) && e_new->content->next)
822     e_new->security &= ~SEC_INLINE;
823
824   /* Do we even support multiple mechanisms? */
825   e_new->security &= WithCrypto | ~(APPLICATION_PGP | APPLICATION_SMIME);
826
827   /* Theoretically, both could be set. Take the one the user wants to set by default. */
828   if ((e_new->security & APPLICATION_PGP) && (e_new->security & APPLICATION_SMIME))
829   {
830     if (C_SmimeIsDefault)
831       e_new->security &= ~APPLICATION_PGP;
832     else
833       e_new->security &= ~APPLICATION_SMIME;
834   }
835
836   mutt_rfc3676_space_unstuff(e_new);
837
838   rc = 0;
839
840 bail:
841
842   /* that's it. */
843   mutt_buffer_pool_release(&file);
844   if (fp_body != fp)
845     mutt_file_fclose(&fp_body);
846   if (msg)
847     mx_msg_close(m, &msg);
848
849   if (rc == -1)
850   {
851     mutt_env_free(&e_new->env);
852     mutt_body_free(&e_new->content);
853   }
854
855   return rc;
856 }