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