mutt stable branch with some hacks
1/*
2 * Copyright (C) 1996-2002,2012-2013 Michael R. Elkins <me@mutt.org>
3 * Copyright (C) 1999-2002,2004 Thomas Roessler <roessler@does-not-exist.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20#if HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "mutt.h"
25#include "mutt_menu.h"
26#include "mime.h"
27#include "mailbox.h"
28#include "mapping.h"
29#include "sort.h"
30#ifdef USE_IMAP
31#include "imap.h"
32#endif
33#include "mutt_crypt.h"
34#include "rfc3676.h"
35
36#include <ctype.h>
37#include <unistd.h>
38#include <string.h>
39#include <sys/stat.h>
40
41static const struct mapping_t PostponeHelp[] = {
42 { N_("Exit"), OP_EXIT },
43 { N_("Del"), OP_DELETE },
44 { N_("Undel"), OP_UNDELETE },
45 { N_("Help"), OP_HELP },
46 { NULL, 0 }
47};
48
49
50
51static short PostCount = 0;
52static CONTEXT *PostContext = NULL;
53static short UpdateNumPostponed = 0;
54
55/* Return the number of postponed messages.
56 * if force is 0, use a cached value if it is costly to get a fresh
57 * count (IMAP) - else check.
58 */
59int mutt_num_postponed (int force)
60{
61 struct stat st;
62 CONTEXT ctx;
63
64 static time_t LastModify = 0;
65 static char *OldPostponed = NULL;
66
67 if (UpdateNumPostponed)
68 {
69 UpdateNumPostponed = 0;
70 force = 1;
71 }
72
73 if (mutt_strcmp (Postponed, OldPostponed))
74 {
75 FREE (&OldPostponed);
76 OldPostponed = safe_strdup (Postponed);
77 LastModify = 0;
78 force = 1;
79 }
80
81 if (!Postponed)
82 return 0;
83
84#ifdef USE_IMAP
85 /* LastModify is useless for IMAP */
86 if (mx_is_imap (Postponed))
87 {
88 if (force)
89 {
90 short newpc;
91
92 newpc = imap_status (Postponed, 0);
93 if (newpc >= 0)
94 {
95 PostCount = newpc;
96 dprint (3, (debugfile, "mutt_num_postponed: %d postponed IMAP messages found.\n", PostCount));
97 }
98 else
99 dprint (3, (debugfile, "mutt_num_postponed: using old IMAP postponed count.\n"));
100 }
101 return PostCount;
102 }
103#endif
104
105 if (stat (Postponed, &st) == -1)
106 {
107 PostCount = 0;
108 LastModify = 0;
109 return (0);
110 }
111
112 if (S_ISDIR (st.st_mode))
113 {
114 /* if we have a maildir mailbox, we need to stat the "new" dir */
115
116 BUFFER *buf;
117
118 buf = mutt_buffer_pool_get ();
119 mutt_buffer_printf (buf, "%s/new", Postponed);
120 if (access (mutt_b2s (buf), F_OK) == 0 &&
121 stat (mutt_b2s (buf), &st) == -1)
122 {
123 PostCount = 0;
124 LastModify = 0;
125 mutt_buffer_pool_release (&buf);
126 return 0;
127 }
128 mutt_buffer_pool_release (&buf);
129 }
130
131 if (LastModify < st.st_mtime)
132 {
133 LastModify = st.st_mtime;
134
135 if (access (Postponed, R_OK | F_OK) != 0)
136 return (PostCount = 0);
137 if (mx_open_mailbox (Postponed, MUTT_NOSORT | MUTT_QUIET, &ctx) == NULL)
138 PostCount = 0;
139 else
140 PostCount = ctx.msgcount;
141 mx_fastclose_mailbox (&ctx);
142 }
143
144 return (PostCount);
145}
146
147void mutt_update_num_postponed (void)
148{
149 UpdateNumPostponed = 1;
150}
151
152static void post_entry (char *s, size_t slen, MUTTMENU *menu, int entry)
153{
154 CONTEXT *ctx = (CONTEXT *) menu->data;
155
156 _mutt_make_string (s, slen, NONULL (HdrFmt), ctx, ctx->hdrs[entry],
157 MUTT_FORMAT_ARROWCURSOR);
158}
159
160static HEADER *select_msg (void)
161{
162 MUTTMENU *menu;
163 int i, done=0, r=-1;
164 char helpstr[LONG_STRING];
165 short orig_sort;
166
167 menu = mutt_new_menu (MENU_POST);
168 menu->make_entry = post_entry;
169 menu->max = PostContext->msgcount;
170 menu->title = _("Postponed Messages");
171 menu->data = PostContext;
172 menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_POST, PostponeHelp);
173 mutt_push_current_menu (menu);
174
175 /* The postponed mailbox is setup to have sorting disabled, but the global
176 * Sort variable may indicate something different. Sorting has to be
177 * disabled while the postpone menu is being displayed. */
178 orig_sort = Sort;
179 Sort = SORT_ORDER;
180
181 while (!done)
182 {
183 switch (i = mutt_menuLoop (menu))
184 {
185 case OP_DELETE:
186 case OP_UNDELETE:
187 /* should deleted draft messages be saved in the trash folder? */
188 mutt_set_flag (PostContext, PostContext->hdrs[menu->current], MUTT_DELETE, (i == OP_DELETE) ? 1 : 0);
189 PostCount = PostContext->msgcount - PostContext->deleted;
190 if (option (OPTRESOLVE) && menu->current < menu->max - 1)
191 {
192 menu->oldcurrent = menu->current;
193 menu->current++;
194 if (menu->current >= menu->top + menu->pagelen)
195 {
196 menu->top = menu->current;
197 menu->redraw |= REDRAW_INDEX | REDRAW_STATUS;
198 }
199 else
200 menu->redraw |= REDRAW_MOTION_RESYNCH;
201 }
202 else
203 menu->redraw |= REDRAW_CURRENT;
204 break;
205
206 case OP_GENERIC_SELECT_ENTRY:
207 r = menu->current;
208 done = 1;
209 break;
210
211 case OP_EXIT:
212 done = 1;
213 break;
214 }
215 }
216
217 Sort = orig_sort;
218 mutt_pop_current_menu (menu);
219 mutt_menuDestroy (&menu);
220 return (r > -1 ? PostContext->hdrs[r] : NULL);
221}
222
223/* args:
224 * ctx Context info, used when recalling a message to which
225 * we reply.
226 * hdr envelope/attachment info for recalled message
227 * cur if message was a reply, `cur' is set to the message which
228 * `hdr' is in reply to
229 * fcc fcc for the recalled message
230 *
231 * return vals:
232 * -1 error/no messages
233 * 0 normal exit
234 * SENDREPLY recalled message is a reply
235 */
236int mutt_get_postponed (CONTEXT *ctx, HEADER *hdr, HEADER **cur, BUFFER *fcc)
237{
238 HEADER *h;
239 int code = SENDPOSTPONED;
240 LIST *tmp;
241 LIST *last = NULL;
242 LIST *next;
243 const char *p;
244 int opt_delete;
245 int close_rc;
246
247 if (!Postponed)
248 return (-1);
249
250 if ((PostContext = mx_open_mailbox (Postponed, MUTT_NOSORT, NULL)) == NULL)
251 {
252 PostCount = 0;
253 mutt_error _("No postponed messages.");
254 return (-1);
255 }
256
257 /* TODO:
258 * mx_open_mailbox() for IMAP leaves IMAP_REOPEN_ALLOW set. For the
259 * index this is papered-over because it calls mx_check_mailbox()
260 * every event loop (which resets that flag).
261 *
262 * For a stable-branch fix, I'm doing the same here, to prevent
263 * context changes from occuring behind the scenes and causing
264 * segvs, but probably the flag needs to be reset after downloading
265 * headers in imap_open_mailbox().
266 */
267 mx_check_mailbox (PostContext, NULL);
268
269 if (! PostContext->msgcount)
270 {
271 PostCount = 0;
272 mx_fastclose_mailbox (PostContext);
273 FREE (&PostContext);
274 mutt_error _("No postponed messages.");
275 return (-1);
276 }
277
278 if (PostContext->msgcount == 1)
279 {
280 /* only one message, so just use that one. */
281 h = PostContext->hdrs[0];
282 }
283 else if ((h = select_msg ()) == NULL)
284 {
285 /* messages might have been marked for deletion.
286 * try once more on reopen before giving up. */
287 close_rc = mx_close_mailbox (PostContext, NULL);
288 if (close_rc > 0)
289 close_rc = mx_close_mailbox (PostContext, NULL);
290 if (close_rc != 0)
291 mx_fastclose_mailbox (PostContext);
292 FREE (&PostContext);
293 return (-1);
294 }
295
296 if (mutt_prepare_template (NULL, PostContext, hdr, h, 0) < 0)
297 {
298 mx_fastclose_mailbox (PostContext);
299 FREE (&PostContext);
300 return (-1);
301 }
302
303 /* finished with this message, so delete it. */
304 mutt_set_flag (PostContext, h, MUTT_DELETE, 1);
305 mutt_set_flag (PostContext, h, MUTT_PURGE, 1);
306
307 /* update the count for the status display */
308 PostCount = PostContext->msgcount - PostContext->deleted;
309
310 /* avoid the "purge deleted messages" prompt */
311 opt_delete = quadoption (OPT_DELETE);
312 set_quadoption (OPT_DELETE, MUTT_YES);
313 close_rc = mx_close_mailbox (PostContext, NULL);
314 if (close_rc > 0)
315 close_rc = mx_close_mailbox (PostContext, NULL);
316 if (close_rc != 0)
317 mx_fastclose_mailbox (PostContext);
318 set_quadoption (OPT_DELETE, opt_delete);
319
320 FREE (&PostContext);
321
322 for (tmp = hdr->env->userhdrs; tmp; )
323 {
324 if (ascii_strncasecmp ("X-Mutt-References:", tmp->data, 18) == 0)
325 {
326 if (ctx)
327 {
328 /* if a mailbox is currently open, look to see if the original message
329 the user attempted to reply to is in this mailbox */
330 p = skip_email_wsp(tmp->data + 18);
331 if (!ctx->id_hash)
332 ctx->id_hash = mutt_make_id_hash (ctx);
333 *cur = hash_find (ctx->id_hash, p);
334 }
335
336 /* Remove the X-Mutt-References: header field. */
337 next = tmp->next;
338 if (last)
339 last->next = tmp->next;
340 else
341 hdr->env->userhdrs = tmp->next;
342 tmp->next = NULL;
343 mutt_free_list (&tmp);
344 tmp = next;
345 if (*cur)
346 code |= SENDREPLY;
347 }
348 else if (ascii_strncasecmp ("X-Mutt-Fcc:", tmp->data, 11) == 0)
349 {
350 p = skip_email_wsp(tmp->data + 11);
351 mutt_buffer_strcpy (fcc, p);
352 mutt_buffer_pretty_mailbox (fcc);
353
354 /* remove the X-Mutt-Fcc: header field */
355 next = tmp->next;
356 if (last)
357 last->next = tmp->next;
358 else
359 hdr->env->userhdrs = tmp->next;
360 tmp->next = NULL;
361 mutt_free_list (&tmp);
362 tmp = next;
363 /* note that x-mutt-fcc was present. we do this because we want to add a
364 * default fcc if the header was missing, but preserve the request of the
365 * user to not make a copy if the header field is present, but empty.
366 * see http://dev.mutt.org/trac/ticket/3653
367 */
368 code |= SENDPOSTPONEDFCC;
369 }
370 else if ((WithCrypto & APPLICATION_PGP)
371 && (mutt_strncmp ("Pgp:", tmp->data, 4) == 0 /* this is generated
372 * by old mutt versions
373 */
374 || mutt_strncmp ("X-Mutt-PGP:", tmp->data, 11) == 0))
375 {
376 hdr->security = mutt_parse_crypt_hdr (strchr (tmp->data, ':') + 1, 1,
377 APPLICATION_PGP);
378 hdr->security |= APPLICATION_PGP;
379
380 /* remove the pgp field */
381 next = tmp->next;
382 if (last)
383 last->next = tmp->next;
384 else
385 hdr->env->userhdrs = tmp->next;
386 tmp->next = NULL;
387 mutt_free_list (&tmp);
388 tmp = next;
389 }
390 else if ((WithCrypto & APPLICATION_SMIME)
391 && mutt_strncmp ("X-Mutt-SMIME:", tmp->data, 13) == 0)
392 {
393 hdr->security = mutt_parse_crypt_hdr (strchr (tmp->data, ':') + 1, 1,
394 APPLICATION_SMIME);
395 hdr->security |= APPLICATION_SMIME;
396
397 /* remove the smime field */
398 next = tmp->next;
399 if (last)
400 last->next = tmp->next;
401 else
402 hdr->env->userhdrs = tmp->next;
403 tmp->next = NULL;
404 mutt_free_list (&tmp);
405 tmp = next;
406 }
407
408#ifdef MIXMASTER
409 else if (mutt_strncmp ("X-Mutt-Mix:", tmp->data, 11) == 0)
410 {
411 char *t;
412 mutt_free_list (&hdr->chain);
413
414 t = strtok (tmp->data + 11, " \t\n");
415 while (t)
416 {
417 hdr->chain = mutt_add_list (hdr->chain, t);
418 t = strtok (NULL, " \t\n");
419 }
420
421 next = tmp->next;
422 if (last)
423 last->next = tmp->next;
424 else
425 hdr->env->userhdrs = tmp->next;
426 tmp->next = NULL;
427 mutt_free_list (&tmp);
428 tmp = next;
429 }
430#endif
431
432 else
433 {
434 last = tmp;
435 tmp = tmp->next;
436 }
437 }
438
439 if (option (OPTCRYPTOPPORTUNISTICENCRYPT))
440 crypt_opportunistic_encrypt (hdr);
441
442 return (code);
443}
444
445
446
447int mutt_parse_crypt_hdr (const char *p, int set_empty_signas, int crypt_app)
448{
449 char smime_cryptalg[LONG_STRING] = "\0";
450 char sign_as[LONG_STRING] = "\0", *q;
451 int flags = 0;
452
453 if (!WithCrypto)
454 return 0;
455
456 p = skip_email_wsp(p);
457 for (; *p; p++)
458 {
459
460 switch (*p)
461 {
462 case 'e':
463 case 'E':
464 flags |= ENCRYPT;
465 break;
466
467 case 'o':
468 case 'O':
469 flags |= OPPENCRYPT;
470 break;
471
472 case 'a':
473 case 'A':
474#ifdef USE_AUTOCRYPT
475 flags |= AUTOCRYPT;
476#endif
477 break;
478
479 case 'z':
480 case 'Z':
481#ifdef USE_AUTOCRYPT
482 flags |= AUTOCRYPT_OVERRIDE;
483#endif
484 break;
485
486 case 's':
487 case 'S':
488 flags |= SIGN;
489 q = sign_as;
490
491 if (*(p+1) == '<')
492 {
493 for (p += 2;
494 *p && *p != '>' && q < sign_as + sizeof (sign_as) - 1;
495 *q++ = *p++)
496 ;
497
498 if (*p!='>')
499 {
500 mutt_error _("Illegal crypto header");
501 return 0;
502 }
503 }
504
505 *q = '\0';
506 break;
507
508 /* This used to be the micalg parameter.
509 *
510 * It's no longer needed, so we just skip the parameter in order
511 * to be able to recall old messages.
512 */
513 case 'm':
514 case 'M':
515 if (*(p+1) == '<')
516 {
517 for (p += 2; *p && *p != '>'; p++)
518 ;
519 if (*p != '>')
520 {
521 mutt_error _("Illegal crypto header");
522 return 0;
523 }
524 }
525
526 break;
527
528
529 case 'c':
530 case 'C':
531 q = smime_cryptalg;
532
533 if (*(p+1) == '<')
534 {
535 for (p += 2; *p && *p != '>' && q < smime_cryptalg + sizeof(smime_cryptalg) - 1;
536 *q++ = *p++)
537 ;
538
539 if (*p != '>')
540 {
541 mutt_error _("Illegal S/MIME header");
542 return 0;
543 }
544 }
545
546 *q = '\0';
547 break;
548
549 case 'i':
550 case 'I':
551 flags |= INLINE;
552 break;
553
554 default:
555 mutt_error _("Illegal crypto header");
556 return 0;
557 }
558
559 }
560
561 /* the cryptalg field must not be empty */
562 if ((WithCrypto & APPLICATION_SMIME) && *smime_cryptalg)
563 mutt_str_replace (&SmimeCryptAlg, smime_cryptalg);
564
565 /* Set {Smime,Pgp}SignAs, if desired. */
566
567 if ((WithCrypto & APPLICATION_PGP) && (crypt_app == APPLICATION_PGP)
568 && (flags & SIGN)
569 && (set_empty_signas || *sign_as))
570 mutt_str_replace (&PgpSignAs, sign_as);
571
572 if ((WithCrypto & APPLICATION_SMIME) && (crypt_app == APPLICATION_SMIME)
573 && (flags & SIGN)
574 && (set_empty_signas || *sign_as))
575 mutt_str_replace (&SmimeSignAs, sign_as);
576
577 return flags;
578}
579
580
581/* args:
582 * fp If not NULL, file containing the template
583 * ctx If fp is NULL, the context containing the header with the template
584 * newhdr The template is read into this HEADER
585 * hdr The message to recall/resend
586 * resend Set if resending (as opposed to recalling a postponed msg).
587 * Resent messages enable header weeding, and also
588 * discard any existing Message-ID and Mail-Followup-To.
589 */
590int mutt_prepare_template (FILE *fp, CONTEXT *ctx, HEADER *newhdr, HEADER *hdr,
591 short resend)
592{
593 MESSAGE *msg = NULL;
594 BUFFER *file = NULL;
595 BODY *b;
596 FILE *bfp;
597 int rv = -1;
598 STATE s;
599 int sec_type;
600 ENVELOPE *protected_headers = NULL;
601
602 memset (&s, 0, sizeof (s));
603
604 if (!fp && (msg = mx_open_message (ctx, hdr->msgno)) == NULL)
605 return (-1);
606
607 if (!fp) fp = msg->fp;
608
609 bfp = fp;
610
611 /* parse the message header and MIME structure */
612
613 fseeko (fp, hdr->offset, 0);
614 newhdr->offset = hdr->offset;
615 /* enable header weeding for resent messages */
616 newhdr->env = mutt_read_rfc822_header (fp, newhdr, 1, resend);
617 newhdr->content->length = hdr->content->length;
618 mutt_parse_part (fp, newhdr->content);
619
620 /* If resending a message, don't keep message_id or mail_followup_to.
621 * Otherwise, we are resuming a postponed message, and want to keep those
622 * headers if they exist.
623 */
624 if (resend)
625 {
626 FREE (&newhdr->env->message_id);
627 FREE (&newhdr->env->mail_followup_to);
628 }
629
630 /* decrypt pgp/mime encoded messages */
631
632 if ((WithCrypto & APPLICATION_PGP) &&
633 (sec_type = mutt_is_multipart_encrypted (newhdr->content)))
634 {
635 newhdr->security |= sec_type;
636 if (!crypt_valid_passphrase (sec_type))
637 goto bail;
638
639 mutt_message _("Decrypting message...");
640 if ((crypt_pgp_decrypt_mime (fp, &bfp, newhdr->content, &b) == -1)
641 || b == NULL)
642 {
643 mutt_error _("Decryption failed.");
644 goto bail;
645 }
646
647 mutt_free_body (&newhdr->content);
648 newhdr->content = b;
649
650 if (b->mime_headers)
651 {
652 protected_headers = b->mime_headers;
653 b->mime_headers = NULL;
654 }
655
656 mutt_clear_error ();
657 }
658
659 /*
660 * remove a potential multipart/signed layer - useful when
661 * resending messages
662 */
663
664 if (WithCrypto && mutt_is_multipart_signed (newhdr->content))
665 {
666 newhdr->security |= SIGN;
667 if ((WithCrypto & APPLICATION_PGP)
668 && ascii_strcasecmp (mutt_get_parameter ("protocol", newhdr->content->parameter), "application/pgp-signature") == 0)
669 newhdr->security |= APPLICATION_PGP;
670 else if ((WithCrypto & APPLICATION_SMIME))
671 newhdr->security |= APPLICATION_SMIME;
672
673 /* destroy the signature */
674 mutt_free_body (&newhdr->content->parts->next);
675 newhdr->content = mutt_remove_multipart (newhdr->content);
676
677 if (newhdr->content->mime_headers)
678 {
679 mutt_free_envelope (&protected_headers);
680 protected_headers = newhdr->content->mime_headers;
681 newhdr->content->mime_headers = NULL;
682 }
683 }
684
685
686 /*
687 * We don't need no primary multipart.
688 * Note: We _do_ preserve messages!
689 */
690 if (newhdr->content->type == TYPEMULTIPART)
691 newhdr->content = mutt_remove_multipart_mixed (newhdr->content);
692
693 /* Note: this just uses the *first* alternative and strips the rest.
694 * It might be better to scan for text/plain. On the other hand,
695 * mutt's alternative generation filter in theory allows composing
696 * text/html and generating the text/plain from that. This way will
697 * preserve the alternative originally composed by the user.
698 */
699 newhdr->content = mutt_remove_multipart_alternative (newhdr->content);
700
701 s.fpin = bfp;
702
703 file = mutt_buffer_pool_get ();
704
705 /* create temporary files for all attachments */
706 for (b = newhdr->content; b; b = b->next)
707 {
708
709 /* what follows is roughly a receive-mode variant of
710 * mutt_get_tmp_attachment () from muttlib.c
711 */
712
713 mutt_buffer_clear (file);
714 if (b->filename)
715 {
716 mutt_buffer_strcpy (file, b->filename);
717 b->d_filename = safe_strdup (b->filename);
718 }
719 else
720 {
721 /* avoid Content-Disposition: header with temporary filename */
722 b->use_disp = 0;
723 }
724
725 /* set up state flags */
726
727 s.flags = 0;
728
729 if (b->type == TYPETEXT)
730 {
731 if (!ascii_strcasecmp ("yes", mutt_get_parameter ("x-mutt-noconv", b->parameter)))
732 b->noconv = 1;
733 else
734 {
735 s.flags |= MUTT_CHARCONV;
736 b->noconv = 0;
737 }
738
739 mutt_delete_parameter ("x-mutt-noconv", &b->parameter);
740 }
741
742 mutt_adv_mktemp (file);
743 if ((s.fpout = safe_fopen (mutt_b2s (file), "w")) == NULL)
744 goto bail;
745
746
747 if ((WithCrypto & APPLICATION_PGP) &&
748 ((sec_type = mutt_is_application_pgp (b)) & (ENCRYPT|SIGN)))
749 {
750 if (sec_type & 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 newhdr->security |= sec_type;
764
765 b->type = TYPETEXT;
766 mutt_str_replace (&b->subtype, "plain");
767 mutt_delete_parameter ("x-action", &b->parameter);
768 }
769 else if ((WithCrypto & APPLICATION_SMIME) &&
770 ((sec_type = mutt_is_application_smime (b)) & (ENCRYPT|SIGN)))
771 {
772 if (sec_type & ENCRYPT)
773 {
774 if (!crypt_valid_passphrase (APPLICATION_SMIME))
775 goto bail;
776 crypt_smime_getkeys (newhdr->env);
777 mutt_message _("Decrypting message...");
778 }
779
780 if (mutt_body_handler (b, &s) < 0)
781 {
782 mutt_error _("Decryption failed.");
783 goto bail;
784 }
785
786 if (b == newhdr->content && !protected_headers)
787 {
788 protected_headers = b->mime_headers;
789 b->mime_headers = NULL;
790 }
791
792 newhdr->security |= sec_type;
793 b->type = TYPETEXT;
794 mutt_str_replace (&b->subtype, "plain");
795 }
796 else
797 mutt_decode_attachment (b, &s);
798
799 if (safe_fclose (&s.fpout) != 0)
800 goto bail;
801
802 mutt_str_replace (&b->filename, mutt_b2s (file));
803 b->unlink = 1;
804
805 mutt_stamp_attachment (b);
806
807 mutt_free_body (&b->parts);
808 if (b->hdr) b->hdr->content = NULL; /* avoid dangling pointer */
809 }
810
811 if (option (OPTCRYPTPROTHDRSREAD) &&
812 protected_headers &&
813 protected_headers->subject &&
814 mutt_strcmp (newhdr->env->subject, protected_headers->subject))
815 {
816 mutt_str_replace (&newhdr->env->subject, protected_headers->subject);
817 }
818 mutt_free_envelope (&protected_headers);
819
820 /* Fix encryption flags. */
821
822 /* No inline if multipart. */
823 if (WithCrypto && (newhdr->security & INLINE) && newhdr->content->next)
824 newhdr->security &= ~INLINE;
825
826 /* Do we even support multiple mechanisms? */
827 newhdr->security &= WithCrypto | ~(APPLICATION_PGP|APPLICATION_SMIME);
828
829 /* Theoretically, both could be set. Take the one the user wants to set by default. */
830 if ((newhdr->security & APPLICATION_PGP) && (newhdr->security & APPLICATION_SMIME))
831 {
832 if (option (OPTSMIMEISDEFAULT))
833 newhdr->security &= ~APPLICATION_PGP;
834 else
835 newhdr->security &= ~APPLICATION_SMIME;
836 }
837
838 mutt_rfc3676_space_unstuff (newhdr);
839
840 rv = 0;
841
842bail:
843
844 /* that's it. */
845 mutt_buffer_pool_release (&file);
846 if (bfp != fp) safe_fclose (&bfp);
847 if (msg) mx_close_message (ctx, &msg);
848
849 if (rv == -1)
850 {
851 mutt_free_envelope (&newhdr->env);
852 mutt_free_body (&newhdr->content);
853 }
854
855 return rv;
856}