gpg: Copy the correct digest for use by TOFU.
[gnupg.git] / g10 / keydb.c
index 263b504..e49e25f 100644 (file)
@@ -1,7 +1,6 @@
 /* keydb.c - key database dispatcher
- * Copyright (C) 2001, 2002, 2003, 2004, 2005,
- *               2008, 2009, 2011, 2013 Free Software Foundation, Inc.
- * Coyrright (C) 2013 Werner Koch
+ * Copyright (C) 2001-2013 Free Software Foundation, Inc.
+ * Coyrright (C) 2001-2015 Werner Koch
  *
  * This file is part of GnuPG.
  *
@@ -24,7 +23,6 @@
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
-#include <assert.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
@@ -61,18 +59,10 @@ struct resource_item
 
 static struct resource_item all_resources[MAX_KEYDB_RESOURCES];
 static int used_resources;
-static void *primary_keyring=NULL;
 
-struct keydb_handle
-{
-  int locked;
-  int found;
-  unsigned long skipped_long_blobs;
-  int no_caching;
-  int current;
-  int used;   /* Number of items in ACTIVE. */
-  struct resource_item active[MAX_KEYDB_RESOURCES];
-};
+/* A pointer used to check for the primary key database by comparing
+   to the struct resource_item's TOKEN.  */
+static void *primary_keydb;
 
 
 /* This is a simple cache used to return the last result of a
@@ -86,38 +76,194 @@ enum keyblock_cache_states {
   KEYBLOCK_CACHE_FILLED
 };
 
-struct {
+struct keyblock_cache {
   enum keyblock_cache_states state;
   byte fpr[MAX_FINGERPRINT_LEN];
   iobuf_t iobuf; /* Image of the keyblock.  */
   u32 *sigstatus;
   int pk_no;
   int uid_no;
-} keyblock_cache;
+  /* Offset of the record in the keybox.  */
+  int resource;
+  off_t offset;
+};
+
+
+struct keydb_handle
+{
+  /* When we locked all of the resources in ACTIVE (using keyring_lock
+     / keybox_lock, as appropriate).  */
+  int locked;
+
+  /* The index into ACTIVE of the resources in which the last search
+     result was found.  Initially -1.  */
+  int found;
+
+  /* Initially -1 (invalid).  This is used to save a search result and
+     later restore it as the selected result.  */
+  int saved_found;
+
+  /* The number of skipped long blobs since the last search
+     (keydb_search_reset).  */
+  unsigned long skipped_long_blobs;
+
+  /* If set, this disables the use of the keyblock cache.  */
+  int no_caching;
+
+  /* Whether the next search will be from the beginning of the
+     database (and thus consider all records).  */
+  int is_reset;
+
+  /* The "file position."  In our case, this is index of the current
+     resource in ACTIVE.  */
+  int current;
+
+  /* The number of resources in ACTIVE.  */
+  int used;
+
+  /* Cache of the last found and parsed key block (only used for
+     keyboxes, not keyrings).  */
+  struct keyblock_cache keyblock_cache;
+
+  /* Copy of ALL_RESOURCES when keydb_new is called.  */
+  struct resource_item active[MAX_KEYDB_RESOURCES];
+};
+
+/* Looking up keys is expensive.  To hide the cost, we cache whether
+   keys exist in the key database.  Then, if we know a key does not
+   exist, we don't have to spend time looking it up.  This
+   particularly helps the --list-sigs and --check-sigs commands.
+
+   The cache stores the results in a hash using separate chaining.
+   Concretely: we use the LSB of the keyid to index the hash table and
+   each bucket consists of a linked list of entries.  An entry
+   consists of the 64-bit key id.  If a key id is not in the cache,
+   then we don't know whether it is in the DB or not.
+
+   To simplify the cache consistency protocol, we simply flush the
+   whole cache whenever a key is inserted or updated.  */
+
+#define KID_NOT_FOUND_CACHE_BUCKETS 256
+static struct kid_not_found_cache_bucket *
+  kid_not_found_cache[KID_NOT_FOUND_CACHE_BUCKETS];
+
+/* The total number of entries in the hash table.  */
+static unsigned int kid_not_found_cache_count;
+
+struct kid_not_found_cache_bucket
+{
+  struct kid_not_found_cache_bucket *next;
+  u32 kid[2];
+};
 
 
 static int lock_all (KEYDB_HANDLE hd);
 static void unlock_all (KEYDB_HANDLE hd);
 
 
+/* Check whether the keyid KID is in key id is definitely not in the
+   database.
+
+   Returns:
+
+     0 - Indeterminate: the key id is not in the cache; we don't know
+         whether the key is in the database or not.  If you want a
+         definitive answer, you'll need to perform a lookup.
+
+     1 - There is definitely no key with this key id in the database.
+         We searched for a key with this key id previously, but we
+         didn't find it in the database.  */
+static int
+kid_not_found_p (u32 *kid)
+{
+  struct kid_not_found_cache_bucket *k;
+
+  for (k = kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS]; k; k = k->next)
+    if (k->kid[0] == kid[0] && k->kid[1] == kid[1])
+      {
+        if (DBG_CACHE)
+          log_debug ("keydb: kid_not_found_p (%08lx%08lx) => not in DB\n",
+                     (ulong)kid[0], (ulong)kid[1]);
+        return 1;
+      }
+
+  if (DBG_CACHE)
+    log_debug ("keydb: kid_not_found_p (%08lx%08lx) => indeterminate\n",
+               (ulong)kid[0], (ulong)kid[1]);
+  return 0;
+}
+
+
+/* Insert the keyid KID into the kid_not_found_cache.  FOUND is whether
+   the key is in the key database or not.
+
+   Note this function does not check whether the key id is already in
+   the cache.  As such, kid_not_found_p() should be called first.  */
 static void
-keyblock_cache_clear (void)
+kid_not_found_insert (u32 *kid)
 {
-  keyblock_cache.state = KEYBLOCK_CACHE_EMPTY;
-  xfree (keyblock_cache.sigstatus);
-  keyblock_cache.sigstatus = NULL;
-  iobuf_close (keyblock_cache.iobuf);
-  keyblock_cache.iobuf = NULL;
+  struct kid_not_found_cache_bucket *k;
+
+  if (DBG_CACHE)
+    log_debug ("keydb: kid_not_found_insert (%08lx%08lx)\n",
+               (ulong)kid[0], (ulong)kid[1]);
+  k = xmalloc (sizeof *k);
+  k->kid[0] = kid[0];
+  k->kid[1] = kid[1];
+  k->next = kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS];
+  kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS] = k;
+  kid_not_found_cache_count++;
+}
+
+
+/* Flush the kid not found cache.  */
+static void
+kid_not_found_flush (void)
+{
+  struct kid_not_found_cache_bucket *k, *knext;
+  int i;
+
+  if (DBG_CACHE)
+    log_debug ("keydb: kid_not_found_flush\n");
+
+  if (!kid_not_found_cache_count)
+    return;
+
+  for (i=0; i < DIM(kid_not_found_cache); i++)
+    {
+      for (k = kid_not_found_cache[i]; k; k = knext)
+        {
+          knext = k->next;
+          xfree (k);
+        }
+      kid_not_found_cache[i] = NULL;
+    }
+  kid_not_found_cache_count = 0;
+}
+
+
+static void
+keyblock_cache_clear (struct keydb_handle *hd)
+{
+  hd->keyblock_cache.state = KEYBLOCK_CACHE_EMPTY;
+  xfree (hd->keyblock_cache.sigstatus);
+  hd->keyblock_cache.sigstatus = NULL;
+  iobuf_close (hd->keyblock_cache.iobuf);
+  hd->keyblock_cache.iobuf = NULL;
+  hd->keyblock_cache.resource = -1;
+  hd->keyblock_cache.offset = -1;
 }
 
 
 /* Handle the creation of a keyring or a keybox if it does not yet
    exist.  Take into account that other processes might have the
    keyring/keybox already locked.  This lock check does not work if
-   the directory itself is not yet available.  If is IS_BOX is true
-   the filename is expected to be a keybox.  If FORCE_CREATE is true
-   the keyring or keybox shall be created.  */
-static int
+   the directory itself is not yet available.  If IS_BOX is true the
+   filename is expected to refer to a keybox.  If FORCE_CREATE is true
+   the keyring or keybox will be created.
+
+   Return 0 if it is okay to access the specified file.  */
+static gpg_error_t
 maybe_create_keyring_or_box (char *filename, int is_box, int force_create)
 {
   dotlock_t lockhd = NULL;
@@ -125,6 +271,8 @@ maybe_create_keyring_or_box (char *filename, int is_box, int force_create)
   int rc;
   mode_t oldmask;
   char *last_slash_in_filename;
+  char *bak_fname = NULL;
+  char *tmp_fname = NULL;
   int save_slash;
 
   /* A quick test whether the filename already exists. */
@@ -203,11 +351,39 @@ maybe_create_keyring_or_box (char *filename, int is_box, int force_create)
     }
 
   /* Now the real test while we are locked. */
+
+  /* Gpg either uses pubring.gpg or pubring.kbx and thus different
+   * lock files.  Now, when one gpg process is updating a pubring.gpg
+   * and thus holding the corresponding lock, a second gpg process may
+   * get to here at the time between the two rename operation used by
+   * the first process to update pubring.gpg.  The lock taken above
+   * may not protect the second process if it tries to create a
+   * pubring.kbx file which would be protected by a different lock
+   * file.
+   *
+   * We can detect this case by checking that the two temporary files
+   * used by the update code exist at the same time.  In that case we
+   * do not create a new file but act as if FORCE_CREATE has not been
+   * given.  Obviously there is a race between our two checks but the
+   * worst thing is that we won't create a new file, which is better
+   * than to accidentally creating one.  */
+  rc = keybox_tmp_names (filename, is_box, &bak_fname, &tmp_fname);
+  if (rc)
+    goto leave;
+
   if (!access (filename, F_OK))
     {
       rc = 0;  /* Okay, we may access the file now.  */
       goto leave;
     }
+  if (!access (bak_fname, F_OK) && !access (tmp_fname, F_OK))
+    {
+      /* Very likely another process is updating a pubring.gpg and we
+         should not create a pubring.kbx.  */
+      rc = gpg_error (GPG_ERR_ENOENT);
+      goto leave;
+    }
+
 
   /* The file does not yet exist, create it now. */
   oldmask = umask (077);
@@ -275,14 +451,21 @@ maybe_create_keyring_or_box (char *filename, int is_box, int force_create)
       dotlock_release (lockhd);
       dotlock_destroy (lockhd);
     }
+  xfree (bak_fname);
+  xfree (tmp_fname);
   return rc;
 }
 
 
-/* Helper for keydb_add_resource.  Opens FILENAME to figures out the
-   resource type.  Returns the resource type and a flag at R_NOTFOUND
-   indicating whether FILENAME could be opened at all.  If the openpgp
-   flag is set in a keybox header, R_OPENPGP will be set to true.  */
+/* Helper for keydb_add_resource.  Opens FILENAME to figure out the
+   resource type.
+
+   Returns the specified file's likely type.  If the file does not
+   exist, returns KEYDB_RESOURCE_TYPE_NONE and sets *R_FOUND to 0.
+   Otherwise, tries to figure out the file's type.  This is either
+   KEYDB_RESOURCE_TYPE_KEYBOX, KEYDB_RESOURCE_TYPE_KEYRING or
+   KEYDB_RESOURCE_TYPE_KEYNONE.  If the file is a keybox and it has
+   the OpenPGP flag set, then R_OPENPGP is also set.  */
 static KeydbResourceType
 rt_from_file (const char *filename, int *r_found, int *r_openpgp)
 {
@@ -321,35 +504,135 @@ rt_from_file (const char *filename, int *r_found, int *r_openpgp)
 
   return rt;
 }
+\f
+char *
+keydb_search_desc_dump (struct keydb_search_desc *desc)
+{
+  char b[MAX_FORMATTED_FINGERPRINT_LEN + 1];
+  char fpr[2 * MAX_FINGERPRINT_LEN + 1];
 
+  switch (desc->mode)
+    {
+    case KEYDB_SEARCH_MODE_EXACT:
+      return xasprintf ("EXACT: '%s'", desc->u.name);
+    case KEYDB_SEARCH_MODE_SUBSTR:
+      return xasprintf ("SUBSTR: '%s'", desc->u.name);
+    case KEYDB_SEARCH_MODE_MAIL:
+      return xasprintf ("MAIL: '%s'", desc->u.name);
+    case KEYDB_SEARCH_MODE_MAILSUB:
+      return xasprintf ("MAILSUB: '%s'", desc->u.name);
+    case KEYDB_SEARCH_MODE_MAILEND:
+      return xasprintf ("MAILEND: '%s'", desc->u.name);
+    case KEYDB_SEARCH_MODE_WORDS:
+      return xasprintf ("WORDS: '%s'", desc->u.name);
+    case KEYDB_SEARCH_MODE_SHORT_KID:
+      return xasprintf ("SHORT_KID: '%s'",
+                        format_keyid (desc->u.kid, KF_SHORT, b, sizeof (b)));
+    case KEYDB_SEARCH_MODE_LONG_KID:
+      return xasprintf ("LONG_KID: '%s'",
+                        format_keyid (desc->u.kid, KF_LONG, b, sizeof (b)));
+    case KEYDB_SEARCH_MODE_FPR16:
+      bin2hex (desc->u.fpr, 16, fpr);
+      return xasprintf ("FPR16: '%s'",
+                        format_hexfingerprint (fpr, b, sizeof (b)));
+    case KEYDB_SEARCH_MODE_FPR20:
+      bin2hex (desc->u.fpr, 20, fpr);
+      return xasprintf ("FPR20: '%s'",
+                        format_hexfingerprint (fpr, b, sizeof (b)));
+    case KEYDB_SEARCH_MODE_FPR:
+      bin2hex (desc->u.fpr, 20, fpr);
+      return xasprintf ("FPR: '%s'",
+                        format_hexfingerprint (fpr, b, sizeof (b)));
+    case KEYDB_SEARCH_MODE_ISSUER:
+      return xasprintf ("ISSUER: '%s'", desc->u.name);
+    case KEYDB_SEARCH_MODE_ISSUER_SN:
+      return xasprintf ("ISSUER_SN: '%*s'",
+                        (int) (desc->snlen == -1
+                               ? strlen (desc->sn) : desc->snlen),
+                        desc->sn);
+    case KEYDB_SEARCH_MODE_SN:
+      return xasprintf ("SN: '%*s'",
+                        (int) (desc->snlen == -1
+                               ? strlen (desc->sn) : desc->snlen),
+                        desc->sn);
+    case KEYDB_SEARCH_MODE_SUBJECT:
+      return xasprintf ("SUBJECT: '%s'", desc->u.name);
+    case KEYDB_SEARCH_MODE_KEYGRIP:
+      return xasprintf ("KEYGRIP: %s", desc->u.grip);
+    case KEYDB_SEARCH_MODE_FIRST:
+      return xasprintf ("FIRST");
+    case KEYDB_SEARCH_MODE_NEXT:
+      return xasprintf ("NEXT");
+    default:
+      return xasprintf ("Bad search mode (%d)", desc->mode);
+    }
+}
 
-/*
- * Register a resource (keyring or aeybox).  The first keyring or
- * keybox which is added by this function is created if it does not
- * exist.  FLAGS are a combination of the KEYDB_RESOURCE_FLAG_
- * constants as defined in keydb.h.
- */
+
+\f
+/* Register a resource (keyring or keybox).  The first keyring or
+ * keybox that is added using this function is created if it does not
+ * already exist and the KEYDB_RESOURCE_FLAG_READONLY is not set.
+ *
+ * FLAGS are a combination of the KEYDB_RESOURCE_FLAG_* constants.
+ *
+ * URL must have the following form:
+ *
+ *   gnupg-ring:filename  = plain keyring
+ *   gnupg-kbx:filename   = keybox file
+ *   filename             = check file's type (create as a plain keyring)
+ *
+ * Note: on systems with drive letters (Windows) invalid URLs (i.e.,
+ * those with an unrecognized part before the ':' such as "c:\...")
+ * will silently be treated as bare filenames.  On other systems, such
+ * URLs will cause this function to return GPG_ERR_GENERAL.
+ *
+ * If KEYDB_RESOURCE_FLAG_DEFAULT is set, the resource is a keyring
+ * and the file ends in ".gpg", then this function also checks if a
+ * file with the same name, but the extension ".kbx" exists, is a
+ * keybox and the OpenPGP flag is set.  If so, this function opens
+ * that resource instead.
+ *
+ * If the file is not found, KEYDB_RESOURCE_FLAG_GPGVDEF is set and
+ * the URL ends in ".kbx", then this function will try opening the
+ * same URL, but with the extension ".gpg".  If that file is a keybox
+ * with the OpenPGP flag set or it is a keyring, then we use that
+ * instead.
+ *
+ * If the file is not found, KEYDB_RESOURCE_FLAG_DEFAULT is set, the
+ * file should be created and the file's extension is ".gpg" then we
+ * replace the extension with ".kbx".
+ *
+ * If the KEYDB_RESOURCE_FLAG_PRIMARY is set and the resource is a
+ * keyring (not a keybox), then this resource is considered the
+ * primary resource.  This is used by keydb_locate_writable().  If
+ * another primary keyring is set, then that keyring is considered the
+ * primary.
+ *
+ * If KEYDB_RESOURCE_FLAG_READONLY is set and the resource is a
+ * keyring (not a keybox), then the keyring is marked as read only and
+ * operations just as keyring_insert_keyblock will return
+ * GPG_ERR_ACCESS.  */
 gpg_error_t
 keydb_add_resource (const char *url, unsigned int flags)
 {
+  /* Whether we have successfully registered a resource.  */
   static int any_registered;
+  /* The file named by the URL (i.e., without the prototype).  */
   const char *resname = url;
+
   char *filename = NULL;
   int create;
   int read_only = !!(flags&KEYDB_RESOURCE_FLAG_READONLY);
   int is_default = !!(flags&KEYDB_RESOURCE_FLAG_DEFAULT);
-  int rc = 0;
+  int is_gpgvdef = !!(flags&KEYDB_RESOURCE_FLAG_GPGVDEF);
+  gpg_error_t err = 0;
   KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE;
   void *token;
 
   /* Create the resource if it is the first registered one.  */
   create = (!read_only && !any_registered);
 
-  /* Do we have an URL?
-   *   gnupg-ring:filename  := this is a plain keyring.
-   *   gnupg-kbx:filename   := this is a keybox file.
-   *   filename := See what is is, but create as plain keyring.
-   */
   if (strlen (resname) > 11 && !strncmp( resname, "gnupg-ring:", 11) )
     {
       rt = KEYDB_RESOURCE_TYPE_KEYRING;
@@ -364,18 +647,26 @@ keydb_add_resource (const char *url, unsigned int flags)
   else if (strchr (resname, ':'))
     {
       log_error ("invalid key resource URL '%s'\n", url );
-      rc = gpg_error (GPG_ERR_GENERAL);
+      err = gpg_error (GPG_ERR_GENERAL);
       goto leave;
     }
 #endif /* !HAVE_DRIVE_LETTERS && !__riscos__ */
 
-  if (*resname != DIRSEP_C )
+  if (*resname != DIRSEP_C
+#ifdef HAVE_W32_SYSTEM
+      && *resname != '/'  /* Fixme: does not handle drive letters.  */
+#endif
+        )
     {
       /* Do tilde expansion etc. */
-      if (strchr(resname, DIRSEP_C) )
+      if (strchr (resname, DIRSEP_C)
+#ifdef HAVE_W32_SYSTEM
+          || strchr (resname, '/')  /* Windows also accepts this.  */
+#endif
+          )
         filename = make_filename (resname, NULL);
       else
-        filename = make_filename (opt.homedir, resname, NULL);
+        filename = make_filename (gnupg_homedir (), resname, NULL);
     }
   else
     filename = xstrdup (resname);
@@ -412,6 +703,23 @@ keydb_add_resource (const char *url, unsigned int flags)
                 strcpy (filename+filenamelen-4, ".gpg");
             }
        }
+      else if (!pass && is_gpgvdef
+               && filenamelen > 4 && !strcmp (filename+filenamelen-4, ".kbx"))
+        {
+          /* Not found but gpgv's default "trustedkeys.kbx" file has
+             been requested.  We did not found it so now check whether
+             a "trustedkeys.gpg" file exists and use that instead.  */
+          KeydbResourceType rttmp;
+
+          strcpy (filename+filenamelen-4, ".gpg");
+          rttmp = rt_from_file (filename, &found, &openpgp_flag);
+          if (found
+              && ((rttmp == KEYDB_RESOURCE_TYPE_KEYBOX && openpgp_flag)
+                  || (rttmp == KEYDB_RESOURCE_TYPE_KEYRING)))
+            rt = rttmp;
+          else /* Restore filename */
+            strcpy (filename+filenamelen-4, ".kbx");
+        }
       else if (!pass
                && is_default && create
                && filenamelen > 4 && !strcmp (filename+filenamelen-4, ".gpg"))
@@ -434,22 +742,22 @@ keydb_add_resource (const char *url, unsigned int flags)
     {
     case KEYDB_RESOURCE_TYPE_NONE:
       log_error ("unknown type of key resource '%s'\n", url );
-      rc = gpg_error (GPG_ERR_GENERAL);
+      err = gpg_error (GPG_ERR_GENERAL);
       goto leave;
 
     case KEYDB_RESOURCE_TYPE_KEYRING:
-      rc = maybe_create_keyring_or_box (filename, 0, create);
-      if (rc)
+      err = maybe_create_keyring_or_box (filename, 0, create);
+      if (err)
         goto leave;
 
       if (keyring_register_filename (filename, read_only, &token))
         {
           if (used_resources >= MAX_KEYDB_RESOURCES)
-            rc = gpg_error (GPG_ERR_RESOURCE_LIMIT);
+            err = gpg_error (GPG_ERR_RESOURCE_LIMIT);
           else
             {
               if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
-                primary_keyring = token;
+                primary_keydb = token;
               all_resources[used_resources].type = rt;
               all_resources[used_resources].u.kr = NULL; /* Not used here */
               all_resources[used_resources].token = token;
@@ -462,26 +770,25 @@ keydb_add_resource (const char *url, unsigned int flags)
              However, we can still mark it as primary even if it was
              already registered.  */
           if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
-            primary_keyring = token;
+            primary_keydb = token;
         }
       break;
 
     case KEYDB_RESOURCE_TYPE_KEYBOX:
       {
-        rc = maybe_create_keyring_or_box (filename, 1, create);
-        if (rc)
+        err = maybe_create_keyring_or_box (filename, 1, create);
+        if (err)
           goto leave;
 
-        /* FIXME: How do we register a read-only keybox?  */
-        token = keybox_register_file (filename, 0);
-        if (token)
+        err = keybox_register_file (filename, 0, &token);
+        if (!err)
           {
             if (used_resources >= MAX_KEYDB_RESOURCES)
-              rc = gpg_error (GPG_ERR_RESOURCE_LIMIT);
+              err = gpg_error (GPG_ERR_RESOURCE_LIMIT);
             else
               {
-                /* if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY)) */
-                /*   primary_keyring = token; */
+                if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
+                  primary_keydb = token;
                 all_resources[used_resources].type = rt;
                 all_resources[used_resources].u.kb = NULL; /* Not used here */
                 all_resources[used_resources].token = token;
@@ -492,51 +799,69 @@ keydb_add_resource (const char *url, unsigned int flags)
                 used_resources++;
               }
           }
-        else
+        else if (gpg_err_code (err) == GPG_ERR_EEXIST)
           {
             /* Already registered.  We will mark it as the primary key
                if requested.  */
-            /* FIXME: How to do that?  Change the keybox interface?  */
-            /* if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY)) */
-            /*   primary_keyring = token; */
+            if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
+              primary_keydb = token;
           }
       }
       break;
 
       default:
        log_error ("resource type of '%s' not supported\n", url);
-       rc = gpg_error (GPG_ERR_GENERAL);
+       err = gpg_error (GPG_ERR_GENERAL);
        goto leave;
     }
 
   /* fixme: check directory permissions and print a warning */
 
  leave:
-  if (rc)
-    log_error (_("keyblock resource '%s': %s\n"), filename, gpg_strerror (rc));
+  if (err)
+    log_error (_("keyblock resource '%s': %s\n"), filename, gpg_strerror (err));
   else
     any_registered = 1;
   xfree (filename);
-  return rc;
+  return err;
 }
 
 
+void
+keydb_dump_stats (void)
+{
+  if (kid_not_found_cache_count)
+    log_info ("keydb: kid_not_found_cache: total: %u\n",
+             kid_not_found_cache_count);
+}
 
 
+/* Create a new database handle.  A database handle is similar to a
+   file handle: it contains a local file position.  This is used when
+   searching: subsequent searches resume where the previous search
+   left off.  To rewind the position, use keydb_search_reset().  This
+   function returns NULL on error, sets ERRNO, and prints an error
+   diagnostic. */
 KEYDB_HANDLE
 keydb_new (void)
 {
   KEYDB_HANDLE hd;
   int i, j;
+  int die = 0;
+  int reterrno;
 
   if (DBG_CLOCK)
     log_clock ("keydb_new");
 
-  hd = xmalloc_clear (sizeof *hd);
+  hd = xtrycalloc (1, sizeof *hd);
+  if (!hd)
+    goto leave;
   hd->found = -1;
+  hd->saved_found = -1;
+  hd->is_reset = 1;
 
-  assert (used_resources <= MAX_KEYDB_RESOURCES);
-  for (i=j=0; i < used_resources; i++)
+  log_assert (used_resources <= MAX_KEYDB_RESOURCES);
+  for (i=j=0; ! die && i < used_resources; i++)
     {
       switch (all_resources[i].type)
         {
@@ -546,10 +871,11 @@ keydb_new (void)
           hd->active[j].type   = all_resources[i].type;
           hd->active[j].token  = all_resources[i].token;
           hd->active[j].u.kr = keyring_new (all_resources[i].token);
-          if (!hd->active[j].u.kr) {
-            xfree (hd);
-            return NULL; /* fixme: release all previously allocated handles*/
-          }
+          if (!hd->active[j].u.kr)
+            {
+              reterrno = errno;
+              die = 1;
+            }
           j++;
           break;
         case KEYDB_RESOURCE_TYPE_KEYBOX:
@@ -558,8 +884,8 @@ keydb_new (void)
           hd->active[j].u.kb   = keybox_new_openpgp (all_resources[i].token, 0);
           if (!hd->active[j].u.kb)
             {
-              xfree (hd);
-              return NULL; /* fixme: release all previously allocated handles*/
+              reterrno = errno;
+              die = 1;
             }
           j++;
           break;
@@ -568,6 +894,19 @@ keydb_new (void)
   hd->used = j;
 
   active_handles++;
+
+  if (die)
+    {
+      keydb_release (hd);
+      gpg_err_set_errno (reterrno);
+      hd = NULL;
+    }
+
+ leave:
+  if (!hd)
+    log_error (_("error opening key DB: %s\n"),
+               gpg_strerror (gpg_error_from_syserror()));
+
   return hd;
 }
 
@@ -579,7 +918,7 @@ keydb_release (KEYDB_HANDLE hd)
 
   if (!hd)
     return;
-  assert (active_handles > 0);
+  log_assert (active_handles > 0);
   active_handles--;
 
   unlock_all (hd);
@@ -598,13 +937,14 @@ keydb_release (KEYDB_HANDLE hd)
         }
     }
 
+  keyblock_cache_clear (hd);
   xfree (hd);
 }
 
 
-/* Set a flag on handle to not use cached results.  This is required
-   for updating a keyring and for key listins.  Fixme: Using a new
  parameter for keydb_new might be a better solution.  */
+/* Set a flag on the handle to suppress use of cached results.  This
+ * is required for updating a keyring and for key listings.  Fixme:
* Using a new parameter for keydb_new might be a better solution.  */
 void
 keydb_disable_caching (KEYDB_HANDLE hd)
 {
@@ -613,14 +953,14 @@ keydb_disable_caching (KEYDB_HANDLE hd)
 }
 
 
-/*
- * Return the name of the current resource.  This is function first
- * looks for the last found found, then for the current search
- * position, and last returns the first available resource.  The
- * returned string is only valid as long as the handle exists.  This
- * function does only return NULL if no handle is specified, in all
- * other error cases an empty string is returned.
- */
+/* Return the file name of the resource in which the current search
+ * result was found or, if there is no search result, the filename of
+ * the current resource (i.e., the resource that the file position
+ * points to).  Note: the filename is not necessarily the URL used to
+ * open it!
+ *
+ * This function only returns NULL if no handle is specified, in all
+ * other error cases an empty string is returned.  */
 const char *
 keydb_get_resource_name (KEYDB_HANDLE hd)
 {
@@ -695,7 +1035,7 @@ lock_all (KEYDB_HANDLE hd)
               keyring_lock (hd->active[i].u.kr, 0);
               break;
             case KEYDB_RESOURCE_TYPE_KEYBOX:
-              rc = keybox_lock (hd->active[i].u.kb, 0);
+              keybox_lock (hd->active[i].u.kb, 0);
               break;
             }
         }
@@ -733,6 +1073,80 @@ unlock_all (KEYDB_HANDLE hd)
 }
 
 
+\f
+/* Save the last found state and invalidate the current selection
+ * (i.e., the entry selected by keydb_search() is invalidated and
+ * something like keydb_get_keyblock() will return an error).  This
+ * does not change the file position.  This makes it possible to do
+ * something like:
+ *
+ *   keydb_search (hd, ...);  // Result 1.
+ *   keydb_push_found_state (hd);
+ *     keydb_search_reset (hd);
+ *     keydb_search (hd, ...);  // Result 2.
+ *   keydb_pop_found_state (hd);
+ *   keydb_get_keyblock (hd, ...);  // -> Result 1.
+ *
+ * Note: it is only possible to save a single save state at a time.
+ * In other words, the the save stack only has room for a single
+ * instance of the state.  */
+void
+keydb_push_found_state (KEYDB_HANDLE hd)
+{
+  if (!hd)
+    return;
+
+  if (hd->found < 0 || hd->found >= hd->used)
+    {
+      hd->saved_found = -1;
+      return;
+    }
+
+  switch (hd->active[hd->found].type)
+    {
+    case KEYDB_RESOURCE_TYPE_NONE:
+      break;
+    case KEYDB_RESOURCE_TYPE_KEYRING:
+      keyring_push_found_state (hd->active[hd->found].u.kr);
+      break;
+    case KEYDB_RESOURCE_TYPE_KEYBOX:
+      keybox_push_found_state (hd->active[hd->found].u.kb);
+      break;
+    }
+
+  hd->saved_found = hd->found;
+  hd->found = -1;
+}
+
+
+/* Restore the previous save state.  If the saved state is NULL or
+   invalid, this is a NOP.  */
+void
+keydb_pop_found_state (KEYDB_HANDLE hd)
+{
+  if (!hd)
+    return;
+
+  hd->found = hd->saved_found;
+  hd->saved_found = -1;
+  if (hd->found < 0 || hd->found >= hd->used)
+    return;
+
+  switch (hd->active[hd->found].type)
+    {
+    case KEYDB_RESOURCE_TYPE_NONE:
+      break;
+    case KEYDB_RESOURCE_TYPE_KEYRING:
+      keyring_pop_found_state (hd->active[hd->found].u.kr);
+      break;
+    case KEYDB_RESOURCE_TYPE_KEYBOX:
+      keybox_pop_found_state (hd->active[hd->found].u.kb);
+      break;
+    }
+}
+
+
+\f
 static gpg_error_t
 parse_keyblock_image (iobuf_t iobuf, int pk_no, int uid_no,
                       const u32 *sigstatus, kbnode_t *r_keyblock)
@@ -771,21 +1185,30 @@ parse_keyblock_image (iobuf_t iobuf, int pk_no, int uid_no,
           err = gpg_error (GPG_ERR_INV_KEYRING);
           break;
         }
-      if (pkt->pkttype == PKT_COMPRESSED)
-        {
-          log_error ("skipped compressed packet in keybox blob\n");
-          free_packet(pkt);
-          init_packet(pkt);
-          continue;
-        }
-      if (pkt->pkttype == PKT_RING_TRUST)
+
+      /* Filter allowed packets.  */
+      switch (pkt->pkttype)
         {
-          log_info ("skipped ring trust packet in keybox blob\n");
+        case PKT_PUBLIC_KEY:
+        case PKT_PUBLIC_SUBKEY:
+        case PKT_SECRET_KEY:
+        case PKT_SECRET_SUBKEY:
+        case PKT_USER_ID:
+        case PKT_ATTRIBUTE:
+        case PKT_SIGNATURE:
+          break; /* Allowed per RFC.  */
+
+        default:
+          /* Note that can't allow ring trust packets here and some of
+             the other GPG specific packets don't make sense either.  */
+          log_error ("skipped packet of type %d in keybox\n",
+                     (int)pkt->pkttype);
           free_packet(pkt);
           init_packet(pkt);
           continue;
         }
 
+      /* Other sanity checks.  */
       if (!in_cert && pkt->pkttype != PKT_PUBLIC_KEY)
         {
           log_error ("parse_keyblock_image: first packet in a keybox blob "
@@ -888,12 +1311,15 @@ parse_keyblock_image (iobuf_t iobuf, int pk_no, int uid_no,
 }
 
 
-/*
- * Return the last found keyring.  Caller must free it.
- * The returned keyblock has the kbode flag bit 0 set for the node with
- * the public key used to locate the keyblock or flag bit 1 set for
- * the user ID node.
- */
+/* Return the keyblock last found by keydb_search() in *RET_KB.
+ *
+ * On success, the function returns 0 and the caller must free *RET_KB
+ * using release_kbnode().  Otherwise, the function returns an error
+ * code.
+ *
+ * The returned keyblock has the kbnode flag bit 0 set for the node
+ * with the public key used to locate the keyblock or flag bit 1 set
+ * for the user ID node.  */
 gpg_error_t
 keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb)
 {
@@ -904,17 +1330,31 @@ keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb)
   if (!hd)
     return gpg_error (GPG_ERR_INV_ARG);
 
-  if (keyblock_cache.state == KEYBLOCK_CACHE_FILLED)
+  if (DBG_CLOCK)
+    log_clock ("keydb_get_keybock enter");
+
+  if (hd->keyblock_cache.state == KEYBLOCK_CACHE_FILLED)
     {
-      iobuf_seek (keyblock_cache.iobuf, 0);
-      err = parse_keyblock_image (keyblock_cache.iobuf,
-                                  keyblock_cache.pk_no,
-                                  keyblock_cache.uid_no,
-                                  keyblock_cache.sigstatus,
-                                  ret_kb);
+      err = iobuf_seek (hd->keyblock_cache.iobuf, 0);
       if (err)
-        keyblock_cache_clear ();
-      return err;
+       {
+         log_error ("keydb_get_keyblock: failed to rewind iobuf for cache\n");
+         keyblock_cache_clear (hd);
+       }
+      else
+       {
+         err = parse_keyblock_image (hd->keyblock_cache.iobuf,
+                                     hd->keyblock_cache.pk_no,
+                                     hd->keyblock_cache.uid_no,
+                                     hd->keyblock_cache.sigstatus,
+                                     ret_kb);
+         if (err)
+           keyblock_cache_clear (hd);
+         if (DBG_CLOCK)
+           log_clock (err? "keydb_get_keyblock leave (cached, failed)"
+                      : "keydb_get_keyblock leave (cached)");
+         return err;
+       }
     }
 
   if (hd->found < 0 || hd->found >= hd->used)
@@ -940,13 +1380,13 @@ keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb)
           {
             err = parse_keyblock_image (iobuf, pk_no, uid_no, sigstatus,
                                         ret_kb);
-            if (!err && keyblock_cache.state == KEYBLOCK_CACHE_PREPARED)
+            if (!err && hd->keyblock_cache.state == KEYBLOCK_CACHE_PREPARED)
               {
-                keyblock_cache.state     = KEYBLOCK_CACHE_FILLED;
-                keyblock_cache.sigstatus = sigstatus;
-                keyblock_cache.iobuf     = iobuf;
-                keyblock_cache.pk_no     = pk_no;
-                keyblock_cache.uid_no    = uid_no;
+                hd->keyblock_cache.state     = KEYBLOCK_CACHE_FILLED;
+                hd->keyblock_cache.sigstatus = sigstatus;
+                hd->keyblock_cache.iobuf     = iobuf;
+                hd->keyblock_cache.pk_no     = pk_no;
+                hd->keyblock_cache.uid_no    = uid_no;
               }
             else
               {
@@ -958,9 +1398,12 @@ keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb)
       break;
     }
 
-  if (keyblock_cache.state != KEYBLOCK_CACHE_FILLED)
-    keyblock_cache_clear ();
+  if (hd->keyblock_cache.state != KEYBLOCK_CACHE_FILLED)
+    keyblock_cache_clear (hd);
 
+  if (DBG_CLOCK)
+    log_clock (err? "keydb_get_keyblock leave (failed)"
+               : "keydb_get_keyblock leave");
   return err;
 }
 
@@ -1055,21 +1498,38 @@ build_keyblock_image (kbnode_t keyblock, iobuf_t *r_iobuf, u32 **r_sigstatus)
 }
 
 
-/*
- * Update the current keyblock with the keyblock KB
- */
+/* Update the keyblock KB (i.e., extract the fingerprint and find the
+ * corresponding keyblock in the keyring).
+ *
+ * This doesn't do anything if --dry-run was specified.
+ *
+ * Returns 0 on success.  Otherwise, it returns an error code.  Note:
+ * if there isn't a keyblock in the keyring corresponding to KB, then
+ * this function returns GPG_ERR_VALUE_NOT_FOUND.
+ *
+ * This function selects the matching record and modifies the current
+ * file position to point to the record just after the selected entry.
+ * Thus, if you do a subsequent search using HD, you should first do a
+ * keydb_search_reset.  Further, if the selected record is important,
+ * you should use keydb_push_found_state and keydb_pop_found_state to
+ * save and restore it.  */
 gpg_error_t
 keydb_update_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
 {
   gpg_error_t err;
+  PKT_public_key *pk;
+  KEYDB_SEARCH_DESC desc;
+  size_t len;
+
+  log_assert (kb);
+  log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
+  pk = kb->pkt->pkt.public_key;
 
   if (!hd)
     return gpg_error (GPG_ERR_INV_ARG);
 
-  keyblock_cache_clear ();
-
-  if (hd->found < 0 || hd->found >= hd->used)
-    return gpg_error (GPG_ERR_VALUE_NOT_FOUND);
+  kid_not_found_flush ();
+  keyblock_cache_clear (hd);
 
   if (opt.dry_run)
     return 0;
@@ -1078,6 +1538,19 @@ keydb_update_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
   if (err)
     return err;
 
+  memset (&desc, 0, sizeof (desc));
+  fingerprint_from_pk (pk, desc.u.fpr, &len);
+  if (len == 20)
+    desc.mode = KEYDB_SEARCH_MODE_FPR20;
+  else
+    log_bug ("%s: Unsupported key length: %zu\n", __func__, len);
+
+  keydb_search_reset (hd);
+  err = keydb_search (hd, &desc, 1, NULL);
+  if (err)
+    return gpg_error (GPG_ERR_VALUE_NOT_FOUND);
+  log_assert (hd->found >= 0 && hd->found < hd->used);
+
   switch (hd->active[hd->found].type)
     {
     case KEYDB_RESOURCE_TYPE_NONE:
@@ -1107,9 +1580,16 @@ keydb_update_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
 }
 
 
-/*
- * Insert a new KB into one of the resources.
- */
+/* Insert a keyblock into one of the underlying keyrings or keyboxes.
+ *
+ * Be default, the keyring / keybox from which the last search result
+ * came is used.  If there was no previous search result (or
+ * keydb_search_reset was called), then the keyring / keybox where the
+ * next search would start is used (i.e., the current file position).
+ *
+ * Note: this doesn't do anything if --dry-run was specified.
+ *
+ * Returns 0 on success.  Otherwise, it returns an error code.  */
 gpg_error_t
 keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
 {
@@ -1119,7 +1599,8 @@ keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
   if (!hd)
     return gpg_error (GPG_ERR_INV_ARG);
 
-  keyblock_cache_clear ();
+  kid_not_found_flush ();
+  keyblock_cache_clear (hd);
 
   if (opt.dry_run)
     return 0;
@@ -1170,9 +1651,11 @@ keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
 }
 
 
-/*
- * Delete the current keyblock.
- */
+/* Delete the currently selected keyblock.  If you haven't done a
+ * search yet on this database handle (or called keydb_search_reset),
+ * then this will return an error.
+ *
+ * Returns 0 on success or an error code, if an error occurs.  */
 gpg_error_t
 keydb_delete_keyblock (KEYDB_HANDLE hd)
 {
@@ -1181,7 +1664,8 @@ keydb_delete_keyblock (KEYDB_HANDLE hd)
   if (!hd)
     return gpg_error (GPG_ERR_INV_ARG);
 
-  keyblock_cache_clear ();
+  kid_not_found_flush ();
+  keyblock_cache_clear (hd);
 
   if (hd->found < 0 || hd->found >= hd->used)
     return gpg_error (GPG_ERR_VALUE_NOT_FOUND);
@@ -1212,18 +1696,20 @@ keydb_delete_keyblock (KEYDB_HANDLE hd)
 
 
 \f
-/*
- * Locate the default writable key resource, so that the next
- * operation (which is only relevant for inserts) will be done on this
- * resource.
- */
+/* A database may consists of multiple keyrings / key boxes.  This
+ * sets the "file position" to the start of the first keyring / key
+ * box that is writable (i.e., doesn't have the read-only flag set).
+ *
+ * This first tries the primary keyring (the last keyring (not
+ * keybox!) added using keydb_add_resource() and with
+ * KEYDB_RESOURCE_FLAG_PRIMARY set).  If that is not writable, then it
+ * tries the keyrings / keyboxes in the order in which they were
+ * added.  */
 gpg_error_t
-keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved)
+keydb_locate_writable (KEYDB_HANDLE hd)
 {
   gpg_error_t rc;
 
-  (void)reserved;
-
   if (!hd)
     return GPG_ERR_INV_ARG;
 
@@ -1232,11 +1718,11 @@ keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved)
     return rc;
 
   /* If we have a primary set, try that one first */
-  if (primary_keyring)
+  if (primary_keydb)
     {
       for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++)
        {
-         if(hd->active[hd->current].token==primary_keyring)
+         if(hd->active[hd->current].token == primary_keydb)
            {
              if(keyring_is_writable (hd->active[hd->current].token))
                return 0;
@@ -1271,16 +1757,13 @@ keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved)
   return gpg_error (GPG_ERR_NOT_FOUND);
 }
 
-/*
- * Rebuild the caches of all key resources.
- */
+
+/* Rebuild the on-disk caches of all key resources.  */
 void
 keydb_rebuild_caches (int noisy)
 {
   int i, rc;
 
-  keyblock_cache_clear ();
-
   for (i=0; i < used_resources; i++)
     {
       if (!keyring_is_writable (all_resources[i].token))
@@ -1303,7 +1786,8 @@ keydb_rebuild_caches (int noisy)
 }
 
 
-/* Return the number of skipped blocks since the last search reset.  */
+/* Return the number of skipped blocks (because they were to large to
+   read from a keybox) since the last search reset.  */
 unsigned long
 keydb_get_skipped_counter (KEYDB_HANDLE hd)
 {
@@ -1311,9 +1795,12 @@ keydb_get_skipped_counter (KEYDB_HANDLE hd)
 }
 
 
-/*
- * Start the next search on this handle right at the beginning
- */
+/* Clears the current search result and resets the handle's position
+ * so that the next search starts at the beginning of the database
+ * (the start of the first resource).
+ *
+ * Returns 0 on success and an error code if an error occurred.
+ * (Currently, this function always returns 0 if HD is valid.)  */
 gpg_error_t
 keydb_search_reset (KEYDB_HANDLE hd)
 {
@@ -1323,7 +1810,7 @@ keydb_search_reset (KEYDB_HANDLE hd)
   if (!hd)
     return gpg_error (GPG_ERR_INV_ARG);
 
-  keyblock_cache_clear ();
+  keyblock_cache_clear (hd);
 
   if (DBG_CLOCK)
     log_clock ("keydb_search_reset");
@@ -1349,68 +1836,38 @@ keydb_search_reset (KEYDB_HANDLE hd)
           break;
         }
     }
+  hd->is_reset = 1;
   return rc;
 }
 
 
-static void
-dump_search_desc (KEYDB_HANDLE hd, const char *text,
-                  KEYDB_SEARCH_DESC *desc, size_t ndesc)
-{
-  int n;
-  const char *s;
-
-  for (n=0; n < ndesc; n++)
-    {
-      switch (desc[n].mode)
-        {
-        case KEYDB_SEARCH_MODE_NONE:      s = "none";      break;
-        case KEYDB_SEARCH_MODE_EXACT:     s = "exact";     break;
-        case KEYDB_SEARCH_MODE_SUBSTR:    s = "substr";    break;
-        case KEYDB_SEARCH_MODE_MAIL:      s = "mail";      break;
-        case KEYDB_SEARCH_MODE_MAILSUB:   s = "mailsub";   break;
-        case KEYDB_SEARCH_MODE_MAILEND:   s = "mailend";   break;
-        case KEYDB_SEARCH_MODE_WORDS:     s = "words";     break;
-        case KEYDB_SEARCH_MODE_SHORT_KID: s = "short_kid"; break;
-        case KEYDB_SEARCH_MODE_LONG_KID:  s = "long_kid";  break;
-        case KEYDB_SEARCH_MODE_FPR16:     s = "fpr16";     break;
-        case KEYDB_SEARCH_MODE_FPR20:     s = "fpr20";     break;
-        case KEYDB_SEARCH_MODE_FPR:       s = "fpr";       break;
-        case KEYDB_SEARCH_MODE_ISSUER:    s = "issuer";    break;
-        case KEYDB_SEARCH_MODE_ISSUER_SN: s = "issuer_sn"; break;
-        case KEYDB_SEARCH_MODE_SN:        s = "sn";        break;
-        case KEYDB_SEARCH_MODE_SUBJECT:   s = "subject";   break;
-        case KEYDB_SEARCH_MODE_KEYGRIP:   s = "keygrip";   break;
-        case KEYDB_SEARCH_MODE_FIRST:     s = "first";     break;
-        case KEYDB_SEARCH_MODE_NEXT:      s = "next";      break;
-        default:                          s = "?";         break;
-        }
-      if (!n)
-        log_debug ("%s: mode=%s  (hd=%p)", text, s, hd);
-      else
-        log_debug ("%*s  mode=%s", (int)strlen (text), "", s);
-      if (desc[n].mode == KEYDB_SEARCH_MODE_LONG_KID)
-        log_printf (" %08lX%08lX", (unsigned long)desc[n].u.kid[0],
-                    (unsigned long)desc[n].u.kid[1]);
-      else if (desc[n].mode == KEYDB_SEARCH_MODE_SHORT_KID)
-        log_printf (" %08lX", (unsigned long)desc[n].u.kid[1]);
-      else if (desc[n].mode == KEYDB_SEARCH_MODE_SUBSTR)
-        log_printf (" '%s'", desc[n].u.name);
-    }
-}
-
-
-/*
- * Search through all keydb resources, starting at the current
- * position, for a keyblock which contains one of the keys described
- * in the DESC array.  Returns GPG_ERR_NOT_FOUND if no matching
- * keyring was found.
- */
+/* Search the database for keys matching the search description.  If
+ * the DB contains any legacy keys, these are silently ignored.
+ *
+ * DESC is an array of search terms with NDESC entries.  The search
+ * terms are or'd together.  That is, the next entry in the DB that
+ * matches any of the descriptions will be returned.
+ *
+ * Note: this function resumes searching where the last search left
+ * off (i.e., at the current file position).  If you want to search
+ * from the start of the database, then you need to first call
+ * keydb_search_reset().
+ *
+ * If no key matches the search description, returns
+ * GPG_ERR_NOT_FOUND.  If there was a match, returns 0.  If an error
+ * occurred, returns an error code.
+ *
+ * The returned key is considered to be selected and the raw data can,
+ * for instance, be returned by calling keydb_get_keyblock().  */
 gpg_error_t
 keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
               size_t ndesc, size_t *descindex)
 {
+  int i;
   gpg_error_t rc;
+  int was_reset = hd->is_reset;
+  /* If an entry is already in the cache, then don't add it again.  */
+  int already_in_cache = 0;
 
   if (descindex)
     *descindex = 0; /* Make sure it is always set on return.  */
@@ -1421,8 +1878,25 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
   if (DBG_CLOCK)
     log_clock ("keydb_search enter");
 
-  if (DBG_CACHE)
-    dump_search_desc (hd, "keydb_search", desc, ndesc);
+  if (DBG_LOOKUP)
+    {
+      log_debug ("%s: %zd search descriptions:\n", __func__, ndesc);
+      for (i = 0; i < ndesc; i ++)
+        {
+          char *t = keydb_search_desc_dump (&desc[i]);
+          log_debug ("%s   %d: %s\n", __func__, i, t);
+          xfree (t);
+        }
+    }
+
+
+  if (ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID
+      && (already_in_cache = kid_not_found_p (desc[0].u.kid)) == 1 )
+    {
+      if (DBG_CLOCK)
+        log_clock ("keydb_search leave (not found, cached)");
+      return gpg_error (GPG_ERR_NOT_FOUND);
+    }
 
   /* NB: If one of the exact search modes below is used in a loop to
      walk over all keys (with the same fingerprint) the caching must
@@ -1431,12 +1905,24 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
       && ndesc == 1
       && (desc[0].mode == KEYDB_SEARCH_MODE_FPR20
           || desc[0].mode == KEYDB_SEARCH_MODE_FPR)
-      && keyblock_cache.state  == KEYBLOCK_CACHE_FILLED
-      && !memcmp (keyblock_cache.fpr, desc[0].u.fpr, 20))
+      && hd->keyblock_cache.state  == KEYBLOCK_CACHE_FILLED
+      && !memcmp (hd->keyblock_cache.fpr, desc[0].u.fpr, 20)
+      /* Make sure the current file position occurs before the cached
+         result to avoid an infinite loop.  */
+      && (hd->current < hd->keyblock_cache.resource
+          || (hd->current == hd->keyblock_cache.resource
+              && (keybox_offset (hd->active[hd->current].u.kb)
+                  <= hd->keyblock_cache.offset))))
     {
       /* (DESCINDEX is already set).  */
       if (DBG_CLOCK)
         log_clock ("keydb_search leave (cached)");
+
+      hd->current = hd->keyblock_cache.resource;
+      /* HD->KEYBLOCK_CACHE.OFFSET is the last byte in the record.
+         Seek just beyond that.  */
+      keybox_seek (hd->active[hd->current].u.kb,
+                   hd->keyblock_cache.offset + 1);
       return 0;
     }
 
@@ -1444,21 +1930,43 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
   while ((rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
          && hd->current >= 0 && hd->current < hd->used)
     {
-      switch (hd->active[hd->current].type)
+      if (DBG_LOOKUP)
+        log_debug ("%s: searching %s (resource %d of %d)\n",
+                   __func__,
+                   hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYRING
+                   ? "keyring"
+                   : (hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX
+                      ? "keybox" : "unknown type"),
+                   hd->current, hd->used);
+
+       switch (hd->active[hd->current].type)
         {
         case KEYDB_RESOURCE_TYPE_NONE:
           BUG(); /* we should never see it here */
           break;
         case KEYDB_RESOURCE_TYPE_KEYRING:
           rc = keyring_search (hd->active[hd->current].u.kr, desc,
-                               ndesc, descindex);
+                               ndesc, descindex, 1);
           break;
         case KEYDB_RESOURCE_TYPE_KEYBOX:
-          rc = keybox_search (hd->active[hd->current].u.kb, desc,
-                              ndesc, KEYBOX_BLOBTYPE_PGP,
-                              descindex, &hd->skipped_long_blobs);
+          do
+            rc = keybox_search (hd->active[hd->current].u.kb, desc,
+                                ndesc, KEYBOX_BLOBTYPE_PGP,
+                                descindex, &hd->skipped_long_blobs);
+          while (rc == GPG_ERR_LEGACY_KEY);
           break;
         }
+
+      if (DBG_LOOKUP)
+        log_debug ("%s: searched %s (resource %d of %d) => %s\n",
+                   __func__,
+                   hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYRING
+                   ? "keyring"
+                   : (hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX
+                      ? "keybox" : "unknown type"),
+                   hd->current, hd->used,
+                   rc == -1 ? "EOF" : gpg_strerror (rc));
+
       if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
         {
           /* EOF -> switch to next resource */
@@ -1467,21 +1975,34 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
       else if (!rc)
         hd->found = hd->current;
     }
+  hd->is_reset = 0;
 
   rc = ((rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
         ? gpg_error (GPG_ERR_NOT_FOUND)
         : rc);
 
-  keyblock_cache_clear ();
+  keyblock_cache_clear (hd);
   if (!hd->no_caching
       && !rc
       && ndesc == 1 && (desc[0].mode == KEYDB_SEARCH_MODE_FPR20
-                        || desc[0].mode == KEYDB_SEARCH_MODE_FPR))
+                        || desc[0].mode == KEYDB_SEARCH_MODE_FPR)
+      && hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX)
     {
-      keyblock_cache.state = KEYBLOCK_CACHE_PREPARED;
-      memcpy (keyblock_cache.fpr, desc[0].u.fpr, 20);
+      hd->keyblock_cache.state = KEYBLOCK_CACHE_PREPARED;
+      hd->keyblock_cache.resource = hd->current;
+      /* The current offset is at the start of the next record.  Since
+         a record is at least 1 byte, we just use offset - 1, which is
+         within the record.  */
+      hd->keyblock_cache.offset
+        = keybox_offset (hd->active[hd->current].u.kb) - 1;
+      memcpy (hd->keyblock_cache.fpr, desc[0].u.fpr, 20);
     }
 
+  if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND
+      && ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID && was_reset
+      && !already_in_cache)
+    kid_not_found_insert (desc[0].u.kid);
+
   if (DBG_CLOCK)
     log_clock (rc? "keydb_search leave (not found)"
                  : "keydb_search leave (found)");
@@ -1489,16 +2010,31 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
 }
 
 
+/* Return the first non-legacy key in the database.
+ *
+ * If you want the very first key in the database, you can directly
+ * call keydb_search with the search description
+ *  KEYDB_SEARCH_MODE_FIRST.  */
 gpg_error_t
 keydb_search_first (KEYDB_HANDLE hd)
 {
+  gpg_error_t err;
   KEYDB_SEARCH_DESC desc;
 
+  err = keydb_search_reset (hd);
+  if (err)
+    return err;
+
   memset (&desc, 0, sizeof desc);
   desc.mode = KEYDB_SEARCH_MODE_FIRST;
   return keydb_search (hd, &desc, 1, NULL);
 }
 
+
+/* Return the next key (not the next matching key!).
+ *
+ * Unlike calling keydb_search with KEYDB_SEARCH_MODE_NEXT, this
+ * function silently skips legacy keys.  */
 gpg_error_t
 keydb_search_next (KEYDB_HANDLE hd)
 {
@@ -1509,6 +2045,13 @@ keydb_search_next (KEYDB_HANDLE hd)
   return keydb_search (hd, &desc, 1, NULL);
 }
 
+
+/* This is a convenience function for searching for keys with a long
+ * key id.
+ *
+ * Note: this function resumes searching where the last search left
+ * off.  If you want to search the whole database, then you need to
+ * first call keydb_search_reset().  */
 gpg_error_t
 keydb_search_kid (KEYDB_HANDLE hd, u32 *kid)
 {
@@ -1521,6 +2064,13 @@ keydb_search_kid (KEYDB_HANDLE hd, u32 *kid)
   return keydb_search (hd, &desc, 1, NULL);
 }
 
+
+/* This is a convenience function for searching for keys with a long
+ * (20 byte) fingerprint.
+ *
+ * Note: this function resumes searching where the last search left
+ * off.  If you want to search the whole database, then you need to
+ * first call keydb_search_reset().  */
 gpg_error_t
 keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr)
 {