+ * Copyright (C) 2002, 2003, 2004 g10 Code GmbH
+ */
+/*
+ * Copyright © 2006 Pierre Habouzit
+ */
+
+#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.