tests: Make ssh test more robust.
[gnupg.git] / g10 / tofu.c
index b73ad93..a2732ff 100644 (file)
@@ -39,7 +39,7 @@
 #include "ttyio.h"
 #include "trustdb.h"
 #include "mkdir_p.h"
-#include "sqlite.h"
+#include "gpgsql.h"
 #include "status.h"
 
 #include "tofu.h"
@@ -84,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
@@ -262,7 +262,7 @@ 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)
@@ -281,7 +281,7 @@ 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)
@@ -316,7 +316,7 @@ end_transaction (struct db *db, int only_batch)
     {
       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)
@@ -337,7 +337,7 @@ 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)
@@ -640,7 +640,7 @@ initdb (sqlite3 *db, enum db_type type)
    *     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"
@@ -659,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"
@@ -747,7 +747,7 @@ opendb (char *filename, enum db_type type)
       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
@@ -782,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;
 };
@@ -814,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;
@@ -894,10 +895,10 @@ getdb (struct dbs *dbs, const char *name, enum db_type type)
         char *name_db;
 
         /* Make the directory.  */
-        rc = gnupg_mkdir_p (opt.homedir, "tofu.d", type_str, prefix, NULL);
+        rc = gnupg_mkdir_p (gnupg_homedir (), "tofu.d", type_str, prefix, NULL);
         if (rc)
           {
-            name_db = xstrconcat (opt.homedir, "tofu.d",
+            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));
@@ -907,7 +908,7 @@ getdb (struct dbs *dbs, const char *name, enum db_type type)
 
         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);
       }
     }
@@ -980,12 +981,15 @@ closedb (struct db *db)
 /* Create a new DB meta-handle.  Returns NULL on error.  */
 /* FIXME: Change to return an error code for better reporting by the
    caller.  */
-static struct dbs *
-opendbs (void)
+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;
@@ -1045,14 +1049,26 @@ opendbs (void)
        }
     }
 
-  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;
@@ -1088,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;
 
@@ -1100,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",
@@ -1142,7 +1167,7 @@ get_single_long_cb2 (void *cookie, int argc, char **argv, char **azColName,
 
    If SHOW_OLD is set, the binding's old policy is displayed.  */
 static gpg_error_t
-record_binding (struct 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);
@@ -1174,6 +1199,7 @@ 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)
@@ -1190,20 +1216,13 @@ record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
       if (rc)
         goto out_revert_one;
     }
-  else
-    {
-      rc = begin_transaction (db_email, 1);
-      if (rc)
-        goto leave;
-    }
-
 
   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_cb2, &policy_old, &err,
         "select policy from bindings where fingerprint = ? and email = ?",
@@ -1242,7 +1261,7 @@ record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
       goto out;
     }
 
-  rc = sqlite3_stepx
+  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"
@@ -1270,7 +1289,7 @@ record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
     {
       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"
@@ -1485,7 +1504,7 @@ time_ago_scale (signed long t)
    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;
@@ -1503,7 +1522,7 @@ 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,
+  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 = ?",
@@ -1585,28 +1604,435 @@ get_policy (struct dbs *dbs, const char *fingerprint, const char *email,
   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);
+        }
 
-   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.  */
+      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);
+}
+
+
+/* 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)
 {
-  char *fingerprint_pp;
   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;
@@ -1629,46 +2055,12 @@ get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
   if (! db)
     return _tofu_GET_TRUST_ERROR;
 
-  fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
-
   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.  */
-    {
-      PKT_public_key *pk;
+    { /* See if the key is ultimately trusted.  If so, we're done.  */
       u32 kid[2];
-      char fpr_bin[MAX_FINGERPRINT_LEN+1];
-      size_t fpr_bin_len;
-
-      if (!hex2str (fingerprint, fpr_bin, sizeof fpr_bin, &fpr_bin_len))
-        {
-          log_error ("error converting fingerprint: %s\n",
-                     gpg_strerror (gpg_error_from_syserror ()));
-          return _tofu_GET_TRUST_ERROR;
-        }
 
-      /* We need to lookup the key by fingerprint again so that we can
-         properly extract the keyid.  Extracting direct from the
-         fingerprint works only for v4 keys and would assume that
-         there is no collision in the low 64 bit.  We can't guarantee
-         the latter in case the Tofu DB is used with a different
-         keyring.  In any case the UTK stuff needs to be changed to
-         use only fingerprints.  */
-      pk = xtrycalloc (1, sizeof *pk);
-      if (!pk)
-         {
-           log_error (_("out of core\n"));
-           return _tofu_GET_TRUST_ERROR;
-         }
-      rc = get_pubkey_byfprint_fast (pk, fpr_bin, fpr_bin_len);
-      if (rc)
-        {
-          log_error (_("public key %s not found: %s\n"),
-                     fingerprint, gpg_strerror (rc));
-          return _tofu_GET_TRUST_ERROR;
-        }
       keyid_from_pk (pk, kid);
-      free_public_key (pk);
 
       if (tdb_keyid_is_utk (kid))
         {
@@ -1704,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));
@@ -1723,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:
@@ -1736,49 +2128,51 @@ 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_cb2, &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 TOFU database: %s\n"), err);
+      log_error (_("error reading TOFU database: %s\n"), sqerr);
       print_further_info ("listing fingerprints");
-      sqlite3_free (err);
+      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.  */
       log_assert (policy == TOFU_POLICY_NONE);
@@ -1801,18 +2195,20 @@ 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).  */
     {
+      /* 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,
@@ -1824,412 +2220,52 @@ 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,
-
-       - 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;
-    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: %s\n",
-                 gpg_strerror (gpg_error_from_syserror()));
-
-    /* Format the first part of the message.  */
-    {
-      estream_t fp1;
-      char *binding = xasprintf ("<%s, %s>", fingerprint, email);
-      int binding_shown = 0;
-      char *tmpstr, *text;
-
-      fp1 = es_fopenmem (0, "rw,samethread");
-      if (!fp1)
-        log_fatal ("error creating memory stream: %s\n",
-                   gpg_strerror (gpg_error_from_syserror()));
-
-      if (policy == TOFU_POLICY_NONE)
-        {
-          es_fprintf (fp1, _("The binding %s is NOT known."), binding);
-          es_fputs ("  ", fp1);
-          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 (fp1,
-                      _("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 ("  ", fp1);
-          binding_shown = 1;
-        }
-
-      /* TRANSLATORS: The %s%s is replaced by either a fingerprint and a
-         blank or by two empty strings.  */
-      es_fprintf (fp1,
-                  _("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', fp1);
-
-      xfree (binding);
-
-      es_fputc (0, fp1);
-      if (es_fclose_snatch (fp1, (void **)&tmpstr, NULL))
-        log_fatal ("error snatching memory stream\n");
-      text = format_text (tmpstr, 0, 72, 80);
-      es_free (tmpstr);
-
-      es_fputs (text, fp);
-      xfree (text);
-    }
-
-    es_fputc ('\n', fp);
-
-    /* Find other user ids associated with this key and whether the
-       bindings are marked as good or bad.  */
-    {
-      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);
-      else
-       db_key = db;
-
-      if (db_key)
-       {
-         rc = sqlite3_stepx
-           (db_key->db, &db_key->s.get_trust_gather_other_user_ids,
-             strings_collect_cb2, &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;
-           }
-       }
-    }
-
-    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.  */
-    /* 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, 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) != 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 address \"%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;
-                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 (is_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") == 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.";
-        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) != 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 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);
 
  out:
   if (change_conflicting_to_ask)
     {
       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);
+        {
+          /* 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
-       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);
+        {
+          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 (rc)
        {
-         log_error (_("error changing TOFU policy: %s\n"), err);
-         sqlite3_free (err);
-         goto out;
+         log_error (_("error changing TOFU policy: %s\n"), sqerr);
+         sqlite3_free (sqerr);
+          sqerr = NULL;
        }
     }
 
   xfree (conflict);
   free_strlist (bindings_with_this_email);
-  xfree (fingerprint_pp);
 
   return trust_level;
 }
@@ -2405,7 +2441,7 @@ write_stats_status (long messages, enum tofu_policy policy,
 }
 
 static void
-show_statistics (struct dbs *dbs, const char *fingerprint,
+show_statistics (tofu_dbs_t dbs, const char *fingerprint,
                 const char *email, const char *user_id,
                 const char *sig_exclude)
 {
@@ -2421,7 +2457,7 @@ show_statistics (struct dbs *dbs, const char *fingerprint,
 
   fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
 
-  rc = sqlite3_exec_printf
+  rc = gpgsql_exec_printf
     (db->db, strings_collect_cb, &strlist, &err,
      "select count (*), strftime('%%s','now') - min (signatures.time),\n"
      "  strftime('%%s','now') - max (signatures.time)\n"
@@ -2468,12 +2504,12 @@ show_statistics (struct dbs *dbs, const char *fingerprint,
         }
       else
        {
-          string_to_long (&first_seen_ago, strlist->next->d, 0, __LINE__);
-         string_to_long (&most_recent_seen_ago, strlist->next->next->d, 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)
+      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"
@@ -2646,14 +2682,13 @@ email_from_user_id (const char *user_id)
    This function returns the binding's trust level on return.  If an
    error occurs, this function returns TRUST_UNKNOWN.  */
 int
-tofu_register (PKT_public_key *pk, 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 *fingerprint_pp = NULL;
   char *email = NULL;
   char *err = NULL;
   int rc;
@@ -2664,7 +2699,7 @@ tofu_register (PKT_public_key *pk, 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 database: %s\n"),
@@ -2673,7 +2708,6 @@ tofu_register (PKT_public_key *pk, const char *user_id,
     }
 
   fingerprint = hexfingerprint (pk, NULL, 0);
-  fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
 
   if (! *user_id)
     {
@@ -2689,7 +2723,7 @@ tofu_register (PKT_public_key *pk, 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.  */
     {
@@ -2714,7 +2748,7 @@ tofu_register (PKT_public_key *pk, 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_cb2, &c, &err,
      "select count (*)\n"
@@ -2764,7 +2798,7 @@ tofu_register (PKT_public_key *pk, const char *user_id,
 
       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"
@@ -2804,10 +2838,7 @@ tofu_register (PKT_public_key *pk, const char *user_id,
                     already_verified ? NULL : sig_digest);
 
   xfree (email);
-  xfree (fingerprint_pp);
   xfree (fingerprint);
-  if (dbs)
-    closedbs (dbs);
   xfree (sig_digest);
 
   return trust_level;
@@ -2887,15 +2918,15 @@ tofu_wot_trust_combine (int tofu_base, int wot_base)
 
    Returns TRUST_UNDEFINED if an error occurs.  */
 int
-tofu_get_validity (PKT_public_key *pk, 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 database: %s\n"),
@@ -2914,7 +2945,7 @@ tofu_get_validity (PKT_public_key *pk, const char *user_id,
 
   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;
@@ -2925,9 +2956,6 @@ tofu_get_validity (PKT_public_key *pk, const char *user_id,
  die:
   xfree (email);
   xfree (fingerprint);
-  if (dbs)
-    closedbs (dbs);
-
   return trust_level;
 }
 
@@ -2939,16 +2967,16 @@ tofu_get_validity (PKT_public_key *pk, 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 = NULL;
 
   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 database: %s\n"),
@@ -2987,8 +3015,6 @@ tofu_set_policy (kbnode_t kb, enum tofu_policy policy)
     }
 
   xfree (fingerprint);
-  closedbs (dbs);
-
   return 0;
 }
 
@@ -3000,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
@@ -3017,10 +3043,10 @@ 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;
+  tofu_dbs_t dbs;
   char *fingerprint;
   char *email;
 
@@ -3028,7 +3054,7 @@ tofu_get_policy (PKT_public_key *pk, PKT_user_id *user_id,
   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 database: %s\n"),
@@ -3044,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;