tests: Make ssh test more robust.
[gnupg.git] / g10 / tofu.c
index 43a6224..a2732ff 100644 (file)
@@ -1,5 +1,5 @@
 /* tofu.c - TOFU trust model.
- * Copyright (C) 2015 g10 Code GmbH
+ * Copyright (C) 2015, 2016 g10 Code GmbH
  *
  * This file is part of GnuPG.
  *
@@ -26,9 +26,7 @@
 #include <config.h>
 #include <stdio.h>
 #include <sys/stat.h>
-#include <assert.h>
 #include <stdarg.h>
-#include <sched.h>
 #include <sqlite3.h>
 
 #include "gpg.h"
 #include "options.h"
 #include "mbox-util.h"
 #include "i18n.h"
+#include "ttyio.h"
 #include "trustdb.h"
 #include "mkdir_p.h"
+#include "gpgsql.h"
+#include "status.h"
 
 #include "tofu.h"
 
+
+#define CONTROL_L ('L' - 'A' + 1)
+
+/* Number of signed messages required to indicate that enough history
+ * is available for basic trust.  */
+#define BASIC_TRUST_THRESHOLD  10
+/* Number of signed messages required to indicate that a lot of
+ * history is available.  */
+#define FULL_TRUST_THRESHOLD  100
+
+
 #define DEBUG_TOFU_CACHE 0
 #if DEBUG_TOFU_CACHE
 static int prepares_saved;
@@ -72,8 +84,8 @@ enum db_type
    theis case, NAME is either the normalized email address or the
    fingerprint.
 
-   To initialize this data structure, call opendbs().  When you are
-   done, clean it up using closedbs().  To get a handle to a database,
+   To initialize this data structure, call opendbs().  Cleanup is done
+   when the CTRL object is released.  To get a handle to a database,
    use the getdb() function.  This will either return an existing
    handle or open a new DB connection, as appropriate.  */
 struct db
@@ -143,31 +155,19 @@ tofu_cache_dump (struct db *db)
 #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
 
-
+\f
 
 const char *
 tofu_policy_str (enum tofu_policy policy)
@@ -213,229 +213,6 @@ tofu_policy_to_trust_level (enum tofu_policy policy)
       return 0;
     }
 }
-
-/* This is a convenience function that combines sqlite3_mprintf and
-   sqlite3_exec.  */
-static int
-sqlite3_exec_printf (sqlite3 *db,
-                    int (*callback)(void*,int,char**,char**), void *cookie,
-                    char **errmsg,
-                    const char *sql, ...)
-{
-  va_list ap;
-  int rc;
-  char *sql2;
-
-  va_start (ap, sql);
-  sql2 = sqlite3_vmprintf (sql, ap);
-  va_end (ap);
-
-#if 0
-  log_debug ("tofo db: executing: '%s'\n", sql2);
-#endif
-
-  rc = sqlite3_exec (db, sql2, callback, cookie, errmsg);
-
-  sqlite3_free (sql2);
-
-  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;
@@ -469,7 +246,7 @@ begin_transaction (struct db *db, int only_batch)
       batch_update_started = gnupg_get_time ();
 
       /* Yield to allow another process a chance to run.  */
-      sched_yield ();
+      gpgrt_yield ();
     }
 
   /* XXX: In split mode, this can end in deadlock.
@@ -485,14 +262,15 @@ begin_transaction (struct db *db, int only_batch)
 
   if (batch_update && ! db->batch_update)
     {
-      rc = sqlite3_stepx (db->db, &db->s.savepoint_batch,
+      rc = gpgsql_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);
+          log_error (_("error beginning transaction on TOFU database: %s\n"),
+                     err);
+          print_further_info ("batch, database '%s'",
+                              *db->name ? db->name : "[combined]");
           sqlite3_free (err);
           return gpg_error (GPG_ERR_GENERAL);
         }
@@ -503,14 +281,15 @@ begin_transaction (struct db *db, int only_batch)
   if (only_batch)
     return 0;
 
-  rc = sqlite3_stepx (db->db, &db->s.savepoint_inner,
+  rc = gpgsql_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);
+      log_error (_("error beginning transaction on TOFU database: %s\n"),
+                 err);
+      print_further_info ("inner, database '%s'",
+                          *db->name ? db->name : "[combined]");
       sqlite3_free (err);
       return gpg_error (GPG_ERR_GENERAL);
     }
@@ -528,20 +307,24 @@ end_transaction (struct db *db, int only_batch)
   int rc;
   char *err = NULL;
 
+  if (!db)
+    return 0;  /* Shortcut to allow for easier cleanup code.  */
+
   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,
+      rc = gpgsql_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);
+          log_error (_("error committing transaction on TOFU database: %s\n"),
+                     err);
+          print_further_info ("batch, database '%s'",
+                              *db->name ? db->name : "[combined]");
           sqlite3_free (err);
           return gpg_error (GPG_ERR_GENERAL);
         }
@@ -554,14 +337,15 @@ end_transaction (struct db *db, int only_batch)
   if (only_batch)
     return 0;
 
-  rc = sqlite3_stepx (db->db, &db->s.savepoint_inner_commit,
+  rc = gpgsql_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);
+      log_error (_("error committing transaction on TOFU database: %s\n"),
+                 err);
+      print_further_info ("inner, database '%s'",
+                          *db->name ? db->name : "[combined]");
       sqlite3_free (err);
       return gpg_error (GPG_ERR_GENERAL);
     }
@@ -575,6 +359,9 @@ rollback_transaction (struct db *db)
   int rc;
   char *err = NULL;
 
+  if (!db)
+    return 0;  /* Shortcut to allow for easier cleanup code.  */
+
   if (db->batch_update)
     /* Just undo the most recent update; don't revert any progress
        made by the batch transaction.  */
@@ -585,9 +372,10 @@ rollback_transaction (struct db *db)
 
   if (rc)
     {
-      log_error
-        (_("error rolling back inner transaction on TOFU database '%s': %s\n"),
-         *db->name ? db->name : "combined", err);
+      log_error (_("error rolling back transaction on TOFU database: %s\n"),
+                 err);
+      print_further_info ("inner, database '%s'",
+                          *db->name ? db->name : "[combined]");
       sqlite3_free (err);
       return gpg_error (GPG_ERR_GENERAL);
     }
@@ -607,7 +395,7 @@ tofu_begin_batch_update (void)
 void
 tofu_end_batch_update (void)
 {
-  assert (batch_update > 0);
+  log_assert (batch_update > 0);
   batch_update --;
 
   if (batch_update == 0)
@@ -618,7 +406,66 @@ tofu_end_batch_update (void)
         end_transaction (db, 1);
     }
 }
+
+
+
 \f
+/* Wrapper around strtol which prints a warning in case of a
+ * conversion error.  On success the converted value is stored at
+ * R_VALUE and 0 is returned; on error FALLBACK is stored at R_VALUE
+ * and an error code is returned.  */
+static gpg_error_t
+string_to_long (long *r_value, const char *string, long fallback, int line)
+{
+  gpg_error_t err;
+  char *tail = NULL;
+
+  gpg_err_set_errno (0);
+  *r_value = strtol (string, &tail, 0);
+  if (errno || !(!strcmp (tail, ".0") || !*tail))
+    {
+      err = errno? gpg_error_from_errno (errno) : gpg_error (GPG_ERR_BAD_DATA);
+      log_debug ("%s:%d: "
+                 "strtol failed for DB returned string (tail=%.10s): %s\n",
+                 __FILE__, line, tail, gpg_strerror (err));
+      *r_value = fallback;
+    }
+  else
+    err = 0;
+
+  return err;
+}
+
+
+/* Wrapper around strtoul which prints a warning in case of a
+ * conversion error.  On success the converted value is stored at
+ * R_VALUE and 0 is returned; on error FALLBACK is stored at R_VALUE
+ * and an error code is returned.  */
+static gpg_error_t
+string_to_ulong (unsigned long *r_value, const char *string,
+                 unsigned long fallback, int line)
+{
+  gpg_error_t err;
+  char *tail = NULL;
+
+  gpg_err_set_errno (0);
+  *r_value = strtoul (string, &tail, 0);
+  if (errno || !(!strcmp (tail, ".0") || !*tail))
+    {
+      err = errno? gpg_error_from_errno (errno) : gpg_error (GPG_ERR_BAD_DATA);
+      log_debug ("%s:%d: "
+                 "strtoul failed for DB returned string (tail=%.10s): %s\n",
+                 __FILE__, line, tail, gpg_strerror (err));
+      *r_value = fallback;
+    }
+  else
+    err = 0;
+
+  return err;
+}
+
+
+
 /* 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
@@ -626,20 +473,24 @@ get_single_unsigned_long_cb (void *cookie, int argc, char **argv,
                             char **azColName)
 {
   unsigned long int *count = cookie;
-  char *tail = NULL;
 
   (void) azColName;
 
-  assert (argc == 1);
+  log_assert (argc == 1);
 
-  errno = 0;
-  *count = strtoul (argv[0], &tail, 0);
-  if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0'))
-    /* Abort.  */
-    return 1;
+  if (string_to_ulong (count, argv[0], 0, __LINE__))
+    return 1; /* Abort.  */
   return 0;
 }
 
+static int
+get_single_unsigned_long_cb2 (void *cookie, int argc, char **argv,
+                            char **azColName, sqlite3_stmt *stmt)
+{
+  (void) stmt;
+  return get_single_unsigned_long_cb (cookie, argc, argv, azColName);
+}
+
 /* We expect a single integer column whose name is "version".  COOKIE
    must point to an int.  This function always aborts.  On error or a
    if the version is bad, sets *VERSION to -1.  */
@@ -658,7 +509,7 @@ version_check_cb (void *cookie, int argc, char **argv, char **azColName)
     *version = 1;
   else
     {
-      log_error (_("unsupported TOFU DB version: %s\n"), argv[0]);
+      log_error (_("unsupported TOFU database version: %s\n"), argv[0]);
       *version = -1;
     }
 
@@ -695,8 +546,8 @@ initdb (sqlite3 *db, enum db_type type)
                     get_single_unsigned_long_cb, &count, &err);
   if (rc)
     {
-      log_error (_("error querying TOFU DB's available tables: %s\n"),
-                err);
+      log_error (_("error reading TOFU database: %s\n"), err);
+      print_further_info ("query available tables");
       sqlite3_free (err);
       goto out;
     }
@@ -723,16 +574,16 @@ initdb (sqlite3 *db, enum db_type type)
       else if (rc)
        /* Some error.  */
        {
-         log_error (_("error determining TOFU DB's version: %s\n"), err);
+         log_error (_("error determining TOFU database's version: %s\n"), err);
          sqlite3_free (err);
           goto out;
        }
       else
-       /* Unexpected success.  This can only happen if there are no
-          rows.  */
-       {
-         log_error (_("error determining TOFU DB's version: %s\n"),
-                    "select returned 0, but expected ABORT");
+        {
+          /* Unexpected success.  This can only happen if there are no
+             rows.  (select returned 0, but expected ABORT.)  */
+         log_error (_("error determining TOFU database's version: %s\n"),
+                     gpg_strerror (GPG_ERR_NO_DATA));
           rc = 1;
           goto out;
        }
@@ -744,8 +595,8 @@ initdb (sqlite3 *db, enum db_type type)
                     NULL, NULL, &err);
   if (rc)
     {
-      log_error (_("error initializing TOFU database (%s): %s\n"),
-                "version", err);
+      log_error (_("error initializing TOFU database: %s\n"), err);
+      print_further_info ("create version");
       sqlite3_free (err);
       goto out;
     }
@@ -757,40 +608,39 @@ initdb (sqlite3 *db, enum db_type type)
                     NULL, NULL, &err);
   if (rc)
     {
-      log_error (_("error initializing TOFU database (%s): %s\n"),
-                "version, init", err);
+      log_error (_("error initializing TOFU database: %s\n"), err);
+      print_further_info ("insert version");
       sqlite3_free (err);
       goto out;
     }
 
   /* The list of <fingerprint, email> bindings and auxiliary data.
-
-       OID is a unique ID identifying this binding (and used by the
-         signatures table, see below).  Note: OIDs will never be
-         reused.
-
-       FINGERPRINT: The key's fingerprint.
-
-       EMAIL: The normalized email address.
-
-       USER_ID: The unmodified user id from which EMAIL was extracted.
-
-       TIME: The time this binding was first observed.
-
-       POLICY: The trust policy (-1, 0, 1, or 2; see the
-         documentation for TOFU_POLICY_BAD, etc. above).
-
-       CONFLICT is either NULL or a fingerprint.  Assume that we have
-         a binding <0xdeadbeef, foo@example.com> and then we observe
-         <0xbaddecaf, foo@example.com>.  There two bindings conflict
-         (they have the same email address).  When we observe the
-         latter binding, we warn the user about the conflict and ask
-         for a policy decision about the new binding.  We also change
-         the old binding's policy to ask if it was auto.  So that we
-         know why this occured, we also set conflict to 0xbaddecaf.
-  */
+   *
+   *  OID is a unique ID identifying this binding (and used by the
+   *    signatures table, see below).  Note: OIDs will never be
+   *    reused.
+   *
+   *  FINGERPRINT: The key's fingerprint.
+   *
+   *  EMAIL: The normalized email address.
+   *
+   *  USER_ID: The unmodified user id from which EMAIL was extracted.
+   *
+   *  TIME: The time this binding was first observed.
+   *
+   *  POLICY: The trust policy (TOFU_POLICY_BAD, etc. as an integer).
+   *
+   *  CONFLICT is either NULL or a fingerprint.  Assume that we have
+   *    a binding <0xdeadbeef, foo@example.com> and then we observe
+   *    <0xbaddecaf, foo@example.com>.  There two bindings conflict
+   *    (they have the same email address).  When we observe the
+   *    latter binding, we warn the user about the conflict and ask
+   *    for a policy decision about the new binding.  We also change
+   *    the old binding's policy to ask if it was auto.  So that we
+   *     know why this occurred, we also set conflict to 0xbaddecaf.
+   */
   if (type == DB_EMAIL || type == DB_COMBINED)
-    rc = sqlite3_exec_printf
+    rc = gpgsql_exec_printf
       (db, NULL, NULL, &err,
        "create table bindings\n"
        " (oid INTEGER PRIMARY KEY AUTOINCREMENT,\n"
@@ -809,7 +659,7 @@ initdb (sqlite3 *db, enum db_type type)
 
        Note: since the data is split on the email address, there is no
        need to index the email column.  */
-    rc = sqlite3_exec_printf
+    rc = gpgsql_exec_printf
       (db, NULL, NULL, &err,
        "create table bindings\n"
        " (oid INTEGER PRIMARY KEY AUTOINCREMENT,\n"
@@ -819,8 +669,8 @@ initdb (sqlite3 *db, enum db_type type)
        " on bindings (fingerprint);\n");
   if (rc)
     {
-      log_error (_("error initializing TOFU database (%s): %s\n"),
-                "bindings", err);
+      log_error (_("error initializing TOFU database: %s\n"), err);
+      print_further_info ("create bindings");
       sqlite3_free (err);
       goto out;
     }
@@ -849,8 +699,8 @@ initdb (sqlite3 *db, enum db_type type)
                         NULL, NULL, &err);
       if (rc)
        {
-         log_error (_("error initializing TOFU database (%s): %s\n"),
-                    "signatures", err);
+          log_error (_("error initializing TOFU database: %s\n"), err);
+          print_further_info ("create signatures");
          sqlite3_free (err);
          goto out;
        }
@@ -862,7 +712,7 @@ initdb (sqlite3 *db, enum db_type type)
       rc = sqlite3_exec (db, "rollback;", NULL, NULL, &err);
       if (rc)
        {
-         log_error (_("error aborting transaction on TOFU DB: %s\n"),
+         log_error (_("error rolling back transaction on TOFU database: %s\n"),
                     err);
          sqlite3_free (err);
        }
@@ -873,7 +723,7 @@ initdb (sqlite3 *db, enum db_type type)
       rc = sqlite3_exec (db, "end transaction;", NULL, NULL, &err);
       if (rc)
        {
-         log_error (_("error committing transaction on TOFU DB: %s\n"),
+         log_error (_("error committing transaction on TOFU database: %s\n"),
                     err);
          sqlite3_free (err);
          return 1;
@@ -894,22 +744,22 @@ opendb (char *filename, enum db_type type)
 
   if (opt.tofu_db_format == TOFU_DB_FLAT)
     {
-      assert (! filename);
-      assert (type == DB_COMBINED);
+      log_assert (! filename);
+      log_assert (type == DB_COMBINED);
 
-      filename = make_filename (opt.homedir, "tofu.db", NULL);
+      filename = make_filename (gnupg_homedir (), "tofu.db", NULL);
       filename_free = 1;
     }
   else
-    assert (type == DB_EMAIL || type == DB_KEY);
+    log_assert (type == DB_EMAIL || type == DB_KEY);
 
-  assert (filename);
+  log_assert (filename);
 
   rc = sqlite3_open (filename, &db);
   if (rc)
     {
-      log_error (_("can't open TOFU DB ('%s'): %s\n"),
-                filename, sqlite3_errmsg (db));
+      log_error (_("error opening TOFU database '%s': %s\n"),
+                 filename, sqlite3_errmsg (db));
       /* Even if an error occurs, DB is guaranteed to be valid.  */
       sqlite3_close (db);
       db = NULL;
@@ -917,7 +767,8 @@ opendb (char *filename, enum db_type type)
 
   /* 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 (db)
+    sqlite3_busy_timeout (db, 5 * 1000);
 
   if (filename_free)
     xfree (filename);
@@ -931,7 +782,8 @@ opendb (char *filename, enum db_type type)
   return db;
 }
 
-struct dbs
+/* Definition of the Tofu dabase meta handle.  */
+struct tofu_dbs_s
 {
   struct db *db;
 };
@@ -963,7 +815,7 @@ link_db (struct db **head, struct db *db)
    TYPE must be either DB_MAIL or DB_KEY.  In the combined format, the
    combined DB is always returned.  */
 static struct db *
-getdb (struct dbs *dbs, const char *name, enum db_type type)
+getdb (tofu_dbs_t dbs, const char *name, enum db_type type)
 {
   struct db *t = NULL;
   char *name_sanitized = NULL;
@@ -971,10 +823,11 @@ getdb (struct dbs *dbs, const char *name, enum db_type type)
   char *filename = NULL;
   int need_link = 1;
   sqlite3 *sqlitedb = NULL;
+  gpg_error_t rc;
 
-  assert (dbs);
-  assert (name);
-  assert (type == DB_EMAIL || type == DB_KEY);
+  log_assert (dbs);
+  log_assert (name);
+  log_assert (type == DB_EMAIL || type == DB_KEY);
 
   if (opt.tofu_db_format == TOFU_DB_FLAT)
     /* When using the flat format, we only have a single DB, the
@@ -982,8 +835,8 @@ getdb (struct dbs *dbs, const char *name, enum db_type type)
     {
       if (dbs->db)
         {
-          assert (dbs->db->type == DB_COMBINED);
-          assert (! dbs->db->next);
+          log_assert (dbs->db->type == DB_COMBINED);
+          log_assert (! dbs->db->next);
           return dbs->db;
         }
 
@@ -1024,7 +877,7 @@ getdb (struct dbs *dbs, const char *name, enum db_type type)
         goto out;
       }
 
-  assert (db_cache_count == count);
+  log_assert (db_cache_count == count);
 
   if (type == DB_COMBINED)
     filename = NULL;
@@ -1042,16 +895,20 @@ getdb (struct dbs *dbs, const char *name, enum db_type type)
         char *name_db;
 
         /* Make the directory.  */
-        if (gnupg_mkdir_p (opt.homedir, "tofu.d", type_str, prefix, NULL) != 0)
+        rc = gnupg_mkdir_p (gnupg_homedir (), "tofu.d", type_str, prefix, NULL);
+        if (rc)
           {
-            log_error (_("unable to create directory %s/%s/%s/%s"),
-                       opt.homedir, "tofu.d", type_str, prefix);
+            name_db = xstrconcat (gnupg_homedir (), "tofu.d",
+                                  type_str, prefix, NULL);
+            log_error (_("can't create directory '%s': %s\n"),
+                       name_db, gpg_strerror (rc));
+            xfree (name_db);
             goto out;
           }
 
         name_db = xstrconcat (name_sanitized, ".db", NULL);
         filename = make_filename
-          (opt.homedir, "tofu.d", type_str, prefix, name_db, NULL);
+          (gnupg_homedir (), "tofu.d", type_str, prefix, name_db, NULL);
         xfree (name_db);
       }
     }
@@ -1089,18 +946,18 @@ closedb (struct db *db)
   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);
+    log_assert (! db->next);
 
   if (db->type == DB_COMBINED)
     {
-      assert (opt.tofu_db_format == TOFU_DB_FLAT);
-      assert (! db->name[0]);
+      log_assert (opt.tofu_db_format == TOFU_DB_FLAT);
+      log_assert (! db->name[0]);
     }
   else
     {
-      assert (opt.tofu_db_format == TOFU_DB_SPLIT);
-      assert (db->type != DB_COMBINED);
-      assert (db->name[0]);
+      log_assert (opt.tofu_db_format == TOFU_DB_SPLIT);
+      log_assert (db->type != DB_COMBINED);
+      log_assert (db->name[0]);
     }
 
   if (db->batch_update)
@@ -1122,12 +979,17 @@ closedb (struct db *db)
 
 
 /* Create a new DB meta-handle.  Returns NULL on error.  */
-static struct dbs *
-opendbs (void)
+/* FIXME: Change to return an error code for better reporting by the
+   caller.  */
+static tofu_dbs_t
+opendbs (ctrl_t ctrl)
 {
+  if (ctrl->tofu.dbs)
+    return ctrl->tofu.dbs;
+
   if (opt.tofu_db_format == TOFU_DB_AUTO)
     {
-      char *filename = make_filename (opt.homedir, "tofu.db", NULL);
+      char *filename = make_filename (gnupg_homedir (), "tofu.db", NULL);
       struct stat s;
       int have_tofu_db = 0;
       int have_tofu_d = 0;
@@ -1162,37 +1024,51 @@ opendbs (void)
 
       if (have_tofu_db && have_tofu_d)
        {
-         log_info (_("Warning: Home directory contains both tofu.db and tofu.d.  Using split format for TOFU DB.\n"));
+         log_info (_("Warning: Home directory contains both tofu.db"
+                      " and tofu.d.\n"));
+          log_info (_("Using split format for TOFU database\n"));
          opt.tofu_db_format = TOFU_DB_SPLIT;
        }
       else if (have_tofu_db)
        {
          opt.tofu_db_format = TOFU_DB_FLAT;
          if (DBG_TRUST)
-           log_debug ("Using flat format for TOFU DB.\n");
+           log_debug ("Using flat format for TOFU database.\n");
        }
       else if (have_tofu_d)
        {
          opt.tofu_db_format = TOFU_DB_SPLIT;
          if (DBG_TRUST)
-           log_debug ("Using split format for TOFU DB.\n");
+           log_debug ("Using split format for TOFU database.\n");
        }
       else
        {
-         opt.tofu_db_format = TOFU_DB_SPLIT;
+         opt.tofu_db_format = TOFU_DB_FLAT;
          if (DBG_TRUST)
-           log_debug ("Using split format for TOFU DB.\n");
+           log_debug ("Using flat format for TOFU database.\n");
        }
     }
 
-  return xmalloc_clear (sizeof (struct dbs));
+  ctrl->tofu.dbs = xmalloc_clear (sizeof (struct tofu_dbs_s));
+  return ctrl->tofu.dbs;
 }
 
+
 /* Release all of the resources associated with a DB meta-handle.  */
-static void
-closedbs (struct dbs *dbs)
+void
+tofu_closedbs (ctrl_t ctrl)
 {
-  if (dbs->db)
+  tofu_dbs_t dbs = ctrl->tofu.dbs;
+
+  if (!dbs)
+    return;  /* Not initialized.  */
+
+  if (dbs->db && dbs->db->type == DB_COMBINED)
+    {
+      log_assert (!dbs->db->next);
+      closedb (dbs->db);
+    }
+  else if (dbs->db)
     {
       struct db *old_head = db_cache;
       struct db *db;
@@ -1204,10 +1080,10 @@ closedbs (struct dbs *dbs)
           /* When we leave batch mode we leave batch mode on any
              cached connections.  */
           if (! batch_update)
-            assert (! db->batch_update);
+            log_assert (! db->batch_update);
         }
       if (! batch_update)
-        assert (! db->batch_update);
+        log_assert (! db->batch_update);
 
       /* Join the two lists.  */
       db->next = db_cache;
@@ -1228,8 +1104,14 @@ closedbs (struct dbs *dbs)
              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;
+          if (skip < 0)
+            for (old_head = db_cache, skip = DB_CACHE_ENTRIES;
+                 skip > 0;
+                 old_head = old_head->next, skip--)
+              { /* Do nothing.  */ }
+          else
+            while (-- skip > 0)
+              old_head = old_head->next;
 
           *old_head->prevp = NULL;
 
@@ -1240,10 +1122,13 @@ closedbs (struct dbs *dbs)
               old_head = db;
               db_cache_count --;
             }
+
+          log_assert (db_cache_count == DB_CACHE_ENTRIES);
         }
     }
 
-  xfree (dbs);
+  xfree (ctrl->tofu.dbs);
+  ctrl->tofu.dbs = NULL;
 
 #if DEBUG_TOFU_CACHE
   log_debug ("Queries: %d (prepares saved: %d)\n",
@@ -1258,33 +1143,41 @@ static int
 get_single_long_cb (void *cookie, int argc, char **argv, char **azColName)
 {
   long *count = cookie;
-  char *tail = NULL;
 
   (void) azColName;
 
-  assert (argc == 1);
+  log_assert (argc == 1);
+
+  if (string_to_long (count, argv[0], 0, __LINE__))
+    return 1; /* Abort.  */
 
-  errno = 0;
-  *count = strtol (argv[0], &tail, 0);
-  if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0'))
-    /* Abort.  */
-    return 1;
   return 0;
 }
 
+static int
+get_single_long_cb2 (void *cookie, int argc, char **argv, char **azColName,
+                     sqlite3_stmt *stmt)
+{
+  (void) stmt;
+  return get_single_long_cb (cookie, argc, argv, azColName);
+}
 
 /* Record (or update) a trust policy about a (possibly new)
    binding.
 
    If SHOW_OLD is set, the binding's old policy is displayed.  */
 static gpg_error_t
-record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
+record_binding (tofu_dbs_t dbs, const char *fingerprint, const char *email,
                const char *user_id, enum tofu_policy policy, int show_old)
 {
+  char *fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
   struct db *db_email = NULL, *db_key = NULL;
-  int rc;
+  gpg_error_t rc;
   char *err = NULL;
-  enum tofu_policy policy_old = TOFU_POLICY_NONE;
+  /* policy_old needs to be a long and not an enum tofu_policy,
+     because we pass it by reference to get_single_long_cb2, which
+     expects a long.  */
+  long policy_old = TOFU_POLICY_NONE;
 
   if (! (policy == TOFU_POLICY_AUTO
         || policy == TOFU_POLICY_GOOD
@@ -1295,7 +1188,10 @@ record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
 
   db_email = getdb (dbs, email, DB_EMAIL);
   if (! db_email)
-    return gpg_error (GPG_ERR_GENERAL);
+    {
+      rc = gpg_error (GPG_ERR_GENERAL);
+      goto leave;
+    }
 
   if (opt.tofu_db_format == TOFU_DB_SPLIT)
     /* In the split format, we need to update two DBs.  To keep them
@@ -1303,35 +1199,32 @@ record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
        only place where we start two transaction and we always start
        transaction on the DB_KEY DB first, thus deadlock is not
        possible.  */
+    /* We only need a transaction for the split format.  */
     {
       db_key = getdb (dbs, fingerprint, DB_KEY);
       if (! db_key)
-       return gpg_error (GPG_ERR_GENERAL);
+        {
+          rc = gpg_error (GPG_ERR_GENERAL);
+          goto leave;
+        }
 
       rc = begin_transaction (db_email, 0);
       if (rc)
-        return gpg_error (GPG_ERR_GENERAL);
+        goto leave;
 
       rc = begin_transaction (db_key, 0);
       if (rc)
         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_stepx
+      rc = gpgsql_stepx
        (db_email->db, &db_email->s.record_binding_get_old_policy,
-         get_single_long_cb, &policy_old, &err,
+         get_single_long_cb2, &policy_old, &err,
         "select policy from bindings where fingerprint = ? and email = ?",
         SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
          SQLITE_ARG_END);
@@ -1362,7 +1255,13 @@ record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
     /* Nothing to do.  */
     goto out;
 
-  rc = sqlite3_stepx
+  if (opt.dry_run)
+    {
+      log_info ("TOFU database update skipped due to --dry-run\n");
+      goto out;
+    }
+
+  rc = gpgsql_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"
@@ -1378,10 +1277,9 @@ record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
      SQLITE_ARG_END);
   if (rc)
     {
-      log_error (_("error updating TOFU binding database"
-                  " (inserting <%s, %s> = %s): %s\n"),
-                fingerprint, email, tofu_policy_str (policy),
-                err);
+      log_error (_("error updating TOFU database: %s\n"), err);
+      print_further_info (" insert bindings <%s, %s> = %s",
+                          fingerprint, email, tofu_policy_str (policy));
       sqlite3_free (err);
       goto out;
     }
@@ -1389,9 +1287,9 @@ record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
   if (db_key)
     /* We also need to update the key DB.  */
     {
-      assert (opt.tofu_db_format == TOFU_DB_SPLIT);
+      log_assert (opt.tofu_db_format == TOFU_DB_SPLIT);
 
-      rc = sqlite3_stepx
+      rc = gpgsql_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"
@@ -1406,32 +1304,28 @@ record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
          SQLITE_ARG_STRING, user_id, SQLITE_ARG_END);
       if (rc)
        {
-         log_error (_("error updating TOFU binding database"
-                      " (inserting <%s, %s>): %s\n"),
-                    fingerprint, email, err);
+         log_error (_("error updating TOFU database: %s\n"), err);
+          print_further_info ("insert bindings <%s, %s>",
+                              fingerprint, email);
          sqlite3_free (err);
          goto out;
        }
     }
   else
-    assert (opt.tofu_db_format == TOFU_DB_FLAT);
+    log_assert (opt.tofu_db_format == TOFU_DB_FLAT);
 
  out:
   if (opt.tofu_db_format == TOFU_DB_SPLIT)
     /* We only need a transaction for the split format.  */
     {
-      int rc2;
+      gpg_error_t rc2;
 
       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"),
-                    err);
-         sqlite3_free (err);
-       }
+        sqlite3_free (err);
 
     out_revert_one:
       if (rc)
@@ -1439,16 +1333,13 @@ record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
       else
         rc2 = end_transaction (db_email, 0);
       if (rc2)
-       {
-         log_error (_("error ending transaction on TOFU database: %s\n"),
-                    err);
-         sqlite3_free (err);
-       }
+        sqlite3_free (err);
     }
 
-  if (rc)
-    return gpg_error (GPG_ERR_GENERAL);
-  return 0;
+ leave:
+  xfree (fingerprint_pp);
+
+  return rc;
 }
 
 
@@ -1483,6 +1374,15 @@ strings_collect_cb (void *cookie, int argc, char **argv, char **azColName)
   return 0;
 }
 
+static int
+strings_collect_cb2 (void *cookie, int argc, char **argv, char **azColName,
+                     sqlite3_stmt *stmt)
+{
+  (void) stmt;
+  return strings_collect_cb (cookie, argc, argv, azColName);
+
+}
+
 /* Auxiliary data structure to collect statistics about
    signatures.  */
 struct signature_stats
@@ -1538,45 +1438,31 @@ signature_stats_prepend (struct signature_stats **statsp,
      <fingerprint, policy, time ago, count>.  */
 static int
 signature_stats_collect_cb (void *cookie, int argc, char **argv,
-                           char **azColName)
+                           char **azColName, sqlite3_stmt *stmt)
 {
   struct signature_stats **statsp = cookie;
-  char *tail;
   int i = 0;
   enum tofu_policy policy;
   long time_ago;
   unsigned long count;
+  long along;
 
   (void) azColName;
+  (void) stmt;
 
   i ++;
 
-  tail = NULL;
-  errno = 0;
-  policy = 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;
-    }
+  if (string_to_long (&along, argv[i], 0, __LINE__))
+    return 1;  /* Abort */
+  policy = along;
   i ++;
 
   if (! argv[i])
     time_ago = 0;
   else
     {
-      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;
-        }
+      if (string_to_long (&time_ago, argv[i], 0, __LINE__))
+        return 1; /* Abort.  */
     }
   i ++;
 
@@ -1586,20 +1472,12 @@ signature_stats_collect_cb (void *cookie, int argc, char **argv,
     count = 0;
   else
     {
-      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;
-        }
+      if (string_to_ulong (&count, argv[i], 0, __LINE__))
+        return 1; /* Abort */
     }
   i ++;
 
-  assert (argc == i);
+  log_assert (argc == i);
 
   signature_stats_prepend (statsp, argv[0], policy, time_ago, count);
 
@@ -1620,45 +1498,21 @@ time_ago_scale (signed long t)
   return t / TIME_AGO_UNIT_LARGE;
 }
 
-/* Return the appropriate unit (respecting whether it is plural or
-   singular).  */
-const char *
-time_ago_unit (signed long t)
-{
-  signed long t_scaled = time_ago_scale (t);
-
-  if (t < TIME_AGO_UNIT_MEDIUM)
-    {
-      if (t_scaled == 1)
-       return TIME_AGO_UNIT_SMALL_NAME;
-      return TIME_AGO_UNIT_SMALL_NAME_PLURAL;
-    }
-  if (t < TIME_AGO_UNIT_LARGE)
-    {
-      if (t_scaled == 1)
-       return TIME_AGO_UNIT_MEDIUM_NAME;
-      return TIME_AGO_UNIT_MEDIUM_NAME_PLURAL;
-    }
-  if (t_scaled == 1)
-    return TIME_AGO_UNIT_LARGE_NAME;
-  return TIME_AGO_UNIT_LARGE_NAME_PLURAL;
-}
-
 
 /* 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 _tofu_GET_POLICY_ERROR if an error
    occurs.  */
 static enum tofu_policy
-get_policy (struct dbs *dbs, const char *fingerprint, const char *email,
+get_policy (tofu_dbs_t dbs, const char *fingerprint, const char *email,
            char **conflict)
 {
   struct db *db;
   int rc;
   char *err = NULL;
   strlist_t strlist = NULL;
-  char *tail = NULL;
   enum tofu_policy policy = _tofu_GET_POLICY_ERROR;
+  long along;
 
   db = getdb (dbs, email, DB_EMAIL);
   if (! db)
@@ -1668,8 +1522,8 @@ get_policy (struct dbs *dbs, const char *fingerprint, const char *email,
      (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_stepx (db->db, &db->s.get_policy_select_policy_and_conflict,
-                      strings_collect_cb, &strlist, &err,
+  rc = gpgsql_stepx (db->db, &db->s.get_policy_select_policy_and_conflict,
+                      strings_collect_cb2, &strlist, &err,
                       "select policy, conflict from bindings\n"
                       " where fingerprint = ? and email = ?",
                       SQLITE_ARG_STRING, fingerprint,
@@ -1677,9 +1531,8 @@ get_policy (struct dbs *dbs, const char *fingerprint, const char *email,
                       SQLITE_ARG_END);
   if (rc)
     {
-      log_error (_("error reading from TOFU database"
-                  " (checking for existing bad bindings): %s\n"),
-                err);
+      log_error (_("error reading TOFU database: %s\n"), err);
+      print_further_info ("checking for existing bad bindings");
       sqlite3_free (err);
       goto out;
     }
@@ -1693,23 +1546,24 @@ get_policy (struct dbs *dbs, const char *fingerprint, const char *email,
   else if (strlist_length (strlist) != 2)
     /* The result has the wrong form.  */
     {
-      log_error (_("error reading from TOFU database"
-                  " (checking for existing bad bindings):"
-                  " expected 2 results, got %d\n"),
-                strlist_length (strlist));
+      log_error (_("error reading TOFU database: %s\n"),
+                 gpg_strerror (GPG_ERR_BAD_DATA));
+      print_further_info ("checking for existing bad bindings:"
+                          " expected 2 results, got %d\n",
+                          strlist_length (strlist));
       goto out;
     }
 
   /* The result has the right form.  */
 
-  errno = 0;
-  policy = strtol (strlist->d, &tail, 0);
-  if (errno || *tail != '\0')
+  if (string_to_long (&along, strlist->d, 0, __LINE__))
     {
-      log_error (_("error reading from TOFU database: bad value for policy: %s\n"),
-                strlist->d);
+      log_error (_("error reading TOFU database: %s\n"),
+                 gpg_strerror (GPG_ERR_BAD_DATA));
+      print_further_info ("bad value for policy: %s", strlist->d);
       goto out;
     }
+  policy = along;
 
   if (! (policy == TOFU_POLICY_AUTO
         || policy == TOFU_POLICY_GOOD
@@ -1717,8 +1571,9 @@ get_policy (struct dbs *dbs, const char *fingerprint, const char *email,
         || policy == TOFU_POLICY_BAD
         || policy == TOFU_POLICY_ASK))
     {
-      log_error (_("TOFU DB is corrupted.  Invalid value for policy (%d).\n"),
-                policy);
+      log_error (_("error reading TOFU database: %s\n"),
+                 gpg_strerror (GPG_ERR_DB_CORRUPTED));
+      print_further_info ("invalid value for policy (%d)", policy);
       policy = _tofu_GET_POLICY_ERROR;
       goto out;
     }
@@ -1736,40 +1591,448 @@ get_policy (struct dbs *dbs, const char *fingerprint, const char *email,
     }
 
  out:
-  assert (policy == _tofu_GET_POLICY_ERROR
-         || policy == TOFU_POLICY_NONE
-         || policy == TOFU_POLICY_AUTO
-         || policy == TOFU_POLICY_GOOD
-         || policy == TOFU_POLICY_UNKNOWN
-         || policy == TOFU_POLICY_BAD
-         || policy == TOFU_POLICY_ASK);
+  log_assert (policy == _tofu_GET_POLICY_ERROR
+              || policy == TOFU_POLICY_NONE
+              || policy == TOFU_POLICY_AUTO
+              || policy == TOFU_POLICY_GOOD
+              || policy == TOFU_POLICY_UNKNOWN
+              || policy == TOFU_POLICY_BAD
+              || policy == TOFU_POLICY_ASK);
 
   free_strlist (strlist);
 
   return policy;
 }
 
-/* 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 _tofu_GET_TRUST_ERROR.
 
-   USER_ID is the unadultered user id.
+/* Format the first part of a conflict message and return that as a
+ * malloced string.  */
+static char *
+format_conflict_msg_part1 (int policy, const char *conflict,
+                           const char *fingerprint, const char *email)
+{
+  estream_t fp;
+  char *binding;
+  int binding_shown = 0;
+  char *tmpstr, *text;
+
+  binding = xasprintf ("<%s, %s>", fingerprint, email);
+
+  fp = es_fopenmem (0, "rw,samethread");
+  if (!fp)
+    log_fatal ("error creating memory stream: %s\n",
+               gpg_strerror (gpg_error_from_syserror()));
+
+  if (policy == TOFU_POLICY_NONE)
+    {
+      es_fprintf (fp, _("The binding %s is NOT known."), binding);
+      es_fputs ("  ", fp);
+      binding_shown = 1;
+    }
+  else if (policy == TOFU_POLICY_ASK
+           /* If there the conflict is with itself, then don't
+            * display this message.  */
+           && conflict && strcmp (conflict, fingerprint))
+    {
+      es_fprintf (fp,
+                  _("The key with fingerprint %s raised a conflict "
+                    "with the binding %s."
+                    "  Since this binding's policy was 'auto', it was "
+                    "changed to 'ask'."),
+                  conflict, binding);
+      es_fputs ("  ", fp);
+      binding_shown = 1;
+    }
+
+  /* TRANSLATORS: The %s%s is replaced by either a fingerprint and a
+     blank or by two empty strings.  */
+  es_fprintf (fp,
+              _("Please indicate whether you believe the binding %s%s"
+                "is legitimate (the key belongs to the stated owner) "
+                "or a forgery (bad)."),
+              binding_shown ? "" : binding,
+              binding_shown ? "" : " ");
+  es_fputc ('\n', fp);
+
+  xfree (binding);
+
+  es_fputc (0, fp);
+  if (es_fclose_snatch (fp, (void **)&tmpstr, NULL))
+    log_fatal ("error snatching memory stream\n");
+  text = format_text (tmpstr, 0, 72, 80);
+  es_free (tmpstr);
+
+  return text;
+}
+
+
+/* Ask the user about the binding.  There are three ways we could end
+ * up here:
+ *
+ *   - This is a new binding and there is a conflict
+ *     (policy == TOFU_POLICY_NONE && bindings_with_this_email_count > 0),
+ *
+ *   - This is a new binding and opt.tofu_default_policy is set to
+ *     ask.  (policy == TOFU_POLICY_NONE && opt.tofu_default_policy ==
+ *     TOFU_POLICY_ASK), or,
+ *
+ *   - The policy is ask (the user deferred last time) (policy ==
+ *     TOFU_POLICY_ASK).
+ */
+static void
+ask_about_binding (tofu_dbs_t dbs,
+                   struct db *db,
+                   enum tofu_policy *policy,
+                   int *trust_level,
+                   int bindings_with_this_email_count,
+                   strlist_t bindings_with_this_email,
+                   char *conflict,
+                   const char *fingerprint,
+                   const char *email,
+                   const char *user_id)
+{
+  char *sqerr = NULL;
+  int rc;
+  estream_t fp;
+  strlist_t other_user_ids = NULL;
+  struct signature_stats *stats = NULL;
+  struct signature_stats *stats_iter = NULL;
+  char *prompt;
+  char *choices;
+  struct db *db_key;
+
+  fp = es_fopenmem (0, "rw,samethread");
+  if (!fp)
+    log_fatal ("error creating memory stream: %s\n",
+               gpg_strerror (gpg_error_from_syserror()));
+
+  {
+    char *text = format_conflict_msg_part1 (*policy, conflict,
+                                            fingerprint, email);
+    es_fputs (text, fp);
+    es_fputc ('\n', fp);
+    xfree (text);
+  }
+
+  /* Find other user ids associated with this key and whether the
+   * bindings are marked as good or bad.  */
+  if (opt.tofu_db_format == TOFU_DB_SPLIT)
+    {
+      /* In the split format, we need to search in the fingerprint DB
+       * for all the emails associated with this key, not the email DB.  */
+      db_key = getdb (dbs, fingerprint, DB_KEY);
+    }
+  else
+    db_key = db;
+
+  if (db_key)
+    {
+      rc = gpgsql_stepx
+        (db_key->db, &db_key->s.get_trust_gather_other_user_ids,
+         strings_collect_cb2, &other_user_ids, &sqerr,
+         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"), sqerr);
+          sqlite3_free (sqerr);
+          sqerr = NULL;
+        }
+    }
+
+  if (other_user_ids)
+    {
+      strlist_t strlist_iter;
+
+      es_fprintf (fp, _("Known user IDs associated with this key:\n"));
+      for (strlist_iter = other_user_ids;
+           strlist_iter;
+           strlist_iter = strlist_iter->next)
+        {
+          char *other_user_id = strlist_iter->d;
+          char *other_thing;
+          enum tofu_policy other_policy;
+
+          log_assert (strlist_iter->next);
+          strlist_iter = strlist_iter->next;
+          other_thing = strlist_iter->d;
+
+          if (opt.tofu_db_format == TOFU_DB_SPLIT)
+            other_policy = get_policy (dbs, fingerprint, other_thing, NULL);
+          else
+            other_policy = atoi (other_thing);
+
+          es_fprintf (fp, "  %s (", other_user_id);
+          es_fprintf (fp, _("policy: %s"), tofu_policy_str (other_policy));
+          es_fprintf (fp, ")\n");
+        }
+      es_fprintf (fp, "\n");
+
+      free_strlist (other_user_ids);
+    }
+
+  /* Find other keys associated with this email address.  */
+  /* FIXME: 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 = gpgsql_stepx
+    (db->db, &db->s.get_trust_gather_other_keys,
+     signature_stats_collect_cb, &stats, &sqerr,
+     "select fingerprint, policy, time_ago, count(*)\n"
+     " from (select bindings.*,\n"
+     "        case\n"
+     /* From the future (but if its just a couple of hours in the
+      * future don't turn it into a warning)?  Or should we use
+      * 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 < -("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 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 = ? asc, fingerprint desc, time_ago desc;\n",
+     SQLITE_ARG_STRING, email, SQLITE_ARG_STRING, fingerprint,
+     SQLITE_ARG_END);
+  if (rc)
+    {
+      strlist_t strlist_iter;
+
+      log_error (_("error gathering signature stats: %s\n"), sqerr);
+      sqlite3_free (sqerr);
+      sqerr = NULL;
+
+      es_fprintf (fp, ngettext("The email address \"%s\" is"
+                               " associated with %d key:\n",
+                               "The email address \"%s\" is"
+                               " associated with %d keys:\n",
+                               bindings_with_this_email_count),
+                  email, bindings_with_this_email_count);
+      for (strlist_iter = bindings_with_this_email;
+           strlist_iter;
+           strlist_iter = strlist_iter->next)
+        es_fprintf (fp, "  %s\n", strlist_iter->d);
+    }
+  else
+    {
+      char *key = NULL;
+
+      if (! stats || strcmp (stats->fingerprint, fingerprint))
+        {
+          /* If we have already added this key to the DB, then it will
+           * be first (see the above select).  Since the first key on
+           * the list is not this key, we must not yet have verified any
+           * messages signed by this key.  Add a dummy entry.  */
+          signature_stats_prepend (&stats, fingerprint, TOFU_POLICY_AUTO, 0, 0);
+        }
+
+      es_fprintf (fp, _("Statistics for keys with the email address \"%s\":\n"),
+                  email);
+      for (stats_iter = stats; stats_iter; stats_iter = stats_iter->next)
+        {
+          if (! key || strcmp (key, stats_iter->fingerprint))
+            {
+              int this_key;
+              char *key_pp;
+
+              key = stats_iter->fingerprint;
+              this_key = strcmp (key, fingerprint) == 0;
+              key_pp = format_hexfingerprint (key, NULL, 0);
+              es_fprintf (fp, "  %s (", key_pp);
+              if (this_key)
+                es_fprintf (fp, _("this key"));
+              else
+                es_fprintf (fp, _("policy: %s"),
+                            tofu_policy_str (stats_iter->policy));
+              es_fputs ("):\n", fp);
+              xfree (key_pp);
+            }
+
+          es_fputs ("    ", fp);
+          if (stats_iter->time_ago == -1)
+            es_fprintf (fp, ngettext("%ld message signed in the future.",
+                                     "%ld messages signed in the future.",
+                                     stats_iter->count), stats_iter->count);
+          else
+            {
+              long t_scaled = time_ago_scale (stats_iter->time_ago);
+
+              /* TANSLATORS: This string is concatenated with one of
+               * the day/week/month strings to form one sentence.  */
+              es_fprintf (fp, ngettext("%ld message signed",
+                                       "%ld messages signed",
+                                       stats_iter->count), stats_iter->count);
+              if (!stats_iter->count)
+                es_fputs (".", fp);
+              else if (stats_iter->time_ago < TIME_AGO_UNIT_MEDIUM)
+                es_fprintf (fp, ngettext(" over the past %ld day.",
+                                         " over the past %ld days.",
+                                         t_scaled), t_scaled);
+              else if (stats_iter->time_ago < TIME_AGO_UNIT_LARGE)
+                es_fprintf (fp, ngettext(" over the past %ld week.",
+                                         " over the past %ld weeks.",
+                                         t_scaled), t_scaled);
+              else
+                es_fprintf (fp, ngettext(" over the past %ld month.",
+                                         " over the past %ld months.",
+                                         t_scaled), t_scaled);
+            }
+          es_fputs ("\n", fp);
+        }
+    }
+
+
+  if ((*policy == TOFU_POLICY_NONE && bindings_with_this_email_count > 0)
+      || (*policy == TOFU_POLICY_ASK && conflict))
+    {
+      /* This is a conflict.  */
+
+      /* TRANSLATORS: Please translate the text found in the source
+       * file below.  We don't directly internationalize that text so
+       * that we can tweak it without breaking translations.  */
+      char *text = _("TOFU detected a binding conflict");
+      char *textbuf;
+      if (!strcmp (text, "TOFU detected a binding conflict"))
+        {
+          /* 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.";
+        }
+      textbuf = format_text (text, 0, 72, 80);
+      es_fprintf (fp, "\n%s\n", text);
+      xfree (textbuf);
+    }
+
+  es_fputc ('\n', fp);
+
+  /* Add a NUL terminator.  */
+  es_fputc (0, fp);
+  if (es_fclose_snatch (fp, (void **) &prompt, NULL))
+    log_fatal ("error snatching memory stream\n");
+
+  /* I think showing the large message once is sufficient.  If we
+   * would move it right before the cpr_get many lines will scroll
+   * away and the user might not realize that he merely entered a
+   * wrong choise (because he does not see that either).  As a small
+   * benefit we allow C-L to redisplay everything.  */
+  tty_printf ("%s", prompt);
+  while (1)
+    {
+      char *response;
+
+      /* TRANSLATORS: Two letters (normally the lower and upper case
+       * version of the hotkey) for each of the five choices.  If
+       * there is only one choice in your language, repeat it.  */
+      choices = _("gG" "aA" "uU" "rR" "bB");
+      if (strlen (choices) != 10)
+        log_bug ("Bad TOFU conflict translation!  Please report.");
+
+      response = cpr_get
+        ("tofu.conflict",
+         _("(G)ood, (A)ccept once, (U)nknown, (R)eject once, (B)ad? "));
+      trim_spaces (response);
+      cpr_kill_prompt ();
+      if (*response == CONTROL_L)
+        tty_printf ("%s", prompt);
+      else if (strlen (response) == 1)
+        {
+          char *choice = strchr (choices, *response);
+          if (choice)
+            {
+              int c = ((size_t) choice - (size_t) choices) / 2;
+
+              switch (c)
+                {
+                case 0: /* Good.  */
+                  *policy = TOFU_POLICY_GOOD;
+                  *trust_level = tofu_policy_to_trust_level (*policy);
+                  break;
+                case 1: /* Accept once.  */
+                  *policy = TOFU_POLICY_ASK;
+                  *trust_level = tofu_policy_to_trust_level (TOFU_POLICY_GOOD);
+                  break;
+                case 2: /* Unknown.  */
+                  *policy = TOFU_POLICY_UNKNOWN;
+                  *trust_level = tofu_policy_to_trust_level (*policy);
+                  break;
+                case 3: /* Reject once.  */
+                  *policy = TOFU_POLICY_ASK;
+                  *trust_level = tofu_policy_to_trust_level (TOFU_POLICY_BAD);
+                  break;
+                case 4: /* Bad.  */
+                  *policy = TOFU_POLICY_BAD;
+                  *trust_level = tofu_policy_to_trust_level (*policy);
+                  break;
+                default:
+                  log_bug ("c should be between 0 and 4 but it is %d!", c);
+                }
+
+              if (record_binding (dbs, fingerprint, email, user_id,
+                                  *policy, 0))
+                {
+                  /* If there's an error registering the
+                   * binding, don't save the signature.  */
+                  *trust_level = _tofu_GET_TRUST_ERROR;
+                }
+              break;
+            }
+        }
+      xfree (response);
+    }
+
+  xfree (prompt);
+
+  signature_stats_free (stats);
+}
+
 
-   If MAY_ASK is set, then we may interact with the user.  This is
-   necessary if there is a conflict or the binding's policy is
-   TOFU_POLICY_ASK.  In the case of a conflict, we set the new
-   conflicting binding's policy to TOFU_POLICY_ASK.  In either case,
-   we return TRUST_UNDEFINED.  */
+/* 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 _tofu_GET_TRUST_ERROR.
+ *
+ * PK is the public key object for FINGERPRINT.
+ *
+ * USER_ID is the unadulterated user id.
+ *
+ * If MAY_ASK is set, then we may interact with the user.  This is
+ * necessary if there is a conflict or the binding's policy is
+ * TOFU_POLICY_ASK.  In the case of a conflict, we set the new
+ * conflicting binding's policy to TOFU_POLICY_ASK.  In either case,
+ * we return TRUST_UNDEFINED.  */
 static enum tofu_policy
-get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
+get_trust (tofu_dbs_t dbs, PKT_public_key *pk,
+           const char *fingerprint, const char *email,
           const char *user_id, int may_ask)
 {
   struct db *db;
   enum tofu_policy policy;
   char *conflict = NULL;
   int rc;
-  char *err = NULL;
+  char *sqerr = NULL;
   strlist_t bindings_with_this_email = NULL;
   int bindings_with_this_email_count;
   int change_conflicting_to_ask = 0;
@@ -1780,13 +2043,13 @@ get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
 
   /* Make sure _tofu_GET_TRUST_ERROR isn't equal to any of the trust
      levels.  */
-  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);
+  log_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)
@@ -1794,47 +2057,20 @@ get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
 
   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.
+    { /* See if the key is ultimately trusted.  If so, we're done.  */
+      u32 kid[2];
 
-         FINGERPRINT has the form:
+      keyid_from_pk (pk, kid);
 
-           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 (tdb_keyid_is_utk (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");
+                  log_error (_("error setting TOFU binding's trust level"
+                               " to %s\n"), "auto");
                   trust_level = _tofu_GET_TRUST_ERROR;
                   goto out;
                 }
@@ -1860,7 +2096,7 @@ get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
     case TOFU_POLICY_UNKNOWN:
     case TOFU_POLICY_BAD:
       /* The saved judgement is auto -> auto, good, unknown or bad.
-        We don't need to ask the user anything.  */
+       * We don't need to ask the user anything.  */
       if (DBG_TRUST)
        log_debug ("TOFU: Known binding <%s, %s>'s policy: %s\n",
                   fingerprint, email, tofu_policy_str (policy));
@@ -1879,7 +2115,7 @@ get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
 
     case TOFU_POLICY_NONE:
       /* The binding is new, we need to check for conflicts.  Case #3
-        below.  */
+       * below.  */
       break;
 
     case _tofu_GET_POLICY_ERROR:
@@ -1892,53 +2128,54 @@ get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
 
 
   /* We get here if:
-
-       1. The saved policy is auto and the default policy is ask
-          (get_policy() == TOFU_POLICY_AUTO
-           && opt.tofu_default_policy == TOFU_POLICY_ASK)
-
-       2. The saved policy is ask (either last time the user selected
-          accept once or reject once or there was a conflict and this
-          binding's policy was changed from auto to ask)
-         (policy == TOFU_POLICY_ASK), or,
-
-       3. We don't have a saved policy (policy == TOFU_POLICY_NONE)
-          (need to check for a conflict).
+   *
+   *   1. The saved policy is auto and the default policy is ask
+   *      (get_policy() == TOFU_POLICY_AUTO
+   *       && opt.tofu_default_policy == TOFU_POLICY_ASK)
+   *
+   *   2. The saved policy is ask (either last time the user selected
+   *      accept once or reject once or there was a conflict and this
+   *      binding's policy was changed from auto to ask)
+   *      (policy == TOFU_POLICY_ASK), or,
+   *
+   *   3. We don't have a saved policy (policy == TOFU_POLICY_NONE)
+   *      (need to check for a conflict).
    */
 
   /* 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_stepx
+   *
+   * 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 = gpgsql_stepx
     (db->db, &db->s.get_trust_bindings_with_this_email,
-     strings_collect_cb, &bindings_with_this_email, &err,
+     strings_collect_cb2, &bindings_with_this_email, &sqerr,
      "select distinct fingerprint from bindings where email = ?;",
      SQLITE_ARG_STRING, email, SQLITE_ARG_END);
   if (rc)
     {
-      log_error (_("error reading from TOFU database"
-                  " (listing fingerprints): %s\n"),
-                err);
-      sqlite3_free (err);
+      log_error (_("error reading TOFU database: %s\n"), sqerr);
+      print_further_info ("listing fingerprints");
+      sqlite3_free (sqerr);
       goto out;
     }
 
   bindings_with_this_email_count = strlist_length (bindings_with_this_email);
   if (bindings_with_this_email_count == 0
       && opt.tofu_default_policy != TOFU_POLICY_ASK)
-    /* New binding with no conflict and a concrete default policy.
-
-       We've never observed a binding with this email address
-       (BINDINGS_WITH_THIS_EMAIL_COUNT is 0 and the above query would return
-       the current binding if it were in the DB) and we have a default
-       policy, which is not to ask the user.  */
     {
+      /* New binding with no conflict and a concrete default policy.
+       *
+       * We've never observed a binding with this email address
+       * BINDINGS_WITH_THIS_EMAIL_COUNT is 0 and the above query would
+       * return the current binding if it were in the DB) and we have
+       * a default policy, which is not to ask the user.
+       */
+
       /* If we've seen this binding, then we've seen this email and
         policy couldn't possibly be TOFU_POLICY_NONE.  */
-      assert (policy == TOFU_POLICY_NONE);
+      log_assert (policy == TOFU_POLICY_NONE);
 
       if (DBG_TRUST)
        log_debug ("TOFU: New binding <%s, %s>, no conflict.\n",
@@ -1958,19 +2195,21 @@ get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
     }
 
   if (policy == TOFU_POLICY_NONE)
-    /* This is a new binding and we have a conflict.  Mark any
-       conflicting bindings that have an automatic policy as now
-       requiring confirmation.  Note: we delay this until after we ask
-       for confirmation so that when the current policy is printed, it
-       is correct.  */
-    change_conflicting_to_ask = 1;
+    {
+      /* This is a new binding and we have a conflict.  Mark any
+       * conflicting bindings that have an automatic policy as now
+       * requiring confirmation.  Note: we delay this until after we
+       * ask for confirmation so that when the current policy is
+       * printed, it is correct.  */
+      change_conflicting_to_ask = 1;
+    }
 
   if (! may_ask)
-    /* We can only get here in the third case (no saved policy) and if
-       there is a conflict.  (If the policy was ask (cases #1 and #2)
-       and we weren't allowed to ask, we'd have already exited).  */
     {
-      assert (policy == TOFU_POLICY_NONE);
+      /* We can only get here in the third case (no saved policy) and
+       * if there is a conflict.  (If the policy was ask (cases #1 and
+       * #2) and we weren't allowed to ask, we'd have already exited).  */
+      log_assert (policy == TOFU_POLICY_NONE);
 
       if (record_binding (dbs, fingerprint, email, user_id,
                          TOFU_POLICY_ASK, 0) != 0)
@@ -1981,360 +2220,233 @@ get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
       goto out;
     }
 
-  /* If we get here, we need to ask the user about the binding.  There
-     are three ways we could end up here:
-
-       - This is a new binding and there is a conflict
-         (policy == TOFU_POLICY_NONE && bindings_with_this_email_count > 0),
-
-       - This is a new binding and opt.tofu_default_policy is set to
-         ask.  (policy == TOFU_POLICY_NONE && opt.tofu_default_policy ==
-         TOFU_POLICY_ASK), or,
+  /* If we get here, we need to ask the user about the binding.  */
+  ask_about_binding (dbs, db,
+                     &policy,
+                     &trust_level,
+                     bindings_with_this_email_count,
+                     bindings_with_this_email,
+                     conflict,
+                     fingerprint,
+                     email,
+                     user_id);
 
-       - The policy is ask (the user deferred last time) (policy ==
-         TOFU_POLICY_ASK).
-   */
-  {
-    int is_conflict =
-      ((policy == TOFU_POLICY_NONE && bindings_with_this_email_count > 0)
-       || (policy == TOFU_POLICY_ASK && conflict));
-    estream_t fp;
-    char *binding;
-    int binding_shown;
-    strlist_t other_user_ids = NULL;
-    struct signature_stats *stats = NULL;
-    struct signature_stats *stats_iter = NULL;
-    char *prompt;
-    char *choices;
-
-    fp = es_fopenmem (0, "rw,samethread");
-    if (! fp)
-      log_fatal ("Error creating memory stream\n");
-
-    binding = xasprintf ("<%s, %s>", fingerprint, email);
-    binding_shown = 0;
-
-    if (policy == TOFU_POLICY_NONE)
-      {
-       es_fprintf (fp, _("The binding %s is NOT known.  "), binding);
-       binding_shown = 1;
-      }
-    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,
-                   _("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,
-               _("Please indicate whether you believe the binding %s%s"
-                 "is legitimate (the key belongs to the stated owner) "
-                 "or a forgery (bad).\n\n"),
-               binding_shown ? "" : binding,
-               binding_shown ? "" : " ");
-
-    xfree (binding);
-
-    /* Find other user ids associated with this key and whether the
-       bindings are marked as good or bad.  */
+ out:
+  if (change_conflicting_to_ask)
     {
-      struct db *db_key;
-
-      if (opt.tofu_db_format == TOFU_DB_SPLIT)
-       /* In the split format, we need to search in the fingerprint
-          DB for all the emails associated with this key, not the
-          email DB.  */
-       db_key = getdb (dbs, fingerprint, DB_KEY);
+      if (! may_ask)
+        {
+          /* If we weren't allowed to ask, also update this key as
+             conflicting with itself.  */
+          rc = gpgsql_exec_printf
+            (db->db, NULL, NULL, &sqerr,
+             "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
-       db_key = db;
+        {
+          rc = gpgsql_exec_printf
+            (db->db, NULL, NULL, &sqerr,
+             "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 (db_key)
+      if (rc)
        {
-         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);
-             sqlite3_free (err);
-             err = NULL;
-           }
+         log_error (_("error changing TOFU policy: %s\n"), sqerr);
+         sqlite3_free (sqerr);
+          sqerr = NULL;
        }
     }
 
-    if (other_user_ids)
-      {
-       strlist_t strlist_iter;
-
-       es_fprintf (fp, _("Known user ids associated with this key:\n"));
-       for (strlist_iter = other_user_ids;
-            strlist_iter;
-            strlist_iter = strlist_iter->next)
-         {
-           char *other_user_id = strlist_iter->d;
-           char *other_thing;
-           enum tofu_policy other_policy;
-
-           assert (strlist_iter->next);
-           strlist_iter = strlist_iter->next;
-           other_thing = strlist_iter->d;
-
-           if (opt.tofu_db_format == TOFU_DB_SPLIT)
-             other_policy = get_policy (dbs, fingerprint, other_thing, NULL);
-           else
-             other_policy = atoi (other_thing);
-
-           es_fprintf (fp, _("  %s (policy: %s)\n"),
-                       other_user_id,
-                       tofu_policy_str (other_policy));
-         }
-       es_fprintf (fp, "\n");
-
-       free_strlist (other_user_ids);
-      }
+  xfree (conflict);
+  free_strlist (bindings_with_this_email);
 
-    /* Find other keys associated with this email address.  */
-    /* 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_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"
-       /* From the future (but if its just a couple of hours in the
-         future don't turn it into a warning)?  Or should we use
-         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 < -("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 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 = ? asc, fingerprint desc, time_ago desc;\n",
-       SQLITE_ARG_STRING, email, SQLITE_ARG_STRING, fingerprint,
-       SQLITE_ARG_END);
-    if (rc)
-      {
-       strlist_t strlist_iter;
-
-       log_error (_("error gathering signature stats: %s.\n"),
-                  err);
-       sqlite3_free (err);
-       err = NULL;
-
-       es_fprintf
-         (fp, _("The email address (%s) is associated with %d keys:\n"),
-          email, bindings_with_this_email_count);
-       for (strlist_iter = bindings_with_this_email;
-            strlist_iter;
-            strlist_iter = strlist_iter->next)
-         es_fprintf (fp, _("  %s\n"), strlist_iter->d);
-      }
-    else
-      {
-       char *key = NULL;
-
-       if (! stats || strcmp (stats->fingerprint, fingerprint) != 0)
-         /* If we have already added this key to the DB, then it will
-            be first (see the above select).  Since the first key on
-            the list is not this key, we must not yet have verified
-            any messages signed by this key.  Add a dummy entry.  */
-         signature_stats_prepend (&stats, fingerprint, TOFU_POLICY_AUTO, 0, 0);
-
-       es_fprintf (fp, _("Statistics for keys with the email '%s':\n"),
-                   email);
-       for (stats_iter = stats; stats_iter; stats_iter = stats_iter->next)
-         {
-           if (! key || strcmp (key, stats_iter->fingerprint) != 0)
-             {
-               int this_key;
-               key = stats_iter->fingerprint;
-               this_key = strcmp (key, fingerprint) == 0;
-               if (this_key)
-                 es_fprintf (fp, _("  %s (this key):"), key);
-               else
-                 es_fprintf (fp, _("  %s (policy: %s):"),
-                             key, tofu_policy_str (stats_iter->policy));
-               es_fprintf (fp, "\n");
-             }
-
-           if (stats_iter->time_ago == -1)
-             es_fprintf (fp, _("    %ld %s signed in the future.\n"),
-                         stats_iter->count,
-                         stats_iter->count == 1
-                         ? _("message") : _("messages"));
-           else if (stats_iter->count == 0)
-             es_fprintf (fp, _("    0 signed messages.\n"));
-           else
-             es_fprintf (fp, _("    %ld %s signed over the past %ld %s.\n"),
-                         stats_iter->count,
-                         stats_iter->count == 1
-                         ? _("message") : _("messages"),
-                         time_ago_scale (stats_iter->time_ago),
-                         time_ago_unit (stats_iter->time_ago));
-         }
-      }
+  return trust_level;
+}
 
-    if (is_conflict)
-      {
-       /* TRANSLATORS: translate the below text.  We don't directly
-          internationalize that text so that we can tweak it without
-          breaking translations.  */
-       char *text = _("TOFU detected a binding conflict");
-       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.";
-       es_fprintf (fp, "\n%s\n", text);
-      }
 
-    es_fputc ('\n', fp);
-    /* TRANSLATORS: Two letters (normally the lower and upper case
-       version of the hotkey) for each of the five choices.  If there
-       is only one choice in your language, repeat it.  */
-    choices = _("gG" "aA" "uU" "rR" "bB");
-    es_fprintf (fp, _("(G)ood/(A)ccept once/(U)nknown/(R)eject once/(B)ad? "));
-
-    /* Add a NUL terminator.  */
-    es_fputc (0, fp);
-    if (es_fclose_snatch (fp, (void **) &prompt, NULL))
-      log_fatal ("error snatching memory stream\n");
-
-    while (1)
-      {
-       char *response;
-
-       if (strlen (choices) != 10)
-         log_bug ("Bad TOFU conflict translation!  Please report.");
-
-       response = cpr_get ("tofu conflict", prompt);
-       trim_spaces (response);
-       cpr_kill_prompt ();
-       if (strlen (response) == 1)
-         {
-           char *choice = strchr (choices, *response);
-           if (choice)
-             {
-               int c = ((size_t) choice - (size_t) choices) / 2;
-               assert (0 <= c && c <= 4);
-
-               switch (c)
-                 {
-                 case 0: /* Good.  */
-                   policy = TOFU_POLICY_GOOD;
-                   trust_level = tofu_policy_to_trust_level (policy);
-                   break;
-                 case 1: /* Accept once.  */
-                   policy = TOFU_POLICY_ASK;
-                   trust_level =
-                     tofu_policy_to_trust_level (TOFU_POLICY_GOOD);
-                   break;
-                 case 2: /* Unknown.  */
-                   policy = TOFU_POLICY_UNKNOWN;
-                   trust_level = tofu_policy_to_trust_level (policy);
-                   break;
-                 case 3: /* Reject once.  */
-                   policy = TOFU_POLICY_ASK;
-                   trust_level =
-                     tofu_policy_to_trust_level (TOFU_POLICY_BAD);
-                   break;
-                 case 4: /* Bad.  */
-                   policy = TOFU_POLICY_BAD;
-                   trust_level = tofu_policy_to_trust_level (policy);
-                   break;
-                 default:
-                   log_bug ("c should be between 0 and 4 but it is %d!", c);
-                 }
-
-               if (record_binding (dbs, fingerprint, email, user_id,
-                                   policy, 0) != 0)
-                 /* If there's an error registering the
-                    binding, don't save the signature.  */
-                 trust_level = _tofu_GET_TRUST_ERROR;
-
-               break;
-             }
-         }
-       xfree (response);
-      }
+/* Return a malloced string of the form
+ *    "7 months, 1 day, 5 minutes, 0 seconds"
+ * The caller should replace all '~' in the returned string by a space
+ * and also free the returned string.
+ *
+ * This is actually a bad hack which may not work correctly with all
+ * languages.
+ */
+static char *
+time_ago_str (long long int t)
+{
+  estream_t fp;
+  int years = 0;
+  int months = 0;
+  int days = 0;
+  int hours = 0;
+  int minutes = 0;
+  int seconds = 0;
+
+  /* The number of units that we've printed so far.  */
+  int count = 0;
+  /* The first unit that we printed (year = 0, month = 1,
+     etc.).  */
+  int first = -1;
+  /* The current unit.  */
+  int i = 0;
 
-    xfree (prompt);
+  char *str;
 
-    signature_stats_free (stats);
-  }
+  /* It would be nice to use a macro to do this, but gettext
+     works on the unpreprocessed code.  */
+#define MIN_SECS (60)
+#define HOUR_SECS (60 * MIN_SECS)
+#define DAY_SECS (24 * HOUR_SECS)
+#define MONTH_SECS (30 * DAY_SECS)
+#define YEAR_SECS (365 * DAY_SECS)
 
- out:
-  if (change_conflicting_to_ask)
+  if (t > YEAR_SECS)
     {
-      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);
-         sqlite3_free (err);
-         goto out;
-       }
+      years = t / YEAR_SECS;
+      t -= years * YEAR_SECS;
+    }
+  if (t > MONTH_SECS)
+    {
+      months = t / MONTH_SECS;
+      t -= months * MONTH_SECS;
     }
+  if (t > DAY_SECS)
+    {
+      days = t / DAY_SECS;
+      t -= days * DAY_SECS;
+    }
+  if (t > HOUR_SECS)
+    {
+      hours = t / HOUR_SECS;
+      t -= hours * HOUR_SECS;
+    }
+  if (t > MIN_SECS)
+    {
+      minutes = t / MIN_SECS;
+      t -= minutes * MIN_SECS;
+    }
+  seconds = t;
 
-  xfree (conflict);
-  free_strlist (bindings_with_this_email);
+#undef MIN_SECS
+#undef HOUR_SECS
+#undef DAY_SECS
+#undef MONTH_SECS
+#undef YEAR_SECS
 
-  return trust_level;
+  fp = es_fopenmem (0, "rw,samethread");
+  if (! fp)
+    log_fatal ("error creating memory stream: %s\n",
+               gpg_strerror (gpg_error_from_syserror()));
+
+  if (years)
+    {
+      /* TRANSLATORS: The tilde ('~') is used here to indicate a
+       * non-breakable space  */
+      es_fprintf (fp, ngettext("%d~year", "%d~years", years), years);
+      count ++;
+      first = i;
+    }
+  i ++;
+  if ((first == -1 || i - first <= 3) && months)
+    {
+      if (count)
+        es_fprintf (fp, ", ");
+      es_fprintf (fp, ngettext("%d~month", "%d~months", months), months);
+      count ++;
+      first = i;
+    }
+  i ++;
+  if ((first == -1 || i - first <= 3) && count < 2 && days)
+    {
+      if (count)
+        es_fprintf (fp, ", ");
+      es_fprintf (fp, ngettext("%d~day", "%d~days", days), days);
+      count ++;
+      first = i;
+    }
+  i ++;
+  if ((first == -1 || i - first <= 3) && count < 2 && hours)
+    {
+      if (count)
+        es_fprintf (fp, ", ");
+      es_fprintf (fp, ngettext("%d~hour", "%d~hours", hours), hours);
+      count ++;
+      first = i;
+    }
+  i ++;
+  if ((first == -1 || i - first <= 3) && count < 2 && minutes)
+    {
+      if (count)
+        es_fprintf (fp, ", ");
+      es_fprintf (fp, ngettext("%d~minute", "%d~minutes", minutes), minutes);
+      count ++;
+      first = i;
+    }
+  i ++;
+  if ((first == -1 || i - first <= 3) && count < 2)
+    {
+      if (count)
+        es_fprintf (fp, ", ");
+      es_fprintf (fp, ngettext("%d~second", "%d~seconds", seconds), seconds);
+    }
+
+  es_fputc (0, fp);
+  if (es_fclose_snatch (fp, (void **) &str, NULL))
+    log_fatal ("error snatching memory stream\n");
+
+  return str;
 }
 
+
+/* Write TOFU_STATS status line.  */
 static void
-show_statistics (struct dbs *dbs, const char *fingerprint,
+write_stats_status (long messages, enum tofu_policy policy,
+                    long first_seen_ago, long most_recent_seen_ago)
+{
+  char numbuf1[35];
+  char numbuf2[35];
+  char numbuf3[35];
+  const char *validity;
+
+  if (messages < 1)
+    validity = "1"; /* Key without history.  */
+  else if (messages < BASIC_TRUST_THRESHOLD)
+    validity = "2"; /* Key with too little history.  */
+  else if (messages < FULL_TRUST_THRESHOLD)
+    validity = "3"; /* Key with enough history for basic trust.  */
+  else
+    validity = "4"; /* Key with a lot of history.  */
+
+  snprintf (numbuf1, sizeof numbuf1, " %ld", messages);
+  *numbuf2 = *numbuf3 = 0;
+  if (first_seen_ago >= 0 && most_recent_seen_ago >= 0)
+    {
+      snprintf (numbuf2, sizeof numbuf2, " %ld", first_seen_ago);
+      snprintf (numbuf3, sizeof numbuf3, " %ld", most_recent_seen_ago);
+    }
+
+  write_status_strings (STATUS_TOFU_STATS,
+                        validity, numbuf1, " 0",
+                        " ", tofu_policy_str (policy),
+                        numbuf2, numbuf3,
+                        NULL);
+}
+
+static void
+show_statistics (tofu_dbs_t dbs, const char *fingerprint,
                 const char *email, const char *user_id,
                 const char *sig_exclude)
 {
   struct db *db;
+  char *fingerprint_pp;
   int rc;
   strlist_t strlist = NULL;
   char *err = NULL;
@@ -2343,9 +2455,12 @@ show_statistics (struct dbs *dbs, const char *fingerprint,
   if (! db)
     return;
 
-  rc = sqlite3_exec_printf
+  fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
+
+  rc = gpgsql_exec_printf
     (db->db, strings_collect_cb, &strlist, &err,
-     "select count (*), strftime('%%s','now') - min (signatures.time)\n"
+     "select count (*), strftime('%%s','now') - min (signatures.time),\n"
+     "  strftime('%%s','now') - max (signatures.time)\n"
      " from signatures\n"
      " left join bindings on signatures.binding = bindings.oid\n"
      " where fingerprint = %Q and email = %Q and sig_digest %s%s%s;",
@@ -2357,248 +2472,174 @@ show_statistics (struct dbs *dbs, const char *fingerprint,
      sig_exclude ? "'" : "");
   if (rc)
     {
-      log_error (_("error reading from TOFU database"
-                  " (getting statistics): %s\n"),
-                err);
+      log_error (_("error reading TOFU database: %s\n"), err);
+      print_further_info ("getting statistics");
       sqlite3_free (err);
       goto out;
     }
 
+  write_status_text_and_buffer (STATUS_TOFU_USER, fingerprint,
+                                email, strlen (email), 0);
+
   if (! strlist)
-    log_info (_("Have never verified a message signed by key %s!\n"),
-             fingerprint);
+    {
+      log_info (_("Have never verified a message signed by key %s!\n"),
+                fingerprint_pp);
+      write_stats_status (0,  TOFU_POLICY_NONE, -1, -1);
+    }
   else
     {
-      char *tail = NULL;
       signed long messages;
       signed long first_seen_ago;
+      signed long most_recent_seen_ago;
 
-      assert (strlist_length (strlist) == 2);
+      log_assert (strlist_length (strlist) == 3);
 
-      errno = 0;
-      messages = strtol (strlist->d, &tail, 0);
-      if (errno || *tail != '\0')
-       /* Abort.  */
-       {
-         log_debug ("%s:%d: Couldn't convert %s (messages) to an int: %s.\n",
-                    __func__, __LINE__, strlist->d, strerror (errno));
-         messages = -1;
-       }
+      string_to_long (&messages, strlist->d, -1, __LINE__);
 
       if (messages == 0 && *strlist->next->d == '\0')
-       /* min(NULL) => NULL => "".  */
-       first_seen_ago = -1;
+        { /* min(NULL) => NULL => "".  */
+          first_seen_ago = -1;
+          most_recent_seen_ago = -1;
+        }
       else
        {
-         errno = 0;
-         first_seen_ago = strtol (strlist->next->d, &tail, 0);
-         if (errno || *tail != '\0')
-           /* Abort.  */
-           {
-             log_debug ("%s:%d: Cound't convert %s (first_seen) to an int: %s.\n",
-                        __func__, __LINE__,
-                        strlist->next->d, strerror (errno));
-             first_seen_ago = 0;
-           }
+          string_to_long (&first_seen_ago, strlist->next->d, -1, __LINE__);
+         string_to_long (&most_recent_seen_ago, strlist->next->next->d, -1,
+                          __LINE__);
        }
 
-      if (messages == -1 || first_seen_ago == 0)
-       log_info (_("Failed to collect signature statistics for \"%s\" (key %s)\n"),
-                 user_id, fingerprint);
+      if (messages == -1 || first_seen_ago == -1)
+        {
+          write_stats_status (0, TOFU_POLICY_NONE, -1, -1);
+          log_info (_("Failed to collect signature statistics for \"%s\"\n"
+                      "(key %s)\n"),
+                    user_id, fingerprint_pp);
+        }
       else
        {
          enum tofu_policy policy = get_policy (dbs, fingerprint, email, NULL);
          estream_t fp;
          char *msg;
 
+          write_stats_status (messages, policy,
+                              first_seen_ago, most_recent_seen_ago);
+
          fp = es_fopenmem (0, "rw,samethread");
          if (! fp)
-           log_fatal ("error creating memory stream\n");
+            log_fatal ("error creating memory stream: %s\n",
+                       gpg_strerror (gpg_error_from_syserror()));
 
          if (messages == 0)
-           es_fprintf (fp,
-                       _("Verified 0 messages signed by \"%s\""
-                         " (key: %s, policy %s)."),
-                       user_id, fingerprint, tofu_policy_str (policy));
+            {
+              es_fprintf (fp, _("Verified %ld messages signed by \"%s\"."),
+                          0L, user_id);
+              es_fputc ('\n', fp);
+            }
          else
            {
-             int years = 0;
-             int months = 0;
-             int days = 0;
-             int hours = 0;
-             int minutes = 0;
-             int seconds = 0;
-
-             /* The number of units that we've printed so far.  */
-             int count = 0;
-             /* The first unit that we printed (year = 0, month = 1,
-                etc.).  */
-             int first = -1;
-             /* The current unit.  */
-             int i = 0;
+              char *first_seen_ago_str = time_ago_str (first_seen_ago);
 
+              /* TRANSLATORS: The final %s is replaced by a string like
+                 "7 months, 1 day, 5 minutes, 0 seconds". */
              es_fprintf (fp,
-                         _("Verified %ld messages signed by \"%s\""
-                           " (key: %s, policy: %s) in the past "),
-                         messages, user_id,
-                         fingerprint, tofu_policy_str (policy));
-
-             /* It would be nice to use a macro to do this, but gettext
-                works on the unpreprocessed code.  */
-#define MIN_SECS (60)
-#define HOUR_SECS (60 * MIN_SECS)
-#define DAY_SECS (24 * HOUR_SECS)
-#define MONTH_SECS (30 * DAY_SECS)
-#define YEAR_SECS (365 * DAY_SECS)
-
-             if (first_seen_ago > YEAR_SECS)
-               {
-                 years = first_seen_ago / YEAR_SECS;
-                 first_seen_ago -= years * YEAR_SECS;
-               }
-             if (first_seen_ago > MONTH_SECS)
-               {
-                 months = first_seen_ago / MONTH_SECS;
-                 first_seen_ago -= months * MONTH_SECS;
-               }
-             if (first_seen_ago > DAY_SECS)
-               {
-                 days = first_seen_ago / DAY_SECS;
-                 first_seen_ago -= days * DAY_SECS;
-               }
-             if (first_seen_ago > HOUR_SECS)
-               {
-                 hours = first_seen_ago / HOUR_SECS;
-                 first_seen_ago -= hours * HOUR_SECS;
-               }
-             if (first_seen_ago > MIN_SECS)
-               {
-                 minutes = first_seen_ago / MIN_SECS;
-                 first_seen_ago -= minutes * MIN_SECS;
-               }
-             seconds = first_seen_ago;
+                          ngettext("Verified %ld message signed by \"%s\"\n"
+                                   "in the past %s.",
+                                   "Verified %ld messages signed by \"%s\"\n"
+                                   "in the past %s.",
+                                   messages),
+                         messages, user_id, first_seen_ago_str);
+
+              if (messages > 1)
+                {
+                  char *tmpstr = time_ago_str (most_recent_seen_ago);
+                  es_fputs ("  ", fp);
+                  es_fprintf (fp, _("The most recent message was"
+                                    " verified %s ago."), tmpstr);
+                  xfree (tmpstr);
+                }
+              xfree (first_seen_ago_str);
 
-#undef MIN_SECS
-#undef HOUR_SECS
-#undef DAY_SECS
-#undef MONTH_SECS
-#undef YEAR_SECS
+              if (opt.verbose)
+                {
+                  es_fputs ("  ", fp);
+                  es_fputc ('(', fp);
+                  es_fprintf (fp, _("policy: %s"), tofu_policy_str (policy));
+                  es_fputs (")\n", fp);
+                }
+              else
+                es_fputs ("\n", fp);
+            }
 
-             if (years)
-               {
-                 if (years > 1)
-                   es_fprintf (fp, _("%d years"), years);
-                 else
-                   es_fprintf (fp, _("%d year"), years);
-                 count ++;
-                 first = i;
-               }
-             i ++;
-             if ((first == -1 || i - first <= 3) && months)
-               {
-                 if (count)
-                   es_fprintf (fp, _(", "));
-
-                 if (months > 1)
-                   es_fprintf (fp, _("%d months"), months);
-                 else
-                   es_fprintf (fp, _("%d month"), months);
-                 count ++;
-                 first = i;
-               }
-             i ++;
-             if ((first == -1 || i - first <= 3) && count < 2 && days)
-               {
-                 if (count)
-                   es_fprintf (fp, _(", "));
-
-                 if (days > 1)
-                   es_fprintf (fp, _("%d days"), days);
-                 else
-                   es_fprintf (fp, _("%d day"), days);
-                 count ++;
-                 first = i;
-               }
-             i ++;
-             if ((first == -1 || i - first <= 3) && count < 2 && hours)
-               {
-                 if (count)
-                   es_fprintf (fp, _(", "));
-
-                 if (hours > 1)
-                   es_fprintf (fp, _("%d hours"), hours);
-                 else
-                   es_fprintf (fp, _("%d hour"), hours);
-                 count ++;
-                 first = i;
-               }
-             i ++;
-             if ((first == -1 || i - first <= 3) && count < 2 && minutes)
-               {
-                 if (count)
-                   es_fprintf (fp, _(", "));
-
-                 if (minutes > 1)
-                   es_fprintf (fp, _("%d minutes"), minutes);
-                 else
-                   es_fprintf (fp, _("%d minute"), minutes);
-                 count ++;
-                 first = i;
-               }
-             i ++;
-             if ((first == -1 || i - first <= 3) && count < 2)
-               {
-                 if (count)
-                   es_fprintf (fp, _(", "));
-
-                 if (seconds > 1)
-                   es_fprintf (fp, _("%d seconds"), seconds);
-                 else
-                   es_fprintf (fp, _("%d second"), seconds);
-               }
-
-             es_fprintf (fp, _("."));
-           }
+          {
+            char *tmpmsg, *p;
+            es_fputc (0, fp);
+            if (es_fclose_snatch (fp, (void **) &tmpmsg, NULL))
+              log_fatal ("error snatching memory stream\n");
+            msg = format_text (tmpmsg, 0, 72, 80);
+            es_free (tmpmsg);
+
+            /* Print a status line but suppress the trailing LF.
+             * Spaces are not percent escaped. */
+            if (*msg)
+              write_status_buffer (STATUS_TOFU_STATS_LONG,
+                                   msg, strlen (msg)-1, -1);
+
+            /* Remove the non-breaking space markers.  */
+            for (p=msg; *p; p++)
+              if (*p == '~')
+                *p = ' ';
 
-         es_fputc (0, fp);
-         if (es_fclose_snatch (fp, (void **) &msg, NULL))
-           log_fatal ("error snatching memory stream\n");
+          }
 
-         log_info ("%s\n", msg);
+         log_string (GPGRT_LOG_INFO, msg);
+          xfree (msg);
 
-         if (policy == TOFU_POLICY_AUTO && messages < 10)
+         if (policy == TOFU_POLICY_AUTO && messages < BASIC_TRUST_THRESHOLD)
            {
              char *set_policy_command;
-             const char *text;
+             char *text;
+              char *tmpmsg;
 
              if (messages == 0)
-               log_info (_("Warning: we've have yet to see a message signed by this key!\n"));
+               log_info (_("Warning: we've have yet to see"
+                            " a message signed by this key!\n"));
              else if (messages == 1)
-               log_info (_("Warning: we've only seen a single message signed by this key!\n"));
+               log_info (_("Warning: we've only seen a"
+                            " single message signed by this key!\n"));
 
              set_policy_command =
-               xasprintf ("gpg --tofu-policy bad \"%s\"", 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 %d %s %s");
-             if (strcmp (text, "TOFU: few signatures %d %s %s") == 0)
-               text =
-                 "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);
+               xasprintf ("gpg --tofu-policy bad %s", fingerprint);
+
+              tmpmsg = xasprintf
+                (ngettext
+                 ("Warning: if you think you've seen more than %ld message "
+                  "signed by this key, then this key might be a forgery!  "
+                  "Carefully examine the email address for small "
+                  "variations.  If the key is suspect, then use\n"
+                  "  %s\n"
+                  "to mark it as being bad.\n",
+                  "Warning: if you think you've seen more than %ld messages "
+                  "signed by this key, then this key might be a forgery!  "
+                      "Carefully examine the email address for small "
+                  "variations.  If the key is suspect, then use\n"
+                  "  %s\n"
+                  "to mark it as being bad.\n",
+                  messages),
+                  messages, set_policy_command);
+              text = format_text (tmpmsg, 0, 72, 80);
+              xfree (tmpmsg);
+             log_string (GPGRT_LOG_INFO, text);
+              xfree (text);
+
+             es_free (set_policy_command);
            }
        }
     }
 
  out:
   free_strlist (strlist);
+  xfree (fingerprint_pp);
 
   return;
 }
@@ -2611,52 +2652,18 @@ email_from_user_id (const char *user_id)
 {
   char *email = mailbox_from_userid (user_id);
   if (! email)
-    /* Hmm, no email address was provided.  Just take the lower-case
-       version of the whole user id.  It could be a hostname, for
-       instance.  */
-    email = ascii_strlwr (xstrdup (user_id));
-
-  return email;
-}
-
-/* Pretty print a MAX_FINGERPRINT_LEN-byte binary fingerprint into a
-   malloc'd string.  */
-static char *
-fingerprint_pp (const byte *fingerprint_bin)
-{
-  char fingerprint[MAX_FINGERPRINT_LEN * 2 + 1];
-  char *fingerprint_pretty;
-  int space = (/* The characters and the NUL.  */
-              sizeof (fingerprint)
-              /* After every fourth character, we add a space (except
-                 the last).  */
-              + (sizeof (fingerprint) - 1) / 4 - 1
-              /* Half way through we add a second space.  */
-              + 1);
-  int i;
-  int j;
-
-  bin2hex (fingerprint_bin, MAX_FINGERPRINT_LEN, fingerprint);
-
-  fingerprint_pretty = xmalloc (space);
-
-  for (i = 0, j = 0; i < MAX_FINGERPRINT_LEN * 2; i ++)
     {
-      if (i && i % 4 == 0)
-       fingerprint_pretty[j ++] = ' ';
-      if (i == MAX_FINGERPRINT_LEN * 2 / 2)
-       fingerprint_pretty[j ++] = ' ';
-
-      fingerprint_pretty[j ++] = fingerprint[i];
+      /* Hmm, no email address was provided or we are out of core.  Just
+         take the lower-case version of the whole user id.  It could be
+         a hostname, for instance.  */
+      email = ascii_strlwr (xstrdup (user_id));
     }
-  fingerprint_pretty[j ++] = 0;
-  assert (j == space);
 
-  return fingerprint_pretty;
+  return email;
 }
 
-/* Register the signature with the binding <FINGERPRINT_BIN, USER_ID>.
-   FINGERPRINT must be MAX_FINGERPRINT_LEN bytes long.
+/* Register the signature with the binding <fingerprint, USER_ID>.
+   The fingerprint is taken from the primary key packet PK.
 
    SIG_DIGEST_BIN is the binary representation of the message's
    digest.  SIG_DIGEST_BIN_LEN is its length.
@@ -2675,11 +2682,11 @@ fingerprint_pp (const byte *fingerprint_bin)
    This function returns the binding's trust level on return.  If an
    error occurs, this function returns TRUST_UNKNOWN.  */
 int
-tofu_register (const byte *fingerprint_bin, const char *user_id,
+tofu_register (ctrl_t ctrl, PKT_public_key *pk, 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 dbs *dbs;
+  tofu_dbs_t dbs;
   struct db *db;
   char *fingerprint = NULL;
   char *email = NULL;
@@ -2692,14 +2699,15 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
 
   sig_digest = make_radix64_string (sig_digest_bin, sig_digest_bin_len);
 
-  dbs = opendbs ();
+  dbs = opendbs (ctrl);
   if (! dbs)
     {
-      log_error (_("error opening TOFU DB.\n"));
+      log_error (_("error opening TOFU database: %s\n"),
+                 gpg_strerror (GPG_ERR_GENERAL));
       goto die;
     }
 
-  fingerprint = fingerprint_pp (fingerprint_bin);
+  fingerprint = hexfingerprint (pk, NULL, 0);
 
   if (! *user_id)
     {
@@ -2715,7 +2723,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);
+  trust_level = get_trust (dbs, pk, fingerprint, email, user_id, may_ask);
   if (trust_level == _tofu_GET_TRUST_ERROR)
     /* An error.  */
     {
@@ -2727,7 +2735,8 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
   db = getdb (dbs, email, DB_EMAIL);
   if (! db)
     {
-      log_error (_("error opening TOFU DB.\n"));
+      log_error (_("error opening TOFU database: %s\n"),
+                 gpg_strerror (GPG_ERR_GENERAL));
       goto die;
     }
 
@@ -2739,9 +2748,9 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
 
   /* If we've already seen this signature before, then don't add
      it again.  */
-  rc = sqlite3_stepx
+  rc = gpgsql_stepx
     (db->db, &db->s.register_already_seen,
-     get_single_unsigned_long_cb, &c, &err,
+     get_single_unsigned_long_cb2, &c, &err,
      "select count (*)\n"
      " from signatures left join bindings\n"
      "  on signatures.binding = bindings.oid\n"
@@ -2753,9 +2762,8 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
      SQLITE_ARG_END);
   if (rc)
     {
-      log_error (_("error reading from signatures database"
-                  " (checking existence): %s\n"),
-                err);
+      log_error (_("error reading TOFU database: %s\n"), err);
+      print_further_info ("checking existence");
       sqlite3_free (err);
     }
   else if (c > 1)
@@ -2763,7 +2771,7 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
        because <fingerprint, email, sig_time, sig_digest> is the
        primary key!  */
     log_debug ("SIGNATURES DB contains duplicate records"
-              " <key: %s, %s, time: 0x%lx, sig: %s, %s>."
+              " <%s, %s, 0x%lx, %s, %s>."
               "  Please report.\n",
               fingerprint, email, (unsigned long) sig_time,
               sig_digest, origin);
@@ -2772,10 +2780,14 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
       already_verified = 1;
       if (DBG_TRUST)
        log_debug ("Already observed the signature"
-                  " <key: %s, %s, time: 0x%lx, sig: %s, %s>\n",
+                  " <%s, %s, 0x%lx, %s, %s>\n",
                   fingerprint, email, (unsigned long) sig_time,
                   sig_digest, origin);
     }
+  else if (opt.dry_run)
+    {
+      log_info ("TOFU database update skipped due to --dry-run\n");
+    }
   else
     /* This is the first time that we've seen this signature.
        Record it.  */
@@ -2784,9 +2796,9 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
        log_debug ("TOFU: Saving signature <%s, %s, %s>\n",
                   fingerprint, email, sig_digest);
 
-      assert (c == 0);
+      log_assert (c == 0);
 
-      rc = sqlite3_stepx
+      rc = gpgsql_stepx
        (db->db, &db->s.register_insert, NULL, NULL, &err,
         "insert into signatures\n"
         " (binding, sig_digest, origin, sig_time, time)\n"
@@ -2800,9 +2812,8 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
          SQLITE_ARG_END);
       if (rc)
        {
-         log_error (_("error updating TOFU DB"
-                      " (inserting into signatures table): %s\n"),
-                    err);
+         log_error (_("error updating TOFU database: %s\n"), err);
+          print_further_info ("insert signatures");
          sqlite3_free (err);
        }
     }
@@ -2815,7 +2826,6 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
     rc = end_transaction (db, 0);
   if (rc)
     {
-      log_error (_("error ending transaction on TOFU database: %s\n"), err);
       sqlite3_free (err);
       goto die;
     }
@@ -2829,8 +2839,6 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
 
   xfree (email);
   xfree (fingerprint);
-  if (dbs)
-    closedbs (dbs);
   xfree (sig_digest);
 
   return trust_level;
@@ -2849,20 +2857,20 @@ tofu_wot_trust_combine (int tofu_base, int wot_base)
   int wot = wot_base & TRUST_MASK;
   int upper = (tofu_base & ~TRUST_MASK) | (wot_base & ~TRUST_MASK);
 
-  assert (tofu == TRUST_UNKNOWN
-         || tofu == TRUST_EXPIRED
-         || tofu == TRUST_UNDEFINED
-         || tofu == TRUST_NEVER
-         || tofu == TRUST_MARGINAL
-         || tofu == TRUST_FULLY
-         || tofu == TRUST_ULTIMATE);
-  assert (wot == TRUST_UNKNOWN
-         || wot == TRUST_EXPIRED
-         || wot == TRUST_UNDEFINED
-         || wot == TRUST_NEVER
-         || wot == TRUST_MARGINAL
-         || wot == TRUST_FULLY
-         || wot == TRUST_ULTIMATE);
+  log_assert (tofu == TRUST_UNKNOWN
+              || tofu == TRUST_EXPIRED
+              || tofu == TRUST_UNDEFINED
+              || tofu == TRUST_NEVER
+              || tofu == TRUST_MARGINAL
+              || tofu == TRUST_FULLY
+              || tofu == TRUST_ULTIMATE);
+  log_assert (wot == TRUST_UNKNOWN
+              || wot == TRUST_EXPIRED
+              || wot == TRUST_UNDEFINED
+              || wot == TRUST_NEVER
+              || wot == TRUST_MARGINAL
+              || wot == TRUST_FULLY
+              || wot == TRUST_ULTIMATE);
 
   /* We first consider negative trust policys.  These trump positive
      trust policies.  */
@@ -2875,21 +2883,34 @@ tofu_wot_trust_combine (int tofu_base, int wot_base)
 
   /* Now we only have positive or neutral trust policies.  We take
      the max.  */
-  if (tofu == TRUST_ULTIMATE || wot == TRUST_ULTIMATE)
+  if (tofu == TRUST_ULTIMATE)
+    return upper | TRUST_ULTIMATE | TRUST_FLAG_TOFU_BASED;
+  if (wot == TRUST_ULTIMATE)
     return upper | TRUST_ULTIMATE;
-  if (tofu == TRUST_FULLY || wot == TRUST_FULLY)
+
+  if (tofu == TRUST_FULLY)
+    return upper | TRUST_FULLY | TRUST_FLAG_TOFU_BASED;
+  if (wot == TRUST_FULLY)
     return upper | TRUST_FULLY;
-  if (tofu == TRUST_MARGINAL || wot == TRUST_MARGINAL)
+
+  if (tofu == TRUST_MARGINAL)
+    return upper | TRUST_MARGINAL | TRUST_FLAG_TOFU_BASED;
+  if (wot == TRUST_MARGINAL)
     return upper | TRUST_MARGINAL;
-  if (tofu == TRUST_UNDEFINED || wot == TRUST_UNDEFINED)
+
+  if (tofu == TRUST_UNDEFINED)
+    return upper | TRUST_UNDEFINED | TRUST_FLAG_TOFU_BASED;
+  if (wot == TRUST_UNDEFINED)
     return upper | TRUST_UNDEFINED;
+
   return upper | TRUST_UNKNOWN;
 }
 
+
 /* Return the validity (TRUST_NEVER, etc.) of the binding
    <FINGERPRINT, USER_ID>.
 
-   FINGERPRINT must be a MAX_FINGERPRINT_LEN-byte fingerprint.
+   PK is the primary key packet.
 
    If MAY_ASK is 1 and the policy is TOFU_POLICY_ASK, then the user
    will be prompted to choose a different policy.  If MAY_ASK is 0 and
@@ -2897,32 +2918,34 @@ tofu_wot_trust_combine (int tofu_base, int wot_base)
 
    Returns TRUST_UNDEFINED if an error occurs.  */
 int
-tofu_get_validity (const byte *fingerprint_bin, const char *user_id,
+tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, const char *user_id,
                   int may_ask)
 {
-  struct dbs *dbs;
+  tofu_dbs_t dbs;
   char *fingerprint = NULL;
   char *email = NULL;
   int trust_level = TRUST_UNDEFINED;
 
-  dbs = opendbs ();
+  dbs = opendbs (ctrl);
   if (! dbs)
     {
-      log_error (_("error opening TOFU DB.\n"));
+      log_error (_("error opening TOFU database: %s\n"),
+                 gpg_strerror (GPG_ERR_GENERAL));
       goto die;
     }
 
-  fingerprint = fingerprint_pp (fingerprint_bin);
+  fingerprint = hexfingerprint (pk, NULL, 0);
 
   if (! *user_id)
     {
-      log_debug ("user id is empty.  Can't get TOFU validity for this binding.\n");
+      log_debug ("user id is empty."
+                 "  Can't get TOFU validity for this binding.\n");
       goto die;
     }
 
   email = email_from_user_id (user_id);
 
-  trust_level = get_trust (dbs, fingerprint, email, user_id, may_ask);
+  trust_level = get_trust (dbs, pk, fingerprint, email, user_id, may_ask);
   if (trust_level == _tofu_GET_TRUST_ERROR)
     /* An error.  */
     trust_level = TRUST_UNDEFINED;
@@ -2933,9 +2956,6 @@ tofu_get_validity (const byte *fingerprint_bin, const char *user_id,
  die:
   xfree (email);
   xfree (fingerprint);
-  if (dbs)
-    closedbs (dbs);
-
   return trust_level;
 }
 
@@ -2947,21 +2967,20 @@ tofu_get_validity (const byte *fingerprint_bin, const char *user_id,
 
    Returns 0 on success and an error code otherwise.  */
 gpg_error_t
-tofu_set_policy (kbnode_t kb, enum tofu_policy policy)
+tofu_set_policy (ctrl_t ctrl, kbnode_t kb, enum tofu_policy policy)
 {
-  struct dbs *dbs;
+  tofu_dbs_t dbs;
   PKT_public_key *pk;
-  char fingerprint_bin[MAX_FINGERPRINT_LEN];
-  size_t fingerprint_bin_len = sizeof (fingerprint_bin);
   char *fingerprint = NULL;
 
-  assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
+  log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
   pk = kb->pkt->pkt.public_key;
 
-  dbs = opendbs ();
+  dbs = opendbs (ctrl);
   if (! dbs)
     {
-      log_error (_("error opening TOFU DB.\n"));
+      log_error (_("error opening TOFU database: %s\n"),
+                 gpg_strerror (GPG_ERR_GENERAL));
       return gpg_error (GPG_ERR_GENERAL);
     }
 
@@ -2972,10 +2991,7 @@ tofu_set_policy (kbnode_t kb, enum tofu_policy policy)
         && pk->main_keyid[1] == pk->keyid[1]))
     log_bug ("%s: Passed a subkey, but expecting a primary key.\n", __func__);
 
-  fingerprint_from_pk (pk, fingerprint_bin, &fingerprint_bin_len);
-  assert (fingerprint_bin_len == sizeof (fingerprint_bin));
-
-  fingerprint = fingerprint_pp (fingerprint_bin);
+  fingerprint = hexfingerprint (pk, NULL, 0);
 
   for (; kb; kb = kb->next)
     {
@@ -2999,8 +3015,6 @@ tofu_set_policy (kbnode_t kb, enum tofu_policy policy)
     }
 
   xfree (fingerprint);
-  closedbs (dbs);
-
   return 0;
 }
 
@@ -3012,13 +3026,13 @@ tofu_set_policy (kbnode_t kb, enum tofu_policy policy)
 
    Returns 0 on success and an error code otherwise.  */
 gpg_error_t
-tofu_set_policy_by_keyid (u32 *keyid, enum tofu_policy policy)
+tofu_set_policy_by_keyid (ctrl_t ctrl, u32 *keyid, enum tofu_policy policy)
 {
   kbnode_t keyblock = get_pubkeyblock (keyid);
   if (! keyblock)
     return gpg_error (GPG_ERR_NO_PUBKEY);
 
-  return tofu_set_policy (keyblock, policy);
+  return tofu_set_policy (ctrl, keyblock, policy);
 }
 
 /* Return the TOFU policy for the specified binding in *POLICY.  If no
@@ -3029,30 +3043,26 @@ tofu_set_policy_by_keyid (u32 *keyid, enum tofu_policy policy)
 
    Returns 0 on success and an error code otherwise.  */
 gpg_error_t
-tofu_get_policy (PKT_public_key *pk, PKT_user_id *user_id,
+tofu_get_policy (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *user_id,
                 enum tofu_policy *policy)
 {
-  struct dbs *dbs;
-  char fingerprint_bin[MAX_FINGERPRINT_LEN];
-  size_t fingerprint_bin_len = sizeof (fingerprint_bin);
+  tofu_dbs_t dbs;
   char *fingerprint;
   char *email;
 
   /* Make sure PK is a primary key.  */
-  assert (pk->main_keyid[0] == pk->keyid[0]
-         && pk->main_keyid[1] == pk->keyid[1]);
+  log_assert (pk->main_keyid[0] == pk->keyid[0]
+              && pk->main_keyid[1] == pk->keyid[1]);
 
-  dbs = opendbs ();
+  dbs = opendbs (ctrl);
   if (! dbs)
     {
-      log_error (_("error opening TOFU DB.\n"));
+      log_error (_("error opening TOFU database: %s\n"),
+                 gpg_strerror (GPG_ERR_GENERAL));
       return gpg_error (GPG_ERR_GENERAL);
     }
 
-  fingerprint_from_pk (pk, fingerprint_bin, &fingerprint_bin_len);
-  assert (fingerprint_bin_len == sizeof (fingerprint_bin));
-
-  fingerprint = fingerprint_pp (fingerprint_bin);
+  fingerprint = hexfingerprint (pk, NULL, 0);
 
   email = email_from_user_id (user_id->name);
 
@@ -3060,8 +3070,6 @@ tofu_get_policy (PKT_public_key *pk, PKT_user_id *user_id,
 
   xfree (email);
   xfree (fingerprint);
-  closedbs (dbs);
-
   if (*policy == _tofu_GET_POLICY_ERROR)
     return gpg_error (GPG_ERR_GENERAL);
   return 0;