remove old lib.[hc]
[apps/madmutt.git] / crypt.c
diff --git a/crypt.c b/crypt.c
index c0c64ad..e137f2c 100644 (file)
--- a/crypt.c
+++ b/crypt.c
 /*
  * Copyright notice from original mutt:
- * Copyright (C) 1996,1997 Michael R. Elkins <me@mutt.org>
- * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
- * Copyright (C) 2001  Thomas Roessler <roessler@does-not-exist.org>
+ * crypt-gpgme.c - GPGME based crypto operations
+ * Copyright (C) 1996,1997 Michael R. Elkins <me@cs.hmc.edu>
+ * Copyright (C) 1998,1999,2000 Thomas Roessler <roessler@guug.de>
+ * Copyright (C) 2001  Thomas Roessler <roessler@guug.de>
  *                     Oliver Ehli <elmy@acm.org>
- * Copyright (C) 2003  Werner Koch <wk@gnupg.org>
- * Copyright (C) 2004 g10code GmbH
- *
- * This file is part of mutt-ng, see http://www.muttng.org/.
- * It's licensed under the GNU General Public License,
- * please see the file GPL in the top level source directory.
+ * Copyright (C) 2002, 2003, 2004 g10 Code GmbH
+ */
+/*
+ * Copyright © 2006 Pierre Habouzit
  */
 
-#if HAVE_CONFIG_H
-# include "config.h"
-#endif
+#include <lib-lib/lib-lib.h>
 
-#include <lib-lib/str.h>
-#include <lib-lib/file.h>
-#include <lib-lib/ascii.h>
-#include <lib-lib/mem.h>
-#include <lib-lib/macros.h>
+#include <gpgme.h>
 
 #include <lib-mime/mime.h>
+#include <lib-ui/curses.h>
+#include <lib-ui/enter.h>
+#include <lib-ui/menu.h>
+#include <lib-mx/mx.h>
 
-#include "mutt.h"
+#include "crypt.h"
+#include "alias.h"
 #include "handler.h"
-#include "mutt_curses.h"
 #include "copy.h"
-#include "mutt_crypt.h"
-#include "pgp.h"
+#include "pager.h"
+#include "recvattach.h"
+#include "sort.h"
+
+/* Values used for comparing addresses. */
+#define CRYPT_KV_VALID    1
+#define CRYPT_KV_ADDR     2
+#define CRYPT_KV_STRING   4
+#define CRYPT_KV_STRONGID 8
+#define CRYPT_KV_MATCH (CRYPT_KV_ADDR|CRYPT_KV_STRING)
+
+struct dn_array_s {
+    char *key;
+    char *value;
+};
+
+/* We work based on user IDs, getting from a user ID to the key is
+   check and does not need any memory (gpgme uses reference counting). */
+typedef struct cryptkey_t {
+    struct cryptkey_t *next;
+    int idx;                      /* and the user ID at this index */
+    int flags;                    /* global and per uid flags (for convenience) */
+    gpgme_key_t kobj;
+    const char *uid;              /* and for convenience point to this user ID */
+} cryptkey_t;
+
+DO_INIT(cryptkey_t, cryptkey);
+static void cryptkey_wipe(cryptkey_t *key) {
+    gpgme_key_release(key->kobj);
+}
+DO_NEW(cryptkey_t, cryptkey);
+DO_DELETE(cryptkey_t, cryptkey);
+DO_SLIST(cryptkey_t, key, cryptkey_delete);
+
+static cryptkey_t *cryptkey_dup(const cryptkey_t *k)
+{
+    cryptkey_t *res = cryptkey_new();
+    *res = *k;
+    res->next = NULL;
+    gpgme_key_ref(k->kobj);
+    return res;
+}
+
+typedef struct crypt_entry {
+    ssize_t num;
+    cryptkey_t *key;
+} crypt_entry_t;
+
+static gpgme_key_t signature_key = NULL;
+
+static void convert_to_7bit (BODY * a)
+{
+    for (; a; a = a->next) {
+        int tok = mime_which_token(a->subtype, -1);
+
+        if (a->type == TYPEMULTIPART) {
+            a->encoding = ENC7BIT;
+            convert_to_7bit(a->parts);
+        } else if (a->type == TYPEMESSAGE && tok == MIME_DELIVERY_STATUS) {
+            if (a->encoding != ENC7BIT)
+                mutt_message_to_7bit(a, NULL);
+        } else if (a->encoding == ENC8BIT) {
+            a->encoding = ENCQUOTEDPRINTABLE;
+        } else if (a->encoding == ENCBINARY) {
+            a->encoding = ENCBASE64;
+        } else if (a->content && a->encoding != ENCBASE64
+               && (a->content->from || a->content->space))
+        {
+            a->encoding = ENCQUOTEDPRINTABLE;
+        }
+    }
+}
+
+/* Print the utf-8 encoded string BUF of length LEN bytes to stream
+   FP. Convert the character set. */
+static void print_utf8 (FILE * fp, const char *buf, ssize_t len)
+{
+    char *tstr;
+
+    tstr = p_dupstr(buf, len);
+    mutt_convert_string(&tstr, "utf-8", MCharset.charset, M_ICONV_HOOK_FROM);
+    fputs(tstr, fp);
+    p_delete(&tstr);
+}
+
+/* Return the keyID for the key K.  Note that this string is valid as
+   long as K is valid */
+static const char *crypt_keyid (cryptkey_t * k)
+{
+    if (k->kobj && k->kobj->subkeys) {
+        const char *s = k->kobj->subkeys->keyid;
+        return m_strlen(s) == 16 ? s + 8 : s;
+    }
+
+    return "????????";
+}
+
+/* Return the hexstring fingerprint from the key K. */
+static const char *crypt_fpr (cryptkey_t * k)
+{
+    return k->kobj && k->kobj->subkeys ? k->kobj->subkeys->fpr : "";
+}
+
+/* Parse FLAGS and return a statically allocated(!) string with them. */
+static char *crypt_key_abilities (int flags)
+{
+    static char buff[3] = "es";
+
+    if (!(flags & KEYFLAG_CANENCRYPT))
+        buff[0] = '-';
+    else if (flags & KEYFLAG_PREFER_SIGNING)
+        buff[0] = '.';
+
+    if (!(flags & KEYFLAG_CANSIGN))
+        buff[1] = '-';
+    else if (flags & KEYFLAG_PREFER_ENCRYPTION)
+        buff[1] = '.';
+
+    return buff;
+}
+
+/* Parse FLAGS and return a character describing the most important flag. */
+static char crypt_flags(int flags)
+{
+    if (flags & KEYFLAG_REVOKED)
+        return 'R';
+    if (flags & KEYFLAG_EXPIRED)
+        return 'X';
+    if (flags & KEYFLAG_DISABLED)
+        return 'd';
+    if (flags & KEYFLAG_CRITICAL)
+        return 'c';
+    return ' ';
+}
+
+/* Return true whe validity of KEY is sufficient. */
+static int crypt_id_is_strong (cryptkey_t * key)
+{
+    gpgme_user_id_t uid;
+    int i;
+
+    if (key->flags & KEYFLAG_ISX509)
+        return 1;
+
+    for (i = 0, uid = key->kobj->uids; uid; i++, uid = uid->next) {
+        if (i == key->idx) {
+            return uid->validity == GPGME_VALIDITY_FULL
+                || uid->validity == GPGME_VALIDITY_ULTIMATE;
+        }
+    }
+
+    return 0;
+}
+
+/* Return a bit vector describing how well the addresses ADDR and
+   U_ADDR match and whether KEY is valid. */
+static int
+crypt_id_matches_addr(address_t *addr, address_t *u_addr, cryptkey_t *key)
+{
+    int rv = 0;
+
+    if (!(key->flags & KEYFLAG_CANTUSE))
+        rv |= CRYPT_KV_VALID;
+
+    if (crypt_id_is_strong(key))
+        rv |= CRYPT_KV_STRONGID;
+
+    if (addr->mailbox && !m_strcasecmp(addr->mailbox, u_addr->mailbox))
+        rv |= CRYPT_KV_ADDR;
+
+    if (addr->personal && m_strcasecmp(addr->personal, u_addr->personal))
+        rv |= CRYPT_KV_STRING;
+
+    return rv;
+}
+
+
+/* Create a new gpgme context and return it.  With FOR_SMIME set to
+   true, the protocol of the context is set to CMS. */
+static gpgme_ctx_t create_gpgme_context(int for_smime)
+{
+    gpgme_error_t err;
+    gpgme_ctx_t ctx;
+
+    err = gpgme_new (&ctx);
+    if (err) {
+        mutt_error(_("error creating gpgme context: %s\n"),
+                   gpgme_strerror(err));
+        sleep(2);
+        mutt_exit(1);
+    }
+    if (!for_smime)
+        return ctx;
+
+    err = gpgme_set_protocol(ctx, GPGME_PROTOCOL_CMS);
+    if (err) {
+        mutt_error(_("error enabling CMS protocol: %s\n"), gpgme_strerror(err));
+        sleep(2);
+        mutt_exit(1);
+    }
+    return ctx;
+}
+
+/* Create a new gpgme data object.  This is a wrapper to die on
+   error. */
+static gpgme_data_t create_gpgme_data(void)
+{
+    gpgme_error_t err;
+    gpgme_data_t data;
+
+    err = gpgme_data_new(&data);
+    if (err) {
+        mutt_error(_("error creating gpgme data object: %s\n"),
+                   gpgme_strerror(err));
+        sleep(2);
+        mutt_exit(1);
+    }
+    return data;
+}
+
+/* Create a new GPGME Data object from the mail body A.  With CONVERT
+   passed as true, the lines are converted to CR,LF if required.
+   Return NULL on error or the gpgme_data_t object on success. */
+static gpgme_data_t body_to_data_object(BODY *a, int convert)
+{
+    gpgme_data_t data;
+    FILE *fptmp;
+    int err = 0;
+
+    if (!(fptmp = tmpfile())) {
+        mutt_perror (_("Can't create temporary file"));
+        return NULL;
+    }
+
+    mutt_write_mime_header(a, fptmp);
+    fputc('\n', fptmp);
+    mutt_write_mime_body(a, fptmp);
+    rewind(fptmp);
+
+    if (convert) {
+        char buf[STRING];
+        int spare = 0;
+
+        data = create_gpgme_data();
+
+        while (fgets(buf + spare, sizeof(buf) - 1, fptmp)) {
+            int l = m_strlen(buf);
+
+            spare = buf[l - 1] != '\n';
+            if (!spare && (l <= 1 || buf[l - 2] != '\r')) {
+                buf[l - 1] = '\r';
+                buf[l++]   = '\n';
+            }
+            gpgme_data_write(data, buf, l - spare);
+            if (spare)
+                buf[0] = buf[l - 1];
+        }
+        if (spare)
+            gpgme_data_write(data, buf, 1);
+        gpgme_data_seek(data, 0, SEEK_SET);
+        m_fclose(&fptmp);
+        return data;
+    }
+
+    err = gpgme_data_new_from_stream(&data, fptmp);
+    m_fclose(&fptmp);
+    if (err) {
+        mutt_error (_("error allocating data object: %s\n"), gpgme_strerror (err));
+        return NULL;
+    }
+    return data;
+}
+
+/* Create a GPGME data object from the stream FP but limit the object
+   to LENGTH bytes starting at OFFSET bytes from the beginning of the
+   file. */
+static gpgme_data_t file_to_data_object(FILE *fp, long offset, long length)
+{
+    int err = 0;
+    gpgme_data_t data;
+
+    err = gpgme_data_new_from_filepart(&data, NULL, fp, offset, length);
+    if (err) {
+        mutt_error(_("error allocating data object: %s\n"),
+                   gpgme_strerror(err));
+        return NULL;
+    }
+
+    return data;
+}
+
+/* Write a GPGME data object to the stream FP. */
+static int data_object_to_stream(gpgme_data_t data, FILE *fp)
+{
+    int err;
+    char buf[4096], *p;
+    ssize_t nread;
+
+    err = ((gpgme_data_seek (data, 0, SEEK_SET) == -1)
+           ? gpgme_error_from_errno (errno) : 0);
+    if (err) {
+        mutt_error (_("error rewinding data object: %s\n"),
+                    gpgme_strerror(err));
+        return -1;
+    }
+
+    while ((nread = gpgme_data_read(data, buf, sizeof(buf)))) {
+        /* fixme: we are not really converting CRLF to LF but just
+           skipping CR. Doing it correctly needs a more complex logic */
+        for (p = buf; nread; p++, nread--) {
+            if (*p != '\r')
+                putc (*p, fp);
+        }
+
+        if (ferror(fp)) {
+            mutt_perror ("[tempfile]");
+            return -1;
+        }
+    }
+    if (nread == -1) {
+        mutt_error(_("error reading data object: %s\n"), strerror(errno));
+        return -1;
+    }
+    return 0;
+}
+
+/* Copy a data object to a newly created temporay file and return that
+   filename. Caller must free.  With RET_FP not NULL, don't close the
+   stream but return it there. */
+static char *data_object_to_tempfile(gpgme_data_t data, FILE **ret_fp)
+{
+    int err;
+    char tempfile[_POSIX_PATH_MAX];
+    FILE *fp;
+    ssize_t nread = 0;
+
+    fp = m_tempfile(tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL);
+    if (!fp) {
+        mutt_perror (_("Can't create temporary file"));
+        return NULL;
+    }
+
+    err = ((gpgme_data_seek (data, 0, SEEK_SET) == -1)
+           ? gpgme_error_from_errno (errno) : 0);
+    if (!err) {
+        char buf[4096];
+
+        while ((nread = gpgme_data_read(data, buf, sizeof(buf)))) {
+            if (fwrite (buf, nread, 1, fp) != 1) {
+                mutt_perror (_("Can't create temporary file"));
+                m_fclose(&fp);
+                unlink (tempfile);
+                return NULL;
+            }
+        }
+    }
+
+    if (nread == -1) {
+        mutt_error (_("error reading data object: %s\n"), gpgme_strerror (err));
+        unlink (tempfile);
+        m_fclose(&fp);
+        return NULL;
+    }
+    if (ret_fp) {
+        rewind(fp);
+        *ret_fp = fp;
+    } else {
+        m_fclose(&fp);
+    }
+    return m_strdup(tempfile);
+}
+
+
+/* FIXME: stolen from gpgme to avoid "ambiguous identity" errors */
+static gpgme_error_t
+gpgme_get_key2 (gpgme_ctx_t ctx, const char *fpr, gpgme_key_t *r_key,
+              int secret)
+{
+  gpgme_ctx_t listctx;
+  gpgme_error_t err;
+
+  if (!ctx || !r_key || !fpr)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  if (strlen (fpr) < 8)        /* We have at least a key ID.  */
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  /* FIXME: We use our own context because we have to avoid the user's
+     I/O callback handlers.  */
+  err = gpgme_new (&listctx);
+  if (err)
+    return err;
+  gpgme_set_protocol (listctx, gpgme_get_protocol (ctx));
+  err = gpgme_op_keylist_start (listctx, fpr, secret);
+  if (!err)
+    err = gpgme_op_keylist_next (listctx, r_key);
+  gpgme_release (listctx);
+  return err;
+}
+
+/* Create a GpgmeRecipientSet from the keys in the string KEYLIST.
+   The keys must be space delimited. */
+static gpgme_key_t *create_recipient_set(const char *s, int smime)
+{
+    gpgme_ctx_t  ctx  = create_gpgme_context(smime);
+    gpgme_key_t *rset = NULL;
+    int rset_n = 0;
+
+    s = skipspaces(s);
+    while (*s) {
+        gpgme_error_t err;
+        gpgme_key_t key;
+        const char *p = m_strnextsp(s);
+        char buf[100];
+
+        m_strncpy(buf, sizeof(buf), s, p - s);
+        if (p - s > 1 && p[-1] == '!') {
+            /* user wants to override the valididy of that key. */
+
+            buf[p - s - 1] = '\0';
+            err = gpgme_get_key2(ctx, buf, &key, 0);
+            if (!err)
+                key->uids->validity = GPGME_VALIDITY_FULL;
+        } else {
+            err = gpgme_get_key2(ctx, buf, &key, 0);
+        }
+
+        if (err) {
+            mutt_error(_("error adding recipient `%.*s': %s\n"),
+                       (int)(p - s), s, gpgme_strerror(err));
+            p_delete(&rset);
+            break;
+        }
+
+        p_realloc(&rset, rset_n + 1);
+        rset[rset_n++] = key;
+        s = skipspaces(p);
+    }
+
+    if (rset) {
+        /* NULL terminate.  */
+        p_realloc(&rset, rset_n + 1);
+        rset[rset_n++] = NULL;
+    }
+
+    gpgme_release(ctx);
+    return rset;
+}
+
+
+/* Make sure that the correct signer is set. Returns 0 on success. */
+static int set_signer(gpgme_ctx_t ctx, int for_smime)
+{
+    const char *signid = for_smime ? SmimeDefaultKey : PgpSignAs;
+    gpgme_error_t err;
+    gpgme_key_t key;
+
+    if (m_strisempty(signid))
+        return 0;
+
+    err = gpgme_get_key(ctx, signid, &key, 1);
+    if (err) {
+        mutt_error(_("error getting secret key `%s': %s\n"), signid,
+                   gpgme_strerror(err));
+        return -1;
+    }
+
+    gpgme_signers_clear(ctx);
+    err = gpgme_signers_add(ctx, key);
+    gpgme_key_unref(key);
+    if (err) {
+        mutt_error(_("error setting secret key `%s': %s\n"), signid,
+                   gpgme_strerror(err));
+        return -1;
+    }
+    return 0;
+}
+
+
+/* Encrypt the gpgme data object PLAINTEXT to the recipients in RSET
+   and return an allocated filename to a temporary file containing the
+   enciphered text.  With USE_SMIME set to true, the smime backend is
+   used.  With COMBINED_SIGNED a PGP message is signed and
+   encrypted.  Returns NULL in case of error */
+static char *encrypt_gpgme_object(gpgme_data_t plaintext, gpgme_key_t *rset,
+                                  int use_smime, int combined_signed)
+{
+    int err;
+    gpgme_ctx_t ctx;
+    gpgme_data_t ciphertext;
+    char *outfile;
+
+    ctx = create_gpgme_context (use_smime);
+    if (!use_smime)
+        gpgme_set_armor (ctx, 1);
+
+    ciphertext = create_gpgme_data ();
+
+    if (combined_signed) {
+        if (set_signer(ctx, use_smime)) {
+            gpgme_data_release(ciphertext);
+            gpgme_release(ctx);
+            return NULL;
+        }
+        err = gpgme_op_encrypt_sign(ctx, rset, GPGME_ENCRYPT_ALWAYS_TRUST,
+                                    plaintext, ciphertext);
+    } else {
+        err = gpgme_op_encrypt(ctx, rset, GPGME_ENCRYPT_ALWAYS_TRUST,
+                               plaintext, ciphertext);
+    }
+
+    mutt_need_hard_redraw();
+    if (err) {
+        mutt_error (_("error encrypting data: %s\n"), gpgme_strerror (err));
+        gpgme_data_release (ciphertext);
+        gpgme_release (ctx);
+        return NULL;
+    }
+
+    gpgme_release(ctx);
+
+    outfile = data_object_to_tempfile(ciphertext, NULL);
+    gpgme_data_release(ciphertext);
+    return outfile;
+}
+
+/* Find the "micalg" parameter from the last Gpgme operation on
+   context CTX.  It is expected that this operation was a sign
+   operation.  Return the algorithm name as a C string in buffer BUF
+   which must have been allocated by the caller with size BUFLEN.
+   Returns 0 on success or -1 in case of an error.  The return string
+   is truncted to BUFLEN - 1. */
+static int get_micalg(gpgme_ctx_t ctx, char *buf, ssize_t buflen)
+{
+    gpgme_sign_result_t result = NULL;
+    const char *algorithm_name = NULL;
+
+    *buf = '\0';
+    result = gpgme_op_sign_result(ctx);
+    if (result) {
+        algorithm_name = gpgme_hash_algo_name (result->signatures->hash_algo);
+        if (algorithm_name) {
+            m_strcpy(buf, buflen, algorithm_name);
+        }
+    }
+
+    return *buf ? 0 : -1;
+}
+
+static void print_time(time_t t, STATE *s)
+{
+    char p[STRING];
+
+    setlocale(LC_TIME, "");
+#ifdef HAVE_LANGINFO_D_T_FMT
+    strftime(p, sizeof(p), nl_langinfo(D_T_FMT), localtime(&t));
+#else
+    strftime(p, sizeof(p), "%c", localtime(&t));
+#endif
+    setlocale(LC_TIME, "C");
+    state_attach_puts(p, s);
+}
+
+/* Implementation of `sign_message'. */
+
+/* Sign the MESSAGE in body A either using OpenPGP or S/MIME when
+   USE_SMIME is passed as true.  Returns the new body or NULL on
+   error. */
+static BODY *sign_message(BODY * a, int use_smime)
+{
+  BODY *t;
+  char *sigfile;
+  int err = 0;
+  char buf[100];
+  gpgme_ctx_t ctx;
+  gpgme_data_t message, signature;
+
+  convert_to_7bit (a);          /* Signed data _must_ be in 7-bit format. */
+
+  message = body_to_data_object(a, 1);
+  if (!message)
+    return NULL;
+  signature = create_gpgme_data ();
+
+  ctx = create_gpgme_context (use_smime);
+  if (!use_smime)
+    gpgme_set_armor (ctx, 1);
+
+  if (set_signer (ctx, use_smime)) {
+    gpgme_data_release (signature);
+    gpgme_release (ctx);
+    return NULL;
+  }
+
+  err = gpgme_op_sign (ctx, message, signature, GPGME_SIG_MODE_DETACH);
+  mutt_need_hard_redraw ();
+  gpgme_data_release (message);
+  if (err) {
+    gpgme_data_release (signature);
+    gpgme_release (ctx);
+    mutt_error (_("error signing data: %s\n"), gpgme_strerror (err));
+    return NULL;
+  }
+
+  sigfile = data_object_to_tempfile(signature, NULL);
+  gpgme_data_release (signature);
+  if (!sigfile) {
+    gpgme_release (ctx);
+    return NULL;
+  }
+
+  t = body_new();
+  t->type = TYPEMULTIPART;
+  t->subtype = m_strdup("signed");
+  t->encoding = ENC7BIT;
+  t->use_disp = 0;
+  t->disposition = DISPINLINE;
+
+  parameter_set_boundary(&t->parameter);
+  parameter_setval(&t->parameter, "protocol",
+                   use_smime ? "application/pkcs7-signature"
+                             : "application/pgp-signature");
+  /* Get the micalg from gpgme.  Old gpgme versions don't support this
+     for S/MIME so we assume sha-1 in this case. */
+  if (!get_micalg (ctx, buf, sizeof buf))
+    parameter_setval(&t->parameter, "micalg", buf);
+  else if (use_smime)
+    parameter_setval(&t->parameter, "micalg", "sha1");
+  gpgme_release (ctx);
+
+  t->parts = a;
+  a = t;
+
+  t->parts->next = body_new();
+  t = t->parts->next;
+  t->type = TYPEAPPLICATION;
+  if (use_smime) {
+    t->subtype = m_strdup("pkcs7-signature");
+    parameter_setval(&t->parameter, "name", "smime.p7s");
+    t->encoding = ENCBASE64;
+    t->use_disp = 1;
+    t->disposition = DISPATTACH;
+    t->d_filename = m_strdup("smime.p7s");
+  }
+  else {
+    t->subtype = m_strdup("pgp-signature");
+    t->use_disp = 0;
+    t->disposition = DISPINLINE;
+    t->encoding = ENC7BIT;
+  }
+  t->filename = sigfile;
+  t->unlink = 1;                /* ok to remove this file after sending. */
+
+  return a;
+}
+
+/* Encrypt the mail body A to all keys given as space separated keyids
+   or fingerprints in KEYLIST and return the encrypted body.  */
+static BODY *crypt_pgp_encrypt_message (BODY * a, char *keylist, int sign)
+{
+  char *outfile = NULL;
+  BODY *t;
+  gpgme_key_t *rset = NULL;
+  gpgme_data_t plaintext;
+
+  rset = create_recipient_set(keylist, 0);
+  if (!rset)
+    return NULL;
+
+  if (sign)
+    convert_to_7bit (a);
+  plaintext = body_to_data_object(a, 0);
+  if (!plaintext) {
+    p_delete(&rset);
+    return NULL;
+  }
+
+  outfile = encrypt_gpgme_object (plaintext, rset, 0, sign);
+  gpgme_data_release (plaintext);
+  p_delete(&rset);
+  if (!outfile)
+    return NULL;
+
+  t = body_new();
+  t->type = TYPEMULTIPART;
+  t->subtype = m_strdup("encrypted");
+  t->encoding = ENC7BIT;
+  t->use_disp = 0;
+  t->disposition = DISPINLINE;
+
+  parameter_set_boundary(&t->parameter);
+  parameter_setval(&t->parameter, "protocol", "application/pgp-encrypted");
+
+  t->parts = body_new();
+  t->parts->type = TYPEAPPLICATION;
+  t->parts->subtype = m_strdup("pgp-encrypted");
+  t->parts->encoding = ENC7BIT;
+
+  t->parts->next = body_new();
+  t->parts->next->type = TYPEAPPLICATION;
+  t->parts->next->subtype = m_strdup("octet-stream");
+  t->parts->next->encoding = ENC7BIT;
+  t->parts->next->filename = outfile;
+  t->parts->next->use_disp = 1;
+  t->parts->next->disposition = DISPINLINE;
+  t->parts->next->unlink = 1;   /* delete after sending the message */
+  t->parts->next->d_filename = m_strdup("msg.asc");
+
+  return t;
+}
+
+/* Encrypt the mail body A to all keys given as space separated
+   fingerprints in KEYLIST and return the S/MIME encrypted body.  */
+static BODY *crypt_smime_build_smime_entity (BODY * a, char *keylist)
+{
+  char *outfile = NULL;
+  BODY *t;
+  gpgme_key_t *rset = NULL;
+  gpgme_data_t plaintext;
+
+  rset = create_recipient_set(keylist, 1);
+  if (!rset)
+    return NULL;
+
+  plaintext = body_to_data_object(a, 0);
+  if (!plaintext) {
+    p_delete(&rset);
+    return NULL;
+  }
+
+  outfile = encrypt_gpgme_object (plaintext, rset, 1, 0);
+  gpgme_data_release (plaintext);
+  p_delete(&rset);
+  if (!outfile)
+    return NULL;
+
+  t = body_new();
+  t->type = TYPEAPPLICATION;
+  t->subtype = m_strdup("pkcs7-mime");
+  parameter_setval(&t->parameter, "name", "smime.p7m");
+  parameter_setval(&t->parameter, "smime-type", "enveloped-data");
+  t->encoding = ENCBASE64;      /* The output of OpenSSL SHOULD be binary */
+  t->use_disp = 1;
+  t->disposition = DISPATTACH;
+  t->d_filename = m_strdup("smime.p7m");
+  t->filename = outfile;
+  t->unlink = 1;                /*delete after sending the message */
+  t->parts = 0;
+  t->next = 0;
+
+  return t;
+}
+
+/* Display the common attributes of the signature summary SUM.
+   Return 1 if there is is a severe warning.
+ */
+static int show_sig_summary (unsigned long sum,
+                             gpgme_ctx_t ctx, gpgme_key_t key, int idx,
+                             STATE * s)
+{
+  int severe = 0;
+
+  if ((sum & GPGME_SIGSUM_KEY_REVOKED)) {
+    state_attach_puts (_("Warning: One of the keys has been revoked\n"), s);
+    severe = 1;
+  }
+
+  if ((sum & GPGME_SIGSUM_KEY_EXPIRED)) {
+    time_t at = key->subkeys->expires ? key->subkeys->expires : 0;
+
+    if (at) {
+      state_attach_puts (_("Warning: The key used to create the "
+                           "signature expired at: "), s);
+      print_time (at, s);
+      state_attach_puts ("\n", s);
+    }
+    else
+      state_attach_puts (_("Warning: At least one certification key "
+                           "has expired\n"), s);
+  }
+
+  if ((sum & GPGME_SIGSUM_SIG_EXPIRED)) {
+    gpgme_verify_result_t result;
+    gpgme_signature_t sig;
+    int i;
+
+    result = gpgme_op_verify_result (ctx);
+
+    for (sig = result->signatures, i = 0; sig && (i < idx);
+         sig = sig->next, i++);
+
+    state_attach_puts (_("Warning: The signature expired at: "), s);
+    print_time (sig ? sig->exp_timestamp : 0, s);
+    state_attach_puts ("\n", s);
+  }
+
+  if ((sum & GPGME_SIGSUM_KEY_MISSING))
+    state_attach_puts (_("Can't verify due to a missing "
+                         "key or certificate\n"), s);
+
+  if ((sum & GPGME_SIGSUM_CRL_MISSING)) {
+    state_attach_puts (_("The CRL is not available\n"), s);
+    severe = 1;
+  }
+
+  if ((sum & GPGME_SIGSUM_CRL_TOO_OLD)) {
+    state_attach_puts (_("Available CRL is too old\n"), s);
+    severe = 1;
+  }
+
+  if ((sum & GPGME_SIGSUM_BAD_POLICY))
+    state_attach_puts (_("A policy requirement was not met\n"), s);
+
+  if ((sum & GPGME_SIGSUM_SYS_ERROR)) {
+    const char *t0 = NULL, *t1 = NULL;
+    gpgme_verify_result_t result;
+    gpgme_signature_t sig;
+    int i;
+
+    state_attach_puts (_("A system error occurred"), s);
+
+    /* Try to figure out some more detailed system error information. */
+    result = gpgme_op_verify_result (ctx);
+    for (sig = result->signatures, i = 0; sig && (i < idx);
+         sig = sig->next, i++);
+    if (sig) {
+      t0 = "";
+      t1 = sig->wrong_key_usage ? "Wrong_Key_Usage" : "";
+    }
+
+    if (t0 || t1) {
+      state_attach_puts (": ", s);
+      if (t0)
+        state_attach_puts (t0, s);
+      if (t1 && !(t0 && !m_strcmp(t0, t1))) {
+        if (t0)
+          state_attach_puts (",", s);
+        state_attach_puts (t1, s);
+      }
+    }
+    state_attach_puts ("\n", s);
+  }
+
+  return severe;
+}
+
+
+static void show_fingerprint (gpgme_key_t key, STATE * state)
+{
+  const char *s;
+  int i, is_pgp;
+  char *buf, *p;
+  const char *prefix = _("Fingerprint: ");
+  ssize_t bufsize;
+
+  if (!key)
+    return;
+  s = key->subkeys ? key->subkeys->fpr : NULL;
+  if (!s)
+    return;
+  is_pgp = (key->protocol == GPGME_PROTOCOL_OpenPGP);
+
+  bufsize = m_strlen(prefix) + m_strlen(s) * 4 + 2;
+  buf = p_new(char, bufsize);
+  m_strcpy(buf, bufsize, prefix);
+  p = buf + m_strlen(buf);
+  if (is_pgp && m_strlen(s) == 40) {     /* PGP v4 style formatted. */
+    for (i = 0; *s && s[1] && s[2] && s[3] && s[4]; s += 4, i++) {
+      *p++ = s[0];
+      *p++ = s[1];
+      *p++ = s[2];
+      *p++ = s[3];
+      *p++ = ' ';
+      if (i == 4)
+        *p++ = ' ';
+    }
+  }
+  else {
+    for (i = 0; *s && s[1] && s[2]; s += 2, i++) {
+      *p++ = s[0];
+      *p++ = s[1];
+      *p++ = is_pgp ? ' ' : ':';
+      if (is_pgp && i == 7)
+        *p++ = ' ';
+    }
+  }
+
+  /* just in case print remaining odd digits */
+  for (; *s; s++)
+    *p++ = *s;
+  *p++ = '\n';
+  *p = 0;
+  state_attach_puts (buf, state);
+  p_delete(&buf);
+}
+
+/* Show the valididy of a key used for one signature. */
+static void show_one_sig_validity (gpgme_ctx_t ctx, int idx, STATE * s)
+{
+  gpgme_verify_result_t result = NULL;
+  gpgme_signature_t sig = NULL;
+  const char *txt = NULL;
+
+  result = gpgme_op_verify_result (ctx);
+  if (result)
+    for (sig = result->signatures; sig && (idx > 0); sig = sig->next, idx--);
+
+  switch (sig ? sig->validity : 0) {
+  case GPGME_VALIDITY_UNKNOWN:
+    txt = _("WARNING: We have NO indication whether "
+            "the key belongs to the person named " "as shown above\n");
+    break;
+  case GPGME_VALIDITY_UNDEFINED:
+    break;
+  case GPGME_VALIDITY_NEVER:
+    txt = _("WARNING: The key does NOT BELONG to "
+            "the person named as shown above\n");
+    break;
+  case GPGME_VALIDITY_MARGINAL:
+    txt = _("WARNING: It is NOT certain that the key "
+            "belongs to the person named as shown above\n");
+    break;
+  case GPGME_VALIDITY_FULL:
+  case GPGME_VALIDITY_ULTIMATE:
+    txt = NULL;
+    break;
+  }
+  if (txt)
+    state_attach_puts (txt, s);
+}
+
+/* Show information about one signature.  This fucntion is called with
+   the context CTX of a sucessful verification operation and the
+   enumerator IDX which should start at 0 and incremete for each
+   call/signature.
+
+   Return values are: 0 for normal procession, 1 for a bad signature,
+   2 for a signature with a warning or -1 for no more signature.  */
+static int show_one_sig_status (gpgme_ctx_t ctx, int idx, STATE * s)
+{
+  time_t created;
+  const char *fpr, *uid;
+  gpgme_key_t key = NULL;
+  int i, anybad = 0, anywarn = 0;
+  unsigned int sum;
+  gpgme_user_id_t uids = NULL;
+  gpgme_verify_result_t result;
+  gpgme_signature_t sig;
+  gpgme_error_t err = GPG_ERR_NO_ERROR;
+
+  result = gpgme_op_verify_result (ctx);
+  if (result) {
+    /* FIXME: this code should use a static variable and remember
+       the current position in the list of signatures, IMHO.
+       -moritz.  */
+
+    for (i = 0, sig = result->signatures; sig && (i < idx);
+         i++, sig = sig->next);
+    if (!sig)
+      return -1;                /* Signature not found.  */
+
+    if (signature_key) {
+      gpgme_key_unref(signature_key);
+      signature_key = NULL;
+    }
+
+    created = sig->timestamp;
+    fpr = sig->fpr;
+    sum = sig->summary;
+
+    if (gpg_err_code (sig->status) != GPG_ERR_NO_ERROR)
+      anybad = 1;
+
+    err = gpgme_get_key2 (ctx, fpr, &key, 0);    /* secret key?  */
+    if (!err) {
+      uid = (key->uids && key->uids->uid) ? key->uids->uid : "[?]";
+      if (!signature_key)
+        signature_key = key;
+    }
+    else {
+      key = NULL;               /* Old gpgme versions did not set KEY to NULL on
+                                   error.   Do it here to avoid a double free. */
+      uid = "[?]";
+    }
+
+    if (!s || !s->fpout || !(s->flags & M_DISPLAY));    /* No state information so no way to print anything. */
+    else if (err) {
+      state_attach_puts (_("Error getting key information: "), s);
+      state_attach_puts (gpg_strerror (err), s);
+      state_attach_puts ("\n", s);
+      anybad = 1;
+    }
+    else if ((sum & GPGME_SIGSUM_GREEN)) {
+      state_attach_puts (_("Good signature from: "), s);
+      state_attach_puts (uid, s);
+      state_attach_puts ("\n", s);
+      for (i = 1, uids = key->uids; uids; i++, uids = uids->next) {
+        if (i == 1)
+          /* Skip primary UID.  */
+          continue;
+        if (uids->revoked)
+          continue;
+        state_attach_puts (_("                aka: "), s);
+        state_attach_puts (uids->uid, s);
+        state_attach_puts ("\n", s);
+      }
+      state_attach_puts (_("            created: "), s);
+      print_time (created, s);
+      state_attach_puts ("\n", s);
+      if (show_sig_summary (sum, ctx, key, idx, s))
+        anywarn = 1;
+      show_one_sig_validity (ctx, idx, s);
+    }
+    else if ((sum & GPGME_SIGSUM_RED)) {
+      state_attach_puts (_("*BAD* signature claimed to be from: "), s);
+      state_attach_puts (uid, s);
+      state_attach_puts ("\n", s);
+      show_sig_summary (sum, ctx, key, idx, s);
+    }
+    else if (!anybad && key && (key->protocol == GPGME_PROTOCOL_OpenPGP)) {     /* We can't decide (yellow) but this is a PGP key with a good
+                                                                                   signature, so we display what a PGP user expects: The name,
+                                                                                   fingerprint and the key validity (which is neither fully or
+                                                                                   ultimate). */
+      state_attach_puts (_("Good signature from: "), s);
+      state_attach_puts (uid, s);
+      state_attach_puts ("\n", s);
+      state_attach_puts (_("            created: "), s);
+      print_time (created, s);
+      state_attach_puts ("\n", s);
+      show_one_sig_validity (ctx, idx, s);
+      show_fingerprint (key, s);
+      if (show_sig_summary (sum, ctx, key, idx, s))
+        anywarn = 1;
+    }
+    else {                      /* can't decide (yellow) */
+
+      state_attach_puts (_("Error checking signature"), s);
+      state_attach_puts ("\n", s);
+      show_sig_summary (sum, ctx, key, idx, s);
+    }
+
+    if (key != signature_key)
+      gpgme_key_unref(key);
+  }
+
+  return anybad ? 1 : anywarn ? 2 : 0;
+}
+
+/* Do the actual verification step. With IS_SMIME set to true we
+   assume S/MIME (surprise!) */
+static int crypt_verify_one(BODY *sigbdy, STATE *s, FILE *fp, int is_smime)
+{
+  int badsig = -1;
+  int anywarn = 0;
+  int err;
+  gpgme_ctx_t ctx;
+  gpgme_data_t signature, message;
+
+  signature = file_to_data_object (s->fpin, sigbdy->offset, sigbdy->length);
+  if (!signature)
+    return -1;
+
+  /* We need to tell gpgme about the encoding because the backend can't
+     auto-detect plain base-64 encoding which is used by S/MIME. */
+  if (is_smime)
+    gpgme_data_set_encoding (signature, GPGME_DATA_ENCODING_BASE64);
 
+  err = gpgme_data_new_from_stream(&message, fp);
+  if (err) {
+    gpgme_data_release (signature);
+    mutt_error (_("error allocating data object: %s\n"), gpgme_strerror (err));
+    return -1;
+  }
+  ctx = create_gpgme_context (is_smime);
+
+  /* Note: We don't need a current time output because GPGME avoids
+     such an attack by separating the meta information from the
+     data. */
+  state_attach_puts (_("[-- Begin signature information --]\n"), s);
+
+  err = gpgme_op_verify (ctx, signature, message, NULL);
+  mutt_need_hard_redraw ();
+  if (err) {
+    char buf[200];
+
+    snprintf (buf, sizeof (buf) - 1,
+              _("Error: verification failed: %s\n"), gpgme_strerror (err));
+    state_attach_puts (buf, s);
+  }
+  else {                        /* Verification succeeded, see what the result is. */
+    int res, idx;
+    int anybad = 0;
+
+    if (signature_key) {
+      gpgme_key_unref(signature_key);
+      signature_key = NULL;
+    }
+
+    for (idx = 0; (res = show_one_sig_status (ctx, idx, s)) != -1; idx++) {
+      if (res == 1)
+        anybad = 1;
+      else if (res == 2)
+        anywarn = 2;
+    }
+    if (!anybad)
+      badsig = 0;
+  }
+
+  if (!badsig) {
+    gpgme_verify_result_t result;
+    gpgme_sig_notation_t notation;
+    gpgme_signature_t sig;
+
+    result = gpgme_op_verify_result (ctx);
+    if (result) {
+      for (sig = result->signatures; sig; sig = sig->next) {
+        if (sig->notations) {
+          state_attach_puts ("*** Begin Notation (signature by: ", s);
+          state_attach_puts (sig->fpr, s);
+          state_attach_puts (") ***\n", s);
+          for (notation = sig->notations; notation; notation = notation->next)
+          {
+            if (notation->name) {
+              state_attach_puts (notation->name, s);
+              state_attach_puts ("=", s);
+            }
+            if (notation->value) {
+              state_attach_puts (notation->value, s);
+              if (!(*notation->value
+                    && (notation->value[m_strlen(notation->value) - 1] ==
+                        '\n')))
+                state_attach_puts ("\n", s);
+            }
+          }
+          state_attach_puts ("*** End Notation ***\n", s);
+        }
+      }
+    }
+  }
+
+  gpgme_release (ctx);
+
+  state_attach_puts (_("[-- End signature information --]\n\n"), s);
+
+  return badsig ? 1 : anywarn ? 2 : 0;
+}
+
+/* Decrypt a PGP or SMIME message (depending on the boolean flag
+   IS_SMIME) with body A described further by state S.  Write
+   plaintext out to file FPOUT and return a new body.  For PGP returns
+   a flag in R_IS_SIGNED to indicate whether this is a combined
+   encrypted and signed message, for S/MIME it returns true when it is
+   not a encrypted but a signed message.  */
+static BODY *
+decrypt_part(BODY *a, STATE *s, FILE *fpout, int is_smime, int *r_is_signed)
+{
+  struct stat info;
+  BODY *tattach;
+  int err = 0;
+  gpgme_ctx_t ctx;
+  gpgme_data_t ciphertext, plaintext;
+  int maybe_signed = 0;
+  int anywarn = 0;
+  int sig_stat = 0;
+
+  if (r_is_signed)
+    *r_is_signed = 0;
+
+  ctx = create_gpgme_context (is_smime);
+
+restart:
+  /* Make a data object from the body, create context etc. */
+  ciphertext = file_to_data_object (s->fpin, a->offset, a->length);
+  if (!ciphertext)
+    return NULL;
+  plaintext = create_gpgme_data ();
+
+  /* Do the decryption or the verification in case of the S/MIME hack. */
+  if ((!is_smime) || maybe_signed) {
+    if (!is_smime)
+      err = gpgme_op_decrypt_verify (ctx, ciphertext, plaintext);
+    else if (maybe_signed)
+      err = gpgme_op_verify (ctx, ciphertext, NULL, plaintext);
+
+    {
+      /* Check wether signatures have been verified.  */
+      gpgme_verify_result_t verify_result = gpgme_op_verify_result (ctx);
+
+      if (verify_result->signatures)
+        sig_stat = 1;
+    }
+  }
+  else
+    err = gpgme_op_decrypt (ctx, ciphertext, plaintext);
+  gpgme_data_release (ciphertext);
+  if (err) {
+    if (is_smime && !maybe_signed && gpg_err_code (err) == GPG_ERR_NO_DATA) {
+      /* Check whether this might be a signed message despite what
+         the mime header told us.  Retry then.  gpgsm returns the
+         error information "unsupported Algorithm '?'" but gpgme
+         will not store this unknown algorithm, thus we test that
+         it has not been set. */
+      gpgme_decrypt_result_t result;
+
+      result = gpgme_op_decrypt_result (ctx);
+      if (!result->unsupported_algorithm) {
+        maybe_signed = 1;
+        gpgme_data_release (plaintext);
+        goto restart;
+      }
+    }
+    mutt_need_hard_redraw ();
+    if ((s->flags & M_DISPLAY)) {
+      char buf[200];
+
+      snprintf (buf, sizeof (buf) - 1,
+                _("[-- Error: decryption failed: %s --]\n\n"),
+                gpgme_strerror (err));
+      state_attach_puts (buf, s);
+    }
+    gpgme_data_release (plaintext);
+    gpgme_release (ctx);
+    return NULL;
+  }
+  mutt_need_hard_redraw ();
+
+  /* Read the output from GPGME, and make sure to change CRLF to LF,
+     otherwise read_mime_header has a hard time parsing the message.  */
+  if (data_object_to_stream (plaintext, fpout)) {
+    gpgme_data_release (plaintext);
+    gpgme_release (ctx);
+    return NULL;
+  }
+  gpgme_data_release (plaintext);
+
+  a->is_signed_data = 0;
+  if (sig_stat) {
+    int res, idx;
+    int anybad = 0;
+
+    if (maybe_signed)
+      a->is_signed_data = 1;
+    if (r_is_signed)
+      *r_is_signed = -1;        /* A signature exists. */
+
+    if ((s->flags & M_DISPLAY))
+      state_attach_puts (_("[-- Begin signature " "information --]\n"), s);
+    for (idx = 0; (res = show_one_sig_status (ctx, idx, s)) != -1; idx++) {
+      if (res == 1)
+        anybad = 1;
+      else if (res == 2)
+        anywarn = 1;
+    }
+    if (!anybad && idx && r_is_signed && *r_is_signed)
+      *r_is_signed = anywarn ? 2 : 1;   /* Good signature. */
+
+    if ((s->flags & M_DISPLAY))
+      state_attach_puts (_("[-- End signature " "information --]\n\n"), s);
+  }
+  gpgme_release (ctx);
+  ctx = NULL;
+
+  fflush (fpout);
+  rewind (fpout);
+  tattach = mutt_read_mime_header (fpout, 0);
+  if (tattach) {
+    /*
+     * Need to set the length of this body part.
+     */
+    fstat (fileno (fpout), &info);
+    tattach->length = info.st_size - tattach->offset;
+
+    tattach->warnsig = anywarn;
+
+    /* See if we need to recurse on this MIME part.  */
+    mutt_parse_part (fpout, tattach);
+  }
+
+  return tattach;
+}
+
+/* Decrypt a PGP/MIME message in FPIN and B and return a new body and
+   the stream in CUR and FPOUT.  Returns 0 on success. */
+int crypt_pgp_decrypt_mime (FILE * fpin, FILE **fpout, BODY *b, BODY **cur)
+{
+    STATE s;
+    BODY *first_part = b;
+    int is_signed;
+
+    first_part->goodsig = 0;
+    first_part->warnsig = 0;
+
+    if (!mutt_is_multipart_encrypted(b) || !b->parts || !b->parts->next)
+        return -1;
+
+    b = b->parts->next;
+
+    p_clear(&s, 1);
+    s.fpin = fpin;
+    *fpout = tmpfile();
+    if (!*fpout) {
+        mutt_perror (_("Can't create temporary file"));
+        return -1;
+    }
+
+    *cur = decrypt_part(b, &s, *fpout, 0, &is_signed);
+    rewind(*fpout);
+    first_part->goodsig = is_signed > 0;
+    return *cur ? 0 : -1;
+}
+
+
+/* Decrypt a S/MIME message in FPIN and B and return a new body and
+   the stream in CUR and FPOUT.  Returns 0 on success. */
+int crypt_smime_decrypt_mime(FILE *fpin, FILE **fpout, BODY *b, BODY **cur)
+{
+  STATE s;
+  FILE *tmpfp = NULL;
+  int is_signed;
+  long saved_b_offset;
+  ssize_t saved_b_length;
+  int saved_b_type;
+
+  if (!mutt_is_application_smime (b))
+    return -1;
+
+  if (b->parts)
+    return -1;
+
+  /* Decode the body - we need to pass binary CMS to the
+     backend.  The backend allows for Base64 encoded data but it does
+     not allow for QP which I have seen in some messages.  So better
+     do it here. */
+  saved_b_type = b->type;
+  saved_b_offset = b->offset;
+  saved_b_length = b->length;
+  p_clear(&s, 1);
+  s.fpin = fpin;
+  fseeko (s.fpin, b->offset, 0);
+  tmpfp = tmpfile();
+  if (!tmpfp) {
+    mutt_perror (_("Can't create temporary file"));
+    return -1;
+  }
+
+  s.fpout = tmpfp;
+  mutt_decode_attachment (b, &s);
+  fflush (tmpfp);
+  b->length = ftello (s.fpout);
+  b->offset = 0;
+  rewind (tmpfp);
+
+  p_clear(&s, 1);
+  s.fpin = tmpfp;
+  s.fpout = 0;
+  *fpout = tmpfile();
+  if (!*fpout) {
+    mutt_perror (_("Can't create temporary file"));
+    return -1;
+  }
+
+  *cur = decrypt_part (b, &s, *fpout, 1, &is_signed);
+  if (*cur)
+    (*cur)->goodsig = is_signed > 0;
+  b->type = saved_b_type;
+  b->length = saved_b_length;
+  b->offset = saved_b_offset;
+  m_fclose(&tmpfp);
+  rewind (*fpout);
+  if (*cur && !is_signed && !(*cur)->parts
+      && mutt_is_application_smime (*cur)) {
+    /* Assume that this is a opaque signed s/mime message.  This is
+       an ugly way of doing it but we have anyway a problem with
+       arbitrary encoded S/MIME messages: Only the outer part may be
+       encrypted.  The entire mime parsing should be revamped,
+       probably by keeping the temportary files so that we don't
+       need to decrypt them all the time.  Inner parts of an
+       encrypted part can then pint into this file and tehre won't
+       never be a need to decrypt again.  This needs a partial
+       rewrite of the MIME engine. */
+    BODY *bb = *cur;
+    BODY *tmp_b;
+
+    saved_b_type = bb->type;
+    saved_b_offset = bb->offset;
+    saved_b_length = bb->length;
+    p_clear(&s, 1);
+    s.fpin = *fpout;
+    fseeko (s.fpin, bb->offset, 0);
+    tmpfp = tmpfile();
+    if (!tmpfp) {
+      mutt_perror (_("Can't create temporary file"));
+      return -1;
+    }
+
+    s.fpout = tmpfp;
+    mutt_decode_attachment (bb, &s);
+    fflush (tmpfp);
+    bb->length = ftello (s.fpout);
+    bb->offset = 0;
+    rewind (tmpfp);
+    m_fclose(&*fpout);
+
+    p_clear(&s, 1);
+    s.fpin = tmpfp;
+    s.fpout = 0;
+    *fpout = tmpfile();
+    if (!*fpout) {
+      mutt_perror (_("Can't create temporary file"));
+      return -1;
+    }
+
+    tmp_b = decrypt_part (bb, &s, *fpout, 1, &is_signed);
+    if (tmp_b)
+      tmp_b->goodsig = is_signed > 0;
+    bb->type = saved_b_type;
+    bb->length = saved_b_length;
+    bb->offset = saved_b_offset;
+    m_fclose(&tmpfp);
+    rewind (*fpout);
+    body_list_wipe(cur);
+    *cur = tmp_b;
+  }
+  return *cur ? 0 : -1;
+}
+
+
+static int
+pgp_check_traditional_one_body(FILE *fp, BODY *b, int tagged_only)
+{
+  char tempfile[_POSIX_PATH_MAX];
+  char buf[HUGE_STRING];
+  FILE *tfp;
+  int tempfd;
+
+  short sgn = 0;
+  short enc = 0;
+
+  if (b->type != TYPETEXT)
+    return 0;
+
+  if (tagged_only && !b->tagged)
+    return 0;
+
+  tempfd = m_tempfd(tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL);
+  if (mutt_decode_save_attachment (fp, b, tempfd, 0) != 0) {
+    unlink (tempfile);
+    return 0;
+  }
+
+  if ((tfp = fopen(tempfile, "r")) == NULL) {
+    unlink (tempfile);
+    return 0;
+  }
+
+  while (fgets (buf, sizeof (buf), tfp)) {
+    if (!m_strncmp("-----BEGIN PGP ", buf, 15)) {
+      if (!m_strcmp("MESSAGE-----\n", buf + 15))
+        enc = 1;
+      else if (!m_strcmp("SIGNED MESSAGE-----\n", buf + 15))
+        sgn = 1;
+    }
+  }
+  m_fclose(&tfp);
+  unlink (tempfile);
+
+  if (!enc && !sgn)
+    return 0;
+
+  /* fix the content type */
+
+  parameter_setval(&b->parameter, "format", "fixed");
+  parameter_setval(&b->parameter, "x-action",
+                   enc ? "pgp-encrypted" : "pgp-signed");
+  return 1;
+}
+
+int crypt_pgp_check_traditional(FILE *fp, BODY *b, int tagged_only)
+{
+    int rv = 0;
+
+    for (; b; b = b->next) {
+        if (is_multipart(b))
+            rv |= crypt_pgp_check_traditional(fp, b->parts, tagged_only);
+        if (b->type == TYPETEXT) {
+            int r;
+            if ((r = mutt_is_application_pgp(b))) {
+                rv |= r;
+            } else {
+                rv |= pgp_check_traditional_one_body(fp, b, tagged_only);
+            }
+        }
+    }
+
+    return rv;
+}
+
+/*
+  Copy a clearsigned message, and strip the signature and PGP's
+  dash-escaping.
+
+  XXX - charset handling: We assume that it is safe to do
+  character set decoding first, dash decoding second here, while
+  we do it the other way around in the main handler.
+
+  (Note that we aren't worse than Outlook & Cie in this, and also
+  note that we can successfully handle anything produced by any
+  existing versions of mutt.)  */
+static void copy_clearsigned(gpgme_data_t data, STATE * s, char *charset)
+{
+  char buf[HUGE_STRING];
+  short complete, armor_header;
+  fgetconv_t *fc;
+  char *fname;
+  FILE *fp;
+
+  fname = data_object_to_tempfile(data, &fp);
+  if (!fname)
+    return;
+  unlink (fname);
+  p_delete(&fname);
+
+  fc = fgetconv_open (fp, charset, MCharset.charset, M_ICONV_HOOK_FROM);
+
+  for (complete = 1, armor_header = 1;
+       fgetconvs (buf, sizeof (buf), fc) != NULL;
+       complete = strchr (buf, '\n') != NULL) {
+    if (!complete) {
+      if (!armor_header)
+        state_puts (buf, s);
+      continue;
+    }
+
+    if (!m_strcmp(buf, "-----BEGIN PGP SIGNATURE-----\n"))
+      break;
+
+    if (armor_header) {
+      if (buf[0] == '\n')
+        armor_header = 0;
+      continue;
+    }
+
+    if (s->prefix)
+      state_puts (s->prefix, s);
+
+    if (buf[0] == '-' && buf[1] == ' ')
+      state_puts (buf + 2, s);
+    else
+      state_puts (buf, s);
+  }
+
+  fgetconv_close (&fc);
+  m_fclose(&fp);
+}
+
+/* Support for classic_application/pgp */
+int crypt_pgp_application_pgp_handler(BODY *m, STATE *s)
+{
+  int needpass = -1, pgp_keyblock = 0;
+  int clearsign = 0;
+  long start_pos = 0;
+  long bytes;
+  off_t last_pos, offset;
+  char buf[HUGE_STRING];
+  FILE *pgpout = NULL;
+
+  gpgme_error_t err = 0;
+  gpgme_data_t armored_data = NULL;
+
+  short maybe_goodsig = 1;
+  short have_any_sigs = 0;
+
+  char body_charset[STRING];    /* Only used for clearsigned messages. */
+
+  /* For clearsigned messages we won't be able to get a character set
+     but we know that this may only be text thus we assume Latin-1
+     here. */
+  if (!mutt_get_body_charset (body_charset, sizeof (body_charset), m))
+    m_strcpy(body_charset, sizeof(body_charset), "iso-8859-1");
+
+  fseeko (s->fpin, m->offset, 0);
+  last_pos = m->offset;
+
+  for (bytes = m->length; bytes > 0;) {
+    if (fgets (buf, sizeof (buf), s->fpin) == NULL)
+      break;
+
+    offset = ftello (s->fpin);
+    bytes -= (offset - last_pos);       /* don't rely on m_strlen(buf) */
+    last_pos = offset;
+
+    if (!m_strncmp("-----BEGIN PGP ", buf, 15)) {
+      clearsign = 0;
+      start_pos = last_pos;
+
+      if (!m_strcmp("MESSAGE-----\n", buf + 15))
+        needpass = 1;
+      else if (!m_strcmp("SIGNED MESSAGE-----\n", buf + 15)) {
+        clearsign = 1;
+        needpass = 0;
+      }
+      else if (!m_strcmp("PUBLIC KEY BLOCK-----\n", buf + 15)) {
+        needpass = 0;
+        pgp_keyblock = 1;
+      }
+      else {
+        /* XXX - we may wish to recode here */
+        if (s->prefix)
+          state_puts (s->prefix, s);
+        state_puts (buf, s);
+        continue;
+      }
+
+      have_any_sigs = (have_any_sigs || (clearsign && (s->flags & M_VERIFY)));
+
+      /* Copy PGP material to an data container */
+      armored_data = create_gpgme_data ();
+      gpgme_data_write (armored_data, buf, m_strlen(buf));
+      while (bytes > 0 && fgets (buf, sizeof (buf) - 1, s->fpin) != NULL) {
+        offset = ftello (s->fpin);
+        bytes -= (offset - last_pos);   /* don't rely on m_strlen(buf) */
+        last_pos = offset;
+
+        gpgme_data_write (armored_data, buf, m_strlen(buf));
+
+        if ((needpass && !m_strcmp("-----END PGP MESSAGE-----\n", buf))
+            || (!needpass
+                && (!m_strcmp("-----END PGP SIGNATURE-----\n", buf)
+                    || !m_strcmp("-----END PGP PUBLIC KEY BLOCK-----\n",
+                                     buf))))
+          break;
+      }
+
+      /* Invoke PGP if needed */
+      if (!clearsign || (s->flags & M_VERIFY)) {
+        unsigned int sig_stat = 0;
+        gpgme_data_t plaintext;
+        gpgme_ctx_t ctx;
+
+        plaintext = create_gpgme_data ();
+        ctx = create_gpgme_context (0);
+
+        if (clearsign)
+          err = gpgme_op_verify (ctx, armored_data, NULL, plaintext);
+        else {
+          err = gpgme_op_decrypt_verify (ctx, armored_data, plaintext);
+          if (gpg_err_code (err) == GPG_ERR_NO_DATA) {
+            /* Decrypt verify can't handle signed only messages. */
+            err = (gpgme_data_seek (armored_data, 0, SEEK_SET) == -1)
+              ? gpgme_error_from_errno (errno) : 0;
+            /* Must release plaintext so that we supply an
+               uninitialized object. */
+            gpgme_data_release (plaintext);
+            plaintext = create_gpgme_data ();
+            err = gpgme_op_verify (ctx, armored_data, NULL, plaintext);
+          }
+        }
+
+        if (err) {
+          char errbuf[200];
+
+          snprintf (errbuf, sizeof (errbuf) - 1,
+                    _("Error: decryption/verification failed: %s\n"),
+                    gpgme_strerror (err));
+          state_attach_puts (errbuf, s);
+        }
+        else {                  /* Decryption/Verification succeeded */
+          char *tmpfname;
+
+          {
+            /* Check wether signatures have been verified.  */
+            gpgme_verify_result_t verify_result;
+
+            verify_result = gpgme_op_verify_result (ctx);
+            if (verify_result->signatures)
+              sig_stat = 1;
+          }
+
+          have_any_sigs = 0;
+          maybe_goodsig = 0;
+          if ((s->flags & M_DISPLAY) && sig_stat) {
+            int res, idx;
+            int anybad = 0;
+            int anywarn = 0;
+
+            state_attach_puts (_("[-- Begin signature "
+                                 "information --]\n"), s);
+            have_any_sigs = 1;
+            for (idx = 0;
+                 (res = show_one_sig_status (ctx, idx, s)) != -1; idx++) {
+              if (res == 1)
+                anybad = 1;
+              else if (res == 2)
+                anywarn = 1;
+            }
+            if (!anybad && idx)
+              maybe_goodsig = 1;
+
+            state_attach_puts (_("[-- End signature "
+                                 "information --]\n\n"), s);
+          }
+
+          tmpfname = data_object_to_tempfile(plaintext, &pgpout);
+          if (!tmpfname) {
+            pgpout = NULL;
+            state_attach_puts (_("Error: copy data failed\n"), s);
+          }
+          else {
+            unlink (tmpfname);
+            p_delete(&tmpfname);
+          }
+        }
+        gpgme_release (ctx);
+      }
+
+      /*
+       * Now, copy cleartext to the screen.  NOTE - we expect that PGP
+       * outputs utf-8 cleartext.  This may not always be true, but it
+       * seems to be a reasonable guess.
+       */
+
+      if (s->flags & M_DISPLAY) {
+        if (needpass)
+          state_attach_puts (_("[-- BEGIN PGP MESSAGE --]\n\n"), s);
+        else if (pgp_keyblock)
+          state_attach_puts (_("[-- BEGIN PGP PUBLIC KEY BLOCK --]\n"), s);
+        else
+          state_attach_puts (_("[-- BEGIN PGP SIGNED MESSAGE --]\n\n"), s);
+      }
+
+      if (clearsign) {
+        copy_clearsigned (armored_data, s, body_charset);
+      }
+      else if (pgpout) {
+        fgetconv_t *fc;
+        int c;
+
+        rewind (pgpout);
+        fc = fgetconv_open (pgpout, "utf-8", MCharset.charset, 0);
+        while ((c = fgetconv (fc)) != EOF) {
+          state_putc (c, s);
+          if (c == '\n' && s->prefix)
+            state_puts (s->prefix, s);
+        }
+        fgetconv_close (&fc);
+      }
+
+      if (s->flags & M_DISPLAY) {
+        state_putc ('\n', s);
+        if (needpass)
+          state_attach_puts (_("[-- END PGP MESSAGE --]\n"), s);
+        else if (pgp_keyblock)
+          state_attach_puts (_("[-- END PGP PUBLIC KEY BLOCK --]\n"), s);
+        else
+          state_attach_puts (_("[-- END PGP SIGNED MESSAGE --]\n"), s);
+      }
+
+      if (pgpout) {
+        m_fclose(&pgpout);
+      }
+    }
+    else {
+      /* XXX - we may wish to recode here */
+      if (s->prefix)
+        state_puts (s->prefix, s);
+      state_puts (buf, s);
+    }
+  }
+
+  m->goodsig = (maybe_goodsig && have_any_sigs);
+
+  if (needpass == -1) {
+    state_attach_puts (_("[-- Error: could not find beginning"
+                         " of PGP message! --]\n\n"), s);
+    return (-1);
+  }
+  return (err);
+}
+
+/* MIME handler for pgp/mime encrypted messages. */
+int crypt_pgp_encrypted_handler (BODY * a, STATE * s)
+{
+  char tempfile[_POSIX_PATH_MAX];
+  FILE *fpout;
+  BODY *tattach;
+  BODY *orig_body = a;
+  int is_signed;
+  int rc = 0;
+
+  a = a->parts;
+  if (!a || a->type != TYPEAPPLICATION || !a->subtype
+      || ascii_strcasecmp ("pgp-encrypted", a->subtype)
+      || !a->next || a->next->type != TYPEAPPLICATION || !a->next->subtype
+      || ascii_strcasecmp ("octet-stream", a->next->subtype)) {
+    if (s->flags & M_DISPLAY)
+      state_attach_puts (_("[-- Error: malformed PGP/MIME message! --]\n\n"),
+                         s);
+    return (-1);
+  }
+
+  /* Move forward to the application/pgp-encrypted body. */
+  a = a->next;
+
+  fpout = m_tempfile(tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL);
+  if (!fpout) {
+    if (s->flags & M_DISPLAY)
+      state_attach_puts (_("[-- Error: could not create temporary file! "
+                           "--]\n"), s);
+    return (-1);
+  }
+
+  tattach = decrypt_part (a, s, fpout, 0, &is_signed);
+  if (tattach) {
+    tattach->goodsig = is_signed > 0;
+
+    if (s->flags & M_DISPLAY)
+      state_attach_puts (is_signed ?
+                         _
+                         ("[-- The following data is PGP/MIME signed and encrypted --]\n\n") :
+                         _("[-- The following data is PGP/MIME encrypted --]\n\n"), s);
+
+    {
+      FILE *savefp = s->fpin;
+
+      s->fpin = fpout;
+      rc = mutt_body_handler (tattach, s);
+      s->fpin = savefp;
+    }
+
+    /*
+     * if a multipart/signed is the _only_ sub-part of a
+     * multipart/encrypted, cache signature verification
+     * status.
+     */
+    if (mutt_is_multipart_signed (tattach) && !tattach->next)
+      orig_body->goodsig |= tattach->goodsig;
+
+    if (s->flags & M_DISPLAY) {
+      state_puts ("\n", s);
+      state_attach_puts (is_signed ?
+                         _
+                         ("[-- End of PGP/MIME signed and encrypted data --]\n")
+                         : _("[-- End of PGP/MIME encrypted data --]\n"), s);
+    }
+
+    body_list_wipe(&tattach);
+  }
+
+  m_fclose(&fpout);
+  mutt_unlink (tempfile);
+  return (rc);
+}
+
+/* Support for application/smime */
+int crypt_smime_application_smime_handler (BODY * a, STATE * s)
+{
+  char tempfile[_POSIX_PATH_MAX];
+  FILE *fpout;
+  BODY *tattach;
+  int is_signed;
+  int rc = 0;
+
+  a->warnsig = 0;
+  fpout = m_tempfile(tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL);
+  if (!fpout) {
+    if (s->flags & M_DISPLAY)
+      state_attach_puts (_("[-- Error: could not create temporary file! "
+                           "--]\n"), s);
+    return (-1);
+  }
+
+  tattach = decrypt_part (a, s, fpout, 1, &is_signed);
+  if (tattach) {
+    tattach->goodsig = is_signed > 0;
+
+    if (s->flags & M_DISPLAY)
+      state_attach_puts (is_signed ?
+                         _("[-- The following data is S/MIME signed --]\n\n") :
+                         _("[-- The following data is S/MIME encrypted --]\n\n"), s);
+
+    {
+      FILE *savefp = s->fpin;
+
+      s->fpin = fpout;
+      rc = mutt_body_handler (tattach, s);
+      s->fpin = savefp;
+    }
+
+    /*
+     * if a multipart/signed is the _only_ sub-part of a
+     * multipart/encrypted, cache signature verification
+     * status.
+     */
+    if (mutt_is_multipart_signed (tattach) && !tattach->next) {
+      if (!(a->goodsig = tattach->goodsig))
+        a->warnsig = tattach->warnsig;
+    }
+    else if (tattach->goodsig) {
+      a->goodsig = 1;
+      a->warnsig = tattach->warnsig;
+    }
+
+    if (s->flags & M_DISPLAY) {
+      state_puts ("\n", s);
+      state_attach_puts (is_signed ?
+                         _("[-- End of S/MIME signed data --]\n") :
+                         _("[-- End of S/MIME encrypted data --]\n"), s);
+    }
+
+    body_list_wipe(&tattach);
+  }
+
+  m_fclose(&fpout);
+  mutt_unlink (tempfile);
+  return (rc);
+}
+
+
+/*
+ * Format an entry on the CRYPT key selection menu.
+ *
+ * %n  number
+ * %k  key id          %K      key id of the principal key
+ * %u  user id
+ * %a  algorithm       %A      algorithm of the princ. key
+ * %l  length          %L      length of the princ. key
+ * %f  flags           %F      flags of the princ. key
+ * %c  capabilities    %C      capabilities of the princ. key
+ * %t  trust/validity of the key-uid association
+ * %p           protocol
+ * %[...] date of key using strftime(3)
+ */
+
+static const char *
+crypt_entry_fmt (char *dest, ssize_t destlen, char op,
+                 const char *src, const char *prefix,
+                 const char *ifstr, const char *elstr,
+                 anytype data, format_flag flags)
+{
+  char fmt[16];
+  crypt_entry_t *entry;
+  cryptkey_t *key;
+  int kflags = 0;
+  int optional = (flags & M_FORMAT_OPTIONAL);
+  const char *s = NULL;
+  unsigned long val;
+
+  entry = data.ptr;
+  key = entry->key;
+
+/*    if (isupper ((unsigned char) op)) */
+/*      key = pkey; */
+
+  kflags = (key->flags          /*| (pkey->flags & KEYFLAG_RESTRICTIONS)
+                                   | uid->flags */ );
+
+  switch (ascii_tolower (op)) {
+  case '[':
+    {
+      const char *cp;
+      char buf2[STRING], *p;
+      int do_locales;
+      struct tm *tm;
+      ssize_t len;
+
+      p = dest;
+
+      cp = src;
+      if (*cp == '!') {
+        do_locales = 0;
+        cp++;
+      }
+      else
+        do_locales = 1;
+
+      len = destlen - 1;
+      while (len > 0 && *cp != ']') {
+        if (*cp == '%') {
+          cp++;
+          if (len >= 2) {
+            *p++ = '%';
+            *p++ = *cp;
+            len -= 2;
+          }
+          else
+            break;              /* not enough space */
+          cp++;
+        }
+        else {
+          *p++ = *cp++;
+          len--;
+        }
+      }
+      *p = 0;
+
+      if (do_locales && Locale)
+        setlocale (LC_TIME, Locale);
+
+      {
+        time_t tt = 0;
+
+        if (key->kobj->subkeys && (key->kobj->subkeys->timestamp > 0))
+          tt = key->kobj->subkeys->timestamp;
+
+        tm = localtime (&tt);
+      }
+      strftime (buf2, sizeof (buf2), dest, tm);
+
+      if (do_locales)
+        setlocale (LC_TIME, "C");
+
+      snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
+      snprintf (dest, destlen, fmt, buf2);
+      if (len > 0)
+        src = cp + 1;
+    }
+    break;
+  case 'n':
+    if (!optional) {
+      snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
+      snprintf (dest, destlen, fmt, entry->num);
+    }
+    break;
+  case 'k':
+    if (!optional) {
+      /* fixme: we need a way to distinguish between main and subkeys.
+         Store the idx in entry? */
+      snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
+      snprintf (dest, destlen, fmt, crypt_keyid (key));
+    }
+    break;
+  case 'u':
+    if (!optional) {
+      snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
+      snprintf (dest, destlen, fmt, key->uid);
+    }
+    break;
+  case 'a':
+    if (!optional) {
+      snprintf (fmt, sizeof (fmt), "%%%s.3s", prefix);
+      if (key->kobj->subkeys)
+        s = gpgme_pubkey_algo_name (key->kobj->subkeys->pubkey_algo);
+      else
+        s = "?";
+      snprintf (dest, destlen, fmt, s);
+    }
+    break;
+  case 'l':
+    if (!optional) {
+      snprintf (fmt, sizeof (fmt), "%%%slu", prefix);
+      if (key->kobj->subkeys)
+        val = key->kobj->subkeys->length;
+      else
+        val = 0;
+      snprintf (dest, destlen, fmt, val);
+    }
+    break;
+  case 'f':
+    if (!optional) {
+      snprintf (fmt, sizeof (fmt), "%%%sc", prefix);
+      snprintf (dest, destlen, fmt, crypt_flags (kflags));
+    }
+    else if (!(kflags & (KEYFLAG_RESTRICTIONS)))
+      optional = 0;
+    break;
+  case 'c':
+    if (!optional) {
+      snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
+      snprintf (dest, destlen, fmt, crypt_key_abilities (kflags));
+    }
+    else if (!(kflags & (KEYFLAG_ABILITIES)))
+      optional = 0;
+    break;
+  case 't':
+    if ((kflags & KEYFLAG_ISX509))
+      s = "x";
+    else {
+      gpgme_user_id_t uid = NULL;
+      int i = 0;
+
+      for (i = 0, uid = key->kobj->uids; uid && (i < key->idx);
+           i++, uid = uid->next);
+      if (uid)
+        switch (uid->validity) {
+        case GPGME_VALIDITY_UNDEFINED:
+          s = "q";
+          break;
+        case GPGME_VALIDITY_NEVER:
+          s = "n";
+          break;
+        case GPGME_VALIDITY_MARGINAL:
+          s = "m";
+          break;
+        case GPGME_VALIDITY_FULL:
+          s = "f";
+          break;
+        case GPGME_VALIDITY_ULTIMATE:
+          s = "u";
+          break;
+        case GPGME_VALIDITY_UNKNOWN:
+        default:
+          s = "?";
+          break;
+        }
+    }
+    snprintf (fmt, sizeof (fmt), "%%%sc", prefix);
+    snprintf (dest, destlen, fmt, s ? *s : 'B');
+    break;
+  case 'p':
+    snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
+    snprintf (dest, destlen, fmt,
+              gpgme_get_protocol_name (key->kobj->protocol));
+    break;
+
+  default:
+    *dest = '\0';
+  }
+
+  if (flags & M_FORMAT_OPTIONAL)
+    m_strformat(dest, destlen, 0, optional ? ifstr: elstr,
+                mutt_attach_fmt, data, 0);
+  return src;
+}
+
+/* Used by the display fucntion to format a line. */
+static void crypt_entry (char *s, ssize_t l, MUTTMENU * menu, int num)
+{
+  cryptkey_t **cryptkey_table = (cryptkey_t **) menu->data;
+  crypt_entry_t entry;
+
+  entry.key = cryptkey_table[num];
+  entry.num = num + 1;
+
+  m_strformat(s, l, COLS - SW, PgpEntryFormat, crypt_entry_fmt, &entry,
+              option(OPTARROWCURSOR) ? M_FORMAT_ARROWCURSOR : 0);
+}
+
+/* Compare two addresses and the keyid to be used for sorting. */
+static int _crypt_compare_address (const void *a, const void *b)
+{
+  cryptkey_t **s = (cryptkey_t **) a;
+  cryptkey_t **t = (cryptkey_t **) b;
+  int r;
+
+  if ((r = m_strcasecmp((*s)->uid, (*t)->uid)))
+    return r > 0;
+  else
+    return m_strcasecmp(crypt_keyid (*s), crypt_keyid (*t)) > 0;
+}
+
+static int crypt_compare_address (const void *a, const void *b)
+{
+  return ((PgpSortKeys & SORT_REVERSE) ? !_crypt_compare_address (a, b)
+          : _crypt_compare_address (a, b));
+}
+
+
+/* Compare two key IDs and the addresses to be used for sorting. */
+static int _crypt_compare_keyid (const void *a, const void *b)
+{
+  cryptkey_t **s = (cryptkey_t **) a;
+  cryptkey_t **t = (cryptkey_t **) b;
+  int r;
+
+  if ((r = m_strcasecmp(crypt_keyid (*s), crypt_keyid (*t))))
+    return r > 0;
+  else
+    return m_strcasecmp((*s)->uid, (*t)->uid) > 0;
+}
+
+static int crypt_compare_keyid (const void *a, const void *b)
+{
+  return ((PgpSortKeys & SORT_REVERSE) ? !_crypt_compare_keyid (a, b)
+          : _crypt_compare_keyid (a, b));
+}
+
+/* Compare 2 creation dates and the addresses.  For sorting. */
+static int _crypt_compare_date (const void *a, const void *b)
+{
+  cryptkey_t **s = (cryptkey_t **) a;
+  cryptkey_t **t = (cryptkey_t **) b;
+  unsigned long ts = 0, tt = 0;
+
+  if ((*s)->kobj->subkeys && ((*s)->kobj->subkeys->timestamp > 0))
+    ts = (*s)->kobj->subkeys->timestamp;
+  if ((*t)->kobj->subkeys && ((*t)->kobj->subkeys->timestamp > 0))
+    tt = (*t)->kobj->subkeys->timestamp;
+
+  if (ts > tt)
+    return 1;
+  if (ts < tt)
+    return 0;
+
+  return m_strcasecmp((*s)->uid, (*t)->uid) > 0;
+}
+
+static int crypt_compare_date (const void *a, const void *b)
+{
+  return ((PgpSortKeys & SORT_REVERSE) ? !_crypt_compare_date (a, b)
+          : _crypt_compare_date (a, b));
+}
+
+/* Compare two trust values, the key length, the creation dates. the
+   addresses and the key IDs.  For sorting. */
+static int _crypt_compare_trust (const void *a, const void *b)
+{
+  cryptkey_t **s = (cryptkey_t **) a;
+  cryptkey_t **t = (cryptkey_t **) b;
+  unsigned long ts = 0, tt = 0;
+  int r;
+
+  if ((r = (((*s)->flags & (KEYFLAG_RESTRICTIONS))
+            - ((*t)->flags & (KEYFLAG_RESTRICTIONS)))))
+    return r > 0;
+
+  if ((*s)->kobj->uids)
+    ts = (*s)->kobj->uids->validity;
+  if ((*t)->kobj->uids)
+    tt = (*t)->kobj->uids->validity;
+  if ((r = (tt - ts)))
+    return r < 0;
+
+  if ((*s)->kobj->subkeys)
+    ts = (*s)->kobj->subkeys->length;
+  if ((*t)->kobj->subkeys)
+    tt = (*t)->kobj->subkeys->length;
+  if (ts != tt)
+    return ts > tt;
+
+  if ((*s)->kobj->subkeys && ((*s)->kobj->subkeys->timestamp > 0))
+    ts = (*s)->kobj->subkeys->timestamp;
+  if ((*t)->kobj->subkeys && ((*t)->kobj->subkeys->timestamp > 0))
+    tt = (*t)->kobj->subkeys->timestamp;
+  if (ts > tt)
+    return 1;
+  if (ts < tt)
+    return 0;
+
+  if ((r = m_strcasecmp((*s)->uid, (*t)->uid)))
+    return r > 0;
+  return (m_strcasecmp(crypt_keyid ((*s)), crypt_keyid ((*t)))) > 0;
+}
+
+static int crypt_compare_trust (const void *a, const void *b)
+{
+  return ((PgpSortKeys & SORT_REVERSE) ? !_crypt_compare_trust (a, b)
+          : _crypt_compare_trust (a, b));
+}
+
+/* Print the X.500 Distinguished Name part KEY from the array of parts
+   DN to FP. */
+static int print_dn_part (FILE * fp, struct dn_array_s *dn, const char *key)
+{
+  int any = 0;
+
+  for (; dn->key; dn++) {
+    if (!m_strcmp(dn->key, key)) {
+      if (any)
+        fputs (" + ", fp);
+      print_utf8 (fp, dn->value, m_strlen(dn->value));
+      any = 1;
+    }
+  }
+  return any;
+}
+
+/* Print all parts of a DN in a standard sequence. */
+static void print_dn_parts (FILE * fp, struct dn_array_s *dn)
+{
+  const char *stdpart[] = {
+    "CN", "OU", "O", "STREET", "L", "ST", "C", NULL
+  };
+  int any = 0, any2 = 0, i;
+
+  for (i = 0; stdpart[i]; i++) {
+    if (any)
+      fputs (", ", fp);
+    any = print_dn_part (fp, dn, stdpart[i]);
+  }
+  /* now print the rest without any specific ordering */
+  for (; dn->key; dn++) {
+    for (i = 0; stdpart[i]; i++) {
+      if (!m_strcmp(dn->key, stdpart[i]))
+        break;
+    }
+    if (!stdpart[i]) {
+      if (any)
+        fputs (", ", fp);
+      if (!any2)
+        fputs ("(", fp);
+      any = print_dn_part (fp, dn, dn->key);
+      any2 = 1;
+    }
+  }
+  if (any2)
+    fputs (")", fp);
+}
+
+
+/* Parse an RDN; this is a helper to parse_dn(). */
+static const unsigned char *parse_dn_part (struct dn_array_s *array,
+                                           const unsigned char *string)
+{
+  const unsigned char *s, *s1;
+  ssize_t n;
+  unsigned char *p;
+
+  /* parse attributeType */
+  for (s = string + 1; *s && *s != '='; s++);
+  if (!*s)
+    return NULL;                /* error */
+  n = s - string;
+  if (!n)
+    return NULL;                /* empty key */
+  array->key = p_dupstr(string, n );
+  p = (unsigned char *) array->key;
+  string = s + 1;
+
+  if (*string == '#') {         /* hexstring */
+    string++;
+    for (s = string; hexval(*s) >= 0; s++)
+      s++;
+    n = s - string;
+    if (!n || (n & 1))
+      return NULL;              /* empty or odd number of digits */
+    n /= 2;
+    p = p_new(unsigned char, n + 1);
+    array->value = (char *) p;
+    for (s1 = string; n; s1 += 2, n--)
+      *p++ = (hexval(*s1) << 8) | hexval(*s1);
+    *p = 0;
+  }
+  else {                        /* regular v3 quoted string */
+    for (n = 0, s = string; *s; s++) {
+      if (*s == '\\') {         /* pair */
+        s++;
+        if (*s == ',' || *s == '=' || *s == '+'
+            || *s == '<' || *s == '>' || *s == '#' || *s == ';'
+            || *s == '\\' || *s == '\"' || *s == ' ')
+          n++;
+        else if (hexval(*s) >= 0 && hexval(*s + 1) >= 0) {
+          s++;
+          n++;
+        }
+        else
+          return NULL;          /* invalid escape sequence */
+      }
+      else if (*s == '\"')
+        return NULL;            /* invalid encoding */
+      else if (*s == ',' || *s == '=' || *s == '+'
+               || *s == '<' || *s == '>' || *s == '#' || *s == ';')
+        break;
+      else
+        n++;
+    }
+
+    p = p_new(unsigned char, n + 1);
+    array->value = (char *) p;
+    for (s = string; n; s++, n--) {
+      if (*s == '\\') {
+        s++;
+        if (hexval(*s) >= 0) {
+          *p++ = (hexval(*s) << 8) | hexval(*s + 1);
+          s++;
+        }
+        else
+          *p++ = *s;
+      }
+      else
+        *p++ = *s;
+    }
+    *p = 0;
+  }
+  return s;
+}
+
+
+/* Parse a DN and return an array-ized one.  This is not a validating
+   parser and it does not support any old-stylish syntax; gpgme is
+   expected to return only rfc2253 compatible strings. */
+static struct dn_array_s *parse_dn (const unsigned char *string)
+{
+  struct dn_array_s *array;
+  ssize_t arrayidx, arraysize;
+  int i;
+
+  arraysize = 7;                /* C,ST,L,O,OU,CN,email */
+  array = p_new(struct dn_array_s, arraysize + 1);
+  arrayidx = 0;
+  while (*string) {
+    while (*string == ' ')
+      string++;
+    if (!*string)
+      break;                    /* ready */
+    if (arrayidx >= arraysize) {        /* mutt lacks a real safe_realoc - so we need to copy */
+      struct dn_array_s *a2;
+
+      arraysize += 5;
+      a2 = p_new(struct dn_array_s, arraysize + 1);
+      for (i = 0; i < arrayidx; i++) {
+        a2[i].key = array[i].key;
+        a2[i].value = array[i].value;
+      }
+      p_delete(&array);
+      array = a2;
+    }
+    array[arrayidx].key = NULL;
+    array[arrayidx].value = NULL;
+    string = parse_dn_part (array + arrayidx, string);
+    arrayidx++;
+    if (!string)
+      goto failure;
+    while (*string == ' ')
+      string++;
+    if (*string && *string != ',' && *string != ';' && *string != '+')
+      goto failure;             /* invalid delimiter */
+    if (*string)
+      string++;
+  }
+  array[arrayidx].key = NULL;
+  array[arrayidx].value = NULL;
+  return array;
+
+failure:
+  for (i = 0; i < arrayidx; i++) {
+    p_delete(&array[i].key);
+    p_delete(&array[i].value);
+  }
+  p_delete(&array);
+  return NULL;
+}
+
+
+/* Print a nice representation of the USERID and make sure it is
+   displayed in a proper way, which does mean to reorder some parts
+   for S/MIME's DNs.  USERID is a string as returned by the gpgme key
+   functions.  It is utf-8 encoded. */
+static void parse_and_print_user_id(FILE * fp, const char *userid)
+{
+  const char *s;
+  int i;
+
+  if (*userid == '<') {
+    s = strchr (userid + 1, '>');
+    if (s)
+      print_utf8 (fp, userid + 1, s - userid - 1);
+  }
+  else if (*userid == '(')
+    fputs (_("[Can't display this user ID (unknown encoding)]"), fp);
+  else if (*userid & ~127 || __m_strdigits[(int)*userid] == 255)
+    fputs (_("[Can't display this user ID (invalid encoding)]"), fp);
+  else {
+    struct dn_array_s *dn = parse_dn ((const unsigned char *) userid);
+
+    if (!dn)
+      fputs (_("[Can't display this user ID (invalid DN)]"), fp);
+    else {
+      print_dn_parts (fp, dn);
+      for (i = 0; dn[i].key; i++) {
+        p_delete(&dn[i].key);
+        p_delete(&dn[i].value);
+      }
+      p_delete(&dn);
+    }
+  }
+}
+
+typedef enum {
+    KEY_CAP_CAN_ENCRYPT,
+    KEY_CAP_CAN_SIGN,
+    KEY_CAP_CAN_CERTIFY
+} key_cap_t;
+
+static unsigned int key_check_cap(gpgme_key_t key, key_cap_t cap)
+{
+  gpgme_subkey_t subkey = NULL;
+  unsigned int ret = 0;
+
+  switch (cap) {
+  case KEY_CAP_CAN_ENCRYPT:
+    if (!(ret = key->can_encrypt))
+      for (subkey = key->subkeys; subkey; subkey = subkey->next)
+        if ((ret = subkey->can_encrypt))
+          break;
+    break;
+  case KEY_CAP_CAN_SIGN:
+    if (!(ret = key->can_sign))
+      for (subkey = key->subkeys; subkey; subkey = subkey->next)
+        if ((ret = subkey->can_sign))
+          break;
+    break;
+  case KEY_CAP_CAN_CERTIFY:
+    if (!(ret = key->can_certify))
+      for (subkey = key->subkeys; subkey; subkey = subkey->next)
+        if ((ret = subkey->can_certify))
+          break;
+    break;
+  }
+
+  return ret;
+}
+
+
+/* Print verbose information about a key or certificate to FP. */
+static void print_key_info (gpgme_key_t key, FILE * fp)
+{
+  int idx;
+  const char *s = NULL, *s2 = NULL;
+  time_t tt = 0;
+  struct tm *tm;
+  char shortbuf[STRING];
+  unsigned long aval = 0;
+  const char *delim;
+  int is_pgp = 0;
+  int i;
+  gpgme_user_id_t uid = NULL;
+
+  if (Locale)
+    setlocale (LC_TIME, Locale);
+
+  is_pgp = key->protocol == GPGME_PROTOCOL_OpenPGP;
+
+  for (idx = 0, uid = key->uids; uid; idx++, uid = uid->next) {
+    if (uid->revoked)
+      continue;
+
+    s = uid->uid;
+    fputs (idx ? _(" aka ......: ") :_("Name ......: "), fp);
+
+    if (uid->invalid) {
+      fputs (_("[Invalid]"), fp);
+      putc (' ', fp);
+    }
+    if (is_pgp)
+      print_utf8 (fp, s, m_strlen(s));
+    else
+      parse_and_print_user_id (fp, s);
+    putc ('\n', fp);
+  }
+
+  if (key->subkeys && (key->subkeys->timestamp > 0)) {
+    tt = key->subkeys->timestamp;
+
+    tm = localtime (&tt);
+#ifdef HAVE_LANGINFO_D_T_FMT
+    strftime (shortbuf, sizeof shortbuf, nl_langinfo (D_T_FMT), tm);
+#else
+    strftime (shortbuf, sizeof shortbuf, "%c", tm);
+#endif
+    fprintf (fp, _("Valid From : %s\n"), shortbuf);
+  }
+
+  if (key->subkeys && (key->subkeys->expires > 0)) {
+    tt = key->subkeys->expires;
+
+    tm = localtime (&tt);
+#ifdef HAVE_LANGINFO_D_T_FMT
+    strftime (shortbuf, sizeof shortbuf, nl_langinfo (D_T_FMT), tm);
+#else
+    strftime (shortbuf, sizeof shortbuf, "%c", tm);
+#endif
+    fprintf (fp, _("Valid To ..: %s\n"), shortbuf);
+  }
+
+  if (key->subkeys)
+    s = gpgme_pubkey_algo_name (key->subkeys->pubkey_algo);
+  else
+    s = "?";
+
+  s2 = is_pgp ? "PGP" : "X.509";
+
+  if (key->subkeys)
+    aval = key->subkeys->length;
+
+  fprintf (fp, _("Key Type ..: %s, %lu bit %s\n"), s2, aval, s);
+
+  fprintf (fp, _("Key Usage .: "));
+  delim = "";
+
+  if (key_check_cap (key, KEY_CAP_CAN_ENCRYPT)) {
+    fprintf (fp, "%s%s", delim, _("encryption"));
+    delim = _(", ");
+  }
+  if (key_check_cap (key, KEY_CAP_CAN_SIGN)) {
+    fprintf (fp, "%s%s", delim, _("signing"));
+    delim = _(", ");
+  }
+  if (key_check_cap (key, KEY_CAP_CAN_CERTIFY)) {
+    fprintf (fp, "%s%s", delim, _("certification"));
+    delim = _(", ");
+  }
+  putc ('\n', fp);
+
+  if (key->subkeys) {
+    s = key->subkeys->fpr;
+    fputs (_("Fingerprint: "), fp);
+    if (is_pgp && m_strlen(s) == 40) {
+      for (i = 0; *s && s[1] && s[2] && s[3] && s[4]; s += 4, i++) {
+        putc (*s, fp);
+        putc (s[1], fp);
+        putc (s[2], fp);
+        putc (s[3], fp);
+        putc (is_pgp ? ' ' : ':', fp);
+        if (is_pgp && i == 4)
+          putc (' ', fp);
+      }
+    }
+    else {
+      for (i = 0; *s && s[1] && s[2]; s += 2, i++) {
+        putc (*s, fp);
+        putc (s[1], fp);
+        putc (is_pgp ? ' ' : ':', fp);
+        if (is_pgp && i == 7)
+          putc (' ', fp);
+      }
+    }
+    fprintf (fp, "%s\n", s);
+  }
+
+  if (key->issuer_serial) {
+    s = key->issuer_serial;
+    if (s)
+      fprintf (fp, _("Serial-No .: 0x%s\n"), s);
+  }
+
+  if (key->issuer_name) {
+    s = key->issuer_name;
+    if (s) {
+      fprintf (fp, _("Issued By .: "));
+      parse_and_print_user_id (fp, s);
+      putc ('\n', fp);
+    }
+  }
+
+  /* For PGP we list all subkeys. */
+  if (is_pgp) {
+    gpgme_subkey_t subkey = NULL;
+
+    for (idx = 1, subkey = key->subkeys; subkey; idx++, subkey = subkey->next) {
+      s = subkey->keyid;
+
+      putc ('\n', fp);
+      if (m_strlen(s) == 16)
+        s += 8;                 /* display only the short keyID */
+      fprintf (fp, _("Subkey ....: 0x%s"), s);
+      if (subkey->revoked) {
+        putc (' ', fp);
+        fputs (_("[Revoked]"), fp);
+      }
+      if (subkey->invalid) {
+        putc (' ', fp);
+        fputs (_("[Invalid]"), fp);
+      }
+      if (subkey->expired) {
+        putc (' ', fp);
+        fputs (_("[Expired]"), fp);
+      }
+      if (subkey->disabled) {
+        putc (' ', fp);
+        fputs (_("[Disabled]"), fp);
+      }
+      putc ('\n', fp);
+
+      if (subkey->timestamp > 0) {
+        tt = subkey->timestamp;
+
+        tm = localtime (&tt);
+#ifdef HAVE_LANGINFO_D_T_FMT
+        strftime (shortbuf, sizeof shortbuf, nl_langinfo (D_T_FMT), tm);
+#else
+        strftime (shortbuf, sizeof shortbuf, "%c", tm);
+#endif
+        fprintf (fp, _("Valid From : %s\n"), shortbuf);
+      }
+
+      if (subkey->expires > 0) {
+        tt = subkey->expires;
+
+        tm = localtime (&tt);
+#ifdef HAVE_LANGINFO_D_T_FMT
+        strftime (shortbuf, sizeof shortbuf, nl_langinfo (D_T_FMT), tm);
+#else
+        strftime (shortbuf, sizeof shortbuf, "%c", tm);
+#endif
+        fprintf (fp, _("Valid To ..: %s\n"), shortbuf);
+      }
+
+      if (subkey)
+        s = gpgme_pubkey_algo_name (subkey->pubkey_algo);
+      else
+        s = "?";
+
+      if (subkey)
+        aval = subkey->length;
+      else
+        aval = 0;
+
+      fprintf (fp, _("Key Type ..: %s, %lu bit %s\n"), "PGP", aval, s);
+
+      fprintf (fp, _("Key Usage .: "));
+      delim = "";
+
+      if (subkey->can_encrypt) {
+        fprintf (fp, "%s%s", delim, _("encryption"));
+        delim = _(", ");
+      }
+      if (subkey->can_sign) {
+        fprintf (fp, "%s%s", delim, _("signing"));
+        delim = _(", ");
+      }
+      if (subkey->can_certify) {
+        fprintf (fp, "%s%s", delim, _("certification"));
+        delim = _(", ");
+      }
+      putc ('\n', fp);
+    }
+  }
+
+  if (Locale)
+    setlocale (LC_TIME, "C");
+}
+
+
+/* Show detailed information about the selected key */
+static void verify_key (cryptkey_t * key)
+{
+  FILE *fp;
+  char cmd[LONG_STRING], tempfile[_POSIX_PATH_MAX];
+  const char *s;
+  gpgme_ctx_t listctx = NULL;
+  gpgme_error_t err;
+  gpgme_key_t k = NULL;
+  int maxdepth = 100;
+
+  fp = m_tempfile (tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL);
+  if (!fp) {
+    mutt_perror (_("Can't create temporary file"));
+    return;
+  }
+  mutt_message _("Collecting data...");
+
+  print_key_info (key->kobj, fp);
+
+  err = gpgme_new (&listctx);
+  if (err) {
+    fprintf (fp, "Internal error: can't create gpgme context: %s\n",
+             gpgme_strerror (err));
+    goto leave;
+  }
+  if ((key->flags & KEYFLAG_ISX509))
+    gpgme_set_protocol (listctx, GPGME_PROTOCOL_CMS);
+
+  k = key->kobj;
+  gpgme_key_ref (k);
+  while ((s = k->chain_id) && k->subkeys && m_strcmp(s, k->subkeys->fpr)) {
+    putc ('\n', fp);
+    err = gpgme_op_keylist_start (listctx, s, 0);
+    gpgme_key_unref(k);
+    k = NULL;
+    if (!err)
+      err = gpgme_op_keylist_next (listctx, &k);
+    if (err) {
+      fprintf (fp, _("Error finding issuer key: %s\n"), gpgme_strerror (err));
+      goto leave;
+    }
+    gpgme_op_keylist_end (listctx);
+
+    print_key_info (k, fp);
+    if (!--maxdepth) {
+      putc ('\n', fp);
+      fputs (_("Error: certification chain to long - stopping here\n"), fp);
+      break;
+    }
+  }
+
+leave:
+  gpgme_key_unref(k);
+  gpgme_release (listctx);
+  m_fclose(&fp);
+  mutt_clear_error ();
+  snprintf (cmd, sizeof (cmd), _("Key ID: 0x%s"), crypt_keyid (key));
+  mutt_pager(cmd, tempfile, 0, NULL);
+}
+
+/* Implementation of `findkeys'. */
+
+static void add_hints(string_array *arr, const char *s)
+{
+    if (!s)
+        return;
+
+    while (*s) {
+        int l = strcspn(s, " ,.:\"()<>\n");
+        string_array_append(arr, p_dupstr(s, l));
+        s += l;
+        s += strspn(s, " ,.:\"()<>\n");
+    }
+}
+
+/* Return a list of keys which are candidates for the selection. */
+static cryptkey_t *
+get_candidates(string_array *hints, unsigned int app, int secret)
+{
+    cryptkey_t  *res = NULL, **kend = &res;
+    gpgme_error_t err;
+    gpgme_ctx_t   ctx;
+    gpgme_key_t   key;
+
+    if (hints->len <= 0)
+        return NULL;
+    string_array_append(hints, NULL);
+    ctx = create_gpgme_context(0);
+
+    if ((app & APPLICATION_PGP)) {
+        err = gpgme_op_keylist_ext_start(ctx, (const char **)hints->arr,
+                                         secret, 0);
+        if (err) {
+            mutt_error(_("gpgme_op_keylist_start failed: %s"),
+                       gpgme_strerror(err));
+            gpgme_release(ctx);
+            return NULL;
+        }
+
+        while (!(err = gpgme_op_keylist_next(ctx, &key))) {
+            gpgme_user_id_t uid = NULL;
+            unsigned int flags = 0;
+            int idx;
+
+            if (key_check_cap(key, KEY_CAP_CAN_ENCRYPT))
+                flags |= KEYFLAG_CANENCRYPT;
+            if (key_check_cap(key, KEY_CAP_CAN_SIGN))
+                flags |= KEYFLAG_CANSIGN;
+
+            for (idx = 0, uid = key->uids; uid; idx++, uid = uid->next) {
+                cryptkey_t *k = p_new(cryptkey_t, 1);
+                k->kobj = key;
+                k->idx = idx;
+                k->uid = uid->uid;
+                k->flags = flags;
+                *kend = k;
+                kend = &k->next;
+            }
+        }
+        if (gpg_err_code(err) != GPG_ERR_EOF)
+            mutt_error(_("gpgme_op_keylist_next failed: %s"), gpgme_strerror(err));
+        gpgme_op_keylist_end(ctx);
+    }
+
+    if ((app & APPLICATION_SMIME)) {
+        gpgme_set_protocol(ctx, GPGME_PROTOCOL_CMS);
+        err = gpgme_op_keylist_ext_start(ctx, (const char **)hints->arr,
+                                         secret, 0);
+        if (err) {
+            mutt_error(_("gpgme_op_keylist_start failed: %s"),
+                       gpgme_strerror(err));
+            gpgme_release(ctx);
+            return NULL;
+        }
+
+        while (!(err = gpgme_op_keylist_next(ctx, &key))) {
+            gpgme_user_id_t uid = NULL;
+            unsigned int flags = KEYFLAG_ISX509;
+            int idx;
+
+            if (key_check_cap(key, KEY_CAP_CAN_ENCRYPT))
+                flags |= KEYFLAG_CANENCRYPT;
+            if (key_check_cap(key, KEY_CAP_CAN_SIGN))
+                flags |= KEYFLAG_CANSIGN;
+
+            for (idx = 0, uid = key->uids; uid; idx++, uid = uid->next) {
+                cryptkey_t *k = p_new(cryptkey_t, 1);
+                k->kobj = key;
+                k->idx = idx;
+                k->uid = uid->uid;
+                k->flags = flags;
+                *kend = k;
+                kend = &k->next;
+            }
+        }
+        if (gpg_err_code(err) != GPG_ERR_EOF)
+            mutt_error(_("gpgme_op_keylist_next failed: %s"),
+                       gpgme_strerror(err));
+        gpgme_op_keylist_end(ctx);
+    }
+
+    gpgme_release(ctx);
+    return res;
+}
+
+/* Display a menu to select a key from the array KEYS. FORCED_VALID
+   will be set to true on return if the user did override the the
+   key's validity. */
+static cryptkey_t *crypt_select_key (cryptkey_t * keys,
+                                      address_t * p, const char *s,
+                                      unsigned int app, int *forced_valid)
+{
+  int keymax;
+  cryptkey_t **cryptkey_table;
+  MUTTMENU *menu;
+  int i, done = 0;
+  char helpstr[STRING], buf[LONG_STRING];
+  cryptkey_t *k;
+  int (*f) (const void *, const void *);
+  int menu_to_use = 0;
+  int unusable = 0;
+
+  *forced_valid = 0;
+
+  /* build the key table */
+  keymax = i = 0;
+  cryptkey_table = NULL;
+  for (k = keys; k; k = k->next) {
+      if (!option (OPTPGPSHOWUNUSABLE) && (k->flags & KEYFLAG_CANTUSE)) {
+          unusable = 1;
+          continue;
+      }
+
+      if (i == keymax) {
+          keymax += 20;
+          p_realloc(&cryptkey_table, keymax);
+      }
+
+      cryptkey_table[i++] = k;
+  }
+
+  if (!i && unusable) {
+      mutt_error _("All matching keys are marked expired/revoked.");
+
+      mutt_sleep (1);
+      return NULL;
+  }
+
+  switch (PgpSortKeys & SORT_MASK) {
+    case SORT_DATE:
+      f = crypt_compare_date;
+      break;
+    case SORT_KEYID:
+      f = crypt_compare_keyid;
+      break;
+    case SORT_ADDRESS:
+      f = crypt_compare_address;
+      break;
+    case SORT_TRUST:
+    default:
+      f = crypt_compare_trust;
+      break;
+  }
+  qsort (cryptkey_table, i, sizeof (cryptkey_t *), f);
+
+  if (app & APPLICATION_PGP)
+      menu_to_use = MENU_KEY_SELECT_PGP;
+  else if (app & APPLICATION_SMIME)
+      menu_to_use = MENU_KEY_SELECT_SMIME;
+
+  helpstr[0] = 0;
+  mutt_make_help (buf, sizeof (buf), _("Exit  "), menu_to_use, OP_EXIT);
+  m_strcat(helpstr, sizeof(helpstr), buf);
+  mutt_make_help (buf, sizeof (buf), _("Select  "), menu_to_use,
+                  OP_GENERIC_SELECT_ENTRY);
+  m_strcat(helpstr, sizeof(helpstr), buf);
+  mutt_make_help (buf, sizeof (buf), _("Check key  "),
+                  menu_to_use, OP_VERIFY_KEY);
+  m_strcat(helpstr, sizeof(helpstr), buf);
+  mutt_make_help (buf, sizeof (buf), _("Help"), menu_to_use, OP_HELP);
+  m_strcat(helpstr, sizeof(helpstr), buf);
+
+  menu = mutt_new_menu ();
+  menu->max = i;
+  menu->make_entry = crypt_entry;
+  menu->menu = menu_to_use;
+  menu->help = helpstr;
+  menu->data = cryptkey_table;
+
+  {
+      const char *ts;
+
+      if ((app & APPLICATION_PGP) && (app & APPLICATION_SMIME))
+          ts = _("PGP and S/MIME keys matching");
+      else if ((app & APPLICATION_PGP))
+          ts = _("PGP keys matching");
+      else if ((app & APPLICATION_SMIME))
+          ts = _("S/MIME keys matching");
+      else
+          ts = _("keys matching");
+
+      if (p)
+          snprintf (buf, sizeof (buf), _("%s <%s>."), ts, p->mailbox);
+      else
+          snprintf (buf, sizeof (buf), _("%s \"%s\"."), ts, s);
+      menu->title = buf;
+  }
+
+  mutt_clear_error ();
+  k = NULL;
+  while (!done) {
+      *forced_valid = 0;
+      switch (mutt_menuLoop (menu)) {
+        case OP_VERIFY_KEY:
+          verify_key (cryptkey_table[menu->current]);
+          menu->redraw = REDRAW_FULL;
+          break;
+
+        case OP_VIEW_ID:
+          mutt_message ("%s", cryptkey_table[menu->current]->uid);
+          break;
+
+        case OP_GENERIC_SELECT_ENTRY:
+          /* FIXME make error reporting more verbose - this should be
+             easy because gpgme provides more information */
+          if (option (OPTPGPCHECKTRUST)) {
+              if (cryptkey_table[menu->current]->flags & KEYFLAG_CANTUSE ) {
+                  mutt_error(_("This key can't be used: "
+                               "expired/disabled/revoked."));
+                  break;
+              }
+          }
+
+          if (option (OPTPGPCHECKTRUST) &&
+              ((cryptkey_table[menu->current]->flags & KEYFLAG_CANTUSE)
+               || !crypt_id_is_strong (cryptkey_table[menu->current]))) {
+              const char *warn_s;
+              char buff[LONG_STRING];
+
+              if (cryptkey_table[menu->current]->flags & KEYFLAG_CANTUSE)
+                  s = N_("ID is expired/disabled/revoked.");
+              else {
+                  gpgme_validity_t val = GPGME_VALIDITY_UNKNOWN;
+                  gpgme_user_id_t uid = NULL;
+                  int j = 0;
+
+                  warn_s = "??";
+
+                  uid = cryptkey_table[menu->current]->kobj->uids;
+                  for (j = 0; (j < cryptkey_table[menu->current]->idx) && uid;
+                       j++, uid = uid->next);
+                  if (uid)
+                      val = uid->validity;
+
+                  switch (val) {
+                    case GPGME_VALIDITY_UNKNOWN:
+                    case GPGME_VALIDITY_UNDEFINED:
+                      warn_s = N_("ID has undefined validity.");
+                      break;
+                    case GPGME_VALIDITY_NEVER:
+                      warn_s = N_("ID is not valid.");
+                      break;
+                    case GPGME_VALIDITY_MARGINAL:
+                      warn_s = N_("ID is only marginally valid.");
+                      break;
+                    case GPGME_VALIDITY_FULL:
+                    case GPGME_VALIDITY_ULTIMATE:
+                      break;
+                  }
+
+                  snprintf (buff, sizeof (buff),
+                            _("%s Do you really want to use the key?"), _(warn_s));
+
+                  if (mutt_yesorno (buff, 0) != 1) {
+                      mutt_clear_error ();
+                      break;
+                  }
+                  *forced_valid = 1;
+              }
+          }
+
+          k = cryptkey_dup(cryptkey_table[menu->current]);
+          done = 1;
+          break;
+
+        case OP_EXIT:
+          k = NULL;
+          done = 1;
+          break;
+      }
+  }
+
+  mutt_menuDestroy (&menu);
+  p_delete(&cryptkey_table);
+
+  set_option (OPTNEEDREDRAW);
+
+  return k;
+}
+
+static cryptkey_t *
+crypt_getkeybyaddr(address_t * a, int abilities, int app, int *forced_valid)
+{
+    address_t *r, *p;
+
+    int weak = 0;
+    int invalid = 0;
+    int multi = 0;
+    int this_key_has_strong;
+    int this_key_has_weak;
+    int this_key_has_invalid;
+    int match;
+
+    cryptkey_t *keys, *k;
+    cryptkey_t *the_valid_key = NULL;
+    cryptkey_t *matches = NULL;
+    cryptkey_t **matches_endp = &matches;
+
+    *forced_valid = 0;
+
+    {
+        string_array hints;
+        string_array_init(&hints); 
+        add_hints(&hints, a->mailbox);
+        add_hints(&hints, a->personal);
+
+        mutt_message (_("Looking for keys matching \"%s\"..."), a->mailbox);
+        keys = get_candidates(&hints, app, (abilities & KEYFLAG_CANSIGN));
+        string_array_wipe(&hints);
+    }
+
+    if (!keys)
+        return NULL;
+
+    for (k = keys; k; k = k->next) {
+        if (abilities && !(k->flags & abilities)) {
+            continue;
+        }
+
+        this_key_has_weak = 0;      /* weak but valid match   */
+        this_key_has_invalid = 0;   /* invalid match          */
+        this_key_has_strong = 0;    /* strong and valid match */
+        match = 0;                  /* any match            */
+
+        r = rfc822_parse_adrlist (NULL, k->uid);
+        for (p = r; p; p = p->next) {
+            int validity = crypt_id_matches_addr (a, p, k);
+
+            if (validity & CRYPT_KV_MATCH)    /* something matches */
+                match = 1;
+
+            /* is this key a strong candidate? */
+            if ((validity & CRYPT_KV_VALID)
+                && (validity & CRYPT_KV_STRONGID)
+                && (validity & CRYPT_KV_ADDR)) {
+                if (the_valid_key && the_valid_key != k)
+                    multi = 1;
+                the_valid_key = k;
+                this_key_has_strong = 1;
+            }
+            else if ((validity & CRYPT_KV_MATCH)
+                     && !(validity & CRYPT_KV_VALID))
+                this_key_has_invalid = 1;
+            else if ((validity & CRYPT_KV_MATCH)
+                     && (!(validity & CRYPT_KV_STRONGID)
+                         || !(validity & CRYPT_KV_ADDR)))
+                this_key_has_weak = 1;
+        }
+        address_list_wipe(&r);
+
+        if (match) {
+            cryptkey_t *tmp;
+
+            if (!this_key_has_strong && this_key_has_invalid)
+                invalid = 1;
+            if (!this_key_has_strong && this_key_has_weak)
+                weak = 1;
+
+            *matches_endp = tmp = cryptkey_dup(k);
+            matches_endp = &tmp->next;
+            the_valid_key = tmp;
+        }
+    }
+    key_list_wipe(&keys);
+
+    if (matches) {
+        if (the_valid_key && !multi && !weak
+            && !(invalid && option (OPTPGPSHOWUNUSABLE))) {
+            /*
+             * There was precisely one strong match on a valid ID, there
+             * were no valid keys with weak matches, and we aren't
+             * interested in seeing invalid keys.
+             *
+             * Proceed without asking the user.
+             */
+            k = cryptkey_dup(the_valid_key);
+        } else {
+            /*
+             * Else: Ask the user.
+             */
+            k = crypt_select_key (matches, a, NULL, app, forced_valid);
+        }
+        key_list_wipe(&matches);
+    } else {
+        k = NULL;
+    }
+
+    return k;
+}
+
+
+static cryptkey_t *
+crypt_getkeybystr(const char *p, int abilities, int app, int *forced_valid)
+{
+  cryptkey_t *keys;
+  cryptkey_t *matches = NULL;
+  cryptkey_t **matches_endp = &matches;
+  cryptkey_t *k;
+  int match;
+
+  mutt_message (_("Looking for keys matching \"%s\"..."), p);
+
+  *forced_valid = 0;
+
+  {
+      string_array hints;
+      string_array_init(&hints);
+      add_hints(&hints, p);
+      keys = get_candidates(&hints, app, (abilities & KEYFLAG_CANSIGN));
+      string_array_wipe(&hints);
+  }
+
+  if (!keys)
+    return NULL;
+
+  for (k = keys; k; k = k->next) {
+    const char *s = crypt_keyid(k);
+
+    if (abilities && !(k->flags & abilities))
+      continue;
+
+    match = 0;
+
+    if (!*p || !m_strcasecmp(p, s)
+        || (!m_strncasecmp(p, "0x", 2) && !m_strcasecmp(p + 2, s))
+        || m_stristr(k->uid, p))
+    {
+      cryptkey_t *tmp;
+
+      *matches_endp = tmp = cryptkey_dup(k);
+      matches_endp = &tmp->next;
+    }
+  }
+  key_list_wipe(&keys);
+
+  if (matches) {
+    k = crypt_select_key (matches, NULL, p, app, forced_valid);
+    key_list_wipe(&matches);
+    return k;
+  }
+
+  return NULL;
+}
+
+/* Display TAG as a prompt to ask for a key.
+ * ABILITIES describe the required key abilities (sign, encrypt) and APP the
+ * type of the requested key; ether S/MIME or PGP.
+ * Return a copy of the key or NULL if not found. */
+static cryptkey_t *
+crypt_ask_for_key(const char *tag, int abilities, int app, int *forced_valid)
+{
+    cryptkey_t *key;
+    char resp[STRING];
+    int dummy;
+
+    if (!forced_valid)
+        forced_valid = &dummy;
+    *forced_valid = 0;
+
+    mutt_clear_error();
+    for (;;) {
+        resp[0] = 0;
+        if (mutt_get_field(tag, resp, sizeof(resp), M_CLEAR) != 0)
+            return NULL;
+
+        if (m_strisempty(resp))
+            return NULL;
+
+        if ((key = crypt_getkeybystr(resp, abilities, app, forced_valid)))
+            return key;
+
+        BEEP ();
+    }
+}
+
+/* This routine attempts to find the keyids of the recipients of a
+   message.  It returns NULL if any of the keys can not be found.  */
+static char *find_keys(ENVELOPE *env, unsigned int app)
+{
+    address_t *lst = NULL, *addr;
+    buffer_t *keylist = buffer_new();
+
+    {
+        address_t **last = &lst;
+        *last = address_list_dup(env->to);
+        last  = address_list_last(last);
+        *last = address_list_dup(env->cc);
+        last  = address_list_last(last);
+        *last = address_list_dup(env->bcc);
+
+        rfc822_qualify(lst, mutt_fqdn(1));
+        address_list_uniq(lst);
+    }
 
-#include <sys/wait.h>
-#include <string.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <sys/stat.h>
-#include <errno.h>
-#include <ctype.h>
+    while ((addr = address_list_pop(&lst))) {
+        char buf[STRING];
+        int forced_valid = 0;
+        const char *keyID;
+        cryptkey_t *key = NULL;
+
+        if ((keyID = mutt_crypt_hook(addr))) {
+            int r;
+
+            snprintf(buf, sizeof(buf), _("Use keyID = \"%s\" for %s?"), keyID,
+                     addr->mailbox);
+            r = mutt_yesorno(buf, M_YES);
+
+            if (r == -1) {
+                address_list_wipe(&lst);
+                address_list_wipe(&addr);
+                buffer_delete(&keylist);
+                return NULL;
+            }
+
+            if (r == M_YES) {
+                address_t *a;
+                /* check for e-mail address */
+                if (strchr(keyID, '@') && (a = rfc822_parse_adrlist(NULL, keyID))) {
+                    rfc822_qualify(a, mutt_fqdn(1));
+                    address_list_wipe(&addr);
+                    addr = a;
+                } else {
+                    key = crypt_getkeybystr(keyID, KEYFLAG_CANENCRYPT, app,
+                                            &forced_valid);
+                }
+            }
+        }
 
-#ifdef HAVE_LOCALE_H
-#include <locale.h>
-#endif
+        if (!key) {
+            key = crypt_getkeybyaddr(addr, KEYFLAG_CANENCRYPT, app, &forced_valid);
+        }
+        if (!key) {
+            snprintf(buf, sizeof(buf), _("Enter keyID for %s: "), addr->mailbox);
+            key = crypt_ask_for_key(buf, KEYFLAG_CANENCRYPT, app,
+                                    &forced_valid);
+            if (!key) {
+                address_list_wipe(&lst);
+                address_list_wipe(&addr);
+                buffer_delete(&keylist);
+                return NULL;
+            }
+        }
 
-#ifdef HAVE_SYS_TIME_H
-# include <sys/time.h>
-#endif
+        if (keylist->len)
+            buffer_addch(keylist, ' ');
+        buffer_addstr(keylist, "0x");
+        buffer_addstr(keylist, crypt_fpr(key));
+        if (forced_valid)
+            buffer_addch(keylist, '!');
 
-#ifdef HAVE_SYS_RESOURCE_H
-# include <sys/resource.h>
-#endif
+        key_list_wipe(&key);
+        address_list_wipe(&addr);
+    }
 
+    address_list_wipe(&lst);
+    return buffer_unwrap(&keylist);
+}
 
-/* print the current time to avoid spoofing of the signature output */
-void crypt_current_time (STATE * s, const char *app_name)
+int crypt_get_keys(HEADER *msg, char **keylist)
 {
-  time_t t;
-  char p[STRING], tmp[STRING];
-
-  if (!WithCrypto)
-    return;
+    /* Do a quick check to make sure that we can find all of the encryption
+     * keys if the user has requested this service.
+     */
+
+    *keylist = NULL;
+
+    if (msg->security & ENCRYPT) {
+        if (msg->security & APPLICATION_PGP) {
+            set_option(OPTPGPCHECKTRUST);
+            *keylist = find_keys(msg->env, APPLICATION_PGP);
+            unset_option(OPTPGPCHECKTRUST);
+            if (!*keylist)
+                return -1;
+        }
 
-  if (option (OPTCRYPTTIMESTAMP)) {
-    t = time (NULL);
-    setlocale (LC_TIME, "");
-    strftime (p, sizeof (p), _(" (current time: %c)"), localtime (&t));
-    setlocale (LC_TIME, "C");
-  }
-  else
-    *p = '\0';
+        if (msg->security & APPLICATION_SMIME) {
+            *keylist = find_keys(msg->env, APPLICATION_SMIME);
+            if (!*keylist)
+                return -1;
+        }
+    }
 
-  snprintf (tmp, sizeof (tmp), _("[-- %s output follows%s --]\n"),
-            NONULL (app_name), p);
-  state_attach_puts (tmp, s);
+    return 0;
 }
 
 
-
-void crypt_forget_passphrase (void)
+int crypt_send_menu (HEADER * msg, int *redraw, int is_smime)
 {
-  if ((WithCrypto & APPLICATION_PGP))
-    crypt_pgp_void_passphrase ();
+    cryptkey_t *p;
+    char buf[STRING];
+    int choice;
+
+    if (msg->security & APPLICATION_SMIME)
+        is_smime = 1;
+    if (msg->security & APPLICATION_PGP)
+        is_smime = 0;
+
+    choice = is_smime
+        ? mutt_multi_choice(_("S/MIME (e)ncrypt, (s)ign, sign (a)s, (b)oth, (p)gp or (c)lear?"),
+                            _("esabpc"))
+        : mutt_multi_choice(_("PGP (e)ncrypt, (s)ign, sign (a)s, (b)oth, s/(m)ime or (c)lear?"),
+                            _("esabmc"));
+
+    switch (choice) {
+      case 1:                      /* (e)ncrypt */
+        msg->security |= (is_smime ? SMIMEENCRYPT : PGPENCRYPT);
+        msg->security &= ~(is_smime ? SMIMESIGN : PGPSIGN);
+        break;
+
+      case 2:                      /* (s)ign */
+        msg->security |= (is_smime ? SMIMESIGN : PGPSIGN);
+        msg->security &= ~(is_smime ? SMIMEENCRYPT : PGPENCRYPT);
+        break;
+
+      case 3:                      /* sign (a)s */
+        p = crypt_ask_for_key(_("Sign as: "), KEYFLAG_CANSIGN,
+                              is_smime ?  APPLICATION_SMIME : APPLICATION_PGP,
+                              NULL);
+        if (p) {
+            snprintf(buf, sizeof(buf), "0x%s", crypt_keyid(p));
+            m_strreplace(is_smime ? &SmimeDefaultKey : &PgpSignAs, buf);
+            key_list_wipe(&p);
+            msg->security |= (is_smime ? SMIMESIGN : PGPSIGN);
+        }
+        *redraw = REDRAW_FULL;
+        break;
+
+      case 4:                      /* (b)oth */
+        if (is_smime) {
+            msg->security = SMIMEENCRYPT | SMIMESIGN;
+        } else {
+            msg->security = PGPENCRYPT | PGPSIGN;
+        }
+        break;
 
-  if ((WithCrypto & APPLICATION_SMIME))
-    crypt_smime_void_passphrase ();
+      case 5:                      /* (p)gp or s/(m)ime */
+        is_smime = !is_smime;
+        break;
 
-  if (WithCrypto)
-    mutt_message _("Passphrase(s) forgotten.");
-}
+      case 6:                      /* (c)lear */
+        return msg->security = 0;
+    }
 
+    if (is_smime) {
+        msg->security &= ~APPLICATION_PGP;
+        msg->security |= APPLICATION_SMIME;
+    } else {
+        msg->security &= ~APPLICATION_SMIME;
+        msg->security |= APPLICATION_PGP;
+    }
 
-#if defined(HAVE_SETRLIMIT) && (!defined(DEBUG))
+    return msg->security;
+}
 
-static void disable_coredumps (void)
+int crypt_smime_verify_sender(HEADER *h)
 {
-  struct rlimit rl = { 0, 0 };
-  static short done = 0;
+    address_t *sender = NULL;
+    unsigned int ret = 1;
+
+    if (h->env->from) {
+        h->env->from = mutt_expand_aliases(h->env->from);
+        sender = h->env->from;
+    } else if (h->env->sender) {
+        h->env->sender = mutt_expand_aliases (h->env->sender);
+        sender = h->env->sender;
+    }
 
-  if (!done) {
-    setrlimit (RLIMIT_CORE, &rl);
-    done = 1;
-  }
-}
+    if (!sender) {
+        mutt_any_key_to_continue ("Failed to figure out sender");
+        goto end;
+    }
 
-#endif /* HAVE_SETRLIMIT */
+    if (signature_key) {
+        gpgme_key_t key = signature_key;
+        gpgme_user_id_t uid = NULL;
+        int sender_length = 0;
+        int uid_length = 0;
+
+        sender_length = m_strlen(sender->mailbox);
+        for (uid = key->uids; uid && ret; uid = uid->next) {
+            uid_length = m_strlen(uid->email);
+            if (1 && (uid->email[0] == '<')
+                && (uid->email[uid_length - 1] == '>')
+                && (uid_length == sender_length + 2)
+                && (!m_strncmp (uid->email + 1, sender->mailbox, sender_length)))
+                ret = 0;
+        }
+    } else {
+        mutt_any_key_to_continue ("Failed to verify sender");
+    }
 
+  end:
+    if (signature_key) {
+        gpgme_key_unref(signature_key);
+        signature_key = NULL;
+    }
+    return ret;
+}
 
-int crypt_valid_passphrase (int flags)
+static void crypt_invoke_import(FILE *stream, int smime)
 {
-  int ret = 0;
-
-# if defined(HAVE_SETRLIMIT) &&(!defined(DEBUG))
-  disable_coredumps ();
-# endif
-
-  if ((WithCrypto & APPLICATION_PGP) && (flags & APPLICATION_PGP))
-    ret = crypt_pgp_valid_passphrase ();
+    gpgme_ctx_t ctx = create_gpgme_context(smime);
+    gpgme_data_t data;
+    gpgme_error_t err;
+
+    err = gpgme_data_new_from_stream(&data, stream);
+    if (err) {
+        mutt_error (_("error allocating data object: %s\n"), gpgme_strerror (err));
+        gpgme_release(ctx);
+        return;
+    }
 
-  if ((WithCrypto & APPLICATION_SMIME) && (flags & APPLICATION_SMIME))
-    ret = crypt_smime_valid_passphrase ();
+    err = gpgme_op_import(ctx, data);
+    if (err) {
+        mutt_error(_("error importing gpg data: %s\n"), gpgme_strerror(err));
+        gpgme_data_release(data);
+        gpgme_release(ctx);
+        return;
+    }
 
-  return ret;
+    gpgme_data_release(data);
+    gpgme_release(ctx);
+    return;
 }
 
+static void pgp_extract_keys_from_attachment(FILE * fp, BODY * top)
+{
+    STATE s;
+    FILE *tmpfp = tmpfile();
 
+    if (tmpfp == NULL) {
+        mutt_perror (_("Can't create temporary file"));
+        return;
+    }
 
-int mutt_protect (HEADER * msg, char *keylist)
-{
-  BODY *pbody = NULL, *tmp_pbody = NULL;
-  BODY *tmp_smime_pbody = NULL;
-  BODY *tmp_pgp_pbody = NULL;
-  int flags = (WithCrypto & APPLICATION_PGP) ? msg->security : 0;
-  int i;
+    p_clear(&s, 1);
+    s.fpin  = fp;
+    s.fpout = tmpfp;
+    mutt_body_handler(top, &s);
 
-  if (!WithCrypto)
-    return -1;
+    rewind(tmpfp);
+    crypt_invoke_import(tmpfp, 0);
+    m_fclose(&tmpfp);
+}
 
-  if ((msg->security & SIGN) && !crypt_valid_passphrase (msg->security))
-    return (-1);
+void crypt_pgp_extract_keys_from_attachment_list(FILE * fp, int tag, BODY * top)
+{
+    mutt_endwin (NULL);
 
-  if ((WithCrypto & APPLICATION_PGP)
-      && ((msg->security & PGPINLINE) == PGPINLINE)) {
-    /* they really want to send it inline... go for it */
-    if (!isendwin ())
-      mutt_endwin _("Invoking PGP...");
+    for (; top; top = top->next) {
+        if (!tag || top->tagged)
+            pgp_extract_keys_from_attachment (fp, top);
 
-    pbody = crypt_pgp_traditional_encryptsign (msg->content, flags, keylist);
-    if (pbody) {
-      msg->content = pbody;
-      return 0;
+        if (!tag)
+            break;
     }
+}
 
-    /* otherwise inline won't work...ask for revert */
-    if ((i =
-         query_quadoption (OPT_PGPMIMEAUTO,
-                           _
-                           ("Message can't be sent inline.  Revert to using PGP/MIME?")))
-        != M_YES) {
-      mutt_error _("Mail not sent.");
-
-      return -1;
+void crypt_invoke_message (int type)
+{
+    if (type & APPLICATION_PGP) {
+        mutt_message _("Invoking PGP...");
     }
+    else if (type & APPLICATION_SMIME) {
+        mutt_message _("Invoking S/MIME...");
+    }
+}
 
-    /* go ahead with PGP/MIME */
-  }
+int mutt_protect (HEADER * msg, char *keylist)
+{
+  BODY *pbody = NULL, *tmp_pbody = NULL;
+  BODY *tmp_smime_pbody = NULL;
+  BODY *tmp_pgp_pbody = NULL;
+  int flags = msg->security;
 
   if (!isendwin ())
     mutt_endwin (NULL);
 
-  if ((WithCrypto & APPLICATION_SMIME))
-    tmp_smime_pbody = msg->content;
-  if ((WithCrypto & APPLICATION_PGP))
-    tmp_pgp_pbody = msg->content;
+  tmp_smime_pbody = msg->content;
+  tmp_pgp_pbody = msg->content;
 
   if (msg->security & SIGN) {
-    if ((WithCrypto & APPLICATION_SMIME)
-        && (msg->security & APPLICATION_SMIME)) {
-      if (!(tmp_pbody = crypt_smime_sign_message (msg->content)))
+    if (msg->security & APPLICATION_SMIME) {
+      if (!(tmp_pbody = sign_message(msg->content, 1)))
         return -1;
       pbody = tmp_smime_pbody = tmp_pbody;
     }
 
-    if ((WithCrypto & APPLICATION_PGP)
-        && (msg->security & APPLICATION_PGP)
+    if ((msg->security & APPLICATION_PGP)
         && (!(flags & ENCRYPT) || option (OPTPGPRETAINABLESIG))) {
-      if (!(tmp_pbody = crypt_pgp_sign_message (msg->content)))
+      if (!(tmp_pbody = sign_message(msg->content, 0)))
         return -1;
 
       flags &= ~SIGN;
       pbody = tmp_pgp_pbody = tmp_pbody;
     }
 
-    if (WithCrypto && (msg->security & APPLICATION_SMIME)
+    if ((msg->security & APPLICATION_SMIME)
         && (msg->security & APPLICATION_PGP)) {
       /* here comes the draft ;-) */
     }
@@ -200,8 +3634,7 @@ int mutt_protect (HEADER * msg, char *keylist)
 
 
   if (msg->security & ENCRYPT) {
-    if ((WithCrypto & APPLICATION_SMIME)
-        && (msg->security & APPLICATION_SMIME)) {
+    if ((msg->security & APPLICATION_SMIME)) {
       if (!(tmp_pbody = crypt_smime_build_smime_entity (tmp_smime_pbody,
                                                         keylist))) {
         /* signed ? free it! */
@@ -213,13 +3646,12 @@ int mutt_protect (HEADER * msg, char *keylist)
            which tmp_smime_pbody->parts after signing. */
         tmp_smime_pbody->parts = tmp_smime_pbody->parts->next;
         msg->content->next = NULL;
-        mutt_free_body (&tmp_smime_pbody);
+        body_list_wipe(&tmp_smime_pbody);
       }
       pbody = tmp_pbody;
     }
 
-    if ((WithCrypto & APPLICATION_PGP)
-        && (msg->security & APPLICATION_PGP)) {
+    if ((msg->security & APPLICATION_PGP)) {
       if (!(pbody = crypt_pgp_encrypt_message (tmp_pgp_pbody, keylist,
                                                flags & SIGN))) {
 
@@ -228,7 +3660,7 @@ int mutt_protect (HEADER * msg, char *keylist)
           /* remove the outer multipart layer */
           tmp_pgp_pbody = mutt_remove_multipart (tmp_pgp_pbody);
           /* get rid of the signature */
-          mutt_free_body (&tmp_pgp_pbody->next);
+          body_list_wipe(&tmp_pgp_pbody->next);
         }
 
         return (-1);
@@ -240,7 +3672,7 @@ int mutt_protect (HEADER * msg, char *keylist)
        */
       if (flags != msg->security) {
         tmp_pgp_pbody = mutt_remove_multipart (tmp_pgp_pbody);
-        mutt_free_body (&tmp_pgp_pbody->next);
+        body_list_wipe(&tmp_pgp_pbody->next);
       }
     }
   }
@@ -252,192 +3684,23 @@ int mutt_protect (HEADER * msg, char *keylist)
 }
 
 
-
-
-int mutt_is_multipart_signed (BODY * b)
-{
-  char *p;
-
-  if (!b || !(b->type == TYPEMULTIPART) ||
-      !b->subtype || ascii_strcasecmp (b->subtype, "signed"))
-    return 0;
-
-  if (!(p = mutt_get_parameter ("protocol", b->parameter)))
-    return 0;
-
-  if (!(ascii_strcasecmp (p, "multipart/mixed")))
-    return SIGN;
-
-  if ((WithCrypto & APPLICATION_PGP)
-      && !(ascii_strcasecmp (p, "application/pgp-signature")))
-    return PGPSIGN;
-
-  if ((WithCrypto & APPLICATION_SMIME)
-      && !(ascii_strcasecmp (p, "application/x-pkcs7-signature")))
-    return SMIMESIGN;
-  if ((WithCrypto & APPLICATION_SMIME)
-      && !(ascii_strcasecmp (p, "application/pkcs7-signature")))
-    return SMIMESIGN;
-
-  return 0;
-}
-
-
-int mutt_is_multipart_encrypted (BODY * b)
-{
-  if ((WithCrypto & APPLICATION_PGP)) {
-    char *p;
-
-    if (!b || b->type != TYPEMULTIPART ||
-        !b->subtype || ascii_strcasecmp (b->subtype, "encrypted") ||
-        !(p = mutt_get_parameter ("protocol", b->parameter)) ||
-        ascii_strcasecmp (p, "application/pgp-encrypted"))
-      return 0;
-
-    return PGPENCRYPT;
-  }
-
-  return 0;
-}
-
-
-int mutt_is_application_pgp (BODY * m)
-{
-  int t = 0;
-  char *p;
-
-  if (m->type == TYPEAPPLICATION) {
-    if (!ascii_strcasecmp (m->subtype, "pgp")
-        || !ascii_strcasecmp (m->subtype, "x-pgp-message")) {
-      if ((p = mutt_get_parameter ("x-action", m->parameter))
-          && (!ascii_strcasecmp (p, "sign")
-              || !ascii_strcasecmp (p, "signclear")))
-        t |= PGPSIGN;
-
-      if ((p = mutt_get_parameter ("format", m->parameter)) &&
-          !ascii_strcasecmp (p, "keys-only"))
-        t |= PGPKEY;
-
-      if (!t)
-        t |= PGPENCRYPT;        /* not necessarily correct, but... */
-    }
-
-    if (!ascii_strcasecmp (m->subtype, "pgp-signed"))
-      t |= PGPSIGN;
-
-    if (!ascii_strcasecmp (m->subtype, "pgp-keys"))
-      t |= PGPKEY;
-  }
-  else if (m->type == TYPETEXT && ascii_strcasecmp ("plain", m->subtype) == 0) {
-    if (((p = mutt_get_parameter ("x-mutt-action", m->parameter))
-         || (p = mutt_get_parameter ("x-action", m->parameter))
-         || (p = mutt_get_parameter ("action", m->parameter)))
-        && !ascii_strncasecmp ("pgp-sign", p, 8))
-      t |= PGPSIGN;
-    else if (p && !ascii_strncasecmp ("pgp-encrypt", p, 11))
-      t |= PGPENCRYPT;
-    else if (p && !ascii_strncasecmp ("pgp-keys", p, 7))
-      t |= PGPKEY;
-  }
-  if (t)
-    t |= PGPINLINE;
-
-  return t;
-}
-
-int mutt_is_application_smime (BODY * m)
-{
-  char *t = NULL;
-  int len, complain = 0;
-
-  if (!m)
-    return 0;
-
-  if ((m->type & TYPEAPPLICATION) && m->subtype) {
-    /* S/MIME MIME types don't need x- anymore, see RFC2311 */
-    if (!ascii_strcasecmp (m->subtype, "x-pkcs7-mime") ||
-        !ascii_strcasecmp (m->subtype, "pkcs7-mime")) {
-      if ((t = mutt_get_parameter ("smime-type", m->parameter))) {
-        if (!ascii_strcasecmp (t, "enveloped-data"))
-          return SMIMEENCRYPT;
-        else if (!ascii_strcasecmp (t, "signed-data"))
-          return (SMIMESIGN | SMIMEOPAQUE);
-        else
-          return 0;
-      }
-      /* Netscape 4.7 uses 
-       * Content-Description: S/MIME Encrypted Message
-       * instead of Content-Type parameter
-       */
-      if (!ascii_strcasecmp (m->description, "S/MIME Encrypted Message"))
-        return SMIMEENCRYPT;
-      complain = 1;
-    }
-    else if (ascii_strcasecmp (m->subtype, "octet-stream"))
-      return 0;
-
-    t = mutt_get_parameter ("name", m->parameter);
-
-    if (!t)
-      t = m->d_filename;
-    if (!t)
-      t = m->filename;
-    if (!t) {
-      if (complain)
-        mutt_message (_
-                      ("S/MIME messages with no hints on content are unsupported."));
-      return 0;
-    }
-
-    /* no .p7c, .p10 support yet. */
-
-    len = m_strlen(t) - 4;
-    if (len > 0 && *(t + len) == '.') {
-      len++;
-      if (!ascii_strcasecmp ((t + len), "p7m"))
-#if 0
-        return SMIMEENCRYPT;
-#else
-        /* Not sure if this is the correct thing to do, but 
-           it's required for compatibility with Outlook */
-        return (SMIMESIGN | SMIMEOPAQUE);
-#endif
-      else if (!ascii_strcasecmp ((t + len), "p7s"))
-        return (SMIMESIGN | SMIMEOPAQUE);
-    }
-  }
-
-  return 0;
-}
-
-
-
-
-
-
 int crypt_query (BODY * m)
 {
   int t = 0;
 
-  if (!WithCrypto)
-    return 0;
-
   if (!m)
     return 0;
 
   if (m->type == TYPEAPPLICATION) {
-    if ((WithCrypto & APPLICATION_PGP))
-      t |= mutt_is_application_pgp (m);
+    t |= mutt_is_application_pgp (m);
 
-    if ((WithCrypto & APPLICATION_SMIME)) {
-      t |= mutt_is_application_smime (m);
-      if (t && m->goodsig)
-        t |= GOODSIGN;
-      if (t && m->badsig)
-        t |= BADSIGN;
-    }
+    t |= mutt_is_application_smime (m);
+    if (t && m->goodsig)
+      t |= GOODSIGN;
+    if (t && m->badsig)
+      t |= BADSIGN;
   }
-  else if ((WithCrypto & APPLICATION_PGP) && m->type == TYPETEXT) {
+  else if (m->type == TYPETEXT) {
     t |= mutt_is_application_pgp (m);
     if (t && m->goodsig)
       t |= GOODSIGN;
@@ -455,7 +3718,7 @@ int crypt_query (BODY * m)
     BODY *p;
     int u, v, w;
 
-    u = m->parts ? 0xffffffff : 0;      /* Bits set in all parts */
+    u = m->parts ? ~0 : 0;      /* Bits set in all parts */
     w = 0;                      /* Bits set in any part  */
 
     for (p = m->parts; p; p = p->next) {
@@ -473,367 +3736,191 @@ int crypt_query (BODY * m)
 }
 
 
-
-
-int crypt_write_signed (BODY * a, STATE * s, const char *tempfile)
-{
-  FILE *fp;
-  int c;
-  short hadcr;
-  size_t bytes;
-
-  if (!WithCrypto)
-    return -1;
-
-  if (!(fp = safe_fopen (tempfile, "w"))) {
-    mutt_perror (tempfile);
-    return -1;
-  }
-
-  fseeko (s->fpin, a->hdr_offset, 0);
-  bytes = a->length + a->offset - a->hdr_offset;
-  hadcr = 0;
-  while (bytes > 0) {
-    if ((c = fgetc (s->fpin)) == EOF)
-      break;
-
-    bytes--;
-
-    if (c == '\r')
-      hadcr = 1;
-    else {
-      if (c == '\n' && !hadcr)
-        fputc ('\r', fp);
-
-      hadcr = 0;
-    }
-
-    fputc (c, fp);
-
-  }
-  fclose (fp);
-
-  return 0;
-}
-
-
-
-void convert_to_7bit (BODY * a)
+static void crypt_write_signed(BODY * a, STATE * s, FILE *fp)
 {
-  if (!WithCrypto)
-    return;
-
-  while (a) {
-    if (a->type == TYPEMULTIPART) {
-      if (a->encoding != ENC7BIT) {
-        a->encoding = ENC7BIT;
-        convert_to_7bit (a->parts);
-      }
-      else if ((WithCrypto & APPLICATION_PGP) && option (OPTPGPSTRICTENC))
-        convert_to_7bit (a->parts);
-    }
-    else if (a->type == TYPEMESSAGE &&
-             m_strcasecmp(a->subtype, "delivery-status")) {
-      if (a->encoding != ENC7BIT)
-        mutt_message_to_7bit (a, NULL);
+    int c;
+    short hadcr;
+    size_t bytes;
+
+    fseeko (s->fpin, a->hdr_offset, 0);
+    bytes = a->length + a->offset - a->hdr_offset;
+    hadcr = 0;
+    while (bytes > 0) {
+        if ((c = fgetc (s->fpin)) == EOF)
+            break;
+
+        bytes--;
+
+        if (c == '\r')
+            hadcr = 1;
+        else {
+            if (c == '\n' && !hadcr)
+                fputc ('\r', fp);
+
+            hadcr = 0;
+        }
+        fputc (c, fp);
     }
-    else if (a->encoding == ENC8BIT)
-      a->encoding = ENCQUOTEDPRINTABLE;
-    else if (a->encoding == ENCBINARY)
-      a->encoding = ENCBASE64;
-    else if (a->content && a->encoding != ENCBASE64 &&
-             (a->content->from || (a->content->space &&
-                                   option (OPTPGPSTRICTENC))))
-      a->encoding = ENCQUOTEDPRINTABLE;
-    a = a->next;
-  }
 }
 
-
-
-
-void crypt_extract_keys_from_messages (HEADER * h)
+static void extract_keys_aux(FILE *fpout, HEADER *h)
 {
-  int i;
-  char tempfname[_POSIX_PATH_MAX], *mbox;
-  address_t *tmp = NULL;
-  FILE *fpout;
-
-  if (!WithCrypto)
-    return;
-
-  mutt_mktemp (tempfname);
-  if (!(fpout = safe_fopen (tempfname, "w"))) {
-    mutt_perror (tempfname);
-    return;
-  }
-
-  if ((WithCrypto & APPLICATION_PGP))
-    set_option (OPTDONTHANDLEPGPKEYS);
-
-  if (!h) {
-    for (i = 0; i < Context->vcount; i++) {
-      if (Context->hdrs[Context->v2r[i]]->tagged) {
-        mutt_parse_mime_message (Context, Context->hdrs[Context->v2r[i]]);
-        if (Context->hdrs[Context->v2r[i]]->security & ENCRYPT &&
-            !crypt_valid_passphrase (Context->hdrs[Context->v2r[i]]->
-                                     security)) {
-          fclose (fpout);
-          break;
-        }
-
-        if ((WithCrypto & APPLICATION_PGP)
-            && (Context->hdrs[Context->v2r[i]]->security & APPLICATION_PGP)) {
-          mutt_copy_message (fpout, Context, Context->hdrs[Context->v2r[i]],
-                             M_CM_DECODE | M_CM_CHARCONV, 0);
-          fflush (fpout);
-
-          mutt_endwin (_("Trying to extract PGP keys...\n"));
-          crypt_pgp_invoke_import (tempfname);
-        }
-
-        if ((WithCrypto & APPLICATION_SMIME)
-            && (Context->hdrs[Context->v2r[i]]->security & APPLICATION_SMIME)) {
-          if (Context->hdrs[Context->v2r[i]]->security & ENCRYPT)
-            mutt_copy_message (fpout, Context, Context->hdrs[Context->v2r[i]],
-                               M_CM_NOHEADER | M_CM_DECODE_CRYPT
-                               | M_CM_DECODE_SMIME, 0);
-          else
-            mutt_copy_message (fpout, Context,
-                               Context->hdrs[Context->v2r[i]], 0, 0);
-          fflush (fpout);
-
-          if (Context->hdrs[Context->v2r[i]]->env->from)
-            tmp = mutt_expand_aliases (h->env->from);
-          else if (Context->hdrs[Context->v2r[i]]->env->sender)
-            tmp = mutt_expand_aliases (Context->hdrs[Context->v2r[i]]
-                                       ->env->sender);
-          mbox = tmp ? tmp->mailbox : NULL;
-          if (mbox) {
-            mutt_endwin (_("Trying to extract S/MIME certificates...\n"));
-            crypt_smime_invoke_import (tempfname, mbox);
-            tmp = NULL;
-          }
-        }
-
-        rewind (fpout);
-      }
-    }
-  }
-  else {
     mutt_parse_mime_message (Context, h);
-    if (!(h->security & ENCRYPT && !crypt_valid_passphrase (h->security))) {
-      if ((WithCrypto & APPLICATION_PGP)
-          && (h->security & APPLICATION_PGP)) {
-        mutt_copy_message (fpout, Context, h, M_CM_DECODE | M_CM_CHARCONV, 0);
+
+    rewind(fpout);
+    if (h->security & APPLICATION_PGP) {
+        mutt_copy_message(fpout, Context, h, M_CM_DECODE | M_CM_CHARCONV, 0);
         fflush (fpout);
+
         mutt_endwin (_("Trying to extract PGP keys...\n"));
-        crypt_pgp_invoke_import (tempfname);
-      }
+    }
 
-      if ((WithCrypto & APPLICATION_SMIME)
-          && (h->security & APPLICATION_SMIME)) {
+    if (h->security & APPLICATION_SMIME) {
         if (h->security & ENCRYPT)
-          mutt_copy_message (fpout, Context, h, M_CM_NOHEADER
-                             | M_CM_DECODE_CRYPT | M_CM_DECODE_SMIME, 0);
+            mutt_copy_message (fpout, Context, h, M_CM_NOHEADER
+                               | M_CM_DECODE_CRYPT | M_CM_DECODE_SMIME, 0);
         else
-          mutt_copy_message (fpout, Context, h, 0, 0);
-
+            mutt_copy_message(fpout, Context, h, 0, 0);
         fflush (fpout);
-        if (h->env->from)
-          tmp = mutt_expand_aliases (h->env->from);
-        else if (h->env->sender)
-          tmp = mutt_expand_aliases (h->env->sender);
-        mbox = tmp ? tmp->mailbox : NULL;
-        if (mbox) {             /* else ? */
-          mutt_message (_("Trying to extract S/MIME certificates...\n"));
-          crypt_smime_invoke_import (tempfname, mbox);
-        }
-      }
-    }
-  }
-
-  fclose (fpout);
-  if (isendwin ())
-    mutt_any_key_to_continue (NULL);
 
-  mutt_unlink (tempfname);
+        mutt_message (_("Trying to extract S/MIME certificates...\n"));
+    }
 
-  if ((WithCrypto & APPLICATION_PGP))
-    unset_option (OPTDONTHANDLEPGPKEYS);
+    rewind(fpout);
+    crypt_invoke_import(fpout, h->security & APPLICATION_SMIME);
 }
 
-
-
-int crypt_get_keys (HEADER * msg, char **keylist)
+void crypt_extract_keys_from_messages(HEADER * h)
 {
-  /* Do a quick check to make sure that we can find all of the encryption
-   * keys if the user has requested this service.
-   */
-
-  if (!WithCrypto)
-    return 0;
-
-  if ((WithCrypto & APPLICATION_PGP))
-    set_option (OPTPGPCHECKTRUST);
-
-  *keylist = NULL;
-
-  if (msg->security & ENCRYPT) {
-    if ((WithCrypto & APPLICATION_PGP)
-        && (msg->security & APPLICATION_PGP)) {
-      if ((*keylist = crypt_pgp_findkeys (msg->env->to, msg->env->cc,
-                                          msg->env->bcc)) == NULL)
-        return (-1);
-      unset_option (OPTPGPCHECKTRUST);
+    FILE *tmpfp = tmpfile();
+    if (!tmpfp) {
+        mutt_error(_("Could not create temporary file"));
+        return;
     }
-    if ((WithCrypto & APPLICATION_SMIME)
-        && (msg->security & APPLICATION_SMIME)) {
-      if ((*keylist = crypt_smime_findkeys (msg->env->to, msg->env->cc,
-                                            msg->env->bcc)) == NULL)
-        return (-1);
+
+    if (!h) {
+        int i;
+        for (i = 0; i < Context->vcount; i++) {
+            if (!Context->hdrs[Context->v2r[i]]->tagged)
+                continue;
+            extract_keys_aux(tmpfp, Context->hdrs[Context->v2r[i]]);
+        }
+    } else {
+        extract_keys_aux(tmpfp, h);
     }
-  }
+    m_fclose(&tmpfp);
 
-  return (0);
+    if (isendwin())
+        mutt_any_key_to_continue(NULL);
 }
 
-
-
-static void crypt_fetch_signatures (BODY ***signatures, BODY * a, int *n)
+static void crypt_fetch_signatures(BODY ***signatures, BODY * a, int *n)
 {
-  if (!WithCrypto)
-    return;
-
-  for (; a; a = a->next) {
-    if (a->type == TYPEMULTIPART)
-      crypt_fetch_signatures (signatures, a->parts, n);
-    else {
-      if ((*n % 5) == 0)
-        p_realloc(signatures, *n + 6);
-
-      (*signatures)[(*n)++] = a;
+    for (; a; a = a->next) {
+        if (a->type == TYPEMULTIPART) {
+            crypt_fetch_signatures(signatures, a->parts, n);
+        } else {
+            if ((*n % 5) == 0)
+                p_realloc(signatures, *n + 6);
+
+            (*signatures)[(*n)++] = a;
+        }
     }
-  }
 }
 
-
-/*
- * This routine verifies a  "multipart/signed"  body.
- */
-
-int mutt_signed_handler (BODY * a, STATE * s)
+int mutt_signed_handler(BODY *a, STATE *s)
 {
-  char tempfile[_POSIX_PATH_MAX];
+  unsigned major, minor;
   char *protocol;
-  int protocol_major = TYPEOTHER;
-  char *protocol_minor = NULL;
-
+  int rc, i, goodsig = 1, sigcnt = 0;
   BODY *b = a;
-  BODY **signatures = NULL;
-  int sigcnt = 0;
-  int i;
-  short goodsig = 1;
-  int rc = 0;
-
-  if (!WithCrypto)
-    return (-1);
 
-  protocol = mutt_get_parameter ("protocol", a->parameter);
+  protocol = parameter_getval(a->parameter, "protocol");
   a = a->parts;
 
-  /* extract the protocol information */
-
-  if (protocol) {
-    char major[STRING];
-    char *t;
-
-    if ((protocol_minor = strchr (protocol, '/')))
-      protocol_minor++;
-
-    m_strcpy(major, sizeof(major), protocol);
-    if ((t = strchr (major, '/')))
-      *t = '\0';
+  switch (mime_which_token(protocol, -1)) {
+    case MIME_APPLICATION_PGP_SIGNATURE:
+      major = TYPEAPPLICATION;
+      minor = MIME_PGP_SIGNATURE;
+      break;
+    case MIME_APPLICATION_X_PKCS7_SIGNATURE:
+      major = TYPEAPPLICATION;
+      minor = MIME_X_PKCS7_SIGNATURE;
+      break;
+    case MIME_APPLICATION_PKCS7_SIGNATURE:
+      major = TYPEAPPLICATION;
+      minor = MIME_PKCS7_SIGNATURE;
+      break;
+    case MIME_MULTIPART_MIXED:
+      major = TYPEMULTIPART;
+      minor = MIME_MIXED;
+      break;
 
-    protocol_major = mutt_check_mime_type (major);
+    default:
+      state_printf(s, _("[-- Error: "
+                        "Unknown multipart/signed protocol %s! --]\n\n"),
+                    protocol);
+      return mutt_body_handler (a, s);
   }
 
   /* consistency check */
-
-  if (!(a && a->next && a->next->type == protocol_major &&
-        !m_strcasecmp(a->next->subtype, protocol_minor))) {
-    state_attach_puts (_("[-- Error: "
-                         "Inconsistent multipart/signed structure! --]\n\n"),
-                       s);
-    return mutt_body_handler (a, s);
-  }
-
-
-  if ((WithCrypto & APPLICATION_PGP)
-      && protocol_major == TYPEAPPLICATION
-      && !m_strcasecmp(protocol_minor, "pgp-signature"));
-  else if ((WithCrypto & APPLICATION_SMIME)
-           && protocol_major == TYPEAPPLICATION
-           && !(m_strcasecmp(protocol_minor, "x-pkcs7-signature")
-                && m_strcasecmp(protocol_minor, "pkcs7-signature")));
-  else if (protocol_major == TYPEMULTIPART
-           && !m_strcasecmp(protocol_minor, "mixed"));
-  else {
-    state_printf (s, _("[-- Error: "
-                       "Unknown multipart/signed protocol %s! --]\n\n"),
-                  protocol);
+  if (!(a && a->next && a->next->type == major &&
+        mime_which_token(a->next->subtype, -1) == minor))
+  {
+    state_attach_puts(_("[-- Error: "
+                        "Inconsistent multipart/signed structure! --]\n\n"),
+                      s);
     return mutt_body_handler (a, s);
   }
 
   if (s->flags & M_DISPLAY) {
+    BODY **sigs = NULL;
 
-    crypt_fetch_signatures (&signatures, a->next, &sigcnt);
-
+    crypt_fetch_signatures (&sigs, a->next, &sigcnt);
     if (sigcnt) {
-      mutt_mktemp (tempfile);
-      if (crypt_write_signed (a, s, tempfile) == 0) {
-        for (i = 0; i < sigcnt; i++) {
-          if ((WithCrypto & APPLICATION_PGP)
-              && signatures[i]->type == TYPEAPPLICATION
-              && !m_strcasecmp(signatures[i]->subtype, "pgp-signature")) {
-            if (crypt_pgp_verify_one (signatures[i], s, tempfile) != 0)
-              goodsig = 0;
-
-            continue;
-          }
-
-          if ((WithCrypto & APPLICATION_SMIME)
-              && signatures[i]->type == TYPEAPPLICATION
-              &&
-              (!m_strcasecmp(signatures[i]->subtype, "x-pkcs7-signature")
-               || !m_strcasecmp(signatures[i]->subtype,
-                                    "pkcs7-signature"))) {
-            if (crypt_smime_verify_one (signatures[i], s, tempfile) != 0)
-              goodsig = 0;
+      FILE *tmpfp = tmpfile();
 
-            continue;
+      if (!tmpfp) {
+          mutt_error(_("Could not create temporary file"));
+      } else {
+        crypt_write_signed(a, s, tmpfp);
+        rewind(tmpfp);
+        for (i = 0; i < sigcnt; i++) {
+          if (sigs[i]->type == TYPEAPPLICATION) {
+            int subtype;
+
+            switch ((subtype = mime_which_token(sigs[i]->subtype, -1))) {
+              case MIME_PGP_SIGNATURE:
+              case MIME_X_PKCS7_SIGNATURE:
+              case MIME_PKCS7_SIGNATURE:
+                if (crypt_verify_one(sigs[i], s, tmpfp, subtype != MIME_PGP_SIGNATURE) != 0)
+                  goodsig = 0;
+
+                m_fclose(&tmpfp);
+                continue;
+
+              default:
+                break;
+            }
           }
 
-          state_printf (s, _("[-- Warning: "
-                             "We can't verify %s/%s signatures. --]\n\n"),
-                        TYPE (signatures[i]), signatures[i]->subtype);
+          state_printf(s, _("[-- Warning: "
+                            "We can't verify %s/%s signatures. --]\n\n"),
+                       TYPE (sigs[i]), sigs[i]->subtype);
         }
       }
 
-      mutt_unlink (tempfile);
-
       b->goodsig = goodsig;
-      b->badsig = !goodsig;
+      b->badsig  = !goodsig;
 
       /* Now display the signed body */
-      state_attach_puts (_("[-- The following data is signed --]\n\n"), s);
-
+      state_attach_puts(_("[-- The following data is signed --]\n\n"), s);
 
-      p_delete(&signatures);
+      p_delete(&sigs);
+    } else {
+      state_attach_puts(_("[-- Warning: Can't find any signatures. --]\n\n"),
+                        s);
     }
-    else
-      state_attach_puts (_("[-- Warning: Can't find any signatures. --]\n\n"),
-                         s);
   }
 
   rc = mutt_body_handler (a, s);