gpg: Use macros to check the signature class.
[gnupg.git] / g10 / keydb.c
index 8a68980..0f28bc3 100644 (file)
@@ -15,7 +15,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
 #include <config.h>
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
-#include <assert.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
 #include "gpg.h"
-#include "util.h"
+#include "../common/util.h"
 #include "options.h"
 #include "main.h" /*try_make_homedir ()*/
 #include "packet.h"
 #include "keyring.h"
 #include "../kbx/keybox.h"
 #include "keydb.h"
-#include "i18n.h"
+#include "../common/i18n.h"
 
 static int active_handles;
 
@@ -60,8 +59,13 @@ struct resource_item
 
 static struct resource_item all_resources[MAX_KEYDB_RESOURCES];
 static int used_resources;
-static void *primary_keyring=NULL;
 
+/* A pointer used to check for the primary key database by comparing
+   to the struct resource_item's TOKEN.  */
+static void *primary_keydb;
+
+/* Whether we have successfully registered any resource.  */
+static int any_registered;
 
 /* This is a simple cache used to return the last result of a
    successful fingerprint search.  This works only for keybox resources
@@ -78,9 +82,11 @@ 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;
+  /* Offset of the record in the keybox.  */
+  int resource;
+  off_t offset;
 };
 
 
@@ -142,15 +148,36 @@ struct keydb_handle
 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];
 };
 
+struct
+{
+  unsigned int count;   /* The current number of entries in the hash table.  */
+  unsigned int peak;    /* The peak of COUNT.  */
+  unsigned int flushes; /* The number of flushes.  */
+} kid_not_found_stats;
+
+struct
+{
+  unsigned int handles; /* Number of handles created.  */
+  unsigned int locks;   /* Number of locks taken.  */
+  unsigned int parse_keyblocks; /* Number of parse_keyblock_image calls.  */
+  unsigned int get_keyblocks;   /* Number of keydb_get_keyblock calls.    */
+  unsigned int build_keyblocks; /* Number of build_keyblock_image calls.  */
+  unsigned int update_keyblocks;/* Number of update_keyblock calls.       */
+  unsigned int insert_keyblocks;/* Number of update_keyblock calls.       */
+  unsigned int delete_keyblocks;/* Number of delete_keyblock calls.       */
+  unsigned int search_resets;   /* Number of keydb_search_reset calls.    */
+  unsigned int found;           /* Number of successful keydb_search calls. */
+  unsigned int found_cached;    /* Ditto but from the cache.              */
+  unsigned int notfound;        /* Number of failed keydb_search calls.   */
+  unsigned int notfound_cached; /* Ditto but from the cache.              */
+} keydb_stats;
+
 
 static int lock_all (KEYDB_HANDLE hd);
 static void unlock_all (KEYDB_HANDLE hd);
@@ -207,7 +234,7 @@ kid_not_found_insert (u32 *kid)
   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++;
+  kid_not_found_stats.count++;
 }
 
 
@@ -221,7 +248,7 @@ kid_not_found_flush (void)
   if (DBG_CACHE)
     log_debug ("keydb: kid_not_found_flush\n");
 
-  if (!kid_not_found_cache_count)
+  if (!kid_not_found_stats.count)
     return;
 
   for (i=0; i < DIM(kid_not_found_cache); i++)
@@ -233,7 +260,10 @@ kid_not_found_flush (void)
         }
       kid_not_found_cache[i] = NULL;
     }
-  kid_not_found_cache_count = 0;
+  if (kid_not_found_stats.count > kid_not_found_stats.peak)
+    kid_not_found_stats.peak = kid_not_found_stats.count;
+  kid_not_found_stats.count = 0;
+  kid_not_found_stats.flushes++;
 }
 
 
@@ -241,10 +271,10 @@ 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;
 }
 
 
@@ -256,7 +286,7 @@ keyblock_cache_clear (struct keydb_handle *hd)
    the keyring or keybox will be created.
 
    Return 0 if it is okay to access the specified file.  */
-static int
+static gpg_error_t
 maybe_create_keyring_or_box (char *filename, int is_box, int force_create)
 {
   dotlock_t lockhd = NULL;
@@ -264,11 +294,13 @@ 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. */
   if (!access (filename, F_OK))
-    return 0;
+    return !access (filename, R_OK)? 0 : gpg_error (GPG_ERR_EACCES);
 
   /* If we don't want to create a new file at all, there is no need to
      go any further - bail out right here.  */
@@ -342,11 +374,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);
@@ -378,7 +438,7 @@ maybe_create_keyring_or_box (char *filename, int is_box, int force_create)
      that the detection magic will work the next time it is used.  */
   if (is_box)
     {
-      FILE *fp = fopen (filename, "w");
+      FILE *fp = fopen (filename, "wb");
       if (!fp)
         rc = gpg_error_from_syserror ();
       else
@@ -414,6 +474,8 @@ 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;
 }
 
@@ -470,7 +532,7 @@ char *
 keydb_search_desc_dump (struct keydb_search_desc *desc)
 {
   char b[MAX_FORMATTED_FINGERPRINT_LEN + 1];
-  char fpr[MAX_FINGERPRINT_LEN + 1];
+  char fpr[2 * MAX_FINGERPRINT_LEN + 1];
 
   switch (desc->mode)
     {
@@ -528,12 +590,55 @@ keydb_search_desc_dump (struct keydb_search_desc *desc)
       return xasprintf ("Bad search mode (%d)", desc->mode);
     }
 }
+
+
 \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;
 
@@ -542,7 +647,7 @@ keydb_add_resource (const char *url, unsigned int flags)
   int read_only = !!(flags&KEYDB_RESOURCE_FLAG_READONLY);
   int is_default = !!(flags&KEYDB_RESOURCE_FLAG_DEFAULT);
   int is_gpgvdef = !!(flags&KEYDB_RESOURCE_FLAG_GPGVDEF);
-  int rc = 0;
+  gpg_error_t err = 0;
   KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE;
   void *token;
 
@@ -563,7 +668,7 @@ 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__ */
@@ -582,7 +687,7 @@ keydb_add_resource (const char *url, unsigned int flags)
           )
         filename = make_filename (resname, NULL);
       else
-        filename = make_filename (opt.homedir, resname, NULL);
+        filename = make_filename (gnupg_homedir (), resname, NULL);
     }
   else
     filename = xstrdup (resname);
@@ -658,22 +763,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;
@@ -686,26 +791,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;
@@ -716,60 +820,89 @@ 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));
+      write_status_error ("add_keyblock_resource", 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);
+  log_info ("keydb: handles=%u locks=%u parse=%u get=%u\n",
+            keydb_stats.handles,
+            keydb_stats.locks,
+            keydb_stats.parse_keyblocks,
+            keydb_stats.get_keyblocks);
+  log_info ("       build=%u update=%u insert=%u delete=%u\n",
+            keydb_stats.build_keyblocks,
+            keydb_stats.update_keyblocks,
+            keydb_stats.insert_keyblocks,
+            keydb_stats.delete_keyblocks);
+  log_info ("       reset=%u found=%u not=%u cache=%u not=%u\n",
+            keydb_stats.search_resets,
+            keydb_stats.found,
+            keydb_stats.notfound,
+            keydb_stats.found_cached,
+            keydb_stats.notfound_cached);
+  log_info ("kid_not_found_cache: count=%u peak=%u flushes=%u\n",
+            kid_not_found_stats.count,
+            kid_not_found_stats.peak,
+            kid_not_found_stats.flushes);
 }
 
 
+/* 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);
+  log_assert (used_resources <= MAX_KEYDB_RESOURCES);
   for (i=j=0; ! die && i < used_resources; i++)
     {
       switch (all_resources[i].type)
@@ -781,7 +914,10 @@ keydb_new (void)
           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)
-           die = 1;
+            {
+              reterrno = errno;
+              die = 1;
+            }
           j++;
           break;
         case KEYDB_RESOURCE_TYPE_KEYBOX:
@@ -789,7 +925,10 @@ keydb_new (void)
           hd->active[j].token  = all_resources[i].token;
           hd->active[j].u.kb   = keybox_new_openpgp (all_resources[i].token, 0);
           if (!hd->active[j].u.kb)
-           die = 1;
+            {
+              reterrno = errno;
+              die = 1;
+            }
           j++;
           break;
         }
@@ -797,13 +936,20 @@ keydb_new (void)
   hd->used = j;
 
   active_handles++;
+  keydb_stats.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;
 }
 
@@ -815,7 +961,7 @@ keydb_release (KEYDB_HANDLE hd)
 
   if (!hd)
     return;
-  assert (active_handles > 0);
+  log_assert (active_handles > 0);
   active_handles--;
 
   unlock_all (hd);
@@ -834,10 +980,14 @@ keydb_release (KEYDB_HANDLE hd)
         }
     }
 
+  keyblock_cache_clear (hd);
   xfree (hd);
 }
 
 
+/* 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)
 {
@@ -846,6 +996,14 @@ keydb_disable_caching (KEYDB_HANDLE hd)
 }
 
 
+/* 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)
 {
@@ -920,13 +1078,16 @@ 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;
             }
         }
     }
   else
-    hd->locked = 1;
+    {
+      hd->locked = 1;
+      keydb_stats.locks++;
+    }
 
   return rc;
 }
@@ -959,6 +1120,22 @@ 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 save stack only has room for a single
+ * instance of the state.  */
 void
 keydb_push_found_state (KEYDB_HANDLE hd)
 {
@@ -988,6 +1165,8 @@ keydb_push_found_state (KEYDB_HANDLE hd)
 }
 
 
+/* 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)
 {
@@ -1016,14 +1195,14 @@ keydb_pop_found_state (KEYDB_HANDLE hd)
 \f
 static gpg_error_t
 parse_keyblock_image (iobuf_t iobuf, int pk_no, int uid_no,
-                      const u32 *sigstatus, kbnode_t *r_keyblock)
+                      kbnode_t *r_keyblock)
 {
   gpg_error_t err;
+  struct parse_packet_ctx_s parsectx;
   PACKET *pkt;
   kbnode_t keyblock = NULL;
   kbnode_t node, *tail;
   int in_cert, save_mode;
-  u32 n_sigs;
   int pk_count, uid_count;
 
   *r_keyblock = NULL;
@@ -1032,16 +1211,16 @@ parse_keyblock_image (iobuf_t iobuf, int pk_no, int uid_no,
   if (!pkt)
     return gpg_error_from_syserror ();
   init_packet (pkt);
+  init_parse_packet (&parsectx, iobuf);
   save_mode = set_packet_list_mode (0);
   in_cert = 0;
-  n_sigs = 0;
   tail = NULL;
   pk_count = uid_count = 0;
-  while ((err = parse_packet (iobuf, pkt)) != -1)
+  while ((err = parse_packet (&parsectx, pkt)) != -1)
     {
       if (gpg_err_code (err) == GPG_ERR_UNKNOWN_PACKET)
         {
-          free_packet (pkt);
+          free_packet (pkt, &parsectx);
           init_packet (pkt);
           continue;
        }
@@ -1063,6 +1242,7 @@ parse_keyblock_image (iobuf_t iobuf, int pk_no, int uid_no,
         case PKT_USER_ID:
         case PKT_ATTRIBUTE:
         case PKT_SIGNATURE:
+        case PKT_RING_TRUST:
           break; /* Allowed per RFC.  */
 
         default:
@@ -1070,7 +1250,7 @@ parse_keyblock_image (iobuf_t iobuf, int pk_no, int uid_no,
              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);
+          free_packet(pkt, &parsectx);
           init_packet(pkt);
           continue;
         }
@@ -1093,36 +1273,6 @@ parse_keyblock_image (iobuf_t iobuf, int pk_no, int uid_no,
         }
       in_cert = 1;
 
-      if (pkt->pkttype == PKT_SIGNATURE && sigstatus)
-        {
-          PKT_signature *sig = pkt->pkt.signature;
-
-          n_sigs++;
-          if (n_sigs > sigstatus[0])
-            {
-              log_error ("parse_keyblock_image: "
-                         "more signatures than found in the meta data\n");
-              err = gpg_error (GPG_ERR_INV_KEYRING);
-              break;
-
-            }
-          if (sigstatus[n_sigs])
-            {
-              sig->flags.checked = 1;
-              if (sigstatus[n_sigs] == 1 )
-                ; /* missing key */
-              else if (sigstatus[n_sigs] == 2 )
-                ; /* bad signature */
-              else if (sigstatus[n_sigs] < 0x10000000)
-                ; /* bad flag */
-              else
-                {
-                  sig->flags.valid = 1;
-                  /* Fixme: Shall we set the expired flag here?  */
-                }
-            }
-        }
-
       node = new_kbnode (pkt);
 
       switch (pkt->pkttype)
@@ -1162,22 +1312,29 @@ parse_keyblock_image (iobuf_t iobuf, int pk_no, int uid_no,
   if (err == -1 && keyblock)
     err = 0; /* Got the entire keyblock.  */
 
-  if (!err && sigstatus && n_sigs != sigstatus[0])
-    {
-      log_error ("parse_keyblock_image: signature count does not match\n");
-      err = gpg_error (GPG_ERR_INV_KEYRING);
-    }
-
   if (err)
     release_kbnode (keyblock);
   else
-    *r_keyblock = keyblock;
-  free_packet (pkt);
+    {
+      *r_keyblock = keyblock;
+      keydb_stats.parse_keyblocks++;
+    }
+  free_packet (pkt, &parsectx);
+  deinit_parse_packet (&parsectx);
   xfree (pkt);
   return err;
 }
 
 
+/* 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)
 {
@@ -1204,7 +1361,6 @@ keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb)
          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);
@@ -1229,26 +1385,22 @@ keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb)
     case KEYDB_RESOURCE_TYPE_KEYBOX:
       {
         iobuf_t iobuf;
-        u32 *sigstatus;
         int pk_no, uid_no;
 
         err = keybox_get_keyblock (hd->active[hd->found].u.kb,
-                                   &iobuf, &pk_no, &uid_no, &sigstatus);
+                                   &iobuf, &pk_no, &uid_no);
         if (!err)
           {
-            err = parse_keyblock_image (iobuf, pk_no, uid_no, sigstatus,
-                                        ret_kb);
+            err = parse_keyblock_image (iobuf, pk_no, uid_no, ret_kb);
             if (!err && hd->keyblock_cache.state == KEYBLOCK_CACHE_PREPARED)
               {
                 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
               {
-                xfree (sigstatus);
                 iobuf_close (iobuf);
               }
           }
@@ -1259,6 +1411,9 @@ keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb)
   if (hd->keyblock_cache.state != KEYBLOCK_CACHE_FILLED)
     keyblock_cache_clear (hd);
 
+  if (!err)
+    keydb_stats.get_keyblocks++;
+
   if (DBG_CLOCK)
     log_clock (err? "keydb_get_keyblock leave (failed)"
                : "keydb_get_keyblock leave");
@@ -1267,39 +1422,18 @@ keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb)
 
 
 /* Build a keyblock image from KEYBLOCK.  Returns 0 on success and
-   only then stores a new iobuf object at R_IOBUF and a signature
-   status vecotor at R_SIGSTATUS.  */
+ * only then stores a new iobuf object at R_IOBUF.  */
 static gpg_error_t
-build_keyblock_image (kbnode_t keyblock, iobuf_t *r_iobuf, u32 **r_sigstatus)
+build_keyblock_image (kbnode_t keyblock, iobuf_t *r_iobuf)
 {
   gpg_error_t err;
   iobuf_t iobuf;
   kbnode_t kbctx, node;
-  u32 n_sigs;
-  u32 *sigstatus;
 
   *r_iobuf = NULL;
-  if (r_sigstatus)
-    *r_sigstatus = NULL;
-
-  /* Allocate a vector for the signature cache.  This is an array of
-     u32 values with the first value giving the number of elements to
-     follow and each element descriping the cache status of the
-     signature.  */
-  if (r_sigstatus)
-    {
-      for (kbctx=NULL, n_sigs=0; (node = walk_kbnode (keyblock, &kbctx, 0));)
-        if (node->pkt->pkttype == PKT_SIGNATURE)
-          n_sigs++;
-      sigstatus = xtrycalloc (1+n_sigs, sizeof *sigstatus);
-      if (!sigstatus)
-        return gpg_error_from_syserror ();
-    }
-  else
-    sigstatus = NULL;
 
   iobuf = iobuf_temp ();
-  for (kbctx = NULL, n_sigs = 0; (node = walk_kbnode (keyblock, &kbctx, 0));)
+  for (kbctx = NULL; (node = walk_kbnode (keyblock, &kbctx, 0));)
     {
       /* Make sure to use only packets valid on a keyblock.  */
       switch (node->pkt->pkttype)
@@ -1309,57 +1443,52 @@ build_keyblock_image (kbnode_t keyblock, iobuf_t *r_iobuf, u32 **r_sigstatus)
         case PKT_SIGNATURE:
         case PKT_USER_ID:
         case PKT_ATTRIBUTE:
-          /* Note that we don't want the ring trust packets.  They are
-             not useful. */
+        case PKT_RING_TRUST:
           break;
         default:
           continue;
         }
 
-      err = build_packet (iobuf, node->pkt);
+      err = build_packet_and_meta (iobuf, node->pkt);
       if (err)
         {
           iobuf_close (iobuf);
           return err;
         }
-
-      /* Build signature status vector.  */
-      if (node->pkt->pkttype == PKT_SIGNATURE)
-        {
-          PKT_signature *sig = node->pkt->pkt.signature;
-
-          n_sigs++;
-          /* Fixme: Detect the "missing key" status.  */
-          if (sig->flags.checked && sigstatus)
-            {
-              if (sig->flags.valid)
-                {
-                  if (!sig->expiredate)
-                    sigstatus[n_sigs] = 0xffffffff;
-                  else if (sig->expiredate < 0x1000000)
-                    sigstatus[n_sigs] = 0x10000000;
-                  else
-                    sigstatus[n_sigs] = sig->expiredate;
-                }
-              else
-                sigstatus[n_sigs] = 0x00000002; /* Bad signature.  */
-            }
-        }
     }
-  if (sigstatus)
-    sigstatus[0] = n_sigs;
 
+  keydb_stats.build_keyblocks++;
   *r_iobuf = iobuf;
-  if (r_sigstatus)
-    *r_sigstatus = sigstatus;
   return 0;
 }
 
 
+/* 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)
+keydb_update_keyblock (ctrl_t ctrl, 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);
@@ -1367,9 +1496,6 @@ keydb_update_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
   kid_not_found_flush ();
   keyblock_cache_clear (hd);
 
-  if (hd->found < 0 || hd->found >= hd->used)
-    return gpg_error (GPG_ERR_VALUE_NOT_FOUND);
-
   if (opt.dry_run)
     return 0;
 
@@ -1377,6 +1503,23 @@ keydb_update_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
   if (err)
     return err;
 
+#ifdef USE_TOFU
+  tofu_notice_key_changed (ctrl, kb);
+#endif
+
+  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:
@@ -1389,7 +1532,7 @@ keydb_update_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
       {
         iobuf_t iobuf;
 
-        err = build_keyblock_image (kb, &iobuf, NULL);
+        err = build_keyblock_image (kb, &iobuf);
         if (!err)
           {
             err = keybox_update_keyblock (hd->active[hd->found].u.kb,
@@ -1402,10 +1545,22 @@ keydb_update_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
     }
 
   unlock_all (hd);
+  if (!err)
+    keydb_stats.update_keyblocks++;
   return err;
 }
 
 
+/* 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)
 {
@@ -1446,16 +1601,13 @@ keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
            included in the keybox code.  Eventually we can change this
            kludge to have the caller pass the image.  */
         iobuf_t iobuf;
-        u32 *sigstatus;
 
-        err = build_keyblock_image (kb, &iobuf, &sigstatus);
+        err = build_keyblock_image (kb, &iobuf);
         if (!err)
           {
             err = keybox_insert_keyblock (hd->active[idx].u.kb,
                                           iobuf_get_temp_buffer (iobuf),
-                                          iobuf_get_temp_length (iobuf),
-                                          sigstatus);
-            xfree (sigstatus);
+                                          iobuf_get_temp_length (iobuf));
             iobuf_close (iobuf);
           }
       }
@@ -1463,10 +1615,17 @@ keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
     }
 
   unlock_all (hd);
+  if (!err)
+    keydb_stats.insert_keyblocks++;
   return err;
 }
 
 
+/* 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)
 {
@@ -1502,11 +1661,22 @@ keydb_delete_keyblock (KEYDB_HANDLE hd)
     }
 
   unlock_all (hd);
+  if (!rc)
+    keydb_stats.delete_keyblocks++;
   return rc;
 }
 
 
 \f
+/* 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)
 {
@@ -1520,11 +1690,11 @@ keydb_locate_writable (KEYDB_HANDLE hd)
     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;
@@ -1559,8 +1729,10 @@ keydb_locate_writable (KEYDB_HANDLE hd)
   return gpg_error (GPG_ERR_NOT_FOUND);
 }
 
+
+/* Rebuild the on-disk caches of all key resources.  */
 void
-keydb_rebuild_caches (int noisy)
+keydb_rebuild_caches (ctrl_t ctrl, int noisy)
 {
   int i, rc;
 
@@ -1573,7 +1745,7 @@ keydb_rebuild_caches (int noisy)
         case KEYDB_RESOURCE_TYPE_NONE: /* ignore */
           break;
         case KEYDB_RESOURCE_TYPE_KEYRING:
-          rc = keyring_rebuild_cache (all_resources[i].token,noisy);
+          rc = keyring_rebuild_cache (ctrl, all_resources[i].token,noisy);
           if (rc)
             log_error (_("failed to rebuild keyring cache: %s\n"),
                        gpg_strerror (rc));
@@ -1586,6 +1758,8 @@ keydb_rebuild_caches (int noisy)
 }
 
 
+/* 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)
 {
@@ -1593,6 +1767,12 @@ keydb_get_skipped_counter (KEYDB_HANDLE hd)
 }
 
 
+/* 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)
 {
@@ -1629,10 +1809,30 @@ keydb_search_reset (KEYDB_HANDLE hd)
         }
     }
   hd->is_reset = 1;
+  if (!rc)
+    keydb_stats.search_resets++;
   return rc;
 }
 
 
+/* 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)
@@ -1649,6 +1849,12 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
   if (!hd)
     return gpg_error (GPG_ERR_INV_ARG);
 
+  if (!any_registered)
+    {
+      write_status_error ("keydb_search", gpg_error (GPG_ERR_KEYRING_OPEN));
+      return gpg_error (GPG_ERR_NOT_FOUND);
+    }
+
   if (DBG_CLOCK)
     log_clock ("keydb_search enter");
 
@@ -1669,6 +1875,7 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
     {
       if (DBG_CLOCK)
         log_clock ("keydb_search leave (not found, cached)");
+      keydb_stats.notfound_cached++;
       return gpg_error (GPG_ERR_NOT_FOUND);
     }
 
@@ -1680,11 +1887,24 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
       && (desc[0].mode == KEYDB_SEARCH_MODE_FPR20
           || desc[0].mode == KEYDB_SEARCH_MODE_FPR)
       && hd->keyblock_cache.state  == KEYBLOCK_CACHE_FILLED
-      && !memcmp (hd->keyblock_cache.fpr, desc[0].u.fpr, 20))
+      && !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);
+      keydb_stats.found_cached++;
       return 0;
     }
 
@@ -1751,6 +1971,12 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
       && hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX)
     {
       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);
     }
 
@@ -1762,10 +1988,19 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
   if (DBG_CLOCK)
     log_clock (rc? "keydb_search leave (not found)"
                  : "keydb_search leave (found)");
+  if (!rc)
+    keydb_stats.found++;
+  else
+    keydb_stats.notfound++;
   return rc;
 }
 
 
+/* 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)
 {
@@ -1782,6 +2017,10 @@ keydb_search_first (KEYDB_HANDLE hd)
 }
 
 
+/* 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)
 {
@@ -1792,6 +2031,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)
 {
@@ -1804,6 +2050,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)
 {