/*
* 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"
+#include <lib-lib/lib-lib.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 "crypt.h"
+#include "lib.h"
+#include "alias.h"
+#include "handler.h"
+#include "copy.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_do_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);
+ }
+
+ 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);
+ }
+ }
+ }
-#include "mutt.h"
-#include "mutt_curses.h"
-#include "mime.h"
-#include "copy.h"
-#include "mutt_crypt.h"
-#include "pgp.h"
-
-#include "lib/mem.h"
-#include "lib/intl.h"
-#include "lib/str.h"
-
-#include <sys/wait.h>
-#include <string.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <sys/stat.h>
-#include <errno.h>
-#include <ctype.h>
-
-#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, 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 ;-) */
}
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! */
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))) {
/* 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);
*/
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);
}
}
}
}
-
-
-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 = mutt_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;
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) {
}
-
-
-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;
- }
-
- fseek (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 &&
- mutt_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 *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)
- safe_realloc (signatures, (*n + 6) * sizeof (BODY **));
-
- (*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.
- */
-
-void 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;
-
- if (!WithCrypto)
- return;
- 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++;
-
- strfcpy (major, protocol, sizeof (major));
- 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 &&
- !mutt_strcasecmp (a->next->subtype, protocol_minor))) {
- state_attach_puts (_("[-- Error: "
- "Inconsistent multipart/signed structure! --]\n\n"),
- s);
- mutt_body_handler (a, s);
- return;
- }
-
-
- if ((WithCrypto & APPLICATION_PGP)
- && protocol_major == TYPEAPPLICATION
- && !mutt_strcasecmp (protocol_minor, "pgp-signature"));
- else if ((WithCrypto & APPLICATION_SMIME)
- && protocol_major == TYPEAPPLICATION
- && !(mutt_strcasecmp (protocol_minor, "x-pkcs7-signature")
- && mutt_strcasecmp (protocol_minor, "pkcs7-signature")));
- else if (protocol_major == TYPEMULTIPART
- && !mutt_strcasecmp (protocol_minor, "mixed"));
- else {
- state_printf (s, _("[-- Error: "
- "Unknown multipart/signed protocol %s! --]\n\n"),
- protocol);
- mutt_body_handler (a, s);
- return;
+ 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
- && !mutt_strcasecmp (signatures[i]->subtype, "pgp-signature")) {
- if (crypt_pgp_verify_one (signatures[i], s, tempfile) != 0)
- goodsig = 0;
-
- continue;
- }
+ FILE *tmpfp = tmpfile();
- if ((WithCrypto & APPLICATION_SMIME)
- && signatures[i]->type == TYPEAPPLICATION
- &&
- (!mutt_strcasecmp (signatures[i]->subtype, "x-pkcs7-signature")
- || !mutt_strcasecmp (signatures[i]->subtype,
- "pkcs7-signature"))) {
- if (crypt_smime_verify_one (signatures[i], s, tempfile) != 0)
- goodsig = 0;
-
- 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);
-
- FREE (&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);
}
- mutt_body_handler (a, s);
+ rc = mutt_body_handler (a, s);
if (s->flags & M_DISPLAY && sigcnt)
state_attach_puts (_("\n[-- End of signed data --]\n"), s);
+
+ return (rc);
}