/* keylist.c - Listing keys.
   Copyright (C) 2000 Werner Koch (dd9jn)
   Copyright (C) 2001, 2002, 2003, 2004, 2006 g10 Code GmbH

   This file is part of GPGME.
 
   GPGME is free software; you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as
   published by the Free Software Foundation; either version 2.1 of
   the License, or (at your option) any later version.
   
   GPGME is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.
   
   You should have received a copy of the GNU Lesser General Public
   License along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
   02110-1301, USA.  */

#if HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>

#include "gpgme.h"
#include "util.h"
#include "context.h"
#include "ops.h"
#include "debug.h"


struct key_queue_item_s
{
  struct key_queue_item_s *next;
  gpgme_key_t key;
};

typedef struct
{
  struct _gpgme_op_keylist_result result;

  gpgme_key_t tmp_key;

  /* This points to the last uid in tmp_key.  */
  gpgme_user_id_t tmp_uid;

  /* This points to the last sig in tmp_uid.  */
  gpgme_key_sig_t tmp_keysig;

  /* Something new is available.  */
  int key_cond;
  struct key_queue_item_s *key_queue;
} *op_data_t;


static void
release_op_data (void *hook)
{
  op_data_t opd = (op_data_t) hook;
  struct key_queue_item_s *key = opd->key_queue;

  if (opd->tmp_key)
    gpgme_key_unref (opd->tmp_key);

  /* opd->tmp_uid and opd->tmp_keysig are actually part of opd->tmp_key,
     so we do not need to release them here.  */

  while (key)
    {
      struct key_queue_item_s *next = key->next;

      gpgme_key_unref (key->key);
      key = next;
    }
}


gpgme_keylist_result_t
gpgme_op_keylist_result (gpgme_ctx_t ctx)
{
  void *hook;
  op_data_t opd;
  gpgme_error_t err;

  err = _gpgme_op_data_lookup (ctx, OPDATA_KEYLIST, &hook, -1, NULL);
  opd = hook;
  if (err || !opd)
    return NULL;

  return &opd->result;
}


static gpgme_error_t
keylist_status_handler (void *priv, gpgme_status_code_t code, char *args)
{
  gpgme_ctx_t ctx = (gpgme_ctx_t) priv;
  gpgme_error_t err;
  void *hook;
  op_data_t opd;

  err = _gpgme_op_data_lookup (ctx, OPDATA_KEYLIST, &hook, -1, NULL);
  opd = hook;
  if (err)
    return err;

  switch (code)
    {
    case GPGME_STATUS_TRUNCATED:
      opd->result.truncated = 1;
      break;

    default:
      break;
    }
  return 0;
}


static void
set_subkey_trust_info (gpgme_subkey_t subkey, const char *src)
{
  while (*src && !isdigit (*src))
    {
      switch (*src)
	{
	case 'e':
	  subkey->expired = 1;
	  break;

	case 'r':
	  subkey->revoked = 1;
	  break;

	case 'd':
          /* Note that gpg 1.3 won't print that anymore but only uses
             the capabilities field. */
	  subkey->disabled = 1;
	  break;

	case 'i':
	  subkey->invalid = 1;
	  break;
        }
      src++;
    }
}


static void
set_mainkey_trust_info (gpgme_key_t key, const char *src)
{
  /* First set the trust info of the main key (the first subkey).  */
  set_subkey_trust_info (key->subkeys, src);

  /* Now set the summarized trust info.  */
  while (*src && !isdigit (*src))
    {
      switch (*src)
	{
	case 'e':
	  key->expired = 1;
	  break;

	case 'r':
	  key->revoked = 1;
	  break;

	case 'd':
          /* Note that gpg 1.3 won't print that anymore but only uses
             the capabilities field.  However, it is still used for
             external key listings.  */
	  key->disabled = 1;
	  break;

	case 'i':
	  key->invalid = 1;
	  break;
        }
      src++;
    }
}


static void
set_userid_flags (gpgme_key_t key, const char *src)
{
  gpgme_user_id_t uid = key->_last_uid;

  assert (uid);
  /* Look at letters and stop at the first digit.  */
  while (*src && !isdigit (*src))
    {
      switch (*src)
	{
	case 'r':
	  uid->revoked = 1;
	  break;
	  
	case 'i':
	  uid->invalid = 1;
	  break;

	case 'n':
	  uid->validity = GPGME_VALIDITY_NEVER;
	  break;

	case 'm':
	  uid->validity = GPGME_VALIDITY_MARGINAL;
	  break;

	case 'f':
	  uid->validity = GPGME_VALIDITY_FULL;
	  break;

	case 'u':
	  uid->validity = GPGME_VALIDITY_ULTIMATE;
	  break;
        }
      src++;
    }
}


static void
set_subkey_capability (gpgme_subkey_t subkey, const char *src)
{
  while (*src)
    {
      switch (*src)
	{
	case 'e':
	  subkey->can_encrypt = 1;
	  break;

	case 's':
	  subkey->can_sign = 1;
	  break;

	case 'c':
	  subkey->can_certify = 1;
	  break;

	case 'a':
	  subkey->can_authenticate = 1;
	  break;

	case 'q':
	  subkey->is_qualified = 1;
	  break;

	case 'd':
	  subkey->disabled = 1;
	  break;
        }
      src++;
    }
}


static void
set_mainkey_capability (gpgme_key_t key, const char *src)
{
  /* First set the capabilities of the main key (the first subkey).  */
  set_subkey_capability (key->subkeys, src);

  while (*src)
    {
      switch (*src)
	{
	case 'd':
        case 'D':
          /* Note, that this flag is also set using the key validity
             field for backward compatibility with gpg 1.2.  We use d
             and D, so that a future gpg version will be able to
             disable certain subkeys. Currently it is expected that
             gpg sets this for the primary key. */
       	  key->disabled = 1;
          break;

	case 'e':
	case 'E':
	  key->can_encrypt = 1;
	  break;

	case 's':
	case 'S':
	  key->can_sign = 1;
	  break;

	case 'c':
	case 'C':
	  key->can_certify = 1;
	  break;

	case 'a':
	case 'A':
	  key->can_authenticate = 1;
	  break;

	case 'q':
	case 'Q':
	  key->is_qualified = 1;
	  break;
        }
      src++;
    }
}


static void
set_ownertrust (gpgme_key_t key, const char *src)
{
  /* Look at letters and stop at the first digit.  */
  while (*src && !isdigit (*src))
    {
      switch (*src)
	{
	case 'n':
	  key->owner_trust = GPGME_VALIDITY_NEVER;
	  break;

	case 'm':
	  key->owner_trust = GPGME_VALIDITY_MARGINAL;
	  break;

	case 'f':
	  key->owner_trust = GPGME_VALIDITY_FULL;
	  break;

	case 'u':
	  key->owner_trust = GPGME_VALIDITY_ULTIMATE;
	  break;

        default:
	  key->owner_trust = GPGME_VALIDITY_UNKNOWN;
	  break;
        }
      src++;
    }
}


/* We have read an entire key into tmp_key and should now finish it.
   It is assumed that this releases tmp_key.  */
static void
finish_key (gpgme_ctx_t ctx, op_data_t opd)
{
  gpgme_key_t key = opd->tmp_key;

  opd->tmp_key = NULL;
  opd->tmp_uid = NULL;
  opd->tmp_keysig = NULL;

  if (key)
    _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_NEXT_KEY, key);
}


/* Note: We are allowed to modify LINE.  */
static gpgme_error_t
keylist_colon_handler (void *priv, char *line)
{
  gpgme_ctx_t ctx = (gpgme_ctx_t) priv;
  enum
    {
      RT_NONE, RT_SIG, RT_UID, RT_SUB, RT_PUB, RT_FPR,
      RT_SSB, RT_SEC, RT_CRT, RT_CRS, RT_REV, RT_SPK
    }
  rectype = RT_NONE;
#define NR_FIELDS 16
  char *field[NR_FIELDS];
  int fields = 0;
  void *hook;
  op_data_t opd;
  gpgme_error_t err;
  gpgme_key_t key;
  gpgme_subkey_t subkey = NULL;
  gpgme_key_sig_t keysig = NULL;

  err = _gpgme_op_data_lookup (ctx, OPDATA_KEYLIST, &hook, -1, NULL);
  opd = hook;
  if (err)
    return err;

  key = opd->tmp_key;

  DEBUG3 ("keylist_colon_handler ctx = %p, key = %p, line = %s\n",
	  ctx, key, line ? line : "(null)");

  if (!line)
    {
      /* End Of File.  */
      finish_key (ctx, opd);
      return 0;
    }

  while (line && fields < NR_FIELDS)
    {
      field[fields++] = line;
      line = strchr (line, ':');
      if (line)
	*(line++) = '\0';
    }

  if (!strcmp (field[0], "sig"))
    rectype = RT_SIG;
  else if (!strcmp (field[0], "rev"))
    rectype = RT_REV;
  else if (!strcmp (field[0], "pub"))
    rectype = RT_PUB;
  else if (!strcmp (field[0], "sec"))
    rectype = RT_SEC;
  else if (!strcmp (field[0], "crt"))
    rectype = RT_CRT;
  else if (!strcmp (field[0], "crs"))
    rectype = RT_CRS;
  else if (!strcmp (field[0], "fpr") && key) 
    rectype = RT_FPR;
  else if (!strcmp (field[0], "uid") && key)
    rectype = RT_UID;
  else if (!strcmp (field[0], "sub") && key)
    rectype = RT_SUB; 
  else if (!strcmp (field[0], "ssb") && key)
    rectype = RT_SSB;
  else if (!strcmp (field[0], "spk") && key)
    rectype = RT_SPK;
  else 
    rectype = RT_NONE;

  /* Only look at signatures immediately following a user ID.  For
     this, clear the user ID pointer when encountering anything but a
     signature.  */
  if (rectype != RT_SIG && rectype != RT_REV)
    opd->tmp_uid = NULL;

  /* Only look at subpackets immediately following a signature.  For
     this, clear the signature pointer when encountering anything but
     a subpacket.  */
  if (rectype != RT_SPK)
    opd->tmp_keysig = NULL;

  switch (rectype)
    {
    case RT_PUB:
    case RT_SEC:
    case RT_CRT:
    case RT_CRS:
      /* Start a new keyblock.  */
      err = _gpgme_key_new (&key);
      if (err)
	return err;
      key->keylist_mode = ctx->keylist_mode;
      err = _gpgme_key_add_subkey (key, &subkey);
      if (err)
	{
	  gpgme_key_unref (key);
	  return err;
	}

      if (rectype == RT_SEC || rectype == RT_CRS)
	key->secret = subkey->secret = 1;
      if (rectype == RT_CRT || rectype == RT_CRS)
	key->protocol = GPGME_PROTOCOL_CMS;
      finish_key (ctx, opd);
      opd->tmp_key = key;

      /* Field 2 has the trust info.  */
      if (fields >= 2)
	set_mainkey_trust_info (key, field[1]);

      /* Field 3 has the key length.  */
      if (fields >= 3)
	{
	  int i = atoi (field[2]);
	  /* Ignore invalid values.  */
	  if (i > 1)
	    subkey->length = i; 
	}

      /* Field 4 has the public key algorithm.  */
      if (fields >= 4)
	{
	  int i = atoi (field[3]);
	  if (i >= 1 && i < 128)
	    subkey->pubkey_algo = i;
	}

      /* Field 5 has the long keyid.  Allow short key IDs for the
	 output of an external keyserver listing.  */
      if (fields >= 5 && strlen (field[4]) <= DIM(subkey->_keyid) - 1)
	strcpy (subkey->_keyid, field[4]);

      /* Field 6 has the timestamp (seconds).  */
      if (fields >= 6)
	subkey->timestamp = _gpgme_parse_timestamp (field[5], NULL);

      /* Field 7 has the expiration time (seconds).  */
      if (fields >= 7)
	subkey->expires = _gpgme_parse_timestamp (field[6], NULL);

      /* Field 8 has the X.509 serial number.  */
      if (fields >= 8 && (rectype == RT_CRT || rectype == RT_CRS))
	{
	  key->issuer_serial = strdup (field[7]);
	  if (!key->issuer_serial)
	    return gpg_error_from_errno (errno);
	}
	  
      /* Field 9 has the ownertrust.  */
      if (fields >= 9)
	set_ownertrust (key, field[8]);

      /* Field 10 is not used for gpg due to --fixed-list-mode option
	 but GPGSM stores the issuer name.  */
      if (fields >= 10 && (rectype == RT_CRT || rectype == RT_CRS))
	if (_gpgme_decode_c_string (field[9], &key->issuer_name, 0))
	  return gpg_error (GPG_ERR_ENOMEM);	/* FIXME */

      /* Field 11 has the signature class.  */

      /* Field 12 has the capabilities.  */
      if (fields >= 12)
	set_mainkey_capability (key, field[11]);

      /* Field 15 carries special flags of a secret key.  We reset the
         SECRET flag of a subkey here if the key is actually only a
         stub. The SECRET flag of the key will be true even then. */
      if (fields >= 15 && key->secret)
        if (*field[14] == '#')
          subkey->secret = 0;
      break;

    case RT_SUB:
    case RT_SSB:
      /* Start a new subkey.  */
      err = _gpgme_key_add_subkey (key, &subkey);
      if (err)
	return err;

      if (rectype == RT_SSB)
	subkey->secret = 1;

      /* Field 2 has the trust info.  */
      if (fields >= 2)
	set_subkey_trust_info (subkey, field[1]);

      /* Field 3 has the key length.  */
      if (fields >= 3)
	{
	  int i = atoi (field[2]);
	  /* Ignore invalid values.  */
	  if (i > 1)
	    subkey->length = i;
	}

      /* Field 4 has the public key algorithm.  */
      if (fields >= 4)
	{
	  int i = atoi (field[3]);
	  if (i >= 1 && i < 128)
	    subkey->pubkey_algo = i;
	}

      /* Field 5 has the long keyid.  */
      if (fields >= 5 && strlen (field[4]) == DIM(subkey->_keyid) - 1)
	strcpy (subkey->_keyid, field[4]);

      /* Field 6 has the timestamp (seconds).  */
      if (fields >= 6)
	subkey->timestamp = _gpgme_parse_timestamp (field[5], NULL);

      /* Field 7 has the expiration time (seconds).  */
      if (fields >= 7)
	subkey->expires = _gpgme_parse_timestamp (field[6], NULL);

      /* Field 8 is reserved (LID).  */
      /* Field 9 has the ownertrust.  */
      /* Field 10, the user ID, is n/a for a subkey.  */
      
      /* Field 11 has the signature class.  */

      /* Field 12 has the capabilities.  */
      if (fields >= 12)
	set_subkey_capability (subkey, field[11]);

      /* Field 15 carries special flags of a secret key. */
      if (fields >= 15 && key->secret)
        if (*field[14] == '#')
          subkey->secret = 0;
      break;

    case RT_UID:
      /* Field 2 has the trust info, and field 10 has the user ID.  */
      if (fields >= 10)
	{
	  if (_gpgme_key_append_name (key, field[9]))
	    return gpg_error_from_errno (GPG_ERR_ENOMEM);	/* FIXME */
	  else
	    {
	      if (field[1])
		set_userid_flags (key, field[1]);
	      opd->tmp_uid = key->_last_uid;
	    }
	}
      break;

    case RT_FPR:
      /* Field 10 has the fingerprint (take only the first one).  */
      if (fields >= 10 && field[9] && *field[9])
	{
          /* Need to apply it to the last subkey because all subkeys
             do have fingerprints. */
          subkey = key->_last_subkey;
          if (!subkey->fpr)
            {
              subkey->fpr = strdup (field[9]);
              if (!subkey->fpr)
                return gpg_error_from_errno (errno);
            }
	}

      /* Field 13 has the gpgsm chain ID (take only the first one).  */
      if (fields >= 13 && !key->chain_id && *field[12])
	{
	  key->chain_id = strdup (field[12]);
	  if (!key->chain_id)
	    return gpg_error_from_errno (errno);
	}
      break;

    case RT_SIG:
    case RT_REV:
      if (!opd->tmp_uid)
	return 0;

      /* Start a new (revoked) signature.  */
      assert (opd->tmp_uid == key->_last_uid);
      keysig = _gpgme_key_add_sig (key, (fields >= 10) ? field[9] : NULL);
      if (!keysig)
	return gpg_error (GPG_ERR_ENOMEM);	/* FIXME */

      /* Field 2 has the calculated trust ('!', '-', '?', '%').  */
      if (fields >= 2)
	switch (field[1][0])
	  {
	  case '!':
	    keysig->status = gpg_error (GPG_ERR_NO_ERROR);
	    break;

	  case '-':
	    keysig->status = gpg_error (GPG_ERR_BAD_SIGNATURE);
	    break;

	  case '?':
	    keysig->status = gpg_error (GPG_ERR_NO_PUBKEY);
	    break;

	  case '%':
	    keysig->status = gpg_error (GPG_ERR_GENERAL);
	    break;

	  default:
	    keysig->status = gpg_error (GPG_ERR_NO_ERROR);
	    break;
	  }

      /* Field 4 has the public key algorithm.  */
      if (fields >= 4)
	{
	  int i = atoi (field[3]);
	  if (i >= 1 && i < 128)
	    keysig->pubkey_algo = i;
	}
      
      /* Field 5 has the long keyid.  */
      if (fields >= 5 && strlen (field[4]) == DIM(keysig->_keyid) - 1)
	strcpy (keysig->_keyid, field[4]);
      
      /* Field 6 has the timestamp (seconds).  */
      if (fields >= 6)
	keysig->timestamp = _gpgme_parse_timestamp (field[5], NULL);

      /* Field 7 has the expiration time (seconds).  */
      if (fields >= 7)
	keysig->expires = _gpgme_parse_timestamp (field[6], NULL);

      /* Field 11 has the signature class (eg, 0x30 means revoked).  */
      if (fields >= 11)
	if (field[10][0] && field[10][1])
	  {
	    int sig_class = _gpgme_hextobyte (field[10]);
	    if (sig_class >= 0)
	      {
		keysig->sig_class = sig_class;
		keysig->class = keysig->sig_class;
		if (sig_class == 0x30)
		  keysig->revoked = 1;
	      }
	    if (field[10][2] == 'x')
	      keysig->exportable = 1;
	  }

      opd->tmp_keysig = keysig;
      break;

    case RT_SPK:
      if (!opd->tmp_keysig)
	return 0;
      assert (opd->tmp_keysig == key->_last_uid->_last_keysig);

      if (fields >= 4)
	{
	  /* Field 2 has the subpacket type.  */
	  int type = atoi (field[1]);

	  /* Field 3 has the flags.  */
	  int flags = atoi (field[2]);

	  /* Field 4 has the length.  */
	  int len = atoi (field[3]);

	  /* Field 5 has the data.  */
	  char *data = field[4];

	  /* Type 20: Notation data.  */
	  /* Type 26: Policy URL.  */
	  if (type == 20 || type == 26)
	    {
	      gpgme_sig_notation_t notation;

	      keysig = opd->tmp_keysig;

	      /* At this time, any error is serious.  */
	      err = _gpgme_parse_notation (&notation, type, flags, len, data);
	      if (err)
		return err;

	      /* Add a new notation.  FIXME: Could be factored out.  */
	      if (!keysig->notations)
		keysig->notations = notation;
	      if (keysig->_last_notation)
		keysig->_last_notation->next = notation;
	      keysig->_last_notation = notation;
	    }
	}
    
    case RT_NONE:
      /* Unknown record.  */
      break;
    }
  return 0;
}


void
_gpgme_op_keylist_event_cb (void *data, gpgme_event_io_t type, void *type_data)
{
  gpgme_error_t err;
  gpgme_ctx_t ctx = (gpgme_ctx_t) data;
  gpgme_key_t key = (gpgme_key_t) type_data;
  void *hook;
  op_data_t opd;
  struct key_queue_item_s *q, *q2;

  assert (type == GPGME_EVENT_NEXT_KEY);

  err = _gpgme_op_data_lookup (ctx, OPDATA_KEYLIST, &hook, -1, NULL);
  opd = hook;
  if (err)
    return;

  q = malloc (sizeof *q);
  if (!q)
    {
      gpgme_key_unref (key);
      /* FIXME       return GPGME_Out_Of_Core; */
      return;
    }
  q->key = key;
  q->next = NULL;
  /* FIXME: Use a tail pointer?  */
  if (!(q2 = opd->key_queue))
    opd->key_queue = q;
  else
    {
      for (; q2->next; q2 = q2->next)
	;
      q2->next = q;
    }
  opd->key_cond = 1;
}


/* Start a keylist operation within CTX, searching for keys which
   match PATTERN.  If SECRET_ONLY is true, only secret keys are
   returned.  */
gpgme_error_t
gpgme_op_keylist_start (gpgme_ctx_t ctx, const char *pattern, int secret_only)
{
  gpgme_error_t err;
  void *hook;
  op_data_t opd;

  err = _gpgme_op_reset (ctx, 2);
  if (err)
    return err;

  err = _gpgme_op_data_lookup (ctx, OPDATA_KEYLIST, &hook,
			       sizeof (*opd), release_op_data);
  opd = hook;
  if (err)
    return err;

  _gpgme_engine_set_status_handler (ctx->engine, keylist_status_handler, ctx);

  err = _gpgme_engine_set_colon_line_handler (ctx->engine,
					      keylist_colon_handler, ctx);
  if (err)
    return err;

  return _gpgme_engine_op_keylist (ctx->engine, pattern, secret_only,
				   ctx->keylist_mode);
}


/* Start a keylist operation within CTX, searching for keys which
   match PATTERN.  If SECRET_ONLY is true, only secret keys are
   returned.  */
gpgme_error_t
gpgme_op_keylist_ext_start (gpgme_ctx_t ctx, const char *pattern[],
			    int secret_only, int reserved)
{
  gpgme_error_t err;
  void *hook;
  op_data_t opd;

  err = _gpgme_op_reset (ctx, 2);
  if (err)
    return err;

  err = _gpgme_op_data_lookup (ctx, OPDATA_KEYLIST, &hook,
			       sizeof (*opd), release_op_data);
  opd = hook;
  if (err)
    return err;

  _gpgme_engine_set_status_handler (ctx->engine, keylist_status_handler, ctx);
  err = _gpgme_engine_set_colon_line_handler (ctx->engine,
					      keylist_colon_handler, ctx);
  if (err)
    return err;

  return _gpgme_engine_op_keylist_ext (ctx->engine, pattern, secret_only,
				       reserved, ctx->keylist_mode);
}


/* Return the next key from the keylist in R_KEY.  */
gpgme_error_t
gpgme_op_keylist_next (gpgme_ctx_t ctx, gpgme_key_t *r_key)
{
  gpgme_error_t err;
  struct key_queue_item_s *queue_item;
  void *hook;
  op_data_t opd;

  if (!ctx || !r_key)
    return gpg_error (GPG_ERR_INV_VALUE);
  *r_key = NULL;
  if (!ctx)
    return gpg_error (GPG_ERR_INV_VALUE);

  err = _gpgme_op_data_lookup (ctx, OPDATA_KEYLIST, &hook, -1, NULL);
  opd = hook;
  if (err)
    return err;
  if (opd == NULL)
    return gpg_error (GPG_ERR_INV_VALUE);

  if (!opd->key_queue)
    {
      err = _gpgme_wait_on_condition (ctx, &opd->key_cond);
      if (err)
	return err;

      if (!opd->key_cond)
	return gpg_error (GPG_ERR_EOF);

      opd->key_cond = 0; 
      assert (opd->key_queue);
    }
  queue_item = opd->key_queue;
  opd->key_queue = queue_item->next;
  if (!opd->key_queue)
    opd->key_cond = 0;
  
  *r_key = queue_item->key;
  free (queue_item);
  return 0;
}


/* Terminate a pending keylist operation within CTX.  */
gpgme_error_t
gpgme_op_keylist_end (gpgme_ctx_t ctx)
{
  if (!ctx)
    return gpg_error (GPG_ERR_INV_VALUE);

  return 0;
}


/* Get the key with the fingerprint FPR from the crypto backend.  If
   SECRET is true, get the secret key.  */
gpgme_error_t
gpgme_get_key (gpgme_ctx_t ctx, const char *fpr, gpgme_key_t *r_key,
	       int secret)
{
  gpgme_ctx_t listctx;
  gpgme_error_t err;
  gpgme_key_t key;

  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_protocol_t proto;
    gpgme_engine_info_t info;

    /* Clone the relevant state.  */
    proto = gpgme_get_protocol (ctx);
    gpgme_set_protocol (listctx, proto);
    gpgme_set_keylist_mode (listctx, gpgme_get_keylist_mode (ctx));
    info = gpgme_ctx_get_engine_info (ctx);
    while (info && info->protocol != proto)
      info = info->next;
    if (info)
      gpgme_ctx_set_engine_info (listctx, proto,
				 info->file_name, info->home_dir);
  }

  err = gpgme_op_keylist_start (listctx, fpr, secret);
  if (!err)
    err = gpgme_op_keylist_next (listctx, r_key);
  if (!err)
    {
      err = gpgme_op_keylist_next (listctx, &key);
      if (gpgme_err_code (err) == GPG_ERR_EOF)
	err = gpg_error (GPG_ERR_NO_ERROR);
      else
	{
	  if (!err)
	    {
	      gpgme_key_unref (key);
	      err = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
	    }
	  gpgme_key_unref (*r_key);
	}
    }
  gpgme_release (listctx);
  return err;
}
