X-Git-Url: http://git.madism.org/?p=apps%2Fmadmutt.git;a=blobdiff_plain;f=crypt.c;h=e137f2c4224da8a1ab78ee54c174c14600daafff;hp=e5019eca6517b99cab4199d7280d1c0ef45b784d;hb=1d2617e5d89468db7cd1b93fd2b31af73cd7c8ff;hpb=617e7d83d14e14e6a520a48e75437211b16c8834 diff --git a/crypt.c b/crypt.c index e5019ec..e137f2c 100644 --- a/crypt.c +++ b/crypt.c @@ -1,196 +1,3632 @@ /* * Copyright notice from original mutt: - * Copyright (C) 1996,1997 Michael R. Elkins - * Copyright (C) 1999-2000 Thomas Roessler - * Copyright (C) 2001 Thomas Roessler + * crypt-gpgme.c - GPGME based crypto operations + * Copyright (C) 1996,1997 Michael R. Elkins + * Copyright (C) 1998,1999,2000 Thomas Roessler + * Copyright (C) 2001 Thomas Roessler * Oliver Ehli - * Copyright (C) 2003 Werner Koch - * Copyright (C) 2004 g10code GmbH + * Copyright (C) 2002, 2003, 2004 g10 Code GmbH + */ +/* + * Copyright © 2006 Pierre Habouzit + */ + +#include + +#include + +#include +#include +#include +#include +#include + +#include "crypt.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. * - * 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. + * %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) */ -#if HAVE_CONFIG_H -# include "config.h" +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); + } -#include + if (key->subkeys && (key->subkeys->expires > 0)) { + tt = key->subkeys->expires; -#include "mutt.h" -#include "ascii.h" -#include "handler.h" -#include "mutt_curses.h" -#include "mime.h" -#include "copy.h" -#include "mutt_crypt.h" -#include "pgp.h" - -#include "lib/intl.h" -#include "lib/str.h" - -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_LOCALE_H -#include + 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); -#ifdef HAVE_SYS_TIME_H -# include + 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; -#ifdef HAVE_SYS_RESOURCE_H -# include + tm = localtime (&tt); +#ifdef HAVE_LANGINFO_D_T_FMT + strftime (shortbuf, sizeof shortbuf, nl_langinfo (D_T_FMT), tm); +#else + strftime (shortbuf, sizeof shortbuf, "%c", tm); #endif + fprintf (fp, _("Valid To ..: %s\n"), shortbuf); + } + + if (subkey) + s = gpgme_pubkey_algo_name (subkey->pubkey_algo); + else + s = "?"; + + if (subkey) + aval = subkey->length; + else + aval = 0; + + fprintf (fp, _("Key Type ..: %s, %lu bit %s\n"), "PGP", aval, s); + + fprintf (fp, _("Key Usage .: ")); + delim = ""; + + if (subkey->can_encrypt) { + fprintf (fp, "%s%s", delim, _("encryption")); + delim = _(", "); + } + if (subkey->can_sign) { + fprintf (fp, "%s%s", delim, _("signing")); + delim = _(", "); + } + if (subkey->can_certify) { + fprintf (fp, "%s%s", delim, _("certification")); + delim = _(", "); + } + putc ('\n', fp); + } + } + + if (Locale) + setlocale (LC_TIME, "C"); +} + + +/* Show detailed information about the selected key */ +static void verify_key (cryptkey_t * key) +{ + FILE *fp; + char cmd[LONG_STRING], tempfile[_POSIX_PATH_MAX]; + const char *s; + gpgme_ctx_t listctx = NULL; + gpgme_error_t err; + gpgme_key_t k = NULL; + int maxdepth = 100; + + fp = m_tempfile (tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL); + if (!fp) { + mutt_perror (_("Can't create temporary file")); + return; + } + mutt_message _("Collecting data..."); + + print_key_info (key->kobj, fp); + + err = gpgme_new (&listctx); + if (err) { + fprintf (fp, "Internal error: can't create gpgme context: %s\n", + gpgme_strerror (err)); + goto leave; + } + if ((key->flags & KEYFLAG_ISX509)) + gpgme_set_protocol (listctx, GPGME_PROTOCOL_CMS); + + k = key->kobj; + gpgme_key_ref (k); + while ((s = k->chain_id) && k->subkeys && m_strcmp(s, k->subkeys->fpr)) { + putc ('\n', fp); + err = gpgme_op_keylist_start (listctx, s, 0); + gpgme_key_unref(k); + k = NULL; + if (!err) + err = gpgme_op_keylist_next (listctx, &k); + if (err) { + fprintf (fp, _("Error finding issuer key: %s\n"), gpgme_strerror (err)); + goto leave; + } + gpgme_op_keylist_end (listctx); + + print_key_info (k, fp); + if (!--maxdepth) { + putc ('\n', fp); + fputs (_("Error: certification chain to long - stopping here\n"), fp); + break; + } + } + +leave: + gpgme_key_unref(k); + gpgme_release (listctx); + m_fclose(&fp); + mutt_clear_error (); + snprintf (cmd, sizeof (cmd), _("Key ID: 0x%s"), crypt_keyid (key)); + mutt_pager(cmd, tempfile, 0, NULL); +} + +/* Implementation of `findkeys'. */ + +static void add_hints(string_array *arr, const char *s) +{ + if (!s) + return; + + while (*s) { + int l = strcspn(s, " ,.:\"()<>\n"); + string_array_append(arr, p_dupstr(s, l)); + s += l; + s += strspn(s, " ,.:\"()<>\n"); + } +} + +/* Return a list of keys which are candidates for the selection. */ +static cryptkey_t * +get_candidates(string_array *hints, unsigned int app, int secret) +{ + cryptkey_t *res = NULL, **kend = &res; + gpgme_error_t err; + gpgme_ctx_t ctx; + gpgme_key_t key; + + if (hints->len <= 0) + return NULL; + string_array_append(hints, NULL); + ctx = create_gpgme_context(0); + + if ((app & APPLICATION_PGP)) { + err = gpgme_op_keylist_ext_start(ctx, (const char **)hints->arr, + secret, 0); + if (err) { + mutt_error(_("gpgme_op_keylist_start failed: %s"), + gpgme_strerror(err)); + gpgme_release(ctx); + return NULL; + } + + while (!(err = gpgme_op_keylist_next(ctx, &key))) { + gpgme_user_id_t uid = NULL; + unsigned int flags = 0; + int idx; + + if (key_check_cap(key, KEY_CAP_CAN_ENCRYPT)) + flags |= KEYFLAG_CANENCRYPT; + if (key_check_cap(key, KEY_CAP_CAN_SIGN)) + flags |= KEYFLAG_CANSIGN; + + for (idx = 0, uid = key->uids; uid; idx++, uid = uid->next) { + cryptkey_t *k = p_new(cryptkey_t, 1); + k->kobj = key; + k->idx = idx; + k->uid = uid->uid; + k->flags = flags; + *kend = k; + kend = &k->next; + } + } + if (gpg_err_code(err) != GPG_ERR_EOF) + mutt_error(_("gpgme_op_keylist_next failed: %s"), gpgme_strerror(err)); + gpgme_op_keylist_end(ctx); + } + + if ((app & APPLICATION_SMIME)) { + gpgme_set_protocol(ctx, GPGME_PROTOCOL_CMS); + err = gpgme_op_keylist_ext_start(ctx, (const char **)hints->arr, + secret, 0); + if (err) { + mutt_error(_("gpgme_op_keylist_start failed: %s"), + gpgme_strerror(err)); + gpgme_release(ctx); + return NULL; + } + + while (!(err = gpgme_op_keylist_next(ctx, &key))) { + gpgme_user_id_t uid = NULL; + unsigned int flags = KEYFLAG_ISX509; + int idx; + + if (key_check_cap(key, KEY_CAP_CAN_ENCRYPT)) + flags |= KEYFLAG_CANENCRYPT; + if (key_check_cap(key, KEY_CAP_CAN_SIGN)) + flags |= KEYFLAG_CANSIGN; + + for (idx = 0, uid = key->uids; uid; idx++, uid = uid->next) { + cryptkey_t *k = p_new(cryptkey_t, 1); + k->kobj = key; + k->idx = idx; + k->uid = uid->uid; + k->flags = flags; + *kend = k; + kend = &k->next; + } + } + if (gpg_err_code(err) != GPG_ERR_EOF) + mutt_error(_("gpgme_op_keylist_next failed: %s"), + gpgme_strerror(err)); + gpgme_op_keylist_end(ctx); + } + + gpgme_release(ctx); + return res; +} + +/* Display a menu to select a key from the array KEYS. FORCED_VALID + will be set to true on return if the user did override the the + key's validity. */ +static cryptkey_t *crypt_select_key (cryptkey_t * keys, + address_t * p, const char *s, + unsigned int app, int *forced_valid) +{ + int keymax; + cryptkey_t **cryptkey_table; + MUTTMENU *menu; + int i, done = 0; + char helpstr[STRING], buf[LONG_STRING]; + cryptkey_t *k; + int (*f) (const void *, const void *); + int menu_to_use = 0; + int unusable = 0; + + *forced_valid = 0; + + /* build the key table */ + keymax = i = 0; + cryptkey_table = NULL; + for (k = keys; k; k = k->next) { + if (!option (OPTPGPSHOWUNUSABLE) && (k->flags & KEYFLAG_CANTUSE)) { + unusable = 1; + continue; + } + + if (i == keymax) { + keymax += 20; + p_realloc(&cryptkey_table, keymax); + } + + cryptkey_table[i++] = k; + } + + if (!i && unusable) { + mutt_error _("All matching keys are marked expired/revoked."); + + mutt_sleep (1); + return NULL; + } + + switch (PgpSortKeys & SORT_MASK) { + case SORT_DATE: + f = crypt_compare_date; + break; + case SORT_KEYID: + f = crypt_compare_keyid; + break; + case SORT_ADDRESS: + f = crypt_compare_address; + break; + case SORT_TRUST: + default: + f = crypt_compare_trust; + break; + } + qsort (cryptkey_table, i, sizeof (cryptkey_t *), f); + + if (app & APPLICATION_PGP) + menu_to_use = MENU_KEY_SELECT_PGP; + else if (app & APPLICATION_SMIME) + menu_to_use = MENU_KEY_SELECT_SMIME; + + helpstr[0] = 0; + mutt_make_help (buf, sizeof (buf), _("Exit "), menu_to_use, OP_EXIT); + m_strcat(helpstr, sizeof(helpstr), buf); + mutt_make_help (buf, sizeof (buf), _("Select "), menu_to_use, + OP_GENERIC_SELECT_ENTRY); + m_strcat(helpstr, sizeof(helpstr), buf); + mutt_make_help (buf, sizeof (buf), _("Check key "), + menu_to_use, OP_VERIFY_KEY); + m_strcat(helpstr, sizeof(helpstr), buf); + mutt_make_help (buf, sizeof (buf), _("Help"), menu_to_use, OP_HELP); + m_strcat(helpstr, sizeof(helpstr), buf); + + menu = mutt_new_menu (); + menu->max = i; + menu->make_entry = crypt_entry; + menu->menu = menu_to_use; + menu->help = helpstr; + menu->data = cryptkey_table; + + { + const char *ts; + + if ((app & APPLICATION_PGP) && (app & APPLICATION_SMIME)) + ts = _("PGP and S/MIME keys matching"); + else if ((app & APPLICATION_PGP)) + ts = _("PGP keys matching"); + else if ((app & APPLICATION_SMIME)) + ts = _("S/MIME keys matching"); + else + ts = _("keys matching"); + + if (p) + snprintf (buf, sizeof (buf), _("%s <%s>."), ts, p->mailbox); + else + snprintf (buf, sizeof (buf), _("%s \"%s\"."), ts, s); + menu->title = buf; + } + + mutt_clear_error (); + k = NULL; + while (!done) { + *forced_valid = 0; + switch (mutt_menuLoop (menu)) { + case OP_VERIFY_KEY: + verify_key (cryptkey_table[menu->current]); + menu->redraw = REDRAW_FULL; + break; + + case OP_VIEW_ID: + mutt_message ("%s", cryptkey_table[menu->current]->uid); + break; + + case OP_GENERIC_SELECT_ENTRY: + /* FIXME make error reporting more verbose - this should be + easy because gpgme provides more information */ + if (option (OPTPGPCHECKTRUST)) { + if (cryptkey_table[menu->current]->flags & KEYFLAG_CANTUSE ) { + mutt_error(_("This key can't be used: " + "expired/disabled/revoked.")); + break; + } + } + + if (option (OPTPGPCHECKTRUST) && + ((cryptkey_table[menu->current]->flags & KEYFLAG_CANTUSE) + || !crypt_id_is_strong (cryptkey_table[menu->current]))) { + const char *warn_s; + char buff[LONG_STRING]; + + if (cryptkey_table[menu->current]->flags & KEYFLAG_CANTUSE) + s = N_("ID is expired/disabled/revoked."); + else { + gpgme_validity_t val = GPGME_VALIDITY_UNKNOWN; + gpgme_user_id_t uid = NULL; + int j = 0; + + warn_s = "??"; + + uid = cryptkey_table[menu->current]->kobj->uids; + for (j = 0; (j < cryptkey_table[menu->current]->idx) && uid; + j++, uid = uid->next); + if (uid) + val = uid->validity; + + switch (val) { + case GPGME_VALIDITY_UNKNOWN: + case GPGME_VALIDITY_UNDEFINED: + warn_s = N_("ID has undefined validity."); + break; + case GPGME_VALIDITY_NEVER: + warn_s = N_("ID is not valid."); + break; + case GPGME_VALIDITY_MARGINAL: + warn_s = N_("ID is only marginally valid."); + break; + case GPGME_VALIDITY_FULL: + case GPGME_VALIDITY_ULTIMATE: + break; + } + + snprintf (buff, sizeof (buff), + _("%s Do you really want to use the key?"), _(warn_s)); + + if (mutt_yesorno (buff, 0) != 1) { + mutt_clear_error (); + break; + } + *forced_valid = 1; + } + } + + k = cryptkey_dup(cryptkey_table[menu->current]); + done = 1; + break; + + case OP_EXIT: + k = NULL; + done = 1; + break; + } + } + + mutt_menuDestroy (&menu); + p_delete(&cryptkey_table); + + set_option (OPTNEEDREDRAW); + + return k; +} + +static cryptkey_t * +crypt_getkeybyaddr(address_t * a, int abilities, int app, int *forced_valid) +{ + address_t *r, *p; + + int weak = 0; + int invalid = 0; + int multi = 0; + int this_key_has_strong; + int this_key_has_weak; + int this_key_has_invalid; + int match; + + cryptkey_t *keys, *k; + cryptkey_t *the_valid_key = NULL; + cryptkey_t *matches = NULL; + cryptkey_t **matches_endp = &matches; + + *forced_valid = 0; + + { + string_array hints; + string_array_init(&hints); + add_hints(&hints, a->mailbox); + add_hints(&hints, a->personal); + + mutt_message (_("Looking for keys matching \"%s\"..."), a->mailbox); + keys = get_candidates(&hints, app, (abilities & KEYFLAG_CANSIGN)); + string_array_wipe(&hints); + } + + if (!keys) + return NULL; + + for (k = keys; k; k = k->next) { + if (abilities && !(k->flags & abilities)) { + continue; + } + + this_key_has_weak = 0; /* weak but valid match */ + this_key_has_invalid = 0; /* invalid match */ + this_key_has_strong = 0; /* strong and valid match */ + match = 0; /* any match */ + + r = rfc822_parse_adrlist (NULL, k->uid); + for (p = r; p; p = p->next) { + int validity = crypt_id_matches_addr (a, p, k); + + if (validity & CRYPT_KV_MATCH) /* something matches */ + match = 1; + + /* is this key a strong candidate? */ + if ((validity & CRYPT_KV_VALID) + && (validity & CRYPT_KV_STRONGID) + && (validity & CRYPT_KV_ADDR)) { + if (the_valid_key && the_valid_key != k) + multi = 1; + the_valid_key = k; + this_key_has_strong = 1; + } + else if ((validity & CRYPT_KV_MATCH) + && !(validity & CRYPT_KV_VALID)) + this_key_has_invalid = 1; + else if ((validity & CRYPT_KV_MATCH) + && (!(validity & CRYPT_KV_STRONGID) + || !(validity & CRYPT_KV_ADDR))) + this_key_has_weak = 1; + } + address_list_wipe(&r); + + if (match) { + cryptkey_t *tmp; + + if (!this_key_has_strong && this_key_has_invalid) + invalid = 1; + if (!this_key_has_strong && this_key_has_weak) + weak = 1; + + *matches_endp = tmp = cryptkey_dup(k); + matches_endp = &tmp->next; + the_valid_key = tmp; + } + } + key_list_wipe(&keys); + + if (matches) { + if (the_valid_key && !multi && !weak + && !(invalid && option (OPTPGPSHOWUNUSABLE))) { + /* + * There was precisely one strong match on a valid ID, there + * were no valid keys with weak matches, and we aren't + * interested in seeing invalid keys. + * + * Proceed without asking the user. + */ + k = cryptkey_dup(the_valid_key); + } else { + /* + * Else: Ask the user. + */ + k = crypt_select_key (matches, a, NULL, app, forced_valid); + } + key_list_wipe(&matches); + } else { + k = NULL; + } + + return k; +} + + +static cryptkey_t * +crypt_getkeybystr(const char *p, int abilities, int app, int *forced_valid) +{ + cryptkey_t *keys; + cryptkey_t *matches = NULL; + cryptkey_t **matches_endp = &matches; + cryptkey_t *k; + int match; + + mutt_message (_("Looking for keys matching \"%s\"..."), p); + + *forced_valid = 0; + + { + string_array hints; + string_array_init(&hints); + add_hints(&hints, p); + keys = get_candidates(&hints, app, (abilities & KEYFLAG_CANSIGN)); + string_array_wipe(&hints); + } + + if (!keys) + return NULL; + + for (k = keys; k; k = k->next) { + const char *s = crypt_keyid(k); + + if (abilities && !(k->flags & abilities)) + continue; + + match = 0; + + if (!*p || !m_strcasecmp(p, s) + || (!m_strncasecmp(p, "0x", 2) && !m_strcasecmp(p + 2, s)) + || m_stristr(k->uid, p)) + { + cryptkey_t *tmp; + + *matches_endp = tmp = cryptkey_dup(k); + matches_endp = &tmp->next; + } + } + key_list_wipe(&keys); + + if (matches) { + k = crypt_select_key (matches, NULL, p, app, forced_valid); + key_list_wipe(&matches); + return k; + } + + return NULL; +} + +/* Display TAG as a prompt to ask for a key. + * ABILITIES describe the required key abilities (sign, encrypt) and APP the + * type of the requested key; ether S/MIME or PGP. + * Return a copy of the key or NULL if not found. */ +static cryptkey_t * +crypt_ask_for_key(const char *tag, int abilities, int app, int *forced_valid) +{ + cryptkey_t *key; + char resp[STRING]; + int dummy; + + if (!forced_valid) + forced_valid = &dummy; + *forced_valid = 0; + + mutt_clear_error(); + for (;;) { + resp[0] = 0; + if (mutt_get_field(tag, resp, sizeof(resp), M_CLEAR) != 0) + return NULL; + + if (m_strisempty(resp)) + return NULL; + + if ((key = crypt_getkeybystr(resp, abilities, app, forced_valid))) + return key; + BEEP (); + } +} -/* print the current time to avoid spoofing of the signature output */ -void crypt_current_time (STATE * s, const char *app_name) +/* 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) { - time_t t; - char p[STRING], tmp[STRING]; + 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); + } - if (!WithCrypto) - return; + 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); + } + } + } - 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 (!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; + } + } - snprintf (tmp, sizeof (tmp), _("[-- %s output follows%s --]\n"), - NONULL (app_name), p); - state_attach_puts (tmp, s); -} + if (keylist->len) + buffer_addch(keylist, ' '); + buffer_addstr(keylist, "0x"); + buffer_addstr(keylist, crypt_fpr(key)); + if (forced_valid) + buffer_addch(keylist, '!'); + key_list_wipe(&key); + address_list_wipe(&addr); + } + address_list_wipe(&lst); + return buffer_unwrap(&keylist); +} -void crypt_forget_passphrase (void) +int crypt_get_keys(HEADER *msg, char **keylist) { - if ((WithCrypto & APPLICATION_PGP)) - crypt_pgp_void_passphrase (); + /* 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 ((WithCrypto & APPLICATION_SMIME)) - crypt_smime_void_passphrase (); + if (msg->security & APPLICATION_SMIME) { + *keylist = find_keys(msg->env, APPLICATION_SMIME); + if (!*keylist) + return -1; + } + } - if (WithCrypto) - mutt_message _("Passphrase(s) forgotten."); + return 0; } -#if defined(HAVE_SETRLIMIT) && (!defined(DEBUG)) - -static void disable_coredumps (void) +int crypt_send_menu (HEADER * msg, int *redraw, int is_smime) { - struct rlimit rl = { 0, 0 }; - static short done = 0; + 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 (!done) { - setrlimit (RLIMIT_CORE, &rl); - done = 1; - } -} + case 5: /* (p)gp or s/(m)ime */ + is_smime = !is_smime; + break; + + case 6: /* (c)lear */ + return msg->security = 0; + } -#endif /* HAVE_SETRLIMIT */ + if (is_smime) { + msg->security &= ~APPLICATION_PGP; + msg->security |= APPLICATION_SMIME; + } else { + msg->security &= ~APPLICATION_SMIME; + msg->security |= APPLICATION_PGP; + } + return msg->security; +} -int crypt_valid_passphrase (int flags) +int crypt_smime_verify_sender(HEADER *h) { - int ret = 0; - -# if defined(HAVE_SETRLIMIT) &&(!defined(DEBUG)) - disable_coredumps (); -# endif + 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 ((WithCrypto & APPLICATION_PGP) && (flags & APPLICATION_PGP)) - ret = crypt_pgp_valid_passphrase (); + if (!sender) { + mutt_any_key_to_continue ("Failed to figure out sender"); + goto end; + } - if ((WithCrypto & APPLICATION_SMIME) && (flags & APPLICATION_SMIME)) - ret = crypt_smime_valid_passphrase (); + 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"); + } - return ret; + end: + if (signature_key) { + gpgme_key_unref(signature_key); + signature_key = NULL; + } + return ret; } +static void crypt_invoke_import(FILE *stream, int smime) +{ + 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; + } + + 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; + } + gpgme_data_release(data); + gpgme_release(ctx); + return; +} -int mutt_protect (HEADER * msg, char *keylist) +static void pgp_extract_keys_from_attachment(FILE * fp, BODY * top) { - 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; + STATE s; + FILE *tmpfp = tmpfile(); - if (!WithCrypto) - return -1; + if (tmpfp == NULL) { + mutt_perror (_("Can't create temporary file")); + return; + } - if ((msg->security & SIGN) && !crypt_valid_passphrase (msg->security)) - return (-1); + p_clear(&s, 1); + s.fpin = fp; + s.fpout = tmpfp; + mutt_body_handler(top, &s); + + rewind(tmpfp); + crypt_invoke_import(tmpfp, 0); + m_fclose(&tmpfp); +} - if ((WithCrypto & APPLICATION_PGP) - && ((msg->security & PGPINLINE) == PGPINLINE)) { - /* they really want to send it inline... go for it */ - if (!isendwin ()) - mutt_endwin _("Invoking PGP..."); +void crypt_pgp_extract_keys_from_attachment_list(FILE * fp, int tag, BODY * top) +{ + mutt_endwin (NULL); - pbody = crypt_pgp_traditional_encryptsign (msg->content, flags, keylist); - if (pbody) { - msg->content = pbody; - return 0; - } + for (; top; top = top->next) { + if (!tag || top->tagged) + pgp_extract_keys_from_attachment (fp, top); - /* 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."); + if (!tag) + break; + } +} - 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 ;-) */ } @@ -198,8 +3634,7 @@ int mutt_protect (HEADER * msg, char *keylist) if (msg->security & ENCRYPT) { - if ((WithCrypto & APPLICATION_SMIME) - && (msg->security & APPLICATION_SMIME)) { + if ((msg->security & APPLICATION_SMIME)) { if (!(tmp_pbody = crypt_smime_build_smime_entity (tmp_smime_pbody, keylist))) { /* signed ? free it! */ @@ -211,13 +3646,12 @@ int mutt_protect (HEADER * msg, char *keylist) which tmp_smime_pbody->parts after signing. */ tmp_smime_pbody->parts = tmp_smime_pbody->parts->next; msg->content->next = NULL; - mutt_free_body (&tmp_smime_pbody); + body_list_wipe(&tmp_smime_pbody); } pbody = tmp_pbody; } - if ((WithCrypto & APPLICATION_PGP) - && (msg->security & APPLICATION_PGP)) { + if ((msg->security & APPLICATION_PGP)) { if (!(pbody = crypt_pgp_encrypt_message (tmp_pgp_pbody, keylist, flags & SIGN))) { @@ -226,7 +3660,7 @@ int mutt_protect (HEADER * msg, char *keylist) /* remove the outer multipart layer */ tmp_pgp_pbody = mutt_remove_multipart (tmp_pgp_pbody); /* get rid of the signature */ - mutt_free_body (&tmp_pgp_pbody->next); + body_list_wipe(&tmp_pgp_pbody->next); } return (-1); @@ -238,7 +3672,7 @@ int mutt_protect (HEADER * msg, char *keylist) */ if (flags != msg->security) { tmp_pgp_pbody = mutt_remove_multipart (tmp_pgp_pbody); - mutt_free_body (&tmp_pgp_pbody->next); + body_list_wipe(&tmp_pgp_pbody->next); } } } @@ -250,192 +3684,23 @@ int mutt_protect (HEADER * msg, char *keylist) } - - -int mutt_is_multipart_signed (BODY * b) -{ - char *p; - - if (!b || !(b->type == TYPEMULTIPART) || - !b->subtype || ascii_strcasecmp (b->subtype, "signed")) - return 0; - - if (!(p = mutt_get_parameter ("protocol", b->parameter))) - return 0; - - if (!(ascii_strcasecmp (p, "multipart/mixed"))) - return SIGN; - - if ((WithCrypto & APPLICATION_PGP) - && !(ascii_strcasecmp (p, "application/pgp-signature"))) - return PGPSIGN; - - if ((WithCrypto & APPLICATION_SMIME) - && !(ascii_strcasecmp (p, "application/x-pkcs7-signature"))) - return SMIMESIGN; - if ((WithCrypto & APPLICATION_SMIME) - && !(ascii_strcasecmp (p, "application/pkcs7-signature"))) - return SMIMESIGN; - - return 0; -} - - -int mutt_is_multipart_encrypted (BODY * b) -{ - if ((WithCrypto & APPLICATION_PGP)) { - char *p; - - if (!b || b->type != TYPEMULTIPART || - !b->subtype || ascii_strcasecmp (b->subtype, "encrypted") || - !(p = mutt_get_parameter ("protocol", b->parameter)) || - ascii_strcasecmp (p, "application/pgp-encrypted")) - return 0; - - return PGPENCRYPT; - } - - return 0; -} - - -int mutt_is_application_pgp (BODY * m) -{ - int t = 0; - char *p; - - if (m->type == TYPEAPPLICATION) { - if (!ascii_strcasecmp (m->subtype, "pgp") - || !ascii_strcasecmp (m->subtype, "x-pgp-message")) { - if ((p = mutt_get_parameter ("x-action", m->parameter)) - && (!ascii_strcasecmp (p, "sign") - || !ascii_strcasecmp (p, "signclear"))) - t |= PGPSIGN; - - if ((p = mutt_get_parameter ("format", m->parameter)) && - !ascii_strcasecmp (p, "keys-only")) - t |= PGPKEY; - - if (!t) - t |= PGPENCRYPT; /* not necessarily correct, but... */ - } - - if (!ascii_strcasecmp (m->subtype, "pgp-signed")) - t |= PGPSIGN; - - if (!ascii_strcasecmp (m->subtype, "pgp-keys")) - t |= PGPKEY; - } - else if (m->type == TYPETEXT && ascii_strcasecmp ("plain", m->subtype) == 0) { - if (((p = mutt_get_parameter ("x-mutt-action", m->parameter)) - || (p = mutt_get_parameter ("x-action", m->parameter)) - || (p = mutt_get_parameter ("action", m->parameter))) - && !ascii_strncasecmp ("pgp-sign", p, 8)) - t |= PGPSIGN; - else if (p && !ascii_strncasecmp ("pgp-encrypt", p, 11)) - t |= PGPENCRYPT; - else if (p && !ascii_strncasecmp ("pgp-keys", p, 7)) - t |= PGPKEY; - } - if (t) - t |= PGPINLINE; - - return t; -} - -int mutt_is_application_smime (BODY * m) -{ - char *t = NULL; - int len, complain = 0; - - if (!m) - return 0; - - if ((m->type & TYPEAPPLICATION) && m->subtype) { - /* S/MIME MIME types don't need x- anymore, see RFC2311 */ - if (!ascii_strcasecmp (m->subtype, "x-pkcs7-mime") || - !ascii_strcasecmp (m->subtype, "pkcs7-mime")) { - if ((t = mutt_get_parameter ("smime-type", m->parameter))) { - if (!ascii_strcasecmp (t, "enveloped-data")) - return SMIMEENCRYPT; - else if (!ascii_strcasecmp (t, "signed-data")) - return (SMIMESIGN | SMIMEOPAQUE); - else - return 0; - } - /* Netscape 4.7 uses - * Content-Description: S/MIME Encrypted Message - * instead of Content-Type parameter - */ - if (!ascii_strcasecmp (m->description, "S/MIME Encrypted Message")) - return SMIMEENCRYPT; - complain = 1; - } - else if (ascii_strcasecmp (m->subtype, "octet-stream")) - return 0; - - t = mutt_get_parameter ("name", m->parameter); - - if (!t) - t = m->d_filename; - if (!t) - t = m->filename; - if (!t) { - if (complain) - mutt_message (_ - ("S/MIME messages with no hints on content are unsupported.")); - return 0; - } - - /* no .p7c, .p10 support yet. */ - - len = str_len (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; @@ -453,7 +3718,7 @@ int crypt_query (BODY * m) BODY *p; int u, v, w; - u = m->parts ? 0xffffffff : 0; /* Bits set in all parts */ + u = m->parts ? ~0 : 0; /* Bits set in all parts */ w = 0; /* Bits set in any part */ for (p = m->parts; p; p = p->next) { @@ -471,367 +3736,191 @@ int crypt_query (BODY * m) } - - -int crypt_write_signed (BODY * a, STATE * s, const char *tempfile) -{ - FILE *fp; - int c; - short hadcr; - size_t bytes; - - if (!WithCrypto) - return -1; - - if (!(fp = safe_fopen (tempfile, "w"))) { - mutt_perror (tempfile); - return -1; - } - - fseeko (s->fpin, a->hdr_offset, 0); - bytes = a->length + a->offset - a->hdr_offset; - hadcr = 0; - while (bytes > 0) { - if ((c = fgetc (s->fpin)) == EOF) - break; - - bytes--; - - if (c == '\r') - hadcr = 1; - else { - if (c == '\n' && !hadcr) - fputc ('\r', fp); - - hadcr = 0; - } - - fputc (c, fp); - - } - fclose (fp); - - return 0; -} - - - -void convert_to_7bit (BODY * a) +static void crypt_write_signed(BODY * a, STATE * s, FILE *fp) { - if (!WithCrypto) - return; - - while (a) { - if (a->type == TYPEMULTIPART) { - if (a->encoding != ENC7BIT) { - a->encoding = ENC7BIT; - convert_to_7bit (a->parts); - } - else if ((WithCrypto & APPLICATION_PGP) && option (OPTPGPSTRICTENC)) - convert_to_7bit (a->parts); - } - else if (a->type == TYPEMESSAGE && - str_casecmp (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) - p_realloc(signatures, *n + 6); - - (*signatures)[(*n)++] = a; + for (; a; a = a->next) { + if (a->type == TYPEMULTIPART) { + crypt_fetch_signatures(signatures, a->parts, n); + } else { + if ((*n % 5) == 0) + p_realloc(signatures, *n + 6); + + (*signatures)[(*n)++] = a; + } } - } } - -/* - * This routine verifies a "multipart/signed" body. - */ - -int mutt_signed_handler (BODY * a, STATE * s) +int mutt_signed_handler(BODY *a, STATE *s) { - char tempfile[_POSIX_PATH_MAX]; + unsigned major, minor; char *protocol; - int protocol_major = TYPEOTHER; - char *protocol_minor = NULL; - + int rc, i, goodsig = 1, sigcnt = 0; BODY *b = a; - BODY **signatures = NULL; - int sigcnt = 0; - int i; - short goodsig = 1; - int rc = 0; - - if (!WithCrypto) - return (-1); - protocol = mutt_get_parameter ("protocol", a->parameter); + protocol = parameter_getval(a->parameter, "protocol"); a = a->parts; - /* extract the protocol information */ - - if (protocol) { - char major[STRING]; - char *t; - - if ((protocol_minor = strchr (protocol, '/'))) - protocol_minor++; - - 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 && - !str_casecmp (a->next->subtype, protocol_minor))) { - state_attach_puts (_("[-- Error: " - "Inconsistent multipart/signed structure! --]\n\n"), - s); - return mutt_body_handler (a, s); - } - - - if ((WithCrypto & APPLICATION_PGP) - && protocol_major == TYPEAPPLICATION - && !str_casecmp (protocol_minor, "pgp-signature")); - else if ((WithCrypto & APPLICATION_SMIME) - && protocol_major == TYPEAPPLICATION - && !(str_casecmp (protocol_minor, "x-pkcs7-signature") - && str_casecmp (protocol_minor, "pkcs7-signature"))); - else if (protocol_major == TYPEMULTIPART - && !str_casecmp (protocol_minor, "mixed")); - else { - state_printf (s, _("[-- Error: " - "Unknown multipart/signed protocol %s! --]\n\n"), - protocol); + if (!(a && a->next && a->next->type == major && + mime_which_token(a->next->subtype, -1) == minor)) + { + state_attach_puts(_("[-- Error: " + "Inconsistent multipart/signed structure! --]\n\n"), + s); return mutt_body_handler (a, s); } if (s->flags & M_DISPLAY) { + BODY **sigs = NULL; - crypt_fetch_signatures (&signatures, a->next, &sigcnt); - + crypt_fetch_signatures (&sigs, a->next, &sigcnt); if (sigcnt) { - mutt_mktemp (tempfile); - if (crypt_write_signed (a, s, tempfile) == 0) { - for (i = 0; i < sigcnt; i++) { - if ((WithCrypto & APPLICATION_PGP) - && signatures[i]->type == TYPEAPPLICATION - && !str_casecmp (signatures[i]->subtype, "pgp-signature")) { - if (crypt_pgp_verify_one (signatures[i], s, tempfile) != 0) - goodsig = 0; - - continue; - } - - if ((WithCrypto & APPLICATION_SMIME) - && signatures[i]->type == TYPEAPPLICATION - && - (!str_casecmp (signatures[i]->subtype, "x-pkcs7-signature") - || !str_casecmp (signatures[i]->subtype, - "pkcs7-signature"))) { - if (crypt_smime_verify_one (signatures[i], s, tempfile) != 0) - goodsig = 0; + FILE *tmpfp = tmpfile(); - continue; + if (!tmpfp) { + mutt_error(_("Could not create temporary file")); + } else { + crypt_write_signed(a, s, tmpfp); + rewind(tmpfp); + for (i = 0; i < sigcnt; i++) { + if (sigs[i]->type == TYPEAPPLICATION) { + int subtype; + + switch ((subtype = mime_which_token(sigs[i]->subtype, -1))) { + case MIME_PGP_SIGNATURE: + case MIME_X_PKCS7_SIGNATURE: + case MIME_PKCS7_SIGNATURE: + if (crypt_verify_one(sigs[i], s, tmpfp, subtype != MIME_PGP_SIGNATURE) != 0) + goodsig = 0; + + m_fclose(&tmpfp); + continue; + + default: + break; + } } - state_printf (s, _("[-- Warning: " - "We can't verify %s/%s signatures. --]\n\n"), - TYPE (signatures[i]), signatures[i]->subtype); + state_printf(s, _("[-- Warning: " + "We can't verify %s/%s signatures. --]\n\n"), + TYPE (sigs[i]), sigs[i]->subtype); } } - mutt_unlink (tempfile); - b->goodsig = goodsig; - b->badsig = !goodsig; + b->badsig = !goodsig; /* Now display the signed body */ - state_attach_puts (_("[-- The following data is signed --]\n\n"), s); - + state_attach_puts(_("[-- The following data is signed --]\n\n"), s); - p_delete(&signatures); + p_delete(&sigs); + } else { + state_attach_puts(_("[-- Warning: Can't find any signatures. --]\n\n"), + s); } - else - state_attach_puts (_("[-- Warning: Can't find any signatures. --]\n\n"), - s); } rc = mutt_body_handler (a, s);