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