gpg: Make sure we only have a single SQL statement.
[gnupg.git] / g10 / tofu.c
index 39377cb..43a6224 100644 (file)
@@ -27,6 +27,8 @@
 #include <stdio.h>
 #include <sys/stat.h>
 #include <assert.h>
+#include <stdarg.h>
+#include <sched.h>
 #include <sqlite3.h>
 
 #include "gpg.h"
 
 #include "tofu.h"
 
+#define DEBUG_TOFU_CACHE 0
+#if DEBUG_TOFU_CACHE
+static int prepares_saved;
+static int queries;
+#endif
+
 /* The TOFU data can be saved in two different formats: either in a
    single combined database (opt.tofu_db_format == TOFU_DB_FLAT) or in
    a split file format (opt.tofu_db_format == TOFU_DB_SPLIT).  In the
@@ -71,17 +79,96 @@ enum db_type
 struct db
 {
   struct db *next;
+  struct db **prevp;
 
   enum db_type type;
 
   sqlite3 *db;
 
+  struct
+  {
+    sqlite3_stmt *savepoint_batch;
+    sqlite3_stmt *savepoint_batch_commit;
+
+    sqlite3_stmt *savepoint_inner;
+    sqlite3_stmt *savepoint_inner_commit;
+
+    sqlite3_stmt *record_binding_get_old_policy;
+    sqlite3_stmt *record_binding_update;
+    sqlite3_stmt *record_binding_update2;
+    sqlite3_stmt *get_policy_select_policy_and_conflict;
+    sqlite3_stmt *get_trust_bindings_with_this_email;
+    sqlite3_stmt *get_trust_gather_other_user_ids;
+    sqlite3_stmt *get_trust_gather_other_keys;
+    sqlite3_stmt *register_already_seen;
+    sqlite3_stmt *register_insert;
+  } s;
+
+#if DEBUG_TOFU_CACHE
+  int hits;
+#endif
+
+  int batch_update;
+
   /* If TYPE is DB_COMBINED, this is "".  Otherwise, it is either the
      fingerprint (type == DB_KEY) or the normalized email address
      (type == DB_EMAIL).  */
   char name[1];
 };
 
+static struct db *db_cache;
+static int db_cache_count;
+#define DB_CACHE_ENTRIES 16
+
+static void tofu_cache_dump (struct db *db) GPGRT_ATTR_USED;
+
+static void
+tofu_cache_dump (struct db *db)
+{
+  log_info ("Connection %p:\n", db);
+  for (; db; db = db->next)
+    log_info ("  %s: %sbatch mode\n", db->name, db->batch_update ? "" : "NOT ");
+  log_info ("Cache:\n");
+  for (db = db_cache; db; db = db->next)
+    log_info ("  %s: %sbatch mode\n", db->name, db->batch_update ? "" : "NOT ");
+}
+
+#define STRINGIFY(s) STRINGIFY2(s)
+#define STRINGIFY2(s) #s
+
+/* The grouping parameters when collecting signature statistics.  */
+
+/* If a message is signed a couple of hours in the future, just assume
+   some clock skew.  */
+#define TIME_AGO_FUTURE_IGNORE (2 * 60 * 60)
+#if 0
+#  define TIME_AGO_UNIT_SMALL 60
+#  define TIME_AGO_UNIT_SMALL_NAME _("minute")
+#  define TIME_AGO_UNIT_SMALL_NAME_PLURAL _("minutes")
+#  define TIME_AGO_MEDIUM_THRESHOLD (60 * TIME_AGO_UNIT_SMALL)
+#  define TIME_AGO_UNIT_MEDIUM (60 * 60)
+#  define TIME_AGO_UNIT_MEDIUM_NAME _("hour")
+#  define TIME_AGO_UNIT_MEDIUM_NAME_PLURAL _("hours")
+#  define TIME_AGO_LARGE_THRESHOLD (24 * 60 * TIME_AGO_UNIT_SMALL)
+#  define TIME_AGO_UNIT_LARGE (24 * 60 * 60)
+#  define TIME_AGO_UNIT_LARGE_NAME _("day")
+#  define TIME_AGO_UNIT_LARGE_NAME_PLURAL _("days")
+#else
+#  define TIME_AGO_UNIT_SMALL (24 * 60 * 60)
+#  define TIME_AGO_UNIT_SMALL_NAME _("day")
+#  define TIME_AGO_UNIT_SMALL_NAME_PLURAL _("days")
+#  define TIME_AGO_MEDIUM_THRESHOLD (4 * TIME_AGO_UNIT_SMALL)
+#  define TIME_AGO_UNIT_MEDIUM (7 * 24 * 60 * 60)
+#  define TIME_AGO_UNIT_MEDIUM_NAME _("week")
+#  define TIME_AGO_UNIT_MEDIUM_NAME_PLURAL _("weeks")
+#  define TIME_AGO_LARGE_THRESHOLD (28 * TIME_AGO_UNIT_SMALL)
+#  define TIME_AGO_UNIT_LARGE (30 * 24 * 60 * 60)
+#  define TIME_AGO_UNIT_LARGE_NAME _("month")
+#  define TIME_AGO_UNIT_LARGE_NAME_PLURAL _("months")
+#endif
+
+
+
 const char *
 tofu_policy_str (enum tofu_policy policy)
 {
@@ -154,7 +241,384 @@ sqlite3_exec_printf (sqlite3 *db,
   return rc;
 }
 
+enum sqlite_arg_type
+  {
+    SQLITE_ARG_END = 0xdead001,
+    SQLITE_ARG_INT,
+    SQLITE_ARG_LONG_LONG,
+    SQLITE_ARG_STRING
+  };
+
+static int
+sqlite3_stepx (sqlite3 *db,
+               sqlite3_stmt **stmtp,
+               int (*callback) (void*,int,char**,char**),
+               void *cookie,
+               char **errmsg,
+               const char *sql, ...)
+{
+  int rc;
+  int err = 0;
+  sqlite3_stmt *stmt = NULL;
+
+  va_list va;
+  int args;
+  enum sqlite_arg_type t;
+  int i;
+
+  int cols;
+  /* Names of the columns.  We initialize this lazily to avoid the
+     overhead in case the query doesn't return any results.  */
+  const char **azColName = 0;
+  int callback_initialized = 0;
+
+  const char **azVals = 0;
+
+  callback_initialized = 0;
+
+  if (stmtp && *stmtp)
+    {
+      stmt = *stmtp;
+
+      /* Make sure this statement is associated with the supplied db.  */
+      assert (db == sqlite3_db_handle (stmt));
+
+#if DEBUG_TOFU_CACHE
+      prepares_saved ++;
+#endif
+    }
+  else
+    {
+      const char *tail = NULL;
+
+      rc = sqlite3_prepare_v2 (db, sql, -1, &stmt, &tail);
+      if (rc)
+        log_fatal ("failed to prepare SQL: %s", sql);
+
+      /* We can only process a single statement.  */
+      if (tail)
+        {
+          while (*tail == ' ' || *tail == ';')
+            tail ++;
+
+          if (*tail)
+            log_fatal
+              ("sqlite3_stepx can only process a single SQL statement."
+               "  Second statement starts with: '%s'\n",
+               tail);
+        }
+
+      if (stmtp)
+        *stmtp = stmt;
+    }
+
+#if DEBUG_TOFU_CACHE
+  queries ++;
+#endif
+
+  args = sqlite3_bind_parameter_count (stmt);
+  va_start (va, sql);
+  if (args)
+    {
+      for (i = 1; i <= args; i ++)
+        {
+          t = va_arg (va, enum sqlite_arg_type);
+          switch (t)
+            {
+            case SQLITE_ARG_INT:
+              {
+                int value = va_arg (va, int);
+                err = sqlite3_bind_int (stmt, i, value);
+                break;
+              }
+            case SQLITE_ARG_LONG_LONG:
+              {
+                long long value = va_arg (va, long long);
+                err = sqlite3_bind_int64 (stmt, i, value);
+                break;
+              }
+            case SQLITE_ARG_STRING:
+              {
+                char *text = va_arg (va, char *);
+                err = sqlite3_bind_text (stmt, i, text, -1, SQLITE_STATIC);
+                break;
+              }
+            default:
+              /* Internal error.  Likely corruption.  */
+              log_fatal ("Bad value for parameter type %d.\n", t);
+            }
+
+          if (err)
+            {
+              log_fatal ("Error binding parameter %d\n", i);
+              goto out;
+            }
+        }
+
+    }
+  t = va_arg (va, enum sqlite_arg_type);
+  assert (t == SQLITE_ARG_END);
+  va_end (va);
+
+  for (;;)
+    {
+      rc = sqlite3_step (stmt);
+
+      if (rc != SQLITE_ROW)
+        /* No more data (SQLITE_DONE) or an error occured.  */
+        break;
+
+      if (! callback)
+        continue;
+
+      if (! callback_initialized)
+        {
+          cols = sqlite3_column_count (stmt);
+          azColName = xmalloc (2 * cols * sizeof (const char *) + 1);
+
+          for (i = 0; i < cols; i ++)
+            azColName[i] = sqlite3_column_name (stmt, i);
+
+          callback_initialized = 1;
+        }
+
+      azVals = &azColName[cols];
+      for (i = 0; i < cols; i ++)
+        {
+          azVals[i] = sqlite3_column_text (stmt, i);
+          if (! azVals[i] && sqlite3_column_type (stmt, i) != SQLITE_NULL)
+            /* Out of memory.  */
+            {
+              err = SQLITE_NOMEM;
+              break;
+            }
+        }
+
+      if (callback (cookie, cols, (char **) azVals, (char **) azColName))
+        /* A non-zero result means to abort.  */
+        {
+          err = SQLITE_ABORT;
+          break;
+        }
+    }
+
+ out:
+  xfree (azColName);
+
+  if (stmtp)
+    rc = sqlite3_reset (stmt);
+  else
+    rc = sqlite3_finalize (stmt);
+  if (rc == SQLITE_OK && err)
+    /* Local error.  */
+    {
+      rc = err;
+      if (errmsg)
+        {
+          const char *e = sqlite3_errstr (err);
+          size_t l = strlen (e) + 1;
+          *errmsg = sqlite3_malloc (l);
+          if (! *errmsg)
+            log_fatal ("Out of memory.\n");
+          memcpy (*errmsg, e, l);
+        }
+    }
+  else if (rc != SQLITE_OK && errmsg)
+    /* Error reported by sqlite.  */
+    {
+      const char * e = sqlite3_errmsg (db);
+      size_t l = strlen (e) + 1;
+      *errmsg = sqlite3_malloc (l);
+      if (! *errmsg)
+        log_fatal ("Out of memory.\n");
+      memcpy (*errmsg, e, l);
+    }
+
+  return rc;
+}
+\f
+static int batch_update;
+static time_t batch_update_started;
+
+static gpg_error_t end_transaction (struct db *db, int only_batch);
+
+/* Start a transaction on DB.  */
+static gpg_error_t
+begin_transaction (struct db *db, int only_batch)
+{
+  int rc;
+  char *err = NULL;
+
+  if (batch_update && batch_update_started != gnupg_get_time ())
+    /* We've been in batch update mode for a while (on average, more
+       than 500 ms).  To prevent starving other gpg processes, we drop
+       and retake the batch lock.
+
+       Note: if we wanted higher resolution, we could use
+       npth_clock_gettime.  */
+    {
+      struct db *t;
+
+      for (t = db_cache; t; t = t->next)
+        if (t->batch_update)
+          end_transaction (t, 1);
+      for (t = db; t; t = t->next)
+        if (t->batch_update)
+          end_transaction (t, 1);
+
+      batch_update_started = gnupg_get_time ();
 
+      /* Yield to allow another process a chance to run.  */
+      sched_yield ();
+    }
+
+  /* XXX: In split mode, this can end in deadlock.
+
+     Consider: we have two gpg processes running simultaneously and
+     they each want to lock DB A and B, but in different orders.  This
+     will be automatically resolved by causing one of them to return
+     EBUSY and aborting.
+
+     A more intelligent approach would be to commit and retake the
+     batch transaction.  This requires a list of all DBs that are
+     currently in batch mode.  */
+
+  if (batch_update && ! db->batch_update)
+    {
+      rc = sqlite3_stepx (db->db, &db->s.savepoint_batch,
+                          NULL, NULL, &err,
+                          "savepoint batch;", SQLITE_ARG_END);
+      if (rc)
+        {
+          log_error
+            (_("error beginning %s transaction on TOFU database '%s': %s\n"),
+             "batch", *db->name ? db->name : "combined", err);
+          sqlite3_free (err);
+          return gpg_error (GPG_ERR_GENERAL);
+        }
+
+      db->batch_update = 1;
+    }
+
+  if (only_batch)
+    return 0;
+
+  rc = sqlite3_stepx (db->db, &db->s.savepoint_inner,
+                      NULL, NULL, &err,
+                      "savepoint inner;", SQLITE_ARG_END);
+  if (rc)
+    {
+      log_error
+        (_("error beginning %s transaction on TOFU database '%s': %s\n"),
+         "inner", *db->name ? db->name : "combined", err);
+      sqlite3_free (err);
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  return 0;
+}
+
+/* Commit a transaction.  If ONLY_BATCH is 1, then this only ends the
+   batch transaction if we have left batch mode.  If ONLY_BATCH is 2,
+   this ends any open batch transaction even if we are still in batch
+   mode.  */
+static gpg_error_t
+end_transaction (struct db *db, int only_batch)
+{
+  int rc;
+  char *err = NULL;
+
+  if ((! batch_update || only_batch == 2) && db->batch_update)
+    /* The batch transaction is still in open, but we left batch
+       mode.  */
+    {
+      db->batch_update = 0;
+
+      rc = sqlite3_stepx (db->db, &db->s.savepoint_batch_commit,
+                          NULL, NULL, &err,
+                          "release batch;", SQLITE_ARG_END);
+      if (rc)
+        {
+          log_error
+            (_("error committing %s transaction on TOFU database '%s': %s\n"),
+             "batch", *db->name ? db->name : "combined", err);
+          sqlite3_free (err);
+          return gpg_error (GPG_ERR_GENERAL);
+        }
+
+      /* Releasing an outer transaction releases an open inner
+         transactions.  We're done.  */
+      return 0;
+    }
+
+  if (only_batch)
+    return 0;
+
+  rc = sqlite3_stepx (db->db, &db->s.savepoint_inner_commit,
+                      NULL, NULL, &err,
+                      "release inner;", SQLITE_ARG_END);
+  if (rc)
+    {
+      log_error
+        (_("error committing %s transaction on TOFU database '%s': %s\n"),
+         "inner", *db->name ? db->name : "combined", err);
+      sqlite3_free (err);
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  return 0;
+}
+
+static gpg_error_t
+rollback_transaction (struct db *db)
+{
+  int rc;
+  char *err = NULL;
+
+  if (db->batch_update)
+    /* Just undo the most recent update; don't revert any progress
+       made by the batch transaction.  */
+    rc = sqlite3_exec (db->db, "rollback to inner;", NULL, NULL, &err);
+  else
+    /* Rollback the whole she-bang.  */
+    rc = sqlite3_exec (db->db, "rollback;", NULL, NULL, &err);
+
+  if (rc)
+    {
+      log_error
+        (_("error rolling back inner transaction on TOFU database '%s': %s\n"),
+         *db->name ? db->name : "combined", err);
+      sqlite3_free (err);
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  return 0;
+}
+
+void
+tofu_begin_batch_update (void)
+{
+  if (! batch_update)
+    batch_update_started = gnupg_get_time ();
+
+  batch_update ++;
+}
+
+void
+tofu_end_batch_update (void)
+{
+  assert (batch_update > 0);
+  batch_update --;
+
+  if (batch_update == 0)
+    {
+      struct db *db;
+
+      for (db = db_cache; db; db = db->next)
+        end_transaction (db, 1);
+    }
+}
+\f
 /* Collect results of a select count (*) ...; style query.  Aborts if
    the argument is not a valid integer (or real of the form X.0).  */
 static int
@@ -215,6 +679,15 @@ initdb (sqlite3 *db, enum db_type type)
   unsigned long int count;
   int version = -1;
 
+  rc = sqlite3_exec (db, "begin transaction;", NULL, NULL, &err);
+  if (rc)
+    {
+      log_error (_("error beginning transaction on TOFU database: %s\n"),
+                err);
+      sqlite3_free (err);
+      return 1;
+    }
+
   /* If the DB has no tables, then assume this is a new DB that needs
      to be initialized.  */
   rc = sqlite3_exec (db,
@@ -225,7 +698,7 @@ initdb (sqlite3 *db, enum db_type type)
       log_error (_("error querying TOFU DB's available tables: %s\n"),
                 err);
       sqlite3_free (err);
-      return 1;
+      goto out;
     }
   else if (count != 0)
     /* Assume that the DB is already initialized.  Make sure the
@@ -237,21 +710,22 @@ initdb (sqlite3 *db, enum db_type type)
        /* Happy, happy, joy, joy.  */
        {
          sqlite3_free (err);
-         return 0;
+          rc = 0;
+          goto out;
        }
       else if (rc == SQLITE_ABORT && version == -1)
        /* Unsupported version.  */
        {
          /* An error message was already displayed.  */
          sqlite3_free (err);
-         return 1;
+          goto out;
        }
       else if (rc)
        /* Some error.  */
        {
          log_error (_("error determining TOFU DB's version: %s\n"), err);
          sqlite3_free (err);
-         return 1;
+          goto out;
        }
       else
        /* Unexpected success.  This can only happen if there are no
@@ -259,19 +733,11 @@ initdb (sqlite3 *db, enum db_type type)
        {
          log_error (_("error determining TOFU DB's version: %s\n"),
                     "select returned 0, but expected ABORT");
-         return 1;
+          rc = 1;
+          goto out;
        }
     }
 
-  rc = sqlite3_exec (db, "begin transaction;", NULL, NULL, &err);
-  if (rc)
-    {
-      log_error (_("error beginning transaction on TOFU database: %s\n"),
-                err);
-      sqlite3_free (err);
-      return 1;
-    }
-
   /* Create the version table.  */
   rc = sqlite3_exec (db,
                     "create table version (version INTEGER);",
@@ -404,7 +870,7 @@ initdb (sqlite3 *db, enum db_type type)
     }
   else
     {
-      rc = sqlite3_exec (db, "commit transaction;", NULL, NULL, &err);
+      rc = sqlite3_exec (db, "end transaction;", NULL, NULL, &err);
       if (rc)
        {
          log_error (_("error committing transaction on TOFU DB: %s\n"),
@@ -416,8 +882,6 @@ initdb (sqlite3 *db, enum db_type type)
     }
 }
 
-static sqlite3 *combined_db;
-
 /* Open and initialize a low-level TOFU database.  Returns NULL on
    failure.  This function should not normally be directly called to
    get a database handle.  Instead, use getdb().  */
@@ -433,9 +897,6 @@ opendb (char *filename, enum db_type type)
       assert (! filename);
       assert (type == DB_COMBINED);
 
-      if (combined_db)
-       return combined_db;
-
       filename = make_filename (opt.homedir, "tofu.db", NULL);
       filename_free = 1;
     }
@@ -454,6 +915,10 @@ opendb (char *filename, enum db_type type)
       db = NULL;
     }
 
+  /* If a DB is locked wait up to 5 seconds for the lock to be cleared
+     before failing.  */
+  sqlite3_busy_timeout (db, 5 * 1000);
+
   if (filename_free)
     xfree (filename);
 
@@ -463,12 +928,32 @@ opendb (char *filename, enum db_type type)
       db = NULL;
     }
 
-  if (opt.tofu_db_format == TOFU_DB_FLAT)
-    combined_db = db;
-
   return db;
 }
 
+struct dbs
+{
+  struct db *db;
+};
+
+static void
+unlink_db (struct db *db)
+{
+  *db->prevp = db->next;
+  if (db->next)
+    db->next->prevp = db->prevp;
+}
+
+static void
+link_db (struct db **head, struct db *db)
+{
+  db->next = *head;
+  if (db->next)
+    db->next->prevp = &db->next;
+  db->prevp = head;
+  *head = db;
+}
+
 /* Return a database handle.  <type, name> describes the required
    database.  If there is a cached handle in DBS, that handle is
    returned.  Otherwise, the database is opened and cached in DBS.
@@ -477,109 +962,169 @@ opendb (char *filename, enum db_type type)
 
    TYPE must be either DB_MAIL or DB_KEY.  In the combined format, the
    combined DB is always returned.  */
-static sqlite3 *
-getdb (struct db *dbs, const char *name, enum db_type type)
+static struct db *
+getdb (struct dbs *dbs, const char *name, enum db_type type)
 {
   struct db *t = NULL;
-  sqlite3 *sqlitedb = NULL;
   char *name_sanitized = NULL;
+  int count;
   char *filename = NULL;
-  int i;
+  int need_link = 1;
+  sqlite3 *sqlitedb = NULL;
 
+  assert (dbs);
   assert (name);
   assert (type == DB_EMAIL || type == DB_KEY);
 
-  assert (dbs);
-  /* The first entry is always for the combined DB.  */
-  assert (dbs->type == DB_COMBINED);
-  assert (! dbs->name[0]);
-
   if (opt.tofu_db_format == TOFU_DB_FLAT)
-    /* When using the flat format, we only have a single combined
-       DB.  */
+    /* When using the flat format, we only have a single DB, the
+       combined DB.  */
     {
-      assert (dbs->db);
-      assert (! dbs->next);
-      return dbs->db;
-    }
-  else
-    /* When using the split format the first entry on the DB list is a
-       dummy entry.  */
-    assert (! dbs->db);
+      if (dbs->db)
+        {
+          assert (dbs->db->type == DB_COMBINED);
+          assert (! dbs->db->next);
+          return dbs->db;
+        }
 
-  /* We have the split format.  */
+      type = DB_COMBINED;
+    }
 
-  /* Only allow alpha-numeric characters in the filename.  */
-  name_sanitized = xstrdup (name);
-  for (i = 0; name[i]; i ++)
+  if (type != DB_COMBINED)
+    /* Only allow alpha-numeric characters in the name.  */
     {
-      char c = name_sanitized[i];
-      if (! (('a' <= c && c <= 'z')
-            || ('A' <= c && c <= 'Z')
-            || ('0' <= c && c <= '9')))
-       name_sanitized[i] = '_';
+      int i;
+
+      name_sanitized = xstrdup (name);
+      for (i = 0; name[i]; i ++)
+        {
+          char c = name_sanitized[i];
+          if (! (('a' <= c && c <= 'z')
+                 || ('A' <= c && c <= 'Z')
+                 || ('0' <= c && c <= '9')))
+            name_sanitized[i] = '_';
+        }
     }
 
   /* See if the DB is cached.  */
-  for (t = dbs->next; t; t = t->next)
-    if (type == t->type && strcmp (t->name, name_sanitized) == 0)
-      goto out;
+  for (t = dbs->db; t; t = t->next)
+    if (t->type == type
+        && (type == DB_COMBINED || strcmp (t->name, name_sanitized) == 0))
+      {
+        need_link = 0;
+        goto out;
+      }
+
+  for (t = db_cache, count = 0; t; t = t->next, count ++)
+    if (type == t->type
+        && (type == DB_COMBINED || strcmp (t->name, name_sanitized) == 0))
+      {
+        unlink_db (t);
+        db_cache_count --;
+        goto out;
+      }
 
-  /* Open the DB.  The filename has the form:
+  assert (db_cache_count == count);
 
-       tofu.d/TYPE/PREFIX/NAME.db
+  if (type == DB_COMBINED)
+    filename = NULL;
+  else
+    {
+      /* Open the DB.  The filename has the form:
 
-     We use a short prefix to try to avoid having many files in a
-     single directory.  */
-  {
-    char *type_str = type == DB_EMAIL ? "email" : "key";
-    char prefix[3] = { name_sanitized[0], name_sanitized[1], 0 };
-    char *name_db;
+         tofu.d/TYPE/PREFIX/NAME.db
 
-    /* Make the directory.  */
-    if (gnupg_mkdir_p (opt.homedir, "tofu.d", type_str, prefix, NULL) != 0)
+         We use a short prefix to try to avoid having many files in a
+         single directory.  */
       {
-       log_error (_("unable to create directory %s/%s/%s/%s"),
-                  opt.homedir, "tofu.d", type_str, prefix);
-       g10_exit (1);
+        char *type_str = type == DB_EMAIL ? "email" : "key";
+        char prefix[3] = { name_sanitized[0], name_sanitized[1], 0 };
+        char *name_db;
+
+        /* Make the directory.  */
+        if (gnupg_mkdir_p (opt.homedir, "tofu.d", type_str, prefix, NULL) != 0)
+          {
+            log_error (_("unable to create directory %s/%s/%s/%s"),
+                       opt.homedir, "tofu.d", type_str, prefix);
+            goto out;
+          }
+
+        name_db = xstrconcat (name_sanitized, ".db", NULL);
+        filename = make_filename
+          (opt.homedir, "tofu.d", type_str, prefix, name_db, NULL);
+        xfree (name_db);
       }
-
-    name_db = xstrconcat (name_sanitized, ".db", NULL);
-    filename = make_filename
-      (opt.homedir, "tofu.d", type_str, prefix, name_db, NULL);
-    xfree (name_db);
-  }
+    }
 
   sqlitedb = opendb (filename, type);
   if (! sqlitedb)
     goto out;
 
-  t = xmalloc (sizeof (struct db) + strlen (name_sanitized));
+  t = xmalloc_clear (sizeof (struct db)
+                     + (name_sanitized ? strlen (name_sanitized) : 0));
   t->type = type;
   t->db = sqlitedb;
-  strcpy (t->name, name_sanitized);
-
-  /* Insert it immediately after the first element.  */
-  t->next = dbs->next;
-  dbs->next = t;
+  if (name_sanitized)
+    strcpy (t->name, name_sanitized);
 
  out:
+  if (t && need_link)
+    link_db (&dbs->db, t);
+
+#if DEBUG_TOFU_CACHE
+  if (t)
+    t->hits ++;
+#endif
+
   xfree (filename);
   xfree (name_sanitized);
+  return t;
+}
+
+static void
+closedb (struct db *db)
+{
+  sqlite3_stmt **statements;
+
+  if (opt.tofu_db_format == TOFU_DB_FLAT)
+    /* If we are using the flat format, then there is only ever the
+       combined DB.  */
+    assert (! db->next);
+
+  if (db->type == DB_COMBINED)
+    {
+      assert (opt.tofu_db_format == TOFU_DB_FLAT);
+      assert (! db->name[0]);
+    }
+  else
+    {
+      assert (opt.tofu_db_format == TOFU_DB_SPLIT);
+      assert (db->type != DB_COMBINED);
+      assert (db->name[0]);
+    }
+
+  if (db->batch_update)
+    end_transaction (db, 2);
+
+  for (statements = (void *) &db->s;
+       (void *) statements < (void *) &(&db->s)[1];
+       statements ++)
+    sqlite3_finalize (*statements);
+
+  sqlite3_close (db->db);
+
+#if DEBUG_TOFU_CACHE
+  log_debug ("Freeing db.  Used %d times.\n", db->hits);
+#endif
 
-  if (! t)
-    return NULL;
-  return t->db;
+  xfree (db);
 }
 
 
 /* Create a new DB meta-handle.  Returns NULL on error.  */
-static struct db *
+static struct dbs *
 opendbs (void)
 {
-  sqlite3 *db = NULL;
-  struct db *dbs;
-
   if (opt.tofu_db_format == TOFU_DB_AUTO)
     {
       char *filename = make_filename (opt.homedir, "tofu.db", NULL);
@@ -640,87 +1185,70 @@ opendbs (void)
        }
     }
 
-  if (opt.tofu_db_format == TOFU_DB_FLAT)
-    {
-      db = opendb (NULL, DB_COMBINED);
-      if (! db)
-       return NULL;
-    }
-  else
-    /* Create a dummy entry so that we have a handle.  */
-    ;
-
-  dbs = xmalloc_clear (sizeof (*dbs));
-  dbs->db = db;
-  dbs->type = DB_COMBINED;
-
-  return dbs;
+  return xmalloc_clear (sizeof (struct dbs));
 }
 
 /* Release all of the resources associated with a DB meta-handle.  */
 static void
-closedbs (struct db *dbs)
+closedbs (struct dbs *dbs)
 {
-  struct db *db;
-  struct db *n;
-
-  /* The first entry is always the combined DB.  */
-  assert (dbs->type == DB_COMBINED);
-  if (opt.tofu_db_format == TOFU_DB_FLAT)
-    {
-      /* If we are using the flat format, then there is only ever the
-        combined DB.  */
-      assert (! dbs->next);
-      assert (dbs->db);
-      assert (dbs->db == combined_db);
-    }
-  else
-    /* In the split format, the combined record is just a place holder
-       so that we have a stable handle.  */
-    assert (! dbs->db);
-
-  for (db = dbs; db; db = n)
-    {
-      n = db->next;
-
-      if (combined_db && db->db == combined_db)
-       {
-         assert (opt.tofu_db_format == TOFU_DB_FLAT);
-         assert (dbs == db);
-         assert (db->type == DB_COMBINED);
-         assert (! db->name[0]);
-       }
-      else if (db->db)
-       /* Not the dummy entry.  */
-       {
-         if (dbs == db)
-           /* The first entry.  */
-           {
-             assert (opt.tofu_db_format == TOFU_DB_FLAT);
-             assert (db->type == DB_COMBINED);
-             assert (! db->name[0]);
-           }
-         else
-           /* Not the first entry.  */
-           {
-             assert (opt.tofu_db_format == TOFU_DB_SPLIT);
-             assert (db->type != DB_COMBINED);
-             assert (db->name[0]);
-           }
-
-         sqlite3_close (db->db);
-       }
-      else
-       /* The dummy entry.  */
-       {
-         assert (opt.tofu_db_format == TOFU_DB_SPLIT);
-         assert (dbs == db);
-         assert (db->type == DB_COMBINED);
-         assert (! db->name[0]);
-       }
-
-      xfree (db);
-    }
+  if (dbs->db)
+    {
+      struct db *old_head = db_cache;
+      struct db *db;
+      int count;
+
+      /* Find the last DB.  */
+      for (db = dbs->db, count = 1; db->next; db = db->next, count ++)
+        {
+          /* When we leave batch mode we leave batch mode on any
+             cached connections.  */
+          if (! batch_update)
+            assert (! db->batch_update);
+        }
+      if (! batch_update)
+        assert (! db->batch_update);
+
+      /* Join the two lists.  */
+      db->next = db_cache;
+      if (db_cache)
+        db_cache->prevp = &db->next;
+
+      /* Update the (new) first element.  */
+      db_cache = dbs->db;
+      dbs->db->prevp = &db_cache;
+
+      db_cache_count += count;
+
+      /* Make sure that we don't have too many DBs on DB_CACHE.  If
+         so, free some.  */
+      if (db_cache_count > DB_CACHE_ENTRIES)
+        {
+          /* We need to find the (DB_CACHE_ENTRIES + 1)th entry.  It
+             is easy to skip the first COUNT entries since we still
+             have a handle on the old head.  */
+          int skip = DB_CACHE_ENTRIES - count;
+          while (-- skip > 0)
+            old_head = old_head->next;
+
+          *old_head->prevp = NULL;
+
+          while (old_head)
+            {
+              db = old_head->next;
+              closedb (old_head);
+              old_head = db;
+              db_cache_count --;
+            }
+        }
+    }
+
+  xfree (dbs);
+
+#if DEBUG_TOFU_CACHE
+  log_debug ("Queries: %d (prepares saved: %d)\n",
+             queries, prepares_saved);
+#endif
 }
 
 
@@ -750,10 +1278,10 @@ get_single_long_cb (void *cookie, int argc, char **argv, char **azColName)
 
    If SHOW_OLD is set, the binding's old policy is displayed.  */
 static gpg_error_t
-record_binding (struct db *dbs, const char *fingerprint, const char *email,
+record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
                const char *user_id, enum tofu_policy policy, int show_old)
 {
-  sqlite3 *db_email = NULL, *db_key = NULL;
+  struct db *db_email = NULL, *db_key = NULL;
   int rc;
   char *err = NULL;
   enum tofu_policy policy_old = TOFU_POLICY_NONE;
@@ -780,34 +1308,33 @@ record_binding (struct db *dbs, const char *fingerprint, const char *email,
       if (! db_key)
        return gpg_error (GPG_ERR_GENERAL);
 
-      rc = sqlite3_exec (db_email, "begin transaction;", NULL, NULL, &err);
+      rc = begin_transaction (db_email, 0);
       if (rc)
-       {
-         log_error (_("error beginning transaction on TOFU %s database: %s\n"),
-                    "email", err);
-         sqlite3_free (err);
-         return gpg_error (GPG_ERR_GENERAL);
-       }
+        return gpg_error (GPG_ERR_GENERAL);
 
-      rc = sqlite3_exec (db_key, "begin transaction;", NULL, NULL, &err);
+      rc = begin_transaction (db_key, 0);
       if (rc)
-       {
-         log_error (_("error beginning transaction on TOFU %s database: %s\n"),
-                    "key", err);
-         sqlite3_free (err);
-         goto out_revert_one;
-       }
+        goto out_revert_one;
+    }
+  else
+    {
+      rc = begin_transaction (db_email, 1);
+      if (rc)
+        return gpg_error (GPG_ERR_GENERAL);
     }
 
+
   if (show_old)
     /* Get the old policy.  Since this is just for informational
        purposes, there is no need to start a transaction or to die if
        there is a failure.  */
     {
-      rc = sqlite3_exec_printf
-       (db_email, get_single_long_cb, &policy_old, &err,
-        "select policy from bindings where fingerprint = %Q and email = %Q",
-        fingerprint, email);
+      rc = sqlite3_stepx
+       (db_email->db, &db_email->s.record_binding_get_old_policy,
+         get_single_long_cb, &policy_old, &err,
+        "select policy from bindings where fingerprint = ? and email = ?",
+        SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
+         SQLITE_ARG_END);
       if (rc)
        {
          log_debug ("TOFU: Error reading from binding database"
@@ -835,17 +1362,20 @@ record_binding (struct db *dbs, const char *fingerprint, const char *email,
     /* Nothing to do.  */
     goto out;
 
-  rc = sqlite3_exec_printf
-    (db_email, NULL, NULL, &err,
+  rc = sqlite3_stepx
+    (db_email->db, &db_email->s.record_binding_update, NULL, NULL, &err,
      "insert or replace into bindings\n"
      " (oid, fingerprint, email, user_id, time, policy)\n"
      " values (\n"
      /* If we don't explicitly reuse the OID, then SQLite will
        reallocate a new one.  We just need to search for the OID
        based on the fingerprint and email since they are unique.  */
-     "  (select oid from bindings where fingerprint = %Q and email = %Q),\n"
-     "  %Q, %Q, %Q, strftime('%%s','now'), %d);",
-     fingerprint, email, fingerprint, email, user_id, policy);
+     "  (select oid from bindings where fingerprint = ? and email = ?),\n"
+     "  ?, ?, ?, strftime('%s','now'), ?);",
+     SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
+     SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
+     SQLITE_ARG_STRING, user_id, SQLITE_ARG_INT, (int) policy,
+     SQLITE_ARG_END);
   if (rc)
     {
       log_error (_("error updating TOFU binding database"
@@ -861,17 +1391,19 @@ record_binding (struct db *dbs, const char *fingerprint, const char *email,
     {
       assert (opt.tofu_db_format == TOFU_DB_SPLIT);
 
-      rc = sqlite3_exec_printf
-       (db_key, NULL, NULL, &err,
+      rc = sqlite3_stepx
+       (db_key->db, &db_key->s.record_binding_update2, NULL, NULL, &err,
         "insert or replace into bindings\n"
         " (oid, fingerprint, email, user_id)\n"
         " values (\n"
         /* If we don't explicitly reuse the OID, then SQLite will
            reallocate a new one.  We just need to search for the OID
            based on the fingerprint and email since they are unique.  */
-        "  (select oid from bindings where fingerprint = %Q and email = %Q),\n"
-        "  %Q, %Q, %Q);",
-        fingerprint, email, fingerprint, email, user_id);
+        "  (select oid from bindings where fingerprint = ? and email = ?),\n"
+        "  ?, ?, ?);",
+        SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
+         SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
+         SQLITE_ARG_STRING, user_id, SQLITE_ARG_END);
       if (rc)
        {
          log_error (_("error updating TOFU binding database"
@@ -890,8 +1422,10 @@ record_binding (struct db *dbs, const char *fingerprint, const char *email,
     {
       int rc2;
 
-      rc2 = sqlite3_exec_printf (db_key, NULL, NULL, &err,
-                                rc ? "rollback;" : "end transaction;");
+      if (rc)
+        rc2 = rollback_transaction (db_key);
+      else
+        rc2 = end_transaction (db_key, 0);
       if (rc2)
        {
          log_error (_("error ending transaction on TOFU database: %s\n"),
@@ -900,8 +1434,10 @@ record_binding (struct db *dbs, const char *fingerprint, const char *email,
        }
 
     out_revert_one:
-      rc2 = sqlite3_exec_printf (db_email, NULL, NULL, &err,
-                                rc ? "rollback;" : "end transaction;");
+      if (rc)
+        rc2 = rollback_transaction (db_email);
+      else
+        rc2 = end_transaction (db_email, 0);
       if (rc2)
        {
          log_error (_("error ending transaction on TOFU database: %s\n"),
@@ -1027,27 +1563,39 @@ signature_stats_collect_cb (void *cookie, int argc, char **argv,
     }
   i ++;
 
-  tail = NULL;
-  errno = 0;
-  time_ago = strtol (argv[i], &tail, 0);
-  if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0'))
+  if (! argv[i])
+    time_ago = 0;
+  else
     {
-      /* Abort.  */
-      log_error ("%s: Error converting %s to an integer (tail = '%s')\n",
-                __func__, argv[i], tail);
-      return 1;
+      tail = NULL;
+      errno = 0;
+      time_ago = strtol (argv[i], &tail, 0);
+      if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0'))
+        {
+          /* Abort.  */
+          log_error ("%s: Error converting %s to an integer (tail = '%s')\n",
+                     __func__, argv[i], tail);
+          return 1;
+        }
     }
   i ++;
 
-  tail = NULL;
-  errno = 0;
-  count = strtoul (argv[i], &tail, 0);
-  if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0'))
+  /* If time_ago is NULL, then we had no messages, but we still have a
+     single row, which count(*) turns into 1.  */
+  if (! argv[i - 1])
+    count = 0;
+  else
     {
-      /* Abort.  */
-      log_error ("%s: Error converting %s to an integer (tail = '%s')\n",
-                __func__, argv[i], tail);
-      return 1;
+      tail = NULL;
+      errno = 0;
+      count = strtoul (argv[i], &tail, 0);
+      if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0'))
+        {
+          /* Abort.  */
+          log_error ("%s: Error converting %s to an integer (tail = '%s')\n",
+                     __func__, argv[i], tail);
+          return 1;
+        }
     }
   i ++;
 
@@ -1058,37 +1606,6 @@ signature_stats_collect_cb (void *cookie, int argc, char **argv,
   return 0;
 }
 
-/* The grouping parameters when collecting signature statistics.  */
-
-/* If a message is signed a couple of hours in the future, just assume
-   some clock skew.  */
-#define TIME_AGO_FUTURE_IGNORE (2 * 60 * 60)
-#if 0
-#  define TIME_AGO_UNIT_SMALL 60
-#  define TIME_AGO_UNIT_SMALL_NAME _("minute")
-#  define TIME_AGO_UNIT_SMALL_NAME_PLURAL _("minutes")
-#  define TIME_AGO_MEDIUM_THRESHOLD (60 * TIME_AGO_UNIT_SMALL)
-#  define TIME_AGO_UNIT_MEDIUM (60 * 60)
-#  define TIME_AGO_UNIT_MEDIUM_NAME _("hour")
-#  define TIME_AGO_UNIT_MEDIUM_NAME_PLURAL _("hours")
-#  define TIME_AGO_LARGE_THRESHOLD (24 * 60 * TIME_AGO_UNIT_SMALL)
-#  define TIME_AGO_UNIT_LARGE (24 * 60 * 60)
-#  define TIME_AGO_UNIT_LARGE_NAME _("day")
-#  define TIME_AGO_UNIT_LARGE_NAME_PLURAL _("days")
-#else
-#  define TIME_AGO_UNIT_SMALL (24 * 60 * 60)
-#  define TIME_AGO_UNIT_SMALL_NAME _("day")
-#  define TIME_AGO_UNIT_SMALL_NAME_PLURAL _("days")
-#  define TIME_AGO_MEDIUM_THRESHOLD (4 * TIME_AGO_UNIT_SMALL)
-#  define TIME_AGO_UNIT_MEDIUM (7 * 24 * 60 * 60)
-#  define TIME_AGO_UNIT_MEDIUM_NAME _("week")
-#  define TIME_AGO_UNIT_MEDIUM_NAME_PLURAL _("weeks")
-#  define TIME_AGO_LARGE_THRESHOLD (28 * TIME_AGO_UNIT_SMALL)
-#  define TIME_AGO_UNIT_LARGE (30 * 24 * 60 * 60)
-#  define TIME_AGO_UNIT_LARGE_NAME _("month")
-#  define TIME_AGO_UNIT_LARGE_NAME_PLURAL _("months")
-#endif
-
 /* Convert from seconds to time units.
 
    Note: T should already be a multiple of TIME_AGO_UNIT_SMALL or
@@ -1128,43 +1645,36 @@ time_ago_unit (signed long t)
 }
 
 
-#define GET_POLICY_ERROR 100
-
 /* Return the policy for the binding <FINGERPRINT, EMAIL> (email has
    already been normalized) and any conflict information in *CONFLICT
-   if CONFLICT is not NULL.  Returns GET_POLICY_ERROR if an error
+   if CONFLICT is not NULL.  Returns _tofu_GET_POLICY_ERROR if an error
    occurs.  */
 static enum tofu_policy
-get_policy (struct db *dbs, const char *fingerprint, const char *email,
+get_policy (struct dbs *dbs, const char *fingerprint, const char *email,
            char **conflict)
 {
-  sqlite3 *db;
+  struct db *db;
   int rc;
   char *err = NULL;
   strlist_t strlist = NULL;
   char *tail = NULL;
-  enum tofu_policy policy = GET_POLICY_ERROR;
-
-  assert (GET_POLICY_ERROR != TOFU_POLICY_NONE
-         && GET_POLICY_ERROR != TOFU_POLICY_AUTO
-         && GET_POLICY_ERROR != TOFU_POLICY_GOOD
-         && GET_POLICY_ERROR != TOFU_POLICY_UNKNOWN
-         && GET_POLICY_ERROR != TOFU_POLICY_BAD
-         && GET_POLICY_ERROR != TOFU_POLICY_ASK);
+  enum tofu_policy policy = _tofu_GET_POLICY_ERROR;
 
   db = getdb (dbs, email, DB_EMAIL);
   if (! db)
-    return GET_POLICY_ERROR;
+    return _tofu_GET_POLICY_ERROR;
 
   /* Check if the <FINGERPRINT, EMAIL> binding is known
      (TOFU_POLICY_NONE cannot appear in the DB.  Thus, if POLICY is
      still TOFU_POLICY_NONE after executing the query, then the
      result set was empty.)  */
-  rc = sqlite3_exec_printf
-    (db, strings_collect_cb, &strlist, &err,
-     "select policy, conflict from bindings\n"
-     " where fingerprint = %Q and email = %Q",
-     fingerprint, email);
+  rc = sqlite3_stepx (db->db, &db->s.get_policy_select_policy_and_conflict,
+                      strings_collect_cb, &strlist, &err,
+                      "select policy, conflict from bindings\n"
+                      " where fingerprint = ? and email = ?",
+                      SQLITE_ARG_STRING, fingerprint,
+                      SQLITE_ARG_STRING, email,
+                      SQLITE_ARG_END);
   if (rc)
     {
       log_error (_("error reading from TOFU database"
@@ -1209,7 +1719,7 @@ get_policy (struct db *dbs, const char *fingerprint, const char *email,
     {
       log_error (_("TOFU DB is corrupted.  Invalid value for policy (%d).\n"),
                 policy);
-      policy = GET_POLICY_ERROR;
+      policy = _tofu_GET_POLICY_ERROR;
       goto out;
     }
 
@@ -1226,7 +1736,7 @@ get_policy (struct db *dbs, const char *fingerprint, const char *email,
     }
 
  out:
-  assert (policy == GET_POLICY_ERROR
+  assert (policy == _tofu_GET_POLICY_ERROR
          || policy == TOFU_POLICY_NONE
          || policy == TOFU_POLICY_AUTO
          || policy == TOFU_POLICY_GOOD
@@ -1239,12 +1749,10 @@ get_policy (struct db *dbs, const char *fingerprint, const char *email,
   return policy;
 }
 
-#define GET_TRUST_ERROR 100
-
 /* Return the trust level (TRUST_NEVER, etc.) for the binding
    <FINGERPRINT, EMAIL> (email is already normalized).  If no policy
    is registered, returns TOFU_POLICY_NONE.  If an error occurs,
-   returns GET_TRUST_ERROR.
+   returns _tofu_GET_TRUST_ERROR.
 
    USER_ID is the unadultered user id.
 
@@ -1254,10 +1762,10 @@ get_policy (struct db *dbs, const char *fingerprint, const char *email,
    conflicting binding's policy to TOFU_POLICY_ASK.  In either case,
    we return TRUST_UNDEFINED.  */
 static enum tofu_policy
-get_trust (struct db *dbs, const char *fingerprint, const char *email,
+get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
           const char *user_id, int may_ask)
 {
-  sqlite3 *db;
+  struct db *db;
   enum tofu_policy policy;
   char *conflict = NULL;
   int rc;
@@ -1270,21 +1778,73 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
   if (opt.batch)
     may_ask = 0;
 
-  /* Make sure GET_TRUST_ERROR isn't equal to any of the trust
+  /* Make sure _tofu_GET_TRUST_ERROR isn't equal to any of the trust
      levels.  */
-  assert (GET_TRUST_ERROR != TRUST_UNKNOWN
-         && GET_TRUST_ERROR != TRUST_EXPIRED
-         && GET_TRUST_ERROR != TRUST_UNDEFINED
-         && GET_TRUST_ERROR != TRUST_NEVER
-         && GET_TRUST_ERROR != TRUST_MARGINAL
-         && GET_TRUST_ERROR != TRUST_FULLY
-         && GET_TRUST_ERROR != TRUST_ULTIMATE);
+  assert (_tofu_GET_TRUST_ERROR != TRUST_UNKNOWN
+         && _tofu_GET_TRUST_ERROR != TRUST_EXPIRED
+         && _tofu_GET_TRUST_ERROR != TRUST_UNDEFINED
+         && _tofu_GET_TRUST_ERROR != TRUST_NEVER
+         && _tofu_GET_TRUST_ERROR != TRUST_MARGINAL
+         && _tofu_GET_TRUST_ERROR != TRUST_FULLY
+         && _tofu_GET_TRUST_ERROR != TRUST_ULTIMATE);
 
   db = getdb (dbs, email, DB_EMAIL);
   if (! db)
-    return GET_TRUST_ERROR;
+    return _tofu_GET_TRUST_ERROR;
 
   policy = get_policy (dbs, fingerprint, email, &conflict);
+  if (policy == TOFU_POLICY_AUTO || policy == TOFU_POLICY_NONE)
+    /* See if the key is ultimately trusted.  If so, we're done.  */
+    {
+      int i, j;
+      char keyid[17];
+      KEYDB_SEARCH_DESC desc;
+
+      /* We need to convert the fingerprint as a string to a long
+         keyid.
+
+         FINGERPRINT has the form:
+
+           362D 3527 F53A AD19 71AA  FDE6 5885 9975 EE37 CF96
+                                          -------------------
+
+         The last 16 characters are the long keyid.
+      */
+      assert (strlen (fingerprint) > 4 * 4 + 3);
+      for (i = strlen (fingerprint) - (4 * 4 + 3), j = 0; j < 16; i ++, j ++)
+        {
+          if (fingerprint[i] == ' ')
+            i ++;
+          keyid[j] = fingerprint[i];
+        }
+      keyid[j] = 0;
+
+      rc = classify_user_id (keyid, &desc, 1);
+      if (rc || desc.mode != KEYDB_SEARCH_MODE_LONG_KID)
+        {
+          log_error (_("'%s' is not a valid long keyID\n"), keyid);
+          return _tofu_GET_TRUST_ERROR;
+        }
+
+      if (tdb_keyid_is_utk (desc.u.kid))
+        {
+          if (policy == TOFU_POLICY_NONE)
+            {
+              if (record_binding (dbs, fingerprint, email, user_id,
+                                  TOFU_POLICY_AUTO, 0) != 0)
+                {
+                  log_error (_("error setting TOFU binding's trust level to %s\n"),
+                             "auto");
+                  trust_level = _tofu_GET_TRUST_ERROR;
+                  goto out;
+                }
+            }
+
+          trust_level = TRUST_ULTIMATE;
+          goto out;
+        }
+    }
+
   if (policy == TOFU_POLICY_AUTO)
     {
       policy = opt.tofu_default_policy;
@@ -1322,8 +1882,8 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
         below.  */
       break;
 
-    case GET_POLICY_ERROR:
-      trust_level = GET_TRUST_ERROR;
+    case _tofu_GET_POLICY_ERROR:
+      trust_level = _tofu_GET_TRUST_ERROR;
       goto out;
 
     default:
@@ -1346,16 +1906,17 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
           (need to check for a conflict).
    */
 
-  /* Look for conflicts.  This is need in all 3 cases.
+  /* Look for conflicts.  This is needed in all 3 cases.
 
      Get the fingerprints of any bindings that share the email
      address.  Note: if the binding in question is in the DB, it will
      also be returned.  Thus, if the result set is empty, then this is
      a new binding.  */
-  rc = sqlite3_exec_printf
-    (db, strings_collect_cb, &bindings_with_this_email, &err,
-     "select distinct fingerprint from bindings where email = %Q;",
-     email);
+  rc = sqlite3_stepx
+    (db->db, &db->s.get_trust_bindings_with_this_email,
+     strings_collect_cb, &bindings_with_this_email, &err,
+     "select distinct fingerprint from bindings where email = ?;",
+     SQLITE_ARG_STRING, email, SQLITE_ARG_END);
   if (rc)
     {
       log_error (_("error reading from TOFU database"
@@ -1388,7 +1949,7 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
        {
          log_error (_("error setting TOFU binding's trust level to %s\n"),
                       "auto");
-         trust_level = GET_TRUST_ERROR;
+         trust_level = _tofu_GET_TRUST_ERROR;
          goto out;
        }
 
@@ -1458,12 +2019,16 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
        es_fprintf (fp, _("The binding %s is NOT known.  "), binding);
        binding_shown = 1;
       }
-    else if (policy == TOFU_POLICY_ASK && conflict)
+    else if (policy == TOFU_POLICY_ASK
+            /* If there the conflict is with itself, then don't
+               display this message.  */
+            && conflict && strcmp (conflict, fingerprint) != 0)
       {
        es_fprintf (fp,
-                   _("%s raised a conflict with this binding.  Since this"
-                     " binding's policy was 'auto', it was changed to 'ask'.  "),
-                   binding);
+                   _("The key %s raised a conflict with this binding (%s)."
+                      "  Since this binding's policy was 'auto', it was"
+                      "changed to 'ask'.  "),
+                   conflict, binding);
        binding_shown = 1;
       }
     es_fprintf (fp,
@@ -1478,7 +2043,7 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
     /* Find other user ids associated with this key and whether the
        bindings are marked as good or bad.  */
     {
-      sqlite3 *db_key;
+      struct db *db_key;
 
       if (opt.tofu_db_format == TOFU_DB_SPLIT)
        /* In the split format, we need to search in the fingerprint
@@ -1490,11 +2055,13 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
 
       if (db_key)
        {
-         rc = sqlite3_exec_printf
-           (db_key, strings_collect_cb, &other_user_ids, &err,
-            "select user_id, %s from bindings where fingerprint = %Q;",
-            opt.tofu_db_format == TOFU_DB_SPLIT ? "email" : "policy",
-            fingerprint);
+         rc = sqlite3_stepx
+           (db_key->db, &db_key->s.get_trust_gather_other_user_ids,
+             strings_collect_cb, &other_user_ids, &err,
+             opt.tofu_db_format == TOFU_DB_SPLIT
+            ? "select user_id, email from bindings where fingerprint = ?;"
+            : "select user_id, policy from bindings where fingerprint = ?;",
+            SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_END);
          if (rc)
            {
              log_error (_("error gathering other user ids: %s.\n"), err);
@@ -1539,8 +2106,9 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
     /* XXX: When generating the statistics, do we want the time
        embedded in the signature (column 'sig_time') or the time that
        we first verified the signature (column 'time').  */
-    rc = sqlite3_exec_printf
-      (db, signature_stats_collect_cb, &stats, &err,
+    rc = sqlite3_stepx
+      (db->db, &db->s.get_trust_gather_other_keys,
+       signature_stats_collect_cb, &stats, &err,
        "select fingerprint, policy, time_ago, count(*)\n"
        " from (select bindings.*,\n"
        "        case\n"
@@ -1549,25 +2117,30 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
          small, medium or large units?  (Note: whatever we do, we
          keep the value in seconds.  Then when we group, everything
          that rounds to the same number of seconds is grouped.)  */
-       "         when delta < -%d then -1\n"
-       "         when delta < %d then max(0, round(delta / %d) * %d)\n"
-       "         when delta < %d then round(delta / %d) * %d\n"
-       "         else round(delta / %d) * %d\n"
+       "         when delta < -("STRINGIFY (TIME_AGO_FUTURE_IGNORE)") then -1\n"
+       "         when delta < ("STRINGIFY (TIME_AGO_MEDIUM_THRESHOLD)")\n"
+       "          then max(0,\n"
+       "                   round(delta / ("STRINGIFY (TIME_AGO_UNIT_SMALL)"))\n"
+       "               * ("STRINGIFY (TIME_AGO_UNIT_SMALL)"))\n"
+       "         when delta < ("STRINGIFY (TIME_AGO_LARGE_THRESHOLD)")\n"
+       "          then round(delta / ("STRINGIFY (TIME_AGO_UNIT_MEDIUM)"))\n"
+       "               * ("STRINGIFY (TIME_AGO_UNIT_MEDIUM)")\n"
+       "         else round(delta / ("STRINGIFY (TIME_AGO_UNIT_LARGE)"))\n"
+       "              * ("STRINGIFY (TIME_AGO_UNIT_LARGE)")\n"
        "        end time_ago,\n"
        "        delta time_ago_raw\n"
-       "       from (select *,\n"
-       "              cast(strftime('%%s','now') - sig_time as real) delta\n"
-       "             from signatures) ss\n"
-       "       left join bindings on ss.binding = bindings.oid)\n"
-       " where email = %Q\n"
+       "       from bindings\n"
+       "       left join\n"
+       "         (select *,\n"
+       "            cast(strftime('%s','now') - sig_time as real) delta\n"
+       "           from signatures) ss\n"
+       "        on ss.binding = bindings.oid)\n"
+       " where email = ?\n"
        " group by fingerprint, time_ago\n"
        /* Make sure the current key is first.  */
-       " order by fingerprint = %Q asc, fingerprint desc, time_ago desc;\n",
-       TIME_AGO_FUTURE_IGNORE,
-       TIME_AGO_MEDIUM_THRESHOLD, TIME_AGO_UNIT_SMALL, TIME_AGO_UNIT_SMALL,
-       TIME_AGO_LARGE_THRESHOLD, TIME_AGO_UNIT_MEDIUM, TIME_AGO_UNIT_MEDIUM,
-       TIME_AGO_UNIT_LARGE, TIME_AGO_UNIT_LARGE,
-       email, fingerprint);
+       " order by fingerprint = ? asc, fingerprint desc, time_ago desc;\n",
+       SQLITE_ARG_STRING, email, SQLITE_ARG_STRING, fingerprint,
+       SQLITE_ARG_END);
     if (rc)
       {
        strlist_t strlist_iter;
@@ -1639,12 +2212,12 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
        if (strcmp (text, "TOFU detected a binding conflict") == 0)
          /* No translation.  Use the English text.  */
          text =
-           "Normally, there is only a single key associated with an email"
-           "address.  However, people sometimes generate a new key if"
-           "their key is too old or they think it might be compromised."
-           "Alternatively, a new key may indicate a man-in-the-middle attack!"
-           "Before accepting this key, you should talk to or call the person"
-           "to make sure this new key is legitimate.";
+           "Normally, there is only a single key associated with an email "
+           "address.  However, people sometimes generate a new key if "
+           "their key is too old or they think it might be compromised.  "
+           "Alternatively, a new key may indicate a man-in-the-middle"
+           "attack!  Before accepting this key, you should talk to or "
+           "call the person to make sure this new key is legitimate.";
        es_fprintf (fp, "\n%s\n", text);
       }
 
@@ -1676,7 +2249,7 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
            if (choice)
              {
                int c = ((size_t) choice - (size_t) choices) / 2;
-               assert (0 <= c && c <= 3);
+               assert (0 <= c && c <= 4);
 
                switch (c)
                  {
@@ -1710,7 +2283,7 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
                                    policy, 0) != 0)
                  /* If there's an error registering the
                     binding, don't save the signature.  */
-                 trust_level = GET_TRUST_ERROR;
+                 trust_level = _tofu_GET_TRUST_ERROR;
 
                break;
              }
@@ -1726,11 +2299,22 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
  out:
   if (change_conflicting_to_ask)
     {
-      rc = sqlite3_exec_printf
-       (db, NULL, NULL, &err,
-        "update bindings set policy = %d, conflict = %Q"
-        " where email = %Q and fingerprint != %Q and policy = %d;",
-        TOFU_POLICY_ASK, fingerprint, email, fingerprint, TOFU_POLICY_AUTO);
+      if (! may_ask)
+       /* If we weren't allowed to ask, also update this key as
+          conflicting with itself.  */
+       rc = sqlite3_exec_printf
+         (db->db, NULL, NULL, &err,
+          "update bindings set policy = %d, conflict = %Q"
+          " where email = %Q"
+          "  and (policy = %d or (policy = %d and fingerprint = %Q));",
+          TOFU_POLICY_ASK, fingerprint, email, TOFU_POLICY_AUTO,
+          TOFU_POLICY_ASK, fingerprint);
+      else
+       rc = sqlite3_exec_printf
+         (db->db, NULL, NULL, &err,
+          "update bindings set policy = %d, conflict = %Q"
+          " where email = %Q and fingerprint != %Q and policy = %d;",
+          TOFU_POLICY_ASK, fingerprint, email, fingerprint, TOFU_POLICY_AUTO);
       if (rc)
        {
          log_error (_("error changing TOFU policy: %s\n"), err);
@@ -1746,11 +2330,11 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
 }
 
 static void
-show_statistics (struct db *dbs, const char *fingerprint,
+show_statistics (struct dbs *dbs, const char *fingerprint,
                 const char *email, const char *user_id,
                 const char *sig_exclude)
 {
-  sqlite3 *db;
+  struct db *db;
   int rc;
   strlist_t strlist = NULL;
   char *err = NULL;
@@ -1760,7 +2344,7 @@ show_statistics (struct db *dbs, const char *fingerprint,
     return;
 
   rc = sqlite3_exec_printf
-    (db, strings_collect_cb, &strlist, &err,
+    (db->db, strings_collect_cb, &strlist, &err,
      "select count (*), strftime('%%s','now') - min (signatures.time)\n"
      " from signatures\n"
      " left join bindings on signatures.binding = bindings.oid\n"
@@ -1894,6 +2478,12 @@ show_statistics (struct db *dbs, const char *fingerprint,
                }
              seconds = first_seen_ago;
 
+#undef MIN_SECS
+#undef HOUR_SECS
+#undef DAY_SECS
+#undef MONTH_SECS
+#undef YEAR_SECS
+
              if (years)
                {
                  if (years > 1)
@@ -1991,15 +2581,17 @@ show_statistics (struct db *dbs, const char *fingerprint,
              /* TRANSLATORS: translate the below text.  We don't
                 directly internationalize that text so that we can
                 tweak it without breaking translations.  */
-             text = _("TOFU: few signatures %s");
-             if (strcmp (text, "TOFU: few signatures %s") == 0)
+             text = _("TOFU: few signatures %d %s %s");
+             if (strcmp (text, "TOFU: few signatures %d %s %s") == 0)
                text =
-                 "Warning: if this value is unexpectedly low, this might "
-                 "indicate that this key is a forgery!  Carefully examine "
-                 "the email address for small variations (e.g., additional "
-                 "white space).  If the key is suspect, then use '%s' to "
-                 "mark the key as being bad.\n";
-             log_info (text, set_policy_command);
+                 "Warning: if you think you've seen more than %d %s "
+                 "signed by this key, then this key might be a forgery!  "
+                 "Carefully examine the email address for small variations "
+                 "(e.g., additional white space).  If the key is suspect, "
+                 "then use '%s' to mark it as being bad.\n";
+             log_info (text,
+                       messages, messages == 1 ? _("message") : _("message"),
+                       set_policy_command);
              free (set_policy_command);
            }
        }
@@ -2087,8 +2679,8 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
               const byte *sig_digest_bin, int sig_digest_bin_len,
               time_t sig_time, const char *origin, int may_ask)
 {
-  struct db *dbs;
-  sqlite3 *db;
+  struct dbs *dbs;
+  struct db *db;
   char *fingerprint = NULL;
   char *email = NULL;
   char *err = NULL;
@@ -2098,6 +2690,8 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
   unsigned long c;
   int already_verified = 0;
 
+  sig_digest = make_radix64_string (sig_digest_bin, sig_digest_bin_len);
+
   dbs = opendbs ();
   if (! dbs)
     {
@@ -2122,7 +2716,7 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
   /* It's necessary to get the trust so that we are certain that the
      binding has been registered.  */
   trust_level = get_trust (dbs, fingerprint, email, user_id, may_ask);
-  if (trust_level == GET_TRUST_ERROR)
+  if (trust_level == _tofu_GET_TRUST_ERROR)
     /* An error.  */
     {
       trust_level = TRUST_UNKNOWN;
@@ -2130,8 +2724,6 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
     }
 
   /* Save the observed signature in the DB.  */
-  sig_digest = make_radix64_string (sig_digest_bin, sig_digest_bin_len);
-
   db = getdb (dbs, email, DB_EMAIL);
   if (! db)
     {
@@ -2141,24 +2733,24 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
 
   /* We do a query and then an insert.  Make sure they are atomic
      by wrapping them in a transaction.  */
-  rc = sqlite3_exec (db, "begin transaction;", NULL, NULL, &err);
+  rc = begin_transaction (db, 0);
   if (rc)
-    {
-      log_error (_("error beginning transaction on TOFU database: %s\n"), err);
-      sqlite3_free (err);
-      goto die;
-    }
+    goto die;
 
   /* If we've already seen this signature before, then don't add
      it again.  */
-  rc = sqlite3_exec_printf
-    (db, get_single_unsigned_long_cb, &c, &err,
+  rc = sqlite3_stepx
+    (db->db, &db->s.register_already_seen,
+     get_single_unsigned_long_cb, &c, &err,
      "select count (*)\n"
      " from signatures left join bindings\n"
      "  on signatures.binding = bindings.oid\n"
-     " where fingerprint = %Q and email = %Q and sig_time = 0x%lx\n"
-     "  and sig_digest = %Q",
-     fingerprint, email, (unsigned long) sig_time, sig_digest);
+     " where fingerprint = ? and email = ? and sig_time = ?\n"
+     "  and sig_digest = ?",
+     SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
+     SQLITE_ARG_LONG_LONG, (long long) sig_time,
+     SQLITE_ARG_STRING, sig_digest,
+     SQLITE_ARG_END);
   if (rc)
     {
       log_error (_("error reading from signatures database"
@@ -2194,15 +2786,18 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
 
       assert (c == 0);
 
-      rc = sqlite3_exec_printf
-       (db, NULL, NULL, &err,
+      rc = sqlite3_stepx
+       (db->db, &db->s.register_insert, NULL, NULL, &err,
         "insert into signatures\n"
         " (binding, sig_digest, origin, sig_time, time)\n"
         " values\n"
         " ((select oid from bindings\n"
-        "    where fingerprint = %Q and email = %Q),\n"
-        "  %Q, %Q, 0x%lx, strftime('%%s', 'now'));",
-        fingerprint, email, sig_digest, origin, (unsigned long) sig_time);
+        "    where fingerprint = ? and email = ?),\n"
+        "  ?, ?, ?, strftime('%s', 'now'));",
+        SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
+         SQLITE_ARG_STRING, sig_digest, SQLITE_ARG_STRING, origin,
+         SQLITE_ARG_LONG_LONG, (long long) sig_time,
+         SQLITE_ARG_END);
       if (rc)
        {
          log_error (_("error updating TOFU DB"
@@ -2215,9 +2810,9 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
   /* It only matters whether we abort or commit the transaction
      (so long as we do something) if we execute the insert.  */
   if (rc)
-    rc = sqlite3_exec (db, "rollback;", NULL, NULL, &err);
+    rc = rollback_transaction (db);
   else
-    rc = sqlite3_exec (db, "commit transaction;", NULL, NULL, &err);
+    rc = end_transaction (db, 0);
   if (rc)
     {
       log_error (_("error ending transaction on TOFU database: %s\n"), err);
@@ -2226,7 +2821,7 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
     }
 
  die:
-  if (may_ask)
+  if (may_ask && trust_level != TRUST_ULTIMATE)
     /* It's only appropriate to show the statistics in an interactive
        context.  */
     show_statistics (dbs, fingerprint, email, user_id,
@@ -2236,6 +2831,7 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
   xfree (fingerprint);
   if (dbs)
     closedbs (dbs);
+  xfree (sig_digest);
 
   return trust_level;
 }
@@ -2304,7 +2900,7 @@ int
 tofu_get_validity (const byte *fingerprint_bin, const char *user_id,
                   int may_ask)
 {
-  struct db *dbs;
+  struct dbs *dbs;
   char *fingerprint = NULL;
   char *email = NULL;
   int trust_level = TRUST_UNDEFINED;
@@ -2327,11 +2923,11 @@ tofu_get_validity (const byte *fingerprint_bin, const char *user_id,
   email = email_from_user_id (user_id);
 
   trust_level = get_trust (dbs, fingerprint, email, user_id, may_ask);
-  if (trust_level == GET_TRUST_ERROR)
+  if (trust_level == _tofu_GET_TRUST_ERROR)
     /* An error.  */
     trust_level = TRUST_UNDEFINED;
 
-  if (may_ask)
+  if (may_ask && trust_level != TRUST_ULTIMATE)
     show_statistics (dbs, fingerprint, email, user_id, NULL);
 
  die:
@@ -2353,7 +2949,7 @@ tofu_get_validity (const byte *fingerprint_bin, const char *user_id,
 gpg_error_t
 tofu_set_policy (kbnode_t kb, enum tofu_policy policy)
 {
-  struct db *dbs;
+  struct dbs *dbs;
   PKT_public_key *pk;
   char fingerprint_bin[MAX_FINGERPRINT_LEN];
   size_t fingerprint_bin_len = sizeof (fingerprint_bin);
@@ -2436,7 +3032,7 @@ gpg_error_t
 tofu_get_policy (PKT_public_key *pk, PKT_user_id *user_id,
                 enum tofu_policy *policy)
 {
-  struct db *dbs;
+  struct dbs *dbs;
   char fingerprint_bin[MAX_FINGERPRINT_LEN];
   size_t fingerprint_bin_len = sizeof (fingerprint_bin);
   char *fingerprint;
@@ -2466,7 +3062,7 @@ tofu_get_policy (PKT_public_key *pk, PKT_user_id *user_id,
   xfree (fingerprint);
   closedbs (dbs);
 
-  if (*policy == GET_POLICY_ERROR)
+  if (*policy == _tofu_GET_POLICY_ERROR)
     return gpg_error (GPG_ERR_GENERAL);
   return 0;
 }