X-Git-Url: http://git.madism.org/?p=apps%2Fmadmutt.git;a=blobdiff_plain;f=crypt.c;h=2fc0924c571cf504a2ce504d281a433b3c3d8490;hp=1512286c09b9faac2385fcd9f556a90490654e2a;hb=569145d4875fd59719a1a3a7d2c7d64d622eacd1;hpb=9a1efcc01ddeca4106847f8eb28a704aca2dcf0b diff --git a/crypt.c b/crypt.c index 1512286..2fc0924 100644 --- a/crypt.c +++ b/crypt.c @@ -1,196 +1,3959 @@ /* * 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 - * - * This file is part of mutt-ng, see http://www.muttng.org/. - * It's licensed under the GNU General Public License, - * please see the file GPL in the top level source directory. + * Copyright (C) 2002, 2003, 2004 g10 Code GmbH + */ +/* + * Copyright © 2006 Pierre Habouzit */ -#if HAVE_CONFIG_H -# include "config.h" -#endif +#include -#include -#include +#include -#include "mutt.h" -#include "ascii.h" +#include +#include +#include +#include +#include + +#include "crypt.h" +#include "lib.h" +#include "alias.h" #include "handler.h" -#include "mutt_curses.h" -#include "mime.h" #include "copy.h" -#include "mutt_crypt.h" -#include "pgp.h" +#include "pager.h" +#include "recvattach.h" +#include "sort.h" -#include "lib/str.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) -#include -#include -#include -#include -#include -#include -#include +/* + * Type definitions. + */ -#ifdef HAVE_LOCALE_H -#include -#endif +struct crypt_cache { + char *what; + char *dflt; + struct crypt_cache *next; +}; -#ifdef HAVE_SYS_TIME_H -# include -#endif +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 crypt_keyinfo { + struct crypt_keyinfo *next; + gpgme_key_t kobj; + int idx; /* and the user ID at this index */ + const char *uid; /* and for convenience point to this user ID */ + unsigned int flags; /* global and per uid flags (for convenience) */ +} crypt_key_t; + +typedef struct crypt_entry { + ssize_t num; + crypt_key_t *key; +} crypt_entry_t; + + +static struct crypt_cache *id_defaults = NULL; +static gpgme_key_t signature_key = NULL; + +/* + * General helper functions. + */ + +static void convert_to_7bit (BODY * a) +{ + while (a) { + if (a->type == TYPEMULTIPART) { + if (a->encoding != ENC7BIT) { + a->encoding = ENC7BIT; + convert_to_7bit (a->parts); + } else { + convert_to_7bit (a->parts); + } + } + else if (a->type == TYPEMESSAGE && + m_strcasecmp(a->subtype, "delivery-status")) { + if (a->encoding != ENC7BIT) + mutt_message_to_7bit (a, NULL); + } + 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; + a = a->next; + } +} + +/* 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); +} + + +/* + * Key management. + */ + +/* Return the keyID for the key K. Note that this string is valid as + long as K is valid */ +static const char *crypt_keyid (crypt_key_t * k) +{ + const char *s = "????????"; + + if (k->kobj && k->kobj->subkeys) { + s = k->kobj->subkeys->keyid; + if ((!option (OPTPGPLONGIDS)) && (m_strlen(s) == 16)) + /* Return only the short keyID. */ + s += 8; + } + + return s; +} + +/* Return the hexstring fingerprint from the key K. */ +static const char *crypt_fpr (crypt_key_t * k) +{ + const char *s = ""; + + if (k->kobj && k->kobj->subkeys) + s = k->kobj->subkeys->fpr; + + return s; +} + +/* Parse FLAGS and return a statically allocated(!) string with them. */ +static char *crypt_key_abilities (int flags) +{ + static char buff[3]; + + if (!(flags & KEYFLAG_CANENCRYPT)) + buff[0] = '-'; + else if (flags & KEYFLAG_PREFER_SIGNING) + buff[0] = '.'; + else + buff[0] = 'e'; + + if (!(flags & KEYFLAG_CANSIGN)) + buff[1] = '-'; + else if (flags & KEYFLAG_PREFER_ENCRYPTION) + buff[1] = '.'; + else + buff[1] = 's'; + + buff[2] = '\0'; + + 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'; + else if (flags & KEYFLAG_EXPIRED) + return 'X'; + else if (flags & KEYFLAG_DISABLED) + return 'd'; + else if (flags & KEYFLAG_CRITICAL) + return 'c'; + else + return ' '; +} + +/* Return a copy of KEY. */ +static crypt_key_t *crypt_copy_key (crypt_key_t *key) +{ + crypt_key_t *k; + + k = p_new(crypt_key_t, 1); + k->kobj = key->kobj; + gpgme_key_ref (key->kobj); + k->idx = key->idx; + k->uid = key->uid; + k->flags = key->flags; + + return k; +} + +/* Release all the keys at the address of KEYLIST and set the address + to NULL. */ +static void crypt_free_key (crypt_key_t ** keylist) +{ + while (*keylist) { + crypt_key_t *k = (*keylist)->next; + + p_delete(&k); + *keylist = k; + } +} + +/* Return trute when key K is valid. */ +static int crypt_key_is_valid (crypt_key_t * k) +{ + if (k->flags & KEYFLAG_CANTUSE) + return 0; + return 1; +} + +/* Return true whe validity of KEY is sufficient. */ +static int crypt_id_is_strong (crypt_key_t * key) +{ + gpgme_validity_t val = GPGME_VALIDITY_UNKNOWN; + gpgme_user_id_t uid = NULL; + int is_strong = 0; + int i = 0; + + if ((key->flags & KEYFLAG_ISX509)) + return 1; + + for (i = 0, uid = key->kobj->uids; (i < key->idx) && uid; + i++, uid = uid->next); + if (uid) + val = uid->validity; + + switch (val) { + case GPGME_VALIDITY_UNKNOWN: + case GPGME_VALIDITY_UNDEFINED: + case GPGME_VALIDITY_NEVER: + case GPGME_VALIDITY_MARGINAL: + is_strong = 0; + break; + + case GPGME_VALIDITY_FULL: + case GPGME_VALIDITY_ULTIMATE: + is_strong = 1; + break; + } + + return is_strong; +} + +/* Return true when the KEY is valid, i.e. not marked as unusable. */ +static int crypt_id_is_valid (crypt_key_t * key) +{ + return !(key->flags & KEYFLAG_CANTUSE); +} + +/* 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, + crypt_key_t * key) +{ + int rv = 0; + + if (crypt_id_is_valid (key)) + rv |= CRYPT_KV_VALID; + + if (crypt_id_is_strong (key)) + rv |= CRYPT_KV_STRONGID; + + if (addr->mailbox && u_addr->mailbox + && m_strcasecmp(addr->mailbox, u_addr->mailbox) == 0) + rv |= CRYPT_KV_ADDR; + + if (addr->personal && u_addr->personal + && m_strcasecmp(addr->personal, u_addr->personal) == 0) + rv |= CRYPT_KV_STRING; + + return rv; +} + + +/* + * GPGME convenient functions. + */ + +/* 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) { + 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) +{ + char tempfile[_POSIX_PATH_MAX]; + FILE *fptmp; + int err = 0; + gpgme_data_t data; + + fptmp = m_tempfile(tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL); + if (!fptmp) { + mutt_perror (_("Can't create temporary file")); + return NULL; + } + + mutt_write_mime_header (a, fptmp); + fputc ('\n', fptmp); + mutt_write_mime_body (a, fptmp); + + if (convert) { + int c, hadcr = 0; + unsigned char buf[1]; + + data = create_gpgme_data (); + rewind (fptmp); + while ((c = fgetc (fptmp)) != EOF) { + if (c == '\r') + hadcr = 1; + else { + if (c == '\n' && !hadcr) { + buf[0] = '\r'; + gpgme_data_write (data, buf, 1); + } + + hadcr = 0; + } + /* FIXME: This is quite suboptimal */ + buf[0] = c; + gpgme_data_write (data, buf, 1); + } + gpgme_data_seek (data, 0, SEEK_SET); + } else { + err = gpgme_data_new_from_file (&data, tempfile, 1); + } + m_fclose(&fptmp); + unlink (tempfile); + if (err) { + mutt_error (_("error allocating data object: %s\n"), gpgme_strerror (err)); + return NULL; + } -#ifdef HAVE_SYS_RESOURCE_H -# include + 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 (ret_fp) + rewind (fp); + else + m_fclose(&fp); + if (nread == -1) { + mutt_error (_("error reading data object: %s\n"), gpgme_strerror (err)); + unlink (tempfile); + m_fclose(&fp); + return NULL; + } + if (ret_fp) + *ret_fp = 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 *keylist, + gpgme_protocol_t protocol) +{ + int err; + const char *s; + char buf[100]; + int i; + gpgme_key_t *rset = NULL; + unsigned int rset_n = 0; + gpgme_key_t key = NULL; + gpgme_ctx_t context = NULL; + + err = gpgme_new (&context); + if (!err) + err = gpgme_set_protocol (context, protocol); + + if (!err) { + s = keylist; + do { + while (*s == ' ') + s++; + for (i = 0; *s && *s != ' ' && i < ssizeof(buf) - 1;) + buf[i++] = *s++; + buf[i] = 0; + if (*buf) { + if (i > 1 && buf[i - 1] == '!') { + /* The user selected to override the valididy of that + key. */ + buf[i - 1] = 0; + + err = gpgme_get_key2 (context, buf, &key, 0); + if (!err) + key->uids->validity = GPGME_VALIDITY_FULL; + buf[i - 1] = '!'; + } + else + err = gpgme_get_key2 (context, buf, &key, 0); + + if (!err) { + p_realloc(&rset, rset_n + 1); + rset[rset_n++] = key; + } + else { + mutt_error (_("error adding recipient `%s': %s\n"), + buf, gpgme_strerror (err)); + p_delete(&rset); + return NULL; + } + } + } while (*s); + } + + /* NULL terminate. */ + p_realloc(&rset, rset_n + 1); + rset[rset_n++] = NULL; + + if (context) + gpgme_release (context); + + 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) +{ + char *signid = for_smime ? SmimeDefaultKey : PgpSignAs; + gpgme_error_t err; + gpgme_ctx_t listctx; + gpgme_key_t key, key2; + + if (!signid || !*signid) + return 0; + + listctx = create_gpgme_context (for_smime); + err = gpgme_op_keylist_start (listctx, signid, 1); + if (!err) + err = gpgme_op_keylist_next (listctx, &key); + if (err) { + gpgme_release (listctx); + mutt_error (_("secret key `%s' not found: %s\n"), + signid, gpgme_strerror (err)); + return -1; + } + err = gpgme_op_keylist_next (listctx, &key2); + if (!err) { + gpgme_key_release (key); + gpgme_key_release (key2); + gpgme_release (listctx); + mutt_error (_("ambiguous specification of secret key `%s'\n"), signid); + return -1; + } + gpgme_op_keylist_end (listctx); + gpgme_release (listctx); + + gpgme_signers_clear (ctx); + err = gpgme_signers_add (ctx, key); + gpgme_key_release (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; + + if (!buflen) + return -1; + + *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'. */ -/* print the current time to avoid spoofing of the signature output */ -void crypt_current_time (STATE * s, const char *app_name) +/* 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) { - time_t t; - char p[STRING], tmp[STRING]; + 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; + } - if (!WithCrypto) - return; + 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; +} + +/* + * Implementation of `encrypt_message'. + */ + +/* 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, GPGME_PROTOCOL_OpenPGP); + 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"); /* non pgp/mime + can save */ + + return t; +} + +/* + * Implementation of `smime_build_smime_entity'. + */ + +/* 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, GPGME_PROTOCOL_CMS); + 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; +} + + +/* Implementation of `verify_one'. */ + +/* 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_release (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_release (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_release (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; +} + +/* + * Implementation of `decrypt_part'. + */ + +/* 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) +{ + char tempfile[_POSIX_PATH_MAX]; + STATE s; + BODY *first_part = b; + int is_signed; + + first_part->goodsig = 0; + first_part->warnsig = 0; + + if (!mutt_is_multipart_encrypted (b)) + return -1; + + if (!b->parts || !b->parts->next) + return -1; + + b = b->parts->next; + + p_clear(&s, 1); + s.fpin = fpin; + *fpout = m_tempfile(tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL); + if (!*fpout) { + mutt_perror (_("Can't create temporary file")); + return -1; + } + unlink (tempfile); + + *cur = decrypt_part (b, &s, *fpout, 0, &is_signed); + rewind (*fpout); + if (is_signed > 0) + first_part->goodsig = 1; + + 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) +{ + char tempfile[_POSIX_PATH_MAX]; + 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 = m_tempfile (tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL); + if (!tmpfp) { + mutt_perror (_("Can't create temporary file")); + return -1; + } + mutt_unlink (tempfile); + + 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 = m_tempfile(tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL); + if (!*fpout) { + mutt_perror (_("Can't create temporary file")); + return -1; + } + mutt_unlink (tempfile); + + *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 = m_tempfile (tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL); + if (!tmpfp) { + mutt_perror (_("Can't create temporary file")); + return -1; + } + mutt_unlink (tempfile); + + 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 = m_tempfile(tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL); + if (!*fpout) { + mutt_perror (_("Can't create temporary file")); + return -1; + } + mutt_unlink (tempfile); + + 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; +} + + +/* + * Implementation of `pgp_check_traditional'. + */ + +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; + int r; + + for (; b; b = b->next) { + if (is_multipart (b)) + rv = (crypt_pgp_check_traditional (fp, b->parts, tagged_only) || rv); + else if (b->type == TYPETEXT) { + if ((r = mutt_is_application_pgp (b))) + rv = (rv || r); + else + rv = (pgp_check_traditional_one_body (fp, b, tagged_only) || rv); + } + } + return rv; +} + + +/* Implementation of `application_handler'. */ + +/* + 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 (!option (OPTDONTHANDLEPGPKEYS) && + !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); +} + +/* Implementation of `encrypted_handler'. */ + +/* MIME handler for pgp/mime encrypted messages. */ +int crypt_pgp_encrypted_handler (BODY * a, STATE * s) +{ + char tempfile[_POSIX_PATH_MAX]; + FILE *fpout; + BODY *tattach; + BODY *orig_body = a; + int is_signed; + int rc = 0; + + a = a->parts; + if (!a || a->type != TYPEAPPLICATION || !a->subtype + || ascii_strcasecmp ("pgp-encrypted", a->subtype) + || !a->next || a->next->type != TYPEAPPLICATION || !a->next->subtype + || ascii_strcasecmp ("octet-stream", a->next->subtype)) { + if (s->flags & M_DISPLAY) + state_attach_puts (_("[-- Error: malformed PGP/MIME message! --]\n\n"), + s); + return (-1); + } + + /* Move forward to the application/pgp-encrypted body. */ + a = a->next; + + fpout = m_tempfile(tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL); + if (!fpout) { + if (s->flags & M_DISPLAY) + state_attach_puts (_("[-- Error: could not create temporary file! " + "--]\n"), s); + return (-1); + } + + tattach = decrypt_part (a, s, fpout, 0, &is_signed); + if (tattach) { + tattach->goodsig = is_signed > 0; + + if (s->flags & M_DISPLAY) + state_attach_puts (is_signed ? + _ + ("[-- The following data is PGP/MIME signed and encrypted --]\n\n") : + _("[-- The following data is PGP/MIME encrypted --]\n\n"), s); + + { + FILE *savefp = s->fpin; + + s->fpin = fpout; + rc = mutt_body_handler (tattach, s); + s->fpin = savefp; + } + + /* + * if a multipart/signed is the _only_ sub-part of a + * multipart/encrypted, cache signature verification + * status. + */ + if (mutt_is_multipart_signed (tattach) && !tattach->next) + orig_body->goodsig |= tattach->goodsig; + + if (s->flags & M_DISPLAY) { + state_puts ("\n", s); + state_attach_puts (is_signed ? + _ + ("[-- End of PGP/MIME signed and encrypted data --]\n") + : _("[-- End of PGP/MIME encrypted data --]\n"), s); + } + + body_list_wipe(&tattach); + } + + m_fclose(&fpout); + mutt_unlink (tempfile); + return (rc); +} + +/* Support for application/smime */ +int crypt_smime_application_smime_handler (BODY * a, STATE * s) +{ + char tempfile[_POSIX_PATH_MAX]; + FILE *fpout; + BODY *tattach; + int is_signed; + int rc = 0; + + a->warnsig = 0; + fpout = m_tempfile(tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL); + if (!fpout) { + if (s->flags & M_DISPLAY) + state_attach_puts (_("[-- Error: could not create temporary file! " + "--]\n"), s); + return (-1); + } + + tattach = decrypt_part (a, s, fpout, 1, &is_signed); + if (tattach) { + tattach->goodsig = is_signed > 0; + + if (s->flags & M_DISPLAY) + state_attach_puts (is_signed ? + _("[-- The following data is S/MIME signed --]\n\n") : + _("[-- The following data is S/MIME encrypted --]\n\n"), s); + + { + FILE *savefp = s->fpin; + + s->fpin = fpout; + rc = mutt_body_handler (tattach, s); + s->fpin = savefp; + } + + /* + * if a multipart/signed is the _only_ sub-part of a + * multipart/encrypted, cache signature verification + * status. + */ + if (mutt_is_multipart_signed (tattach) && !tattach->next) { + if (!(a->goodsig = tattach->goodsig)) + a->warnsig = tattach->warnsig; + } + else if (tattach->goodsig) { + a->goodsig = 1; + a->warnsig = tattach->warnsig; + } + + if (s->flags & M_DISPLAY) { + state_puts ("\n", s); + state_attach_puts (is_signed ? + _("[-- End of S/MIME signed data --]\n") : + _("[-- End of S/MIME encrypted data --]\n"), s); + } + + body_list_wipe(&tattach); + } + + m_fclose(&fpout); + mutt_unlink (tempfile); + return (rc); +} + + +/* + * Format an entry on the CRYPT key selection menu. + * + * %n number + * %k key id %K key id of the principal key + * %u user id + * %a algorithm %A algorithm of the princ. key + * %l length %L length of the princ. key + * %f flags %F flags of the princ. key + * %c capabilities %C capabilities of the princ. key + * %t trust/validity of the key-uid association + * %p protocol + * %[...] date of key using strftime(3) + */ + +static const char * +crypt_entry_fmt (char *dest, ssize_t destlen, char op, + const char *src, const char *prefix, + const char *ifstr, const char *elstr, + anytype data, format_flag flags) +{ + char fmt[16]; + crypt_entry_t *entry; + crypt_key_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) +{ + crypt_key_t **key_table = (crypt_key_t **) menu->data; + crypt_entry_t entry; + + entry.key = key_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) +{ + crypt_key_t **s = (crypt_key_t **) a; + crypt_key_t **t = (crypt_key_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) +{ + crypt_key_t **s = (crypt_key_t **) a; + crypt_key_t **t = (crypt_key_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) +{ + crypt_key_t **s = (crypt_key_t **) a; + crypt_key_t **t = (crypt_key_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) +{ + crypt_key_t **s = (crypt_key_t **) a; + crypt_key_t **t = (crypt_key_t **) b; + unsigned long ts = 0, tt = 0; + int r; + + if ((r = (((*s)->flags & (KEYFLAG_RESTRICTIONS)) + - ((*t)->flags & (KEYFLAG_RESTRICTIONS))))) + return r > 0; + + if ((*s)->kobj->uids) + ts = (*s)->kobj->uids->validity; + if ((*t)->kobj->uids) + tt = (*t)->kobj->uids->validity; + if ((r = (tt - ts))) + return r < 0; + + if ((*s)->kobj->subkeys) + ts = (*s)->kobj->subkeys->length; + if ((*t)->kobj->subkeys) + tt = (*t)->kobj->subkeys->length; + if (ts != tt) + return ts > tt; + + if ((*s)->kobj->subkeys && ((*s)->kobj->subkeys->timestamp > 0)) + ts = (*s)->kobj->subkeys->timestamp; + if ((*t)->kobj->subkeys && ((*t)->kobj->subkeys->timestamp > 0)) + tt = (*t)->kobj->subkeys->timestamp; + if (ts > tt) + return 1; + if (ts < tt) + return 0; + + if ((r = m_strcasecmp((*s)->uid, (*t)->uid))) + return r > 0; + return (m_strcasecmp(crypt_keyid ((*s)), crypt_keyid ((*t)))) > 0; +} + +static int crypt_compare_trust (const void *a, const void *b) +{ + return ((PgpSortKeys & SORT_REVERSE) ? !_crypt_compare_trust (a, b) + : _crypt_compare_trust (a, b)); +} + +/* Print the X.500 Distinguished Name part KEY from the array of parts + DN to FP. */ +static int print_dn_part (FILE * fp, struct dn_array_s *dn, const char *key) +{ + int any = 0; + + for (; dn->key; dn++) { + if (!m_strcmp(dn->key, key)) { + if (any) + fputs (" + ", fp); + print_utf8 (fp, dn->value, m_strlen(dn->value)); + any = 1; + } + } + return any; +} + +/* Print all parts of a DN in a standard sequence. */ +static void print_dn_parts (FILE * fp, struct dn_array_s *dn) +{ + const char *stdpart[] = { + "CN", "OU", "O", "STREET", "L", "ST", "C", NULL + }; + int any = 0, any2 = 0, i; + + for (i = 0; stdpart[i]; i++) { + if (any) + fputs (", ", fp); + any = print_dn_part (fp, dn, stdpart[i]); + } + /* now print the rest without any specific ordering */ + for (; dn->key; dn++) { + for (i = 0; stdpart[i]; i++) { + if (!m_strcmp(dn->key, stdpart[i])) + break; + } + if (!stdpart[i]) { + if (any) + fputs (", ", fp); + if (!any2) + fputs ("(", fp); + any = print_dn_part (fp, dn, dn->key); + any2 = 1; + } + } + if (any2) + fputs (")", fp); +} + + +/* Parse an RDN; this is a helper to parse_dn(). */ +static const unsigned char *parse_dn_part (struct dn_array_s *array, + const unsigned char *string) +{ + const unsigned char *s, *s1; + ssize_t n; + unsigned char *p; + + /* parse attributeType */ + for (s = string + 1; *s && *s != '='; s++); + if (!*s) + return NULL; /* error */ + n = s - string; + if (!n) + return NULL; /* empty key */ + array->key = p_dupstr(string, n ); + p = (unsigned char *) array->key; + string = s + 1; + + if (*string == '#') { /* hexstring */ + string++; + for (s = string; hexval(*s) >= 0; s++) + s++; + n = s - string; + if (!n || (n & 1)) + return NULL; /* empty or odd number of digits */ + n /= 2; + p = p_new(unsigned char, n + 1); + array->value = (char *) p; + for (s1 = string; n; s1 += 2, n--) + *p++ = (hexval(*s1) << 8) | hexval(*s1); + *p = 0; + } + else { /* regular v3 quoted string */ + for (n = 0, s = string; *s; s++) { + if (*s == '\\') { /* pair */ + s++; + if (*s == ',' || *s == '=' || *s == '+' + || *s == '<' || *s == '>' || *s == '#' || *s == ';' + || *s == '\\' || *s == '\"' || *s == ' ') + n++; + else if (hexval(*s) >= 0 && hexval(*s + 1) >= 0) { + s++; + n++; + } + else + return NULL; /* invalid escape sequence */ + } + else if (*s == '\"') + return NULL; /* invalid encoding */ + else if (*s == ',' || *s == '=' || *s == '+' + || *s == '<' || *s == '>' || *s == '#' || *s == ';') + break; + else + n++; + } + + p = p_new(unsigned char, n + 1); + array->value = (char *) p; + for (s = string; n; s++, n--) { + if (*s == '\\') { + s++; + if (hexval(*s) >= 0) { + *p++ = (hexval(*s) << 8) | hexval(*s + 1); + s++; + } + else + *p++ = *s; + } + else + *p++ = *s; + } + *p = 0; + } + return s; +} + + +/* Parse a DN and return an array-ized one. This is not a validating + parser and it does not support any old-stylish syntax; gpgme is + expected to return only rfc2253 compatible strings. */ +static struct dn_array_s *parse_dn (const unsigned char *string) +{ + struct dn_array_s *array; + ssize_t arrayidx, arraysize; + int i; + + arraysize = 7; /* C,ST,L,O,OU,CN,email */ + array = p_new(struct dn_array_s, arraysize + 1); + arrayidx = 0; + while (*string) { + while (*string == ' ') + string++; + if (!*string) + break; /* ready */ + if (arrayidx >= arraysize) { /* mutt lacks a real safe_realoc - so we need to copy */ + struct dn_array_s *a2; + + arraysize += 5; + a2 = p_new(struct dn_array_s, arraysize + 1); + for (i = 0; i < arrayidx; i++) { + a2[i].key = array[i].key; + a2[i].value = array[i].value; + } + p_delete(&array); + array = a2; + } + array[arrayidx].key = NULL; + array[arrayidx].value = NULL; + string = parse_dn_part (array + arrayidx, string); + arrayidx++; + if (!string) + goto failure; + while (*string == ' ') + string++; + if (*string && *string != ',' && *string != ';' && *string != '+') + goto failure; /* invalid delimiter */ + if (*string) + string++; + } + array[arrayidx].key = NULL; + array[arrayidx].value = NULL; + return array; + +failure: + for (i = 0; i < arrayidx; i++) { + p_delete(&array[i].key); + p_delete(&array[i].value); + } + p_delete(&array); + return NULL; +} + + +/* Print a nice representation of the USERID and make sure it is + displayed in a proper way, which does mean to reorder some parts + for S/MIME's DNs. USERID is a string as returned by the gpgme key + functions. It is utf-8 encoded. */ +static void parse_and_print_user_id(FILE * fp, const char *userid) +{ + const char *s; + int i; + + if (*userid == '<') { + s = strchr (userid + 1, '>'); + if (s) + print_utf8 (fp, userid + 1, s - userid - 1); + } + else if (*userid == '(') + fputs (_("[Can't display this user ID (unknown encoding)]"), fp); + else if (*userid & ~127 || __m_strdigits[(int)*userid] == 255) + fputs (_("[Can't display this user ID (invalid encoding)]"), fp); + else { + struct dn_array_s *dn = parse_dn ((const unsigned char *) userid); + + if (!dn) + fputs (_("[Can't display this user ID (invalid DN)]"), fp); + else { + print_dn_parts (fp, dn); + for (i = 0; dn[i].key; i++) { + p_delete(&dn[i].key); + p_delete(&dn[i].value); + } + p_delete(&dn); + } + } +} + +typedef enum { + KEY_CAP_CAN_ENCRYPT, + KEY_CAP_CAN_SIGN, + KEY_CAP_CAN_CERTIFY +} key_cap_t; + +static unsigned int key_check_cap (gpgme_key_t key, key_cap_t cap) +{ + gpgme_subkey_t subkey = NULL; + unsigned int ret = 0; + + switch (cap) { + case KEY_CAP_CAN_ENCRYPT: + if (!(ret = key->can_encrypt)) + for (subkey = key->subkeys; subkey; subkey = subkey->next) + if ((ret = subkey->can_encrypt)) + break; + break; + case KEY_CAP_CAN_SIGN: + if (!(ret = key->can_sign)) + for (subkey = key->subkeys; subkey; subkey = subkey->next) + if ((ret = subkey->can_sign)) + break; + break; + case KEY_CAP_CAN_CERTIFY: + if (!(ret = key->can_certify)) + for (subkey = key->subkeys; subkey; subkey = subkey->next) + if ((ret = subkey->can_certify)) + break; + break; + } + + return ret; +} + + +/* Print verbose information about a key or certificate to FP. */ +static void print_key_info (gpgme_key_t key, FILE * fp) +{ + int idx; + const char *s = NULL, *s2 = NULL; + time_t tt = 0; + struct tm *tm; + char shortbuf[STRING]; + unsigned long aval = 0; + const char *delim; + int is_pgp = 0; + int i; + gpgme_user_id_t uid = NULL; + + if (Locale) + setlocale (LC_TIME, Locale); + + is_pgp = key->protocol == GPGME_PROTOCOL_OpenPGP; + + for (idx = 0, uid = key->uids; uid; idx++, uid = uid->next) { + if (uid->revoked) + continue; + + s = uid->uid; + fputs (idx ? _(" aka ......: ") :_("Name ......: "), fp); + + if (uid->invalid) { + fputs (_("[Invalid]"), fp); + putc (' ', fp); + } + if (is_pgp) + print_utf8 (fp, s, m_strlen(s)); + else + parse_and_print_user_id (fp, s); + putc ('\n', fp); + } + + if (key->subkeys && (key->subkeys->timestamp > 0)) { + tt = key->subkeys->timestamp; + + tm = localtime (&tt); +#ifdef HAVE_LANGINFO_D_T_FMT + strftime (shortbuf, sizeof shortbuf, nl_langinfo (D_T_FMT), tm); +#else + strftime (shortbuf, sizeof shortbuf, "%c", tm); +#endif + fprintf (fp, _("Valid From : %s\n"), shortbuf); + } + + if (key->subkeys && (key->subkeys->expires > 0)) { + tt = key->subkeys->expires; + + tm = localtime (&tt); +#ifdef HAVE_LANGINFO_D_T_FMT + strftime (shortbuf, sizeof shortbuf, nl_langinfo (D_T_FMT), tm); +#else + strftime (shortbuf, sizeof shortbuf, "%c", tm); +#endif + fprintf (fp, _("Valid To ..: %s\n"), shortbuf); + } + + if (key->subkeys) + s = gpgme_pubkey_algo_name (key->subkeys->pubkey_algo); + else + s = "?"; + + s2 = is_pgp ? "PGP" : "X.509"; + + if (key->subkeys) + aval = key->subkeys->length; + + fprintf (fp, _("Key Type ..: %s, %lu bit %s\n"), s2, aval, s); + + fprintf (fp, _("Key Usage .: ")); + delim = ""; + + if (key_check_cap (key, KEY_CAP_CAN_ENCRYPT)) { + fprintf (fp, "%s%s", delim, _("encryption")); + delim = _(", "); + } + if (key_check_cap (key, KEY_CAP_CAN_SIGN)) { + fprintf (fp, "%s%s", delim, _("signing")); + delim = _(", "); + } + if (key_check_cap (key, KEY_CAP_CAN_CERTIFY)) { + fprintf (fp, "%s%s", delim, _("certification")); + delim = _(", "); + } + putc ('\n', fp); + + if (key->subkeys) { + s = key->subkeys->fpr; + fputs (_("Fingerprint: "), fp); + if (is_pgp && m_strlen(s) == 40) { + for (i = 0; *s && s[1] && s[2] && s[3] && s[4]; s += 4, i++) { + putc (*s, fp); + putc (s[1], fp); + putc (s[2], fp); + putc (s[3], fp); + putc (is_pgp ? ' ' : ':', fp); + if (is_pgp && i == 4) + putc (' ', fp); + } + } + else { + for (i = 0; *s && s[1] && s[2]; s += 2, i++) { + putc (*s, fp); + putc (s[1], fp); + putc (is_pgp ? ' ' : ':', fp); + if (is_pgp && i == 7) + putc (' ', fp); + } + } + fprintf (fp, "%s\n", s); + } + + if (key->issuer_serial) { + s = key->issuer_serial; + if (s) + fprintf (fp, _("Serial-No .: 0x%s\n"), s); + } + + if (key->issuer_name) { + s = key->issuer_name; + if (s) { + fprintf (fp, _("Issued By .: ")); + parse_and_print_user_id (fp, s); + putc ('\n', fp); + } + } + + /* For PGP we list all subkeys. */ + if (is_pgp) { + gpgme_subkey_t subkey = NULL; + + for (idx = 1, subkey = key->subkeys; subkey; idx++, subkey = subkey->next) { + s = subkey->keyid; + + putc ('\n', fp); + if (m_strlen(s) == 16) + s += 8; /* display only the short keyID */ + fprintf (fp, _("Subkey ....: 0x%s"), s); + if (subkey->revoked) { + putc (' ', fp); + fputs (_("[Revoked]"), fp); + } + if (subkey->invalid) { + putc (' ', fp); + fputs (_("[Invalid]"), fp); + } + if (subkey->expired) { + putc (' ', fp); + fputs (_("[Expired]"), fp); + } + if (subkey->disabled) { + putc (' ', fp); + fputs (_("[Disabled]"), fp); + } + putc ('\n', fp); + + if (subkey->timestamp > 0) { + tt = subkey->timestamp; + + tm = localtime (&tt); +#ifdef HAVE_LANGINFO_D_T_FMT + strftime (shortbuf, sizeof shortbuf, nl_langinfo (D_T_FMT), tm); +#else + strftime (shortbuf, sizeof shortbuf, "%c", tm); +#endif + fprintf (fp, _("Valid From : %s\n"), shortbuf); + } + + if (subkey->expires > 0) { + tt = subkey->expires; + + tm = localtime (&tt); +#ifdef HAVE_LANGINFO_D_T_FMT + strftime (shortbuf, sizeof shortbuf, nl_langinfo (D_T_FMT), tm); +#else + strftime (shortbuf, sizeof shortbuf, "%c", tm); +#endif + fprintf (fp, _("Valid To ..: %s\n"), shortbuf); + } + + if (subkey) + s = gpgme_pubkey_algo_name (subkey->pubkey_algo); + else + s = "?"; + + if (subkey) + aval = subkey->length; + else + aval = 0; + + fprintf (fp, _("Key Type ..: %s, %lu bit %s\n"), "PGP", aval, s); + + fprintf (fp, _("Key Usage .: ")); + delim = ""; + + if (subkey->can_encrypt) { + fprintf (fp, "%s%s", delim, _("encryption")); + delim = _(", "); + } + if (subkey->can_sign) { + fprintf (fp, "%s%s", delim, _("signing")); + delim = _(", "); + } + if (subkey->can_certify) { + fprintf (fp, "%s%s", delim, _("certification")); + delim = _(", "); + } + putc ('\n', fp); + } + } + + if (Locale) + setlocale (LC_TIME, "C"); +} + + +/* Show detailed information about the selected key */ +static void verify_key (crypt_key_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_release (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_release (k); + gpgme_release (listctx); + m_fclose(&fp); + mutt_clear_error (); + snprintf (cmd, sizeof (cmd), _("Key ID: 0x%s"), crypt_keyid (key)); + mutt_do_pager (cmd, tempfile, 0, NULL); +} + +/* Implementation of `findkeys'. */ + +/* Convert string_list_t into a pattern string suitable to be passed to GPGME. + We need to convert spaces in an item into a '+' and '%' into + "%25". */ +static char *list_to_pattern (string_list_t * list) +{ + string_list_t *l; + char *pattern, *p; + const char *s; + ssize_t n; + + n = 0; + for (l = list; l; l = l->next) { + for (s = l->data; *s; s++) { + if (*s == '%') + n += 2; + n++; + } + n++; /* delimiter or end of string */ + } + n++; /* make sure to allocate at least one byte */ + pattern = p = p_new(char, n); + for (l = list; l; l = l->next) { + s = l->data; + if (*s) { + if (l != list) + *p++ = ' '; + for (s = l->data; *s; s++) { + if (*s == '%') { + *p++ = '%'; + *p++ = '2'; + *p++ = '5'; + } + else if (*s == '+') { + *p++ = '%'; + *p++ = '2'; + *p++ = 'B'; + } + else if (*s == ' ') + *p++ = '+'; + else + *p++ = *s; + } + } + } + *p = 0; + return pattern; +} + +/* Return a list of keys which are candidates for the selection. + Select by looking at the HINTS list. */ +static crypt_key_t *get_candidates (string_list_t * hints, unsigned int app, + int secret) +{ + crypt_key_t *db, *k, **kend; + char *pattern; + gpgme_error_t err; + gpgme_ctx_t ctx; + gpgme_key_t key; + int idx; + gpgme_user_id_t uid = NULL; + + pattern = list_to_pattern (hints); + if (!pattern) + return NULL; + + err = gpgme_new (&ctx); + if (err) { + mutt_error (_("gpgme_new failed: %s"), gpgme_strerror (err)); + p_delete(&pattern); + return NULL; + } + + db = NULL; + kend = &db; + + if ((app & APPLICATION_PGP)) { + /* Its all a mess. That old GPGME expects different things + depending on the protocol. For gpg we don' t need percent + escaped pappert but simple strings passed in an array to the + keylist_ext_start function. */ + string_list_t *l; + ssize_t n; + char **patarr; + + for (l = hints, n = 0; l; l = l->next) { + if (l->data && *l->data) + n++; + } + if (!n) + goto no_pgphints; + + patarr = p_new(char *, n + 1); + for (l = hints, n = 0; l; l = l->next) { + if (l->data && *l->data) + patarr[n++] = m_strdup(l->data); + } + patarr[n] = NULL; + err = gpgme_op_keylist_ext_start (ctx, (const char **) patarr, secret, 0); + for (n = 0; patarr[n]; n++) + p_delete(&patarr[n]); + p_delete(&patarr); + if (err) { + mutt_error (_("gpgme_op_keylist_start failed: %s"), gpgme_strerror (err)); + gpgme_release (ctx); + p_delete(&pattern); + return NULL; + } + + while (!(err = gpgme_op_keylist_next (ctx, &key))) { + unsigned int flags = 0; + + 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) { + k = p_new(crypt_key_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); + no_pgphints: + ; + } + + if ((app & APPLICATION_SMIME)) { + /* and now look for x509 certificates */ + gpgme_set_protocol (ctx, GPGME_PROTOCOL_CMS); + err = gpgme_op_keylist_start (ctx, pattern, 0); + if (err) { + mutt_error (_("gpgme_op_keylist_start failed: %s"), gpgme_strerror (err)); + gpgme_release (ctx); + p_delete(&pattern); + return NULL; + } + + while (!(err = gpgme_op_keylist_next (ctx, &key))) { + unsigned int flags = KEYFLAG_ISX509; + + 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) { + k = p_new(crypt_key_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); + p_delete(&pattern); + return db; +} + +/* Add the string STR to the list HINTS. This list is later used to + match addresses. */ +static string_list_t *crypt_add_string_to_hints (string_list_t * hints, const char *str) +{ + char *scratch; + char *t; + + if ((scratch = m_strdup(str)) == NULL) + return hints; + + for (t = strtok (scratch, " ,.:\"()<>\n"); t; + t = strtok (NULL, " ,.:\"()<>\n")) { + if (m_strlen(t) > 3) + hints = mutt_add_list(hints, t); + } + + p_delete(&scratch); + return hints; +} + +/* 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 crypt_key_t *crypt_select_key (crypt_key_t * keys, + address_t * p, const char *s, + unsigned int app, int *forced_valid) +{ + int keymax; + crypt_key_t **key_table; + MUTTMENU *menu; + int i, done = 0; + char helpstr[STRING], buf[LONG_STRING]; + crypt_key_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; + key_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(&key_table, keymax); + } + + key_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 (key_table, i, sizeof (crypt_key_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 = key_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 (key_table[menu->current]); + menu->redraw = REDRAW_FULL; + break; + + case OP_VIEW_ID: + mutt_message ("%s", key_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 (!crypt_key_is_valid (key_table[menu->current])) { + mutt_error _("This key can't be used: " + "expired/disabled/revoked."); + break; + } + } + + if (option (OPTPGPCHECKTRUST) && + (!crypt_id_is_valid (key_table[menu->current]) + || !crypt_id_is_strong (key_table[menu->current]))) { + const char *warn_s; + char buff[LONG_STRING]; + + if (key_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 = key_table[menu->current]->kobj->uids; + for (j = 0; (j < key_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 = crypt_copy_key (key_table[menu->current]); + done = 1; + break; + + case OP_EXIT: + k = NULL; + done = 1; + break; + } + } + + mutt_menuDestroy (&menu); + p_delete(&key_table); + + set_option (OPTNEEDREDRAW); + + return k; +} + +static crypt_key_t *crypt_getkeybyaddr (address_t * a, short abilities, + unsigned int app, int *forced_valid) +{ + address_t *r, *p; + string_list_t *hints = NULL; + + 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; + + crypt_key_t *keys, *k; + crypt_key_t *the_valid_key = NULL; + crypt_key_t *matches = NULL; + crypt_key_t **matches_endp = &matches; + + *forced_valid = 0; + + if (a && a->mailbox) + hints = crypt_add_string_to_hints (hints, a->mailbox); + if (a && a->personal) + hints = crypt_add_string_to_hints (hints, a->personal); + + mutt_message (_("Looking for keys matching \"%s\"..."), a->mailbox); + keys = get_candidates (hints, app, (abilities & KEYFLAG_CANSIGN)); + + string_list_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) { + crypt_key_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 = crypt_copy_key (k); + matches_endp = &tmp->next; + the_valid_key = tmp; + } + } + + crypt_free_key (&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 = crypt_copy_key (the_valid_key); + } + else { + /* + * Else: Ask the user. + */ + k = crypt_select_key (matches, a, NULL, app, forced_valid); + } + crypt_free_key (&matches); + } + else + k = NULL; + + return k; +} + + +static crypt_key_t *crypt_getkeybystr (const char *p, short abilities, + unsigned int app, int *forced_valid) +{ + string_list_t *hints = NULL; + crypt_key_t *keys; + crypt_key_t *matches = NULL; + crypt_key_t **matches_endp = &matches; + crypt_key_t *k; + int match; + + mutt_message (_("Looking for keys matching \"%s\"..."), p); + + *forced_valid = 0; + + hints = crypt_add_string_to_hints (hints, p); + keys = get_candidates (hints, app, (abilities & KEYFLAG_CANSIGN)); + string_list_wipe(&hints); + + if (!keys) + return NULL; + + for (k = keys; k; k = k->next) { + if (abilities && !(k->flags & abilities)) + continue; + + match = 0; + + if (!*p || !m_strcasecmp(p, crypt_keyid (k)) + || (!m_strncasecmp(p, "0x", 2) + && !m_strcasecmp(p + 2, crypt_keyid (k))) + || (option (OPTPGPLONGIDS) + && !m_strncasecmp(p, "0x", 2) + && !m_strcasecmp(p + 2, crypt_keyid (k) + 8)) + || m_stristr(k->uid, p)) { + crypt_key_t *tmp; + + *matches_endp = tmp = crypt_copy_key (k); + matches_endp = &tmp->next; + } + } + + crypt_free_key (&keys); + + if (matches) { + k = crypt_select_key (matches, NULL, p, app, forced_valid); + crypt_free_key (&matches); + return k; + } + + return NULL; +} + +/* Display TAG as a prompt to ask for a key. If WHATFOR is not null + use it as default and store it under that label as the next + default. 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 crypt_key_t * +crypt_ask_for_key(char *tag, char *whatfor, short abilities, + unsigned int app, int *forced_valid) +{ + crypt_key_t *key; + char resp[STRING]; + struct crypt_cache *l = NULL; + int dummy; + + if (!forced_valid) + forced_valid = &dummy; + + mutt_clear_error (); + + *forced_valid = 0; + resp[0] = 0; + if (whatfor) { + for (l = id_defaults; l; l = l->next) + if (!m_strcasecmp(whatfor, l->what)) { + m_strcpy(resp, sizeof(resp), NONULL(l->dflt)); + break; + } + } + + for (;;) { + resp[0] = 0; + if (mutt_get_field (tag, resp, sizeof (resp), M_CLEAR) != 0) + return NULL; + + if (whatfor) { + if (l) + m_strreplace(&l->dflt, resp); + else { + l = p_new(struct crypt_cache, 1); + l->next = id_defaults; + id_defaults = l; + l->what = m_strdup(whatfor); + l->dflt = m_strdup(resp); + } + } + + if ((key = crypt_getkeybystr (resp, abilities, app, forced_valid))) + return key; + + BEEP (); + } + /* not reached */ +} + +/* 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 (address_t * to, address_t * cc, address_t * bcc, + unsigned int app) +{ + char *keylist = NULL, *t; + const char *keyID; + ssize_t keylist_size = 0; + ssize_t keylist_used = 0; + address_t *tmp = NULL, *addr = NULL; + address_t **last = &tmp; + address_t *p, *q; + int i; + crypt_key_t *k_info, *key; + const char *fqdn = mutt_fqdn (1); + +#if 0 + *r_application = APPLICATION_PGP | APPLICATION_SMIME; +#endif + + for (i = 0; i < 3; i++) { + switch (i) { + case 0: + p = to; + break; + case 1: + p = cc; + break; + case 2: + p = bcc; + break; + default: + abort (); + } + + *last = address_list_dup (p); + while (*last) + last = &((*last)->next); + } + + rfc822_qualify(tmp, fqdn); + address_list_uniq(tmp); + + for (p = tmp; p; p = p->next) { + char buf[LONG_STRING]; + int forced_valid = 0; + + q = p; + k_info = NULL; + + if ((keyID = mutt_crypt_hook (p)) != NULL) { + int r; + + snprintf (buf, sizeof (buf), _("Use keyID = \"%s\" for %s?"), + keyID, p->mailbox); + if ((r = mutt_yesorno (buf, M_YES)) == M_YES) { + /* check for e-mail address */ + if ((t = strchr (keyID, '@')) && + (addr = rfc822_parse_adrlist (NULL, keyID))) { + rfc822_qualify(addr, fqdn); + q = addr; + } + else { + k_info = crypt_getkeybystr (keyID, KEYFLAG_CANENCRYPT, + app, &forced_valid); + } + } + else if (r == -1) { + p_delete(&keylist); + address_list_wipe(&tmp); + address_list_wipe(&addr); + return NULL; + } + } + + if (k_info == NULL + && (k_info = crypt_getkeybyaddr (q, KEYFLAG_CANENCRYPT, + app, &forced_valid)) == NULL) { + snprintf (buf, sizeof (buf), _("Enter keyID for %s: "), q->mailbox); + + if ((key = crypt_ask_for_key (buf, q->mailbox, KEYFLAG_CANENCRYPT, app, + &forced_valid)) == NULL) + { + p_delete(&keylist); + address_list_wipe(&tmp); + address_list_wipe(&addr); + return NULL; + } + } + else + key = k_info; + + { + const char *s = crypt_fpr (key); + + keylist_size += m_strlen(s) + 4 + 1; + p_realloc(&keylist, keylist_size); + sprintf (keylist + keylist_used, "%s0x%s%s", + keylist_used ? " " : "", s, forced_valid ? "!" : ""); + } + keylist_used = m_strlen(keylist); + + crypt_free_key (&key); + address_list_wipe(&addr); + } + address_list_wipe(&tmp); + return (keylist); +} + +int crypt_get_keys (HEADER * msg, char **keylist) +{ + /* 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->to, msg->env->cc, msg->env->bcc, + APPLICATION_PGP); + unset_option(OPTPGPCHECKTRUST); + if (!*keylist) + return -1; + } + + if (msg->security & APPLICATION_SMIME) { + *keylist = find_keys(msg->env->to, msg->env->cc, msg->env->bcc, + APPLICATION_SMIME); + if (!*keylist) + return -1; + } + } + + return (0); +} + + +int crypt_send_menu (HEADER * msg, int *redraw, int is_smime) +{ + crypt_key_t *p; + char input_signas[STRING]; + int choice; + + if (msg->security & APPLICATION_PGP) + is_smime = 0; + else if (msg->security & APPLICATION_SMIME) + is_smime = 1; + + if (is_smime) + choice = + mutt_multi_choice (_ + ("S/MIME (e)ncrypt, (s)ign, sign (a)s, (b)oth, (p)gp or (c)lear?"), + _("esabpc")); + else + choice = + 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 */ + if ((p = crypt_ask_for_key (_("Sign as: "), NULL, KEYFLAG_CANSIGN, + is_smime ? APPLICATION_SMIME : + APPLICATION_PGP, NULL))) + { + snprintf (input_signas, sizeof (input_signas), "0x%s", crypt_keyid (p)); + m_strreplace(is_smime ? &SmimeDefaultKey : &PgpSignAs, + input_signas); + crypt_free_key (&p); + + msg->security |= (is_smime ? SMIMESIGN : PGPSIGN); + } + *redraw = REDRAW_FULL; + break; + + case 4: /* (b)oth */ + msg->security = + (is_smime ? (SMIMEENCRYPT | SMIMESIGN) : (PGPENCRYPT | PGPSIGN)); + break; + + case 5: /* (p)gp or s/(m)ime */ + is_smime = !is_smime; + break; + + case 6: /* (c)lear */ + return msg->security = 0; + } + + if (is_smime) { + msg->security &= ~APPLICATION_PGP; + msg->security |= APPLICATION_SMIME; + } else { + msg->security &= ~APPLICATION_SMIME; + msg->security |= APPLICATION_PGP; + } + + return msg->security; +} + +int crypt_smime_verify_sender (HEADER * h) +{ + 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 (option (OPTCRYPTTIMESTAMP)) { - t = time (NULL); - setlocale (LC_TIME, ""); - strftime (p, sizeof (p), _(" (current time: %c)"), localtime (&t)); - setlocale (LC_TIME, "C"); + if (sender) { + 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"); } else - *p = '\0'; - - snprintf (tmp, sizeof (tmp), _("[-- %s output follows%s --]\n"), - NONULL (app_name), p); - state_attach_puts (tmp, s); -} + mutt_any_key_to_continue ("Failed to figure out sender"); + if (signature_key) { + gpgme_key_release (signature_key); + signature_key = NULL; + } + return ret; +} -void crypt_forget_passphrase (void) +static void crypt_invoke_import(FILE *stream, int smime) { - if ((WithCrypto & APPLICATION_PGP)) - crypt_pgp_void_passphrase (); + gpgme_ctx_t ctx = create_gpgme_context(smime); + gpgme_data_t data; + gpgme_error_t err; + + err = gpgme_data_new_from_stream(&data, stream); + if (err) { + mutt_error (_("error allocating data object: %s\n"), gpgme_strerror (err)); + gpgme_release(ctx); + return; + } - if ((WithCrypto & APPLICATION_SMIME)) - crypt_smime_void_passphrase (); + err = gpgme_op_import(ctx, data); + if (err) { + mutt_error(_("error importing gpg data: %s\n"), gpgme_strerror(err)); + gpgme_data_release(data); + gpgme_release(ctx); + return; + } - if (WithCrypto) - mutt_message _("Passphrase(s) forgotten."); + gpgme_data_release(data); + gpgme_release(ctx); + return; } +static void pgp_extract_keys_from_attachment(FILE * fp, BODY * top) +{ + STATE s; + FILE *tmpfp = tmpfile(); + + if (tmpfp == NULL) { + mutt_perror (_("Can't create temporary file")); + return; + } + + p_clear(&s, 1); + s.fpin = fp; + s.fpout = tmpfp; + mutt_body_handler(top, &s); -#if defined(HAVE_SETRLIMIT) && (!defined(DEBUG)) + rewind(tmpfp); + crypt_invoke_import(tmpfp, 0); + m_fclose(&tmpfp); +} -static void disable_coredumps (void) +void crypt_pgp_extract_keys_from_attachment_list(FILE * fp, int tag, BODY * top) { - struct rlimit rl = { 0, 0 }; - static short done = 0; + mutt_endwin (NULL); + set_option (OPTDONTHANDLEPGPKEYS); - if (!done) { - setrlimit (RLIMIT_CORE, &rl); - done = 1; + for (; top; top = top->next) { + if (!tag || top->tagged) + pgp_extract_keys_from_attachment (fp, top); + + if (!tag) + break; } + + unset_option (OPTDONTHANDLEPGPKEYS); } -#endif /* HAVE_SETRLIMIT */ +/* TODO */ -int crypt_valid_passphrase (int flags) +/* fixme: needs documentation. */ +void crypt_pgp_invoke_getkeys (address_t * addr) { - int ret = 0; - -# if defined(HAVE_SETRLIMIT) &&(!defined(DEBUG)) - disable_coredumps (); -# endif +} - if ((WithCrypto & APPLICATION_PGP) && (flags & APPLICATION_PGP)) - ret = crypt_pgp_valid_passphrase (); +/* Generate a PGP public key attachment. */ +BODY *crypt_pgp_make_key_attachment (char *tempf) +{ + return NULL; +} - if ((WithCrypto & APPLICATION_SMIME) && (flags & APPLICATION_SMIME)) - ret = crypt_smime_valid_passphrase (); +/* S/MIME */ - return ret; +/* fixme: Needs documentation. */ +void crypt_smime_getkeys (ENVELOPE * env) +{ } +/***************************************************************************/ +void crypt_invoke_message (int type) +{ + if (type & APPLICATION_PGP) { + mutt_message _("Invoking PGP..."); + } + else if (type & APPLICATION_SMIME) { + mutt_message _("Invoking S/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 = (WithCrypto & APPLICATION_PGP) ? msg->security : 0; - int i; - - if (!WithCrypto) - return -1; - - if ((msg->security & SIGN) && !crypt_valid_passphrase (msg->security)) - return (-1); - - if ((WithCrypto & APPLICATION_PGP) - && ((msg->security & PGPINLINE) == PGPINLINE)) { - /* they really want to send it inline... go for it */ - if (!isendwin ()) - mutt_endwin _("Invoking PGP..."); - - pbody = crypt_pgp_traditional_encryptsign (msg->content, flags, keylist); - if (pbody) { - msg->content = pbody; - return 0; - } - - /* otherwise inline won't work...ask for revert */ - if ((i = - query_quadoption (OPT_PGPMIMEAUTO, - _ - ("Message can't be sent inline. Revert to using PGP/MIME?"))) - != M_YES) { - mutt_error _("Mail not sent."); - - return -1; - } - - /* go ahead with PGP/MIME */ - } + 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 +3961,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 +3973,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 +3987,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 +3999,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 +4011,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 +4045,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,238 +4063,90 @@ 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); + + set_option(OPTDONTHANDLEPGPKEYS); + 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); } - } + unset_option(OPTDONTHANDLEPGPKEYS); + m_fclose(&tmpfp); - return (0); + if (isendwin()) + mutt_any_key_to_continue(NULL); } 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); @@ -722,116 +4166,97 @@ static void crypt_fetch_signatures (BODY ***signatures, BODY * a, int *n) 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);