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