+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!) */
+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 (!digit_or_letter ((const unsigned char *) userid))
+ 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.
+ */