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