gpg: Make function mk_datestr public.
[gnupg.git] / g10 / tofu.c
index 0cd3f12..c183fc6 100644 (file)
@@ -14,7 +14,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
 /* TODO:
 #include <sys/stat.h>
 #include <stdarg.h>
 #include <sqlite3.h>
+#include <time.h>
 
 #include "gpg.h"
-#include "types.h"
-#include "logging.h"
-#include "stringhelp.h"
+#include "../common/types.h"
+#include "../common/logging.h"
+#include "../common/stringhelp.h"
 #include "options.h"
-#include "mbox-util.h"
-#include "i18n.h"
-#include "ttyio.h"
+#include "../common/mbox-util.h"
+#include "../common/i18n.h"
+#include "../common/ttyio.h"
 #include "trustdb.h"
-#include "mkdir_p.h"
+#include "../common/mkdir_p.h"
 #include "gpgsql.h"
-#include "status.h"
-#include "sqrtu32.h"
+#include "../common/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
+/* Number of days with signed / ecnrypted messages required to
+ * indicate that enough history is available for basic trust.  */
+#define BASIC_TRUST_THRESHOLD  4
+/* Number of days with signed / encrypted messages required to
+ * indicate that a lot of history is available.  */
+#define FULL_TRUST_THRESHOLD  21
 
 
-/* An struct with data pertaining to the tofu DB.
-
-   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.  */
+/* A struct with data pertaining to the tofu DB.  There is one such
+   struct per session and it is cached in session's ctrl structure.
+   To initialize this or get the current singleton, call opendbs().
+   There is no need to explicitly release it; cleanup is done when the
+   CTRL object is released.  */
 struct tofu_dbs_s
 {
   sqlite3 *db;
+  char *want_lock_file;
+  time_t want_lock_file_ctime;
 
   struct
   {
@@ -73,14 +74,14 @@ struct tofu_dbs_s
 
     sqlite3_stmt *record_binding_get_old_policy;
     sqlite3_stmt *record_binding_update;
-    sqlite3_stmt *record_binding_update2;
     sqlite3_stmt *get_policy_select_policy_and_conflict;
     sqlite3_stmt *get_trust_bindings_with_this_email;
     sqlite3_stmt *get_trust_gather_other_user_ids;
     sqlite3_stmt *get_trust_gather_signature_stats;
     sqlite3_stmt *get_trust_gather_encryption_stats;
     sqlite3_stmt *register_already_seen;
-    sqlite3_stmt *register_insert;
+    sqlite3_stmt *register_signature;
+    sqlite3_stmt *register_encryption;
   } s;
 
   int in_batch_transaction;
@@ -110,8 +111,10 @@ struct tofu_dbs_s
 /* Local prototypes.  */
 static gpg_error_t end_transaction (ctrl_t ctrl, int only_batch);
 static char *email_from_user_id (const char *user_id);
-
-
+static int show_statistics (tofu_dbs_t dbs,
+                            const char *fingerprint, const char *email,
+                            enum tofu_policy policy,
+                            estream_t outfp, int only_status_fd, time_t now);
 \f
 const char *
 tofu_policy_str (enum tofu_policy policy)
@@ -176,8 +179,8 @@ begin_transaction (ctrl_t ctrl, int only_batch)
    * than 500 ms), to prevent starving other gpg processes, we drop
    * and retake the batch lock.
    *
-   * Note: if we wanted higher resolution, we could use
-   * npth_clock_gettime.  */
+   * Note: gnupg_get_time has a one second resolution, if we wanted a
+   * higher resolution, we could use npth_clock_gettime.  */
   if (/* No real transactions.  */
       dbs->in_transaction == 0
       /* There is an open batch transaction.  */
@@ -185,21 +188,36 @@ begin_transaction (ctrl_t ctrl, int only_batch)
       /* And some time has gone by since it was started.  */
       && dbs->batch_update_started != gnupg_get_time ())
     {
+      struct stat statbuf;
+
       /* If we are in a batch update, then batch updates better have
          been enabled.  */
       log_assert (ctrl->tofu.batch_updated_wanted);
 
-      end_transaction (ctrl, 2);
+      /* Check if another process wants to run.  (We just ignore any
+       * stat failure.  A waiter might have to wait a bit longer, but
+       * otherwise there should be no impact.)  */
+      if (stat (dbs->want_lock_file, &statbuf) == 0
+          && statbuf.st_ctime != dbs->want_lock_file_ctime)
+        {
+          end_transaction (ctrl, 2);
 
-      /* Yield to allow another process a chance to run.  */
-      gpgrt_yield ();
+          /* Yield to allow another process a chance to run.  Note:
+           * testing suggests that anything less than a 100ms tends to
+           * not result in the other process getting the lock.  */
+          gnupg_usleep (100000);
+        }
+      else
+        dbs->batch_update_started = gnupg_get_time ();
     }
 
-  if (/* Batch mode is enabled.  */
-      ctrl->tofu.batch_updated_wanted
-      /* But we don't have an open batch transaction.  */
-      && !dbs->in_batch_transaction)
+  if (/* We don't have an open batch transaction.  */
+      !dbs->in_batch_transaction
+      && (/* Batch mode is enabled or we are starting a new transaction.  */
+          ctrl->tofu.batch_updated_wanted || dbs->in_transaction == 0))
     {
+      struct stat statbuf;
+
       /* We are in batch mode, but we don't have an open batch
        * transaction.  Since the batch save point must be the outer
        * save point, it must be taken before the inner save point.  */
@@ -207,7 +225,7 @@ begin_transaction (ctrl_t ctrl, int only_batch)
 
       rc = gpgsql_stepx (dbs->db, &dbs->s.savepoint_batch,
                           NULL, NULL, &err,
-                          "savepoint batch;", GPGSQL_ARG_END);
+                          "begin immediate transaction;", GPGSQL_ARG_END);
       if (rc)
         {
           log_error (_("error beginning transaction on TOFU database: %s\n"),
@@ -218,12 +236,15 @@ begin_transaction (ctrl_t ctrl, int only_batch)
 
       dbs->in_batch_transaction = 1;
       dbs->batch_update_started = gnupg_get_time ();
+
+      if (stat (dbs->want_lock_file, &statbuf) == 0)
+        dbs->want_lock_file_ctime = statbuf.st_ctime;
     }
 
   if (only_batch)
     return 0;
 
-  log_assert(dbs->in_transaction >= 0);
+  log_assert (dbs->in_transaction >= 0);
   dbs->in_transaction ++;
 
   rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err,
@@ -243,8 +264,8 @@ begin_transaction (ctrl_t ctrl, int only_batch)
 
 /* Commit a transaction.  If ONLY_BATCH is 1, then this only ends the
  * batch transaction if we have left batch mode.  If ONLY_BATCH is 2,
- * this ends any open batch transaction even if we are still in batch
- * mode.  */
+ * this commits any open batch transaction even if we are still in
+ * batch mode.  */
 static gpg_error_t
 end_transaction (ctrl_t ctrl, int only_batch)
 {
@@ -252,14 +273,15 @@ end_transaction (ctrl_t ctrl, int only_batch)
   int rc;
   char *err = NULL;
 
-  if (only_batch)
+  if (only_batch || (! only_batch && dbs->in_transaction == 1))
     {
       if (!dbs)
         return 0;  /* Shortcut to allow for easier cleanup code.  */
 
       /* If we are releasing the batch transaction, then we better not
          be in a normal transaction.  */
-      log_assert (dbs->in_transaction == 0);
+      if (only_batch)
+        log_assert (dbs->in_transaction == 0);
 
       if (/* Batch mode disabled?  */
           (!ctrl->tofu.batch_updated_wanted || only_batch == 2)
@@ -269,10 +291,11 @@ end_transaction (ctrl_t ctrl, int only_batch)
           /* The batch transaction is still in open, but we've left
            * batch mode.  */
           dbs->in_batch_transaction = 0;
+          dbs->in_transaction = 0;
 
           rc = gpgsql_stepx (dbs->db, &dbs->s.savepoint_batch_commit,
                              NULL, NULL, &err,
-                             "release batch;", GPGSQL_ARG_END);
+                             "commit transaction;", GPGSQL_ARG_END);
           if (rc)
             {
               log_error (_("error committing transaction on TOFU database: %s\n"),
@@ -284,7 +307,8 @@ end_transaction (ctrl_t ctrl, int only_batch)
           return 0;
         }
 
-      return 0;
+      if (only_batch)
+        return 0;
     }
 
   log_assert (dbs);
@@ -317,7 +341,7 @@ rollback_transaction (ctrl_t ctrl)
   log_assert (dbs);
   log_assert (dbs->in_transaction > 0);
 
-  /* Be careful to not any progress made by closed transactions in
+  /* Be careful to not undo any progress made by closed transactions in
      batch mode.  */
   rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err,
                            "rollback to inner%d;",
@@ -387,9 +411,12 @@ string_to_long (long *r_value, const char *string, long fallback, int line)
   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));
+      log_debug ("%s:%d: strtol failed for TOFU DB data; returned string"
+                 " (string='%.10s%s'; tail='%.10s%s'): %s\n",
+                 __FILE__, line,
+                 string, string && strlen(string) > 10 ? "..." : "",
+                 tail, tail && strlen(tail) > 10 ? "..." : "",
+                 gpg_strerror (err));
       *r_value = fallback;
     }
   else
@@ -415,9 +442,12 @@ string_to_ulong (unsigned long *r_value, const char *string,
   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));
+      log_debug ("%s:%d: strtoul failed for TOFU DB data; returned string"
+                 " (string='%.10s%s'; tail='%.10s%s'): %s\n",
+                 __FILE__, line,
+                 string, string && strlen(string) > 10 ? "..." : "",
+                 tail, tail && strlen(tail) > 10 ? "..." : "",
+                 gpg_strerror (err));
       *r_value = fallback;
     }
   else
@@ -479,6 +509,152 @@ version_check_cb (void *cookie, int argc, char **argv, char **azColName)
   return 1;
 }
 
+static int
+check_utks (sqlite3 *db)
+{
+  int rc;
+  char *err = NULL;
+  struct key_item *utks;
+  struct key_item *ki;
+  int utk_count;
+  char *utks_string = NULL;
+  char keyid_str[16+1];
+  long utks_unchanged = 0;
+
+  /* An early version of the v1 format did not include the list of
+   * known ultimately trusted keys.
+   *
+   * This list is used to detect when the set of ultimately trusted
+   * keys changes.  We need to detect this to invalidate the effective
+   * policy, which can change if an ultimately trusted key is added or
+   * removed.  */
+  rc = sqlite3_exec (db,
+                     "create table if not exists ultimately_trusted_keys"
+                     " (keyid);\n",
+                     NULL, NULL, &err);
+  if (rc)
+    {
+      log_error (_("error creating 'ultimately_trusted_keys' TOFU table: %s\n"),
+                 err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+
+  utks = tdb_utks ();
+  for (ki = utks, utk_count = 0; ki; ki = ki->next, utk_count ++)
+    ;
+
+  if (utk_count)
+    {
+      /* Build a list of keyids of the form "XXX","YYY","ZZZ".  */
+      int len = (1 + 16 + 1 + 1) * utk_count;
+      int o = 0;
+
+      utks_string = xmalloc (len);
+      *utks_string = 0;
+      for (ki = utks, utk_count = 0; ki; ki = ki->next, utk_count ++)
+        {
+          utks_string[o ++] = '\'';
+          format_keyid (ki->kid, KF_LONG,
+                        keyid_str, sizeof (keyid_str));
+          memcpy (&utks_string[o], keyid_str, 16);
+          o += 16;
+          utks_string[o ++] = '\'';
+          utks_string[o ++] = ',';
+        }
+      utks_string[o - 1] = 0;
+      log_assert (o == len);
+    }
+
+  rc = gpgsql_exec_printf
+    (db, get_single_unsigned_long_cb, &utks_unchanged, &err,
+     "select"
+     /* Removed UTKs?  (Known UTKs in current UTKs.)  */
+     "  ((select count(*) from ultimately_trusted_keys"
+     "     where (keyid in (%s))) == %d)"
+     " and"
+     /* New UTKs?  */
+     "  ((select count(*) from ultimately_trusted_keys"
+     "     where keyid not in (%s)) == 0);",
+     utks_string ? utks_string : "",
+     utk_count,
+     utks_string ? utks_string : "");
+  xfree (utks_string);
+  if (rc)
+    {
+      log_error (_("TOFU DB error"));
+      print_further_info ("checking if ultimately trusted keys changed: %s",
+                         err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+  if (utks_unchanged)
+    goto out;
+
+  if (DBG_TRUST)
+    log_debug ("TOFU: ultimately trusted keys changed.\n");
+
+  /* Given that the set of ultimately trusted keys
+   * changed, clear any cached policies.  */
+  rc = gpgsql_exec_printf
+    (db, NULL, NULL, &err,
+     "update bindings set effective_policy = %d;",
+     TOFU_POLICY_NONE);
+  if (rc)
+    {
+      log_error (_("TOFU DB error"));
+      print_further_info ("clearing cached policies: %s", err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+  /* Now, update the UTK table.  */
+  rc = sqlite3_exec (db,
+                     "drop table ultimately_trusted_keys;",
+                     NULL, NULL, &err);
+  if (rc)
+    {
+      log_error (_("TOFU DB error"));
+      print_further_info ("dropping ultimately_trusted_keys: %s", err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+  rc = sqlite3_exec (db,
+                     "create table if not exists"
+                     " ultimately_trusted_keys (keyid);\n",
+                     NULL, NULL, &err);
+  if (rc)
+    {
+      log_error (_("TOFU DB error"));
+      print_further_info ("creating ultimately_trusted_keys: %s", err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+  for (ki = utks; ki; ki = ki->next)
+    {
+      format_keyid (ki->kid, KF_LONG,
+                    keyid_str, sizeof (keyid_str));
+      rc = gpgsql_exec_printf
+        (db, NULL, NULL, &err,
+         "insert into ultimately_trusted_keys values ('%s');",
+         keyid_str);
+      if (rc)
+        {
+          log_error (_("TOFU DB error"));
+          print_further_info ("updating ultimately_trusted_keys: %s",
+                              err);
+          sqlite3_free (err);
+          goto out;
+        }
+    }
+
+ out:
+  return rc;
+}
 
 /* If the DB is new, initialize it.  Otherwise, check the DB's
    version.
@@ -606,7 +782,7 @@ initdb (sqlite3 *db)
        "create table bindings\n"
        " (oid INTEGER PRIMARY KEY AUTOINCREMENT,\n"
        "  fingerprint TEXT, email TEXT, user_id TEXT, time INTEGER,\n"
-       "  policy BOOLEAN CHECK (policy in (%d, %d, %d, %d, %d)),\n"
+       "  policy INTEGER CHECK (policy in (%d, %d, %d, %d, %d)),\n"
        "  conflict STRING,\n"
        "  unique (fingerprint, email));\n"
        "create index bindings_fingerprint_email\n"
@@ -655,15 +831,54 @@ initdb (sqlite3 *db)
     {
       /* Early version of the v1 format did not include the encryption
          table.  Add it.  */
-      sqlite3_exec (db,
-                    "create table if not exists encryptions"
-                    " (binding INTEGER NOT NULL,"
-                    "  time INTEGER);"
-                    "create index if not exists encryptions_binding"
-                    " on encryptions (binding);\n",
-                    NULL, NULL, &err);
+      rc = sqlite3_exec (db,
+                         "create table if not exists encryptions"
+                         " (binding INTEGER NOT NULL,"
+                         "  time INTEGER);"
+                         "create index if not exists encryptions_binding"
+                         " on encryptions (binding);\n",
+                         NULL, NULL, &err);
+      if (rc)
+        {
+         log_error (_("error creating 'encryptions' TOFU table: %s\n"),
+                    err);
+          sqlite3_free (err);
+        }
+    }
+  if (! rc)
+    {
+      /* The effective policy for a binding.  If a key is ultimately
+       * trusted, then the effective policy of all of its bindings is
+       * good.  Likewise if a key is signed by an ultimately trusted
+       * key, etc.  If the effective policy is NONE, then we need to
+       * recompute the effective policy.  Otherwise, the effective
+       * policy is considered to be up to date, i.e., effective_policy
+       * is a cache of the computed policy.  */
+      rc = gpgsql_exec_printf
+        (db, NULL, NULL, &err,
+         "alter table bindings"
+         " add column effective_policy INTEGER"
+         " DEFAULT %d"
+         " CHECK (effective_policy in (%d, %d, %d, %d, %d, %d));",
+         TOFU_POLICY_NONE,
+         TOFU_POLICY_NONE, TOFU_POLICY_AUTO, TOFU_POLICY_GOOD,
+         TOFU_POLICY_UNKNOWN, TOFU_POLICY_BAD, TOFU_POLICY_ASK);
+      if (rc)
+       {
+          if (rc == SQLITE_ERROR)
+            /* Almost certainly "duplicate column name", which we can
+             * safely ignore.  */
+            rc = 0;
+          else
+            log_error (_("adding column effective_policy to bindings DB: %s\n"),
+                       err);
+         sqlite3_free (err);
+       }
     }
 
+  if (! rc)
+    rc = check_utks (db);
+
   if (rc)
     {
       rc = sqlite3_exec (db, "rollback;", NULL, NULL, &err);
@@ -689,6 +904,36 @@ initdb (sqlite3 *db)
     }
 }
 
+static int
+busy_handler (void *cookie, int call_count)
+{
+  ctrl_t ctrl = cookie;
+  tofu_dbs_t dbs = ctrl->tofu.dbs;
+
+  (void) call_count;
+
+  /* Update the want-lock-file time stamp (specifically, the ctime) so
+   * that the current owner knows that we (well, someone) want the
+   * lock.  */
+  if (dbs)
+    {
+      /* Note: we don't fail if we can't create the lock file: this
+       * process will have to wait a bit longer, but otherwise nothing
+       * horrible should happen.  */
+
+      estream_t fp;
+
+      fp = es_fopen (dbs->want_lock_file, "w");
+      if (! fp)
+        log_debug ("TOFU: Error opening '%s': %s\n",
+                   dbs->want_lock_file, strerror (errno));
+      else
+        es_fclose (fp);
+    }
+
+  /* Call again.  */
+  return 1;
+}
 
 /* Create a new DB handle.  Returns NULL on error.  */
 /* FIXME: Change to return an error code for better reporting by the
@@ -713,12 +958,14 @@ opendbs (ctrl_t ctrl)
           sqlite3_close (db);
           db = NULL;
         }
-      xfree (filename);
 
       /* If a DB is locked wait up to 5 seconds for the lock to be cleared
          before failing.  */
       if (db)
-        sqlite3_busy_timeout (db, 5 * 1000);
+        {
+          sqlite3_busy_timeout (db, 5 * 1000);
+          sqlite3_busy_handler (db, busy_handler, ctrl);
+        }
 
       if (db && initdb (db))
         {
@@ -730,7 +977,10 @@ opendbs (ctrl_t ctrl)
         {
           ctrl->tofu.dbs = xmalloc_clear (sizeof *ctrl->tofu.dbs);
           ctrl->tofu.dbs->db = db;
+          ctrl->tofu.dbs->want_lock_file = xasprintf ("%s-want-lock", filename);
         }
+
+      xfree (filename);
     }
   else
     log_assert (ctrl->tofu.dbs->db);
@@ -761,6 +1011,7 @@ tofu_closedbs (ctrl_t ctrl)
     sqlite3_finalize (*statements);
 
   sqlite3_close (dbs->db);
+  xfree (dbs->want_lock_file);
   xfree (dbs);
   ctrl->tofu.dbs = NULL;
 }
@@ -797,8 +1048,10 @@ 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 (tofu_dbs_t dbs, const char *fingerprint, const char *email,
-               const char *user_id, enum tofu_policy policy, int show_old,
-                time_t now)
+               const char *user_id,
+                enum tofu_policy policy, enum tofu_policy effective_policy,
+                const char *conflict, int set_conflict,
+                int show_old, time_t now)
 {
   char *fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
   gpg_error_t rc;
@@ -850,12 +1103,6 @@ record_binding (tofu_dbs_t dbs, const char *fingerprint, const char *email,
            " <key: %s, user id: %s> to %s.\n",
            fingerprint, show_old ? user_id : email,
            tofu_policy_str (policy));
-
-      if (policy_old == policy)
-        {
-          rc = 0;
-          goto leave; /* Nothing to do.  */
-        }
     }
 
   if (opt.dry_run)
@@ -868,18 +1115,33 @@ record_binding (tofu_dbs_t dbs, const char *fingerprint, const char *email,
   rc = gpgsql_stepx
     (dbs->db, &dbs->s.record_binding_update, NULL, NULL, &err,
      "insert or replace into bindings\n"
-     " (oid, fingerprint, email, user_id, time, policy)\n"
+     " (oid, fingerprint, email, user_id, time,"
+     "  policy, conflict, effective_policy)\n"
      " values (\n"
      /* If we don't explicitly reuse the OID, then SQLite will
-       reallocate a new one.  We just need to search for the OID
-       based on the fingerprint and email since they are unique.  */
+      * reallocate a new one.  We just need to search for the OID
+      * based on the fingerprint and email since they are unique.  */
      "  (select oid from bindings where fingerprint = ? and email = ?),\n"
-     "  ?, ?, ?, ?, ?);",
+     "  ?, ?, ?, ?, ?,"
+     /* If SET_CONFLICT is 0, then preserve conflict's current value.  */
+     "  case ?"
+     "    when 0 then"
+     "      (select conflict from bindings where fingerprint = ? and email = ?)"
+     "    else ?"
+     "  end,"
+     "  ?);",
+     /* oid subquery.  */
      GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
+     /* values 2 through 6.  */
      GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
      GPGSQL_ARG_STRING, user_id,
      GPGSQL_ARG_LONG_LONG, (long long) now,
      GPGSQL_ARG_INT, (int) policy,
+     /* conflict subquery.  */
+     GPGSQL_ARG_INT, set_conflict ? 1 : 0,
+     GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
+     GPGSQL_ARG_STRING, conflict ? conflict : "",
+     GPGSQL_ARG_INT, (int) effective_policy,
      GPGSQL_ARG_END);
   if (rc)
     {
@@ -896,7 +1158,7 @@ record_binding (tofu_dbs_t dbs, const char *fingerprint, const char *email,
 }
 
 
-/* Collect the strings returned by a query in a simply string list.
+/* Collect the strings returned by a query in a simple string list.
    Any NULL values are converted to the empty string.
 
    If a result has 3 rows and each row contains two columns, then the
@@ -1041,124 +1303,8 @@ signature_stats_collect_cb (void *cookie, int argc, char **argv,
   return 0;
 }
 
-/* Convert from seconds to time units.
-
-   Note: T should already be a multiple of TIME_AGO_UNIT_SMALL or
-   TIME_AGO_UNIT_MEDIUM or TIME_AGO_UNIT_LARGE.  */
-signed long
-time_ago_scale (signed long t)
-{
-  if (t < TIME_AGO_UNIT_MEDIUM)
-    return t / TIME_AGO_UNIT_SMALL;
-  if (t < TIME_AGO_UNIT_LARGE)
-    return t / TIME_AGO_UNIT_MEDIUM;
-  return t / TIME_AGO_UNIT_LARGE;
-}
-
-
-/* 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 (tofu_dbs_t dbs, const char *fingerprint, const char *email,
-           char **conflict)
-{
-  int rc;
-  char *err = NULL;
-  strlist_t strlist = NULL;
-  enum tofu_policy policy = _tofu_GET_POLICY_ERROR;
-  long along;
-
-  /* Check if the <FINGERPRINT, EMAIL> binding is known
-     (TOFU_POLICY_NONE cannot appear in the DB.  Thus, if POLICY is
-     still TOFU_POLICY_NONE after executing the query, then the
-     result set was empty.)  */
-  rc = gpgsql_stepx (dbs->db, &dbs->s.get_policy_select_policy_and_conflict,
-                      strings_collect_cb2, &strlist, &err,
-                      "select policy, conflict from bindings\n"
-                      " where fingerprint = ? and email = ?",
-                      GPGSQL_ARG_STRING, fingerprint,
-                      GPGSQL_ARG_STRING, email,
-                      GPGSQL_ARG_END);
-  if (rc)
-    {
-      log_error (_("error reading TOFU database: %s\n"), err);
-      print_further_info ("checking for existing bad bindings");
-      sqlite3_free (err);
-      goto out;
-    }
-
-  if (strlist_length (strlist) == 0)
-    /* No results.  */
-    {
-      policy = TOFU_POLICY_NONE;
-      goto out;
-    }
-  else if (strlist_length (strlist) != 2)
-    /* The result has the wrong form.  */
-    {
-      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.  */
-
-  if (string_to_long (&along, strlist->d, 0, __LINE__))
-    {
-      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
-        || policy == TOFU_POLICY_UNKNOWN
-        || policy == TOFU_POLICY_BAD
-        || policy == TOFU_POLICY_ASK))
-    {
-      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;
-    }
-
-
-  /* If CONFLICT is set, then policy should be TOFU_POLICY_ASK.  But,
-     just in case, we do the check again here and ignore the conflict
-     if POLICY is not TOFU_POLICY_ASK.  */
-  if (conflict)
-    {
-      if (policy == TOFU_POLICY_ASK && *strlist->next->d)
-       *conflict = xstrdup (strlist->next->d);
-      else
-       *conflict = NULL;
-    }
-
- out:
-  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;
-}
-
-
 /* Format the first part of a conflict message and return that as a
- * malloced string.  */
+ * malloced string. Returns NULL on error. */
 static char *
 format_conflict_msg_part1 (int policy, strlist_t conflict_set,
                            const char *email)
@@ -1186,8 +1332,11 @@ format_conflict_msg_part1 (int policy, strlist_t conflict_set,
   else if (policy == TOFU_POLICY_ASK && conflict_set->next)
     {
       int conflicts = strlist_length (conflict_set);
-      es_fprintf (fp, _("The email address \"%s\" is associated with %d keys!"),
-                  email, conflicts);
+      es_fprintf
+        (fp, ngettext("The email address \"%s\" is associated with %d key!",
+                      "The email address \"%s\" is associated with %d keys!",
+                      conflicts),
+         email, conflicts);
       if (opt.verbose)
         es_fprintf (fp,
                     _("  Since this binding's policy was 'auto', it has been "
@@ -1205,7 +1354,7 @@ format_conflict_msg_part1 (int policy, strlist_t conflict_set,
   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);
+  text = format_text (tmpstr, 72, 80);
   es_free (tmpstr);
 
   return text;
@@ -1214,7 +1363,7 @@ format_conflict_msg_part1 (int policy, strlist_t conflict_set,
 
 /* Return 1 if A signed B and B signed A.  */
 static int
-cross_sigs (kbnode_t a, kbnode_t b)
+cross_sigs (const char *email, kbnode_t a, kbnode_t b)
 {
   int i;
 
@@ -1243,12 +1392,36 @@ cross_sigs (kbnode_t a, kbnode_t b)
       u32 *signer_kid = pk_main_keyid (signer_pk);
       kbnode_t n;
 
+      int saw_email = 0;
+
       /* Iterate over SIGNEE's keyblock and see if there is a valid
          signature from SIGNER.  */
       for (n = signee; n; n = n->next)
         {
           PKT_signature *sig;
 
+          if (n->pkt->pkttype == PKT_USER_ID)
+            {
+              if (saw_email)
+                /* We're done: we've processed all signatures on the
+                   user id.  */
+                break;
+              else
+                {
+                  /* See if this is the matching user id.  */
+                  PKT_user_id *user_id = n->pkt->pkt.user_id;
+                  char *email2 = email_from_user_id (user_id->name);
+
+                  if (strcmp (email, email2) == 0)
+                    saw_email = 1;
+
+                  xfree (email2);
+                }
+            }
+
+          if (! saw_email)
+            continue;
+
           if (n->pkt->pkttype != PKT_SIGNATURE)
             continue;
 
@@ -1287,14 +1460,37 @@ cross_sigs (kbnode_t a, kbnode_t b)
 
 /* Return whether the key was signed by an ultimately trusted key.  */
 static int
-signed_by_utk (kbnode_t a)
+signed_by_utk (const char *email, kbnode_t a)
 {
   kbnode_t n;
+  int saw_email = 0;
 
   for (n = a; n; n = n->next)
     {
       PKT_signature *sig;
 
+      if (n->pkt->pkttype == PKT_USER_ID)
+        {
+          if (saw_email)
+            /* We're done: we've processed all signatures on the
+               user id.  */
+            break;
+          else
+            {
+              /* See if this is the matching user id.  */
+              PKT_user_id *user_id = n->pkt->pkt.user_id;
+              char *email2 = email_from_user_id (user_id->name);
+
+              if (strcmp (email, email2) == 0)
+                saw_email = 1;
+
+              xfree (email2);
+            }
+        }
+
+      if (! saw_email)
+        continue;
+
       if (n->pkt->pkttype != PKT_SIGNATURE)
         continue;
 
@@ -1376,7 +1572,7 @@ ask_about_binding (ctrl_t ctrl,
   struct signature_stats *stats = NULL;
   struct signature_stats *stats_iter = NULL;
   char *prompt = NULL;
-  char *choices;
+  const char *choices;
 
   dbs = ctrl->tofu.dbs;
   log_assert (dbs);
@@ -1389,6 +1585,10 @@ ask_about_binding (ctrl_t ctrl,
 
   {
     char *text = format_conflict_msg_part1 (*policy, conflict_set, email);
+    if (!text) /* FIXME: Return the error all the way up.  */
+      log_fatal ("format failed: %s\n",
+                 gpg_strerror (gpg_error_from_syserror()));
+
     es_fputs (text, fp);
     es_fputc ('\n', fp);
     xfree (text);
@@ -1408,6 +1608,7 @@ ask_about_binding (ctrl_t ctrl,
       log_error (_("error gathering other user IDs: %s\n"), sqerr);
       sqlite3_free (sqerr);
       sqerr = NULL;
+      rc = gpg_error (GPG_ERR_GENERAL);
     }
 
   if (other_user_ids)
@@ -1485,7 +1686,10 @@ ask_about_binding (ctrl_t ctrl,
          GPGSQL_ARG_STRING, iter->d,
          GPGSQL_ARG_END);
       if (rc)
-        break;
+        {
+          rc = gpg_error (GPG_ERR_GENERAL);
+          break;
+        }
 
       if (!stats || strcmp (iter->d, stats->fingerprint) != 0)
         /* No stats for this binding.  Add a dummy entry.  */
@@ -1500,7 +1704,10 @@ ask_about_binding (ctrl_t ctrl,
          GPGSQL_ARG_STRING, iter->d,
          GPGSQL_ARG_END);
       if (rc)
-        break;
+        {
+          rc = gpg_error (GPG_ERR_GENERAL);
+          break;
+        }
 
 #undef STATS_SQL
 
@@ -1535,6 +1742,7 @@ ask_about_binding (ctrl_t ctrl,
       char *key = NULL;
       strlist_t binding;
       int seen_in_past = 0;
+      int encrypted = 1;
 
       es_fprintf (fp, _("Statistics for keys"
                         " with the email address \"%s\":\n"),
@@ -1548,6 +1756,14 @@ ask_about_binding (ctrl_t ctrl,
                      stats_iter->count);
 #endif
 
+          if (stats_iter->time_ago > 0 && encrypted)
+            {
+              /* We've change from the encrypted stats to the verified
+               * stats.  Reset SEEN_IN_PAST.  */
+              encrypted = 0;
+              seen_in_past = 0;
+            }
+
           if (! key || strcmp (key, stats_iter->fingerprint))
             {
               int this_key;
@@ -1569,12 +1785,12 @@ ask_about_binding (ctrl_t ctrl,
               if ((binding->flags & BINDING_REVOKED))
                 {
                   es_fprintf (fp, _("revoked"));
-                  es_fprintf (fp, _(", "));
+                  es_fprintf (fp, ", ");
                 }
               else if ((binding->flags & BINDING_EXPIRED))
                 {
                   es_fprintf (fp, _("expired"));
-                  es_fprintf (fp, _(", "));
+                  es_fprintf (fp, ", ");
                 }
 
               if (this_key)
@@ -1586,9 +1802,12 @@ ask_about_binding (ctrl_t ctrl,
               xfree (key_pp);
 
               seen_in_past = 0;
+
+              show_statistics (dbs, stats_iter->fingerprint, email,
+                               TOFU_POLICY_ASK, NULL, 1, now);
             }
 
-          if (abs(stats_iter->time_ago) == 1)
+          if (labs(stats_iter->time_ago) == 1)
             {
               /* The 1 in this case is the NULL entry.  */
               log_assert (stats_iter->count == 1);
@@ -1597,47 +1816,92 @@ ask_about_binding (ctrl_t ctrl,
           seen_in_past += stats_iter->count;
 
           es_fputs ("    ", fp);
-          /* TANSLATORS: This string is concatenated with one of
-           * the day/week/month strings to form one sentence.  */
-          if (stats_iter->time_ago > 0)
-            es_fprintf (fp, ngettext("Verified %d message",
-                                     "Verified %d messages",
-                                     seen_in_past), seen_in_past);
-          else
-            es_fprintf (fp, ngettext("Encrypted %d message",
-                                     "Encrypted %d messages",
-                                     seen_in_past), seen_in_past);
 
           if (!stats_iter->count)
-            es_fputs (".", fp);
-          else if (abs(stats_iter->time_ago) == 2)
             {
-              es_fprintf (fp, "in the future.");
+              if (stats_iter->time_ago > 0)
+                es_fprintf (fp, ngettext("Verified %d message.",
+                                         "Verified %d messages.",
+                                         seen_in_past), seen_in_past);
+              else
+                es_fprintf (fp, ngettext("Encrypted %d message.",
+                                         "Encrypted %d messages.",
+                                         seen_in_past), seen_in_past);
+            }
+          else if (labs(stats_iter->time_ago) == 2)
+            {
+              if (stats_iter->time_ago > 0)
+                es_fprintf (fp, ngettext("Verified %d message in the future.",
+                                         "Verified %d messages in the future.",
+                                         seen_in_past), seen_in_past);
+              else
+                es_fprintf (fp, ngettext("Encrypted %d message in the future.",
+                                         "Encrypted %d messages in the future.",
+                                         seen_in_past), seen_in_past);
               /* Reset it.  */
               seen_in_past = 0;
             }
           else
             {
-              if (abs(stats_iter->time_ago) == 3)
-                es_fprintf (fp, ngettext(" over the past days.",
-                                         " over the past %d days.",
-                                         seen_in_past),
-                            TIME_AGO_SMALL_THRESHOLD
-                            / TIME_AGO_UNIT_SMALL);
-              else if (abs(stats_iter->time_ago) == 4)
-                es_fprintf (fp, ngettext(" over the past month.",
-                                         " over the past %d months.",
-                                         seen_in_past),
-                            TIME_AGO_MEDIUM_THRESHOLD
-                            / TIME_AGO_UNIT_MEDIUM);
-              else if (abs(stats_iter->time_ago) == 5)
-                es_fprintf (fp, ngettext(" over the past year.",
-                                         " over the past %d years.",
-                                         seen_in_past),
-                            TIME_AGO_LARGE_THRESHOLD
-                            / TIME_AGO_UNIT_LARGE);
-              else if (abs(stats_iter->time_ago) == 6)
-                es_fprintf (fp, _(" in the past."));
+              if (labs(stats_iter->time_ago) == 3)
+                {
+                  int days = 1 + stats_iter->time_ago / TIME_AGO_UNIT_SMALL;
+                  if (stats_iter->time_ago > 0)
+                    es_fprintf
+                      (fp,
+                       ngettext("Messages verified over the past %d day: %d.",
+                                "Messages verified over the past %d days: %d.",
+                                days), days, seen_in_past);
+                  else
+                    es_fprintf
+                      (fp,
+                       ngettext("Messages encrypted over the past %d day: %d.",
+                                "Messages encrypted over the past %d days: %d.",
+                                days), days, seen_in_past);
+                }
+              else if (labs(stats_iter->time_ago) == 4)
+                {
+                  int months = 1 + stats_iter->time_ago / TIME_AGO_UNIT_MEDIUM;
+                  if (stats_iter->time_ago > 0)
+                    es_fprintf
+                      (fp,
+                       ngettext("Messages verified over the past %d month: %d.",
+                                "Messages verified over the past %d months: %d.",
+                                months), months, seen_in_past);
+                  else
+                    es_fprintf
+                      (fp,
+                       ngettext("Messages encrypted over the past %d month: %d.",
+                                "Messages encrypted over the past %d months: %d.",
+                                months), months, seen_in_past);
+                }
+              else if (labs(stats_iter->time_ago) == 5)
+                {
+                  int years = 1 + stats_iter->time_ago / TIME_AGO_UNIT_LARGE;
+                  if (stats_iter->time_ago > 0)
+                    es_fprintf
+                      (fp,
+                       ngettext("Messages verified over the past %d year: %d.",
+                                "Messages verified over the past %d years: %d.",
+                                years), years, seen_in_past);
+                  else
+                    es_fprintf
+                      (fp,
+                       ngettext("Messages encrypted over the past %d year: %d.",
+                                "Messages encrypted over the past %d years: %d.",
+                                years), years, seen_in_past);
+                }
+              else if (labs(stats_iter->time_ago) == 6)
+                {
+                  if (stats_iter->time_ago > 0)
+                    es_fprintf
+                      (fp, _("Messages verified in the past: %d."),
+                       seen_in_past);
+                  else
+                    es_fprintf
+                      (fp, _("Messages encrypted in the past: %d."),
+                       seen_in_past);
+                }
               else
                 log_assert (! "Broken SQL.\n");
             }
@@ -1652,7 +1916,7 @@ ask_about_binding (ctrl_t ctrl,
       /* 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");
+      const char *text = _("TOFU detected a binding conflict");
       char *textbuf;
       if (!strcmp (text, "TOFU detected a binding conflict"))
         {
@@ -1665,8 +1929,8 @@ ask_about_binding (ctrl_t ctrl,
             "attack!  Before accepting this association, 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", textbuf);
+      textbuf = format_text (text, 72, 80);
+      es_fprintf (fp, "\n%s\n", textbuf? textbuf : "[OUT OF CORE!]");
       xfree (textbuf);
     }
 
@@ -1680,7 +1944,7 @@ ask_about_binding (ctrl_t ctrl,
   /* 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
+   * wrong choice (because he does not see that either).  As a small
    * benefit we allow C-L to redisplay everything.  */
   tty_printf ("%s", prompt);
 
@@ -1708,7 +1972,7 @@ ask_about_binding (ctrl_t ctrl,
       else if (!response[0])
         /* Default to unknown.  Don't save it.  */
         {
-          tty_printf (_("Defaulting to unknown."));
+          tty_printf (_("Defaulting to unknown.\n"));
           *policy = TOFU_POLICY_UNKNOWN;
           break;
         }
@@ -1747,7 +2011,7 @@ ask_about_binding (ctrl_t ctrl,
                 }
 
               if (record_binding (dbs, fingerprint, email, user_id,
-                                  *policy, 0, now))
+                                  *policy, TOFU_POLICY_NONE, NULL, 0, 0, now))
                 {
                   /* If there's an error registering the
                    * binding, don't save the signature.  */
@@ -1770,7 +2034,9 @@ ask_about_binding (ctrl_t ctrl,
    email> (including the binding itself, which will be first in the
    list).  For each returned key also sets BINDING_NEW, etc.  */
 static strlist_t
-build_conflict_set (tofu_dbs_t dbs, const char *fingerprint, const char *email)
+build_conflict_set (ctrl_t ctrl, tofu_dbs_t dbs,
+                    PKT_public_key *pk, const char *fingerprint,
+                    const char *email)
 {
   gpg_error_t rc;
   char *sqerr;
@@ -1793,7 +2059,7 @@ build_conflict_set (tofu_dbs_t dbs, const char *fingerprint, const char *email)
      "select"
      /* A binding should only appear once, but try not to break in the
       * case of corruption.  */
-     "  fingerprint || case sum(conflict ISNULL) when 0 then '' else '!' end"
+     "  fingerprint || case sum(conflict NOTNULL) when 0 then '' else '!' end"
      " from bindings where email = ?"
      "  group by fingerprint"
      /* Make sure the current key comes first in the result list (if
@@ -1807,6 +2073,7 @@ build_conflict_set (tofu_dbs_t dbs, const char *fingerprint, const char *email)
       log_error (_("error reading TOFU database: %s\n"), sqerr);
       print_further_info ("listing fingerprints");
       sqlite3_free (sqerr);
+      rc = gpg_error (GPG_ERR_GENERAL);
       return NULL;
     }
 
@@ -1846,6 +2113,18 @@ build_conflict_set (tofu_dbs_t dbs, const char *fingerprint, const char *email)
 
   /* Eliminate false conflicts.  */
 
+  if (conflict_set_count == 1)
+    /* We only have a single key.  There are no false conflicts to
+       eliminate.  But, we do need to set the flags.  */
+    {
+      if (pk->has_expired)
+        conflict_set->flags |= BINDING_EXPIRED;
+      if (pk->flags.revoked)
+        conflict_set->flags |= BINDING_REVOKED;
+
+      return conflict_set;
+    }
+
   /* If two keys have cross signatures, then they are controlled by
    * the same person and thus are not in conflict.  */
   kb_all = xcalloc (sizeof (kb_all[0]), conflict_set_count);
@@ -1898,7 +2177,7 @@ build_conflict_set (tofu_dbs_t dbs, const char *fingerprint, const char *email)
           continue;
         }
 
-      merge_keys_and_selfsig (kb);
+      merge_keys_and_selfsig (ctrl, kb);
 
       log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
 
@@ -1911,9 +2190,9 @@ build_conflict_set (tofu_dbs_t dbs, const char *fingerprint, const char *email)
       /* The binding is always expired/revoked if the key is
        * expired/revoked.  */
       if (binding_pk->has_expired)
-        iter->flags &= BINDING_EXPIRED;
+        iter->flags |= BINDING_EXPIRED;
       if (binding_pk->flags.revoked)
-        iter->flags &= BINDING_REVOKED;
+        iter->flags |= BINDING_REVOKED;
 
       /* The binding is also expired/revoked if the user id is
        * expired/revoked.  */
@@ -1933,10 +2212,10 @@ build_conflict_set (tofu_dbs_t dbs, const char *fingerprint, const char *email)
             {
               found_user_id = 1;
 
-              if (user_id2->is_revoked)
-                iter->flags &= BINDING_REVOKED;
-              if (user_id2->is_expired)
-                iter->flags &= BINDING_EXPIRED;
+              if (user_id2->flags.revoked)
+                iter->flags |= BINDING_REVOKED;
+              if (user_id2->flags.expired)
+                iter->flags |= BINDING_EXPIRED;
             }
 
           xfree (email2);
@@ -1956,9 +2235,15 @@ build_conflict_set (tofu_dbs_t dbs, const char *fingerprint, const char *email)
     int j;
     strlist_t *prevp;
     strlist_t iter_next;
-    int die[conflict_set_count];
+    int *die;
 
-    memset (die, 0, sizeof (die));
+    log_assert (conflict_set_count > 0);
+    die = xtrycalloc (conflict_set_count, sizeof *die);
+    if (!die)
+      {
+        /*err = gpg_error_from_syserror ();*/
+        xoutofcore (); /* Fixme: Let the function return an error.  */
+      }
 
     for (i = 0; i < conflict_set_count; i ++)
       {
@@ -1969,7 +2254,7 @@ build_conflict_set (tofu_dbs_t dbs, const char *fingerprint, const char *email)
 
         for (j = i + 1; j < conflict_set_count; j ++)
           /* Be careful: we might not have a key block for a key.  */
-          if (kb_all[i] && kb_all[j] && cross_sigs (kb_all[i], kb_all[j]))
+          if (kb_all[i] && kb_all[j] && cross_sigs (email, kb_all[i], kb_all[j]))
             die[j] = 1;
       }
 
@@ -1998,7 +2283,9 @@ build_conflict_set (tofu_dbs_t dbs, const char *fingerprint, const char *email)
     /* We shouldn't have removed the head.  */
     log_assert (conflict_set);
     log_assert (conflict_set_count >= 1);
+    xfree (die);
   }
+  xfree (kb_all);
 
   if (DBG_TRUST)
     {
@@ -2019,12 +2306,344 @@ build_conflict_set (tofu_dbs_t dbs, const char *fingerprint, const char *email)
 }
 
 
-/* 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.
+/* Return the effective policy for the binding <FINGERPRINT, EMAIL>
+ * (email has already been normalized).  Returns
+ * _tofu_GET_POLICY_ERROR if an error occurs.  Returns any conflict
+ * information in *CONFLICT_SETP if CONFLICT_SETP is not NULL and the
+ * returned policy is TOFU_POLICY_ASK (consequently, if there is a
+ * conflict, but the user set the policy to good *CONFLICT_SETP will
+ * empty).  Note: as per build_conflict_set, which is used to build
+ * the conflict information, the conflict information includes the
+ * current user id as the first element of the linked list.
  *
- * PK is the public key object for FINGERPRINT.
+ * This function registers the binding in the bindings table if it has
+ * not yet been registered.
+ */
+static enum tofu_policy
+get_policy (ctrl_t ctrl, tofu_dbs_t dbs, PKT_public_key *pk,
+            const char *fingerprint, const char *user_id, const char *email,
+           strlist_t *conflict_setp, time_t now)
+{
+  int rc;
+  char *err = NULL;
+  strlist_t results = NULL;
+  enum tofu_policy policy = _tofu_GET_POLICY_ERROR;
+  enum tofu_policy effective_policy_orig = TOFU_POLICY_NONE;
+  enum tofu_policy effective_policy = _tofu_GET_POLICY_ERROR;
+  long along;
+  char *conflict_orig = NULL;
+  char *conflict = NULL;
+  strlist_t conflict_set = NULL;
+  int conflict_set_count;
+
+  /* Check if the <FINGERPRINT, EMAIL> binding is known
+     (TOFU_POLICY_NONE cannot appear in the DB.  Thus, if POLICY is
+     still TOFU_POLICY_NONE after executing the query, then the
+     result set was empty.)  */
+  rc = gpgsql_stepx (dbs->db, &dbs->s.get_policy_select_policy_and_conflict,
+                      strings_collect_cb2, &results, &err,
+                      "select policy, conflict, effective_policy from bindings\n"
+                      " where fingerprint = ? and email = ?",
+                      GPGSQL_ARG_STRING, fingerprint,
+                      GPGSQL_ARG_STRING, email,
+                      GPGSQL_ARG_END);
+  if (rc)
+    {
+      log_error (_("error reading TOFU database: %s\n"), err);
+      print_further_info ("reading the policy");
+      sqlite3_free (err);
+      rc = gpg_error (GPG_ERR_GENERAL);
+      goto out;
+    }
+
+  if (strlist_length (results) == 0)
+    {
+      /* No results.  Use the defaults.  */
+      policy = TOFU_POLICY_NONE;
+      effective_policy = TOFU_POLICY_NONE;
+    }
+  else if (strlist_length (results) == 3)
+    {
+      /* Parse and sanity check the results.  */
+
+      if (string_to_long (&along, results->d, 0, __LINE__))
+        {
+          log_error (_("error reading TOFU database: %s\n"),
+                     gpg_strerror (GPG_ERR_BAD_DATA));
+          print_further_info ("bad value for policy: %s", results->d);
+          goto out;
+        }
+      policy = along;
+
+      if (! (policy == TOFU_POLICY_AUTO
+             || policy == TOFU_POLICY_GOOD
+             || policy == TOFU_POLICY_UNKNOWN
+             || policy == TOFU_POLICY_BAD
+             || policy == TOFU_POLICY_ASK))
+        {
+          log_error (_("error reading TOFU database: %s\n"),
+                     gpg_strerror (GPG_ERR_DB_CORRUPTED));
+          print_further_info ("invalid value for policy (%d)", policy);
+          effective_policy = _tofu_GET_POLICY_ERROR;
+          goto out;
+        }
+
+      if (*results->next->d)
+        conflict = xstrdup (results->next->d);
+
+      if (string_to_long (&along, results->next->next->d, 0, __LINE__))
+        {
+          log_error (_("error reading TOFU database: %s\n"),
+                     gpg_strerror (GPG_ERR_BAD_DATA));
+          print_further_info ("bad value for effective policy: %s",
+                              results->next->next->d);
+          goto out;
+        }
+      effective_policy = along;
+
+      if (! (effective_policy == TOFU_POLICY_NONE
+             || effective_policy == TOFU_POLICY_AUTO
+             || effective_policy == TOFU_POLICY_GOOD
+             || effective_policy == TOFU_POLICY_UNKNOWN
+             || effective_policy == TOFU_POLICY_BAD
+             || effective_policy == TOFU_POLICY_ASK))
+        {
+          log_error (_("error reading TOFU database: %s\n"),
+                     gpg_strerror (GPG_ERR_DB_CORRUPTED));
+          print_further_info ("invalid value for effective_policy (%d)",
+                              effective_policy);
+          effective_policy = _tofu_GET_POLICY_ERROR;
+          goto out;
+        }
+    }
+  else
+    {
+      /* The result has the wrong form.  */
+
+      log_error (_("error reading TOFU database: %s\n"),
+                 gpg_strerror (GPG_ERR_BAD_DATA));
+      print_further_info ("reading policy: expected 3 columns, got %d\n",
+                          strlist_length (results));
+      goto out;
+    }
+
+  /* Save the effective policy and conflict so we know if we changed
+   * them.  */
+  effective_policy_orig = effective_policy;
+  conflict_orig = conflict;
+
+  /* Unless there is a conflict, if the effective policy is cached,
+   * just return it.  The reason we don't do this when there is a
+   * conflict is because of the following scenario: assume A and B
+   * conflict and B has signed A's key.  Now, later we import A's
+   * signature on B.  We need to recheck A, but the signature was on
+   * B, i.e., when B changes, we invalidate B's effective policy, but
+   * we also need to invalidate A's effective policy.  Instead, we
+   * assume that conflicts are rare and don't optimize for them, which
+   * would complicate the code.  */
+  if (effective_policy != TOFU_POLICY_NONE && !conflict)
+    goto out;
+
+  /* If the user explicitly set the policy, then respect that.  */
+  if (policy != TOFU_POLICY_AUTO && policy != TOFU_POLICY_NONE)
+    {
+      effective_policy = policy;
+      goto out;
+    }
+
+  /* Unless proven wrong, assume the effective policy is 'auto'.  */
+  effective_policy = TOFU_POLICY_AUTO;
+
+  /* See if the key is ultimately trusted.  */
+  {
+    u32 kid[2];
+
+    keyid_from_pk (pk, kid);
+    if (tdb_keyid_is_utk (kid))
+      {
+        effective_policy = TOFU_POLICY_GOOD;
+        goto out;
+      }
+  }
+
+  /* See if the key is signed by an ultimately trusted key.  */
+  {
+    int fingerprint_raw_len = strlen (fingerprint) / 2;
+    char fingerprint_raw[20];
+    int len = 0;
+
+    if (fingerprint_raw_len != sizeof fingerprint_raw
+        || ((len = hex2bin (fingerprint,
+                            fingerprint_raw, fingerprint_raw_len))
+            != strlen (fingerprint)))
+      {
+        if (DBG_TRUST)
+          log_debug ("TOFU: Bad fingerprint: %s (len: %zu, parsed: %d)\n",
+                     fingerprint, strlen (fingerprint), len);
+      }
+    else
+      {
+        int lookup_err;
+        kbnode_t kb;
+
+        lookup_err = get_pubkey_byfprint (ctrl, NULL, &kb,
+                                          fingerprint_raw,
+                                          fingerprint_raw_len);
+        if (lookup_err)
+          {
+            if (DBG_TRUST)
+              log_debug ("TOFU: Looking up %s: %s\n",
+                         fingerprint, gpg_strerror (lookup_err));
+          }
+        else
+          {
+            int is_signed_by_utk = signed_by_utk (email, kb);
+            release_kbnode (kb);
+            if (is_signed_by_utk)
+              {
+                effective_policy = TOFU_POLICY_GOOD;
+                goto out;
+              }
+          }
+      }
+  }
+
+  /* Check for any conflicts / see if a previously discovered conflict
+   * disappeared.  The latter can happen if the conflicting bindings
+   * are now cross signed, for instance.  */
+
+  conflict_set = build_conflict_set (ctrl, dbs, pk, fingerprint, email);
+  conflict_set_count = strlist_length (conflict_set);
+  if (conflict_set_count == 0)
+    {
+      /* build_conflict_set should always at least return the current
+         binding.  Something went wrong.  */
+      effective_policy = _tofu_GET_POLICY_ERROR;
+      goto out;
+    }
+
+  if (conflict_set_count == 1
+      && (conflict_set->flags & BINDING_NEW))
+    {
+      /* We've never observed a binding with this email address 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);
+
+      if (DBG_TRUST)
+       log_debug ("TOFU: New binding <key: %s, user id: %s>, no conflict.\n",
+                  fingerprint, email);
+
+      effective_policy = TOFU_POLICY_AUTO;
+      goto out;
+    }
+
+  if (conflict_set_count == 1
+      && (conflict_set->flags & BINDING_CONFLICT))
+    {
+      /* No known conflicts now, but there was a conflict.  This means
+       * at some point, there was a conflict and we changed this
+       * binding's policy to ask and set the conflicting key.  The
+       * conflict can go away if there is not a cross sig between the
+       * two keys.  In this case, just silently clear the conflict and
+       * reset the policy to auto.  */
+
+      if (DBG_TRUST)
+        log_debug ("TOFU: binding <key: %s, user id: %s> had a conflict, but it's been resolved (probably via  cross sig).\n",
+                   fingerprint, email);
+
+      effective_policy = TOFU_POLICY_AUTO;
+      conflict = NULL;
+
+      goto out;
+    }
+
+  if (conflict_set_count == 1)
+    {
+      /* No conflicts and never marked as conflicting.  */
+
+      log_assert (!conflict);
+
+      effective_policy = TOFU_POLICY_AUTO;
+
+      goto out;
+    }
+
+  /* There is a conflicting key.  */
+  log_assert (conflict_set_count > 1);
+  effective_policy = TOFU_POLICY_ASK;
+  conflict = xstrdup (conflict_set->next->d);
+
+ out:
+  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);
+  /* Everything but NONE.  */
+  log_assert (effective_policy == _tofu_GET_POLICY_ERROR
+              || effective_policy == TOFU_POLICY_AUTO
+              || effective_policy == TOFU_POLICY_GOOD
+              || effective_policy == TOFU_POLICY_UNKNOWN
+              || effective_policy == TOFU_POLICY_BAD
+              || effective_policy == TOFU_POLICY_ASK);
+
+  if (effective_policy != TOFU_POLICY_ASK && conflict)
+    conflict = NULL;
+
+  /* If we don't have a record of this binding, its effective policy
+   * changed, or conflict changed, update the DB.  */
+  if (effective_policy != _tofu_GET_POLICY_ERROR
+      && (/* New binding.  */
+          policy == TOFU_POLICY_NONE
+          /* effective_policy changed.  */
+          || effective_policy != effective_policy_orig
+          /* conflict changed.  */
+          || (conflict != conflict_orig
+              && (!conflict || !conflict_orig
+                  || strcmp (conflict, conflict_orig) != 0))))
+    {
+      if (record_binding (dbs, fingerprint, email, user_id,
+                          policy == TOFU_POLICY_NONE ? TOFU_POLICY_AUTO : policy,
+                          effective_policy, conflict, 1, 0, now) != 0)
+        log_error (_("error setting TOFU binding's policy"
+                     " to %s\n"), tofu_policy_str (policy));
+    }
+
+  /* If the caller wants the set of conflicts, return it.  */
+  if (effective_policy == TOFU_POLICY_ASK && conflict_setp)
+    {
+      if (! conflict_set)
+        conflict_set = build_conflict_set (ctrl, dbs, pk, fingerprint, email);
+      *conflict_setp = conflict_set;
+    }
+  else
+    {
+      free_strlist (conflict_set);
+
+      if (conflict_setp)
+        *conflict_setp = NULL;
+    }
+
+  xfree (conflict_orig);
+  if (conflict != conflict_orig)
+    xfree (conflict);
+  free_strlist (results);
+
+  return effective_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.
+ *
+ * PK is the public key object for FINGERPRINT.
  *
  * USER_ID is the unadulterated user id.
  *
@@ -2037,16 +2656,16 @@ build_conflict_set (tofu_dbs_t dbs, const char *fingerprint, const char *email)
 static enum tofu_policy
 get_trust (ctrl_t ctrl, PKT_public_key *pk,
            const char *fingerprint, const char *email,
-          const char *user_id, int may_ask, time_t now)
+           const char *user_id, int may_ask,
+           enum tofu_policy *policyp, strlist_t *conflict_setp,
+           time_t now)
 {
   tofu_dbs_t dbs = ctrl->tofu.dbs;
   int in_transaction = 0;
   enum tofu_policy policy;
   int rc;
   char *sqerr = NULL;
-  int change_conflicting_to_ask = 0;
   strlist_t conflict_set = NULL;
-  int conflict_set_count;
   int trust_level = TRUST_UNKNOWN;
   strlist_t iter;
 
@@ -2058,7 +2677,7 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
   if (opt.batch)
     may_ask = 0;
 
-  log_assert (keyid_cmp (pk_keyid (pk), pk->main_keyid) == 0);
+  log_assert (pk_is_primary (pk));
 
   /* Make sure _tofu_GET_TRUST_ERROR isn't equal to any of the trust
      levels.  */
@@ -2073,28 +2692,29 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
   begin_transaction (ctrl, 0);
   in_transaction = 1;
 
-  policy = get_policy (dbs, fingerprint, email, NULL);
+  /* We need to call get_policy even if the key is ultimately trusted
+   * to make sure the binding has been registered.  */
+  policy = get_policy (ctrl, dbs, pk, fingerprint, user_id, email,
+                       &conflict_set, now);
+
+  if (policy == TOFU_POLICY_ASK)
+    /* The conflict set should always contain at least one element:
+     * the current key.  */
+    log_assert (conflict_set);
+  else
+    /* If the policy is not TOFU_POLICY_ASK, then conflict_set will be
+     * NULL.  */
+    log_assert (! conflict_set);
+
+  /* If the key is ultimately trusted, there is nothing to do.  */
   {
-    /* See if the key is ultimately trusted.  If so, we're done.  */
     u32 kid[2];
 
     keyid_from_pk (pk, kid);
-
     if (tdb_keyid_is_utk (kid))
       {
-        if (policy == TOFU_POLICY_NONE)
-          {
-            if (record_binding (dbs, fingerprint, email, user_id,
-                                TOFU_POLICY_GOOD, 0, now) != 0)
-              {
-                log_error (_("error setting TOFU binding's trust level"
-                             " to %s\n"), "good");
-                trust_level = _tofu_GET_TRUST_ERROR;
-                goto out;
-              }
-          }
-
         trust_level = TRUST_ULTIMATE;
+        policy = TOFU_POLICY_GOOD;
         goto out;
       }
   }
@@ -2107,6 +2727,14 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
                    " auto (default: %s).\n",
                   fingerprint, email,
                   tofu_policy_str (opt.tofu_default_policy));
+
+      if (policy == TOFU_POLICY_ASK)
+        /* The default policy is ASK, but there is no conflict (policy
+         * was 'auto').  In this case, we need to make sure the
+         * conflict set includes at least the current user id.  */
+        {
+          add_to_strlist (&conflict_set, fingerprint);
+        }
     }
   switch (policy)
     {
@@ -2123,18 +2751,7 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
       goto out;
 
     case TOFU_POLICY_ASK:
-      /* We need to ask the user what to do.  Case #1 or #2 below.  */
-      if (! may_ask)
-       {
-         trust_level = TRUST_UNDEFINED;
-         goto out;
-       }
-
-      break;
-
-    case TOFU_POLICY_NONE:
-      /* The binding is new, we need to check for conflicts.  Case #3
-       * below.  */
+      /* We need to ask the user what to do.  */
       break;
 
     case _tofu_GET_POLICY_ERROR:
@@ -2155,217 +2772,87 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
    *   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).
-   *
-   * In summary: POLICY is ask or none.
+   *      (policy == TOFU_POLICY_ASK).
    */
+  log_assert (policy == TOFU_POLICY_ASK);
 
-  /* Before continuing, see if the key is signed by an ultimately
-     trusted key.  */
-  {
-    int fingerprint_raw_len = strlen (fingerprint) / 2;
-    char fingerprint_raw[fingerprint_raw_len];
-    int len = 0;
-    int is_signed_by_utk = 0;
-
-    if (fingerprint_raw_len != 20
-        || ((len = hex2bin (fingerprint,
-                            fingerprint_raw, fingerprint_raw_len))
-            != strlen (fingerprint)))
-      {
-        if (DBG_TRUST)
-          log_debug ("TOFU: Bad fingerprint: %s (len: %zd, parsed: %d)\n",
-                     fingerprint, strlen (fingerprint), len);
-      }
-    else
-      {
-        int lookup_err;
-        kbnode_t kb;
-
-        lookup_err = get_pubkey_byfprint (NULL, &kb,
-                                          fingerprint_raw,
-                                          fingerprint_raw_len);
-        if (lookup_err)
-          {
-            if (DBG_TRUST)
-              log_debug ("TOFU: Looking up %s: %s\n",
-                         fingerprint, gpg_strerror (lookup_err));
-          }
-        else
-          {
-            is_signed_by_utk = signed_by_utk (kb);
-            release_kbnode (kb);
-          }
-      }
-
-    if (is_signed_by_utk)
-      {
-        if (record_binding (dbs, fingerprint, email, user_id,
-                            TOFU_POLICY_GOOD, 0, now) != 0)
-          {
-            log_error (_("error setting TOFU binding's trust level"
-                         " to %s\n"), "good");
-            trust_level = _tofu_GET_TRUST_ERROR;
-          }
-        else
-          trust_level = TRUST_FULLY;
-
-        goto out;
-      }
-  }
-
-
-  /* Look for conflicts.  This is needed in all 3 cases.  */
-  conflict_set = build_conflict_set (dbs, fingerprint, email);
-  conflict_set_count = strlist_length (conflict_set);
-  if (conflict_set_count == 0)
-    {
-      /* We should always at least have the current binding.  */
-      trust_level = _tofu_GET_TRUST_ERROR;
-      goto out;
-    }
-
-  if (conflict_set_count == 1
-      && (conflict_set->flags & BINDING_NEW)
-      && opt.tofu_default_policy != TOFU_POLICY_ASK)
+  if (may_ask)
     {
-      /* We've never observed a binding with this email address 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);
+      /* We can't be in a normal transaction in ask_about_binding.  */
+      end_transaction (ctrl, 0);
+      in_transaction = 0;
 
-      if (DBG_TRUST)
-       log_debug ("TOFU: New binding <key: %s, user id: %s>, no conflict.\n",
-                  fingerprint, email);
-
-      if (record_binding (dbs, fingerprint, email, user_id,
-                         TOFU_POLICY_AUTO, 0, now) != 0)
-       {
-         log_error (_("error setting TOFU binding's trust level to %s\n"),
-                      "auto");
-         trust_level = _tofu_GET_TRUST_ERROR;
-         goto out;
-       }
-
-      trust_level = tofu_policy_to_trust_level (TOFU_POLICY_AUTO);
-      goto out;
+      /* If we get here, we need to ask the user about the binding.  */
+      ask_about_binding (ctrl,
+                         &policy,
+                         &trust_level,
+                         conflict_set,
+                         fingerprint,
+                         email,
+                         user_id,
+                         now);
     }
-
-  if (conflict_set_count == 1
-      && (conflict_set->flags & BINDING_CONFLICT))
+  else
     {
-      /* No known conflicts now, but there was a conflict.  This means
-       * at somepoint, there was a conflict and we changed this
-       * binding's policy to ask and set the conflicting key.  The
-       * conflict can go away if there is not a cross sig between the
-       * two keys.  In this case, just silently clear the conflict and
-       * reset the policy to auto.  */
-
-      log_assert (policy == TOFU_POLICY_ASK);
-
-      if (DBG_TRUST)
-        log_debug ("TOFU: binding <key: %s, user id: %s> had a conflict, but it's been resolved (probably via  cross sig).\n",
-                   fingerprint, email);
-
-      if (record_binding (dbs, fingerprint, email, user_id,
-                         TOFU_POLICY_AUTO, 0, now) != 0)
-       log_error (_("error setting TOFU binding's trust level to %s\n"),
-                  "auto");
-
-      trust_level = tofu_policy_to_trust_level (TOFU_POLICY_AUTO);
-      goto out;
+      trust_level = TRUST_UNDEFINED;
     }
 
-  /* 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)
+  /* Mark any conflicting bindings that have an automatic policy as
+   * now requiring confirmation.  Note: we do this after we ask for
+   * confirmation so that when the current policy is printed, it is
+   * correct.  */
+  if (! in_transaction)
     {
-      /* 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, now) != 0)
-       log_error (_("error setting TOFU binding's trust level to %s\n"),
-                  "ask");
-
-      trust_level = TRUST_UNDEFINED;
-      goto out;
+      begin_transaction (ctrl, 0);
+      in_transaction = 1;
     }
 
-  /* We can't be in a normal transaction in ask_about_binding.  */
-  end_transaction (ctrl, 0);
-  in_transaction = 0;
-
-  /* If we get here, we need to ask the user about the binding.  */
-  ask_about_binding (ctrl,
-                     &policy,
-                     &trust_level,
-                     conflict_set,
-                     fingerprint,
-                     email,
-                     user_id,
-                     now);
-
- out:
+  /* The conflict set should always contain at least one element:
+   * the current key.  */
+  log_assert (conflict_set);
 
-  if (change_conflicting_to_ask)
+  for (iter = conflict_set->next; iter; iter = iter->next)
     {
-      /* Mark any conflicting bindings that have an automatic policy as
-       * now requiring confirmation.  */
-
-      if (! in_transaction)
-        {
-          begin_transaction (ctrl, 0);
-          in_transaction = 1;
-        }
-
-      /* If we weren't allowed to ask, also update this key as
-       * conflicting with itself.  */
-      for (iter = may_ask ? conflict_set->next : conflict_set;
-           iter; iter = iter->next)
+      /* We don't immediately set the effective policy to 'ask,
+         because  */
+      rc = gpgsql_exec_printf
+        (dbs->db, NULL, NULL, &sqerr,
+         "update bindings set effective_policy = %d, conflict = %Q"
+         " where email = %Q and fingerprint = %Q and effective_policy != %d;",
+         TOFU_POLICY_NONE, fingerprint,
+         email, iter->d, TOFU_POLICY_ASK);
+      if (rc)
         {
-          rc = gpgsql_exec_printf
-            (dbs->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, iter->d, TOFU_POLICY_AUTO);
-          if (rc)
-            {
-              log_error (_("error changing TOFU policy: %s\n"), sqerr);
-              print_further_info ("binding: <key: %s, user id: %s>",
-                                  fingerprint, user_id);
-              sqlite3_free (sqerr);
-              sqerr = NULL;
-            }
-          else if (DBG_TRUST)
-            log_debug ("Set %s to conflict with %s\n",
-                       iter->d, fingerprint);
+          log_error (_("error changing TOFU policy: %s\n"), sqerr);
+          print_further_info ("binding: <key: %s, user id: %s>",
+                              fingerprint, user_id);
+          sqlite3_free (sqerr);
+          sqerr = NULL;
+          rc = gpg_error (GPG_ERR_GENERAL);
         }
+      else if (DBG_TRUST)
+        log_debug ("Set %s to conflict with %s\n",
+                   iter->d, fingerprint);
     }
 
+ out:
   if (in_transaction)
     end_transaction (ctrl, 0);
 
-  free_strlist (conflict_set);
+  if (policyp)
+    *policyp = policy;
+
+  if (conflict_setp)
+    *conflict_setp = conflict_set;
+  else
+    free_strlist (conflict_set);
 
   return trust_level;
 }
 
 
 /* Return a malloced string of the form
- *    "7 months, 1 day, 5 minutes, 0 seconds"
+ *    "7~months"
  * The caller should replace all '~' in the returned string by a space
  * and also free the returned string.
  *
@@ -2375,127 +2862,46 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
 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;
-
-  char *str;
-
   /* 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 WEEK_SECS (7 * DAY_SECS)
 #define MONTH_SECS (30 * DAY_SECS)
 #define YEAR_SECS (365 * DAY_SECS)
 
-  if (t > YEAR_SECS)
-    {
-      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;
-
-#undef MIN_SECS
-#undef HOUR_SECS
-#undef DAY_SECS
-#undef MONTH_SECS
-#undef YEAR_SECS
-
-  fp = es_fopenmem (0, "rw,samethread");
-  if (! fp)
-    log_fatal ("error creating memory stream: %s\n",
-               gpg_strerror (gpg_error_from_syserror()));
-
-  if (years)
+  if (t > 2 * YEAR_SECS)
     {
-      /* 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;
+      long long int c = t / YEAR_SECS;
+      return xtryasprintf (ngettext("%lld~year", "%lld~years", c), c);
     }
-  i ++;
-  if ((first == -1 || i - first <= 3) && count <= 0 && months)
+  if (t > 2 * MONTH_SECS)
     {
-      if (count)
-        es_fprintf (fp, ", ");
-      es_fprintf (fp, ngettext("%d~month", "%d~months", months), months);
-      count ++;
-      first = i;
+      long long int c = t / MONTH_SECS;
+      return xtryasprintf (ngettext("%lld~month", "%lld~months", c), c);
     }
-  i ++;
-  if ((first == -1 || i - first <= 3) && count <= 0 && days)
+  if (t > 2 * WEEK_SECS)
     {
-      if (count)
-        es_fprintf (fp, ", ");
-      es_fprintf (fp, ngettext("%d~day", "%d~days", days), days);
-      count ++;
-      first = i;
+      long long int c = t / WEEK_SECS;
+      return xtryasprintf (ngettext("%lld~week", "%lld~weeks", c), c);
     }
-  i ++;
-  if ((first == -1 || i - first <= 3) && count <= 0 && hours)
+  if (t > 2 * DAY_SECS)
     {
-      if (count)
-        es_fprintf (fp, ", ");
-      es_fprintf (fp, ngettext("%d~hour", "%d~hours", hours), hours);
-      count ++;
-      first = i;
+      long long int c = t / DAY_SECS;
+      return xtryasprintf (ngettext("%lld~day", "%lld~days", c), c);
     }
-  i ++;
-  if ((first == -1 || i - first <= 3) && count <= 0 && minutes)
+  if (t > 2 * HOUR_SECS)
     {
-      if (count)
-        es_fprintf (fp, ", ");
-      es_fprintf (fp, ngettext("%d~minute", "%d~minutes", minutes), minutes);
-      count ++;
-      first = i;
+      long long int c = t / HOUR_SECS;
+      return xtryasprintf (ngettext("%lld~hour", "%lld~hours", c), c);
     }
-  i ++;
-  if ((first == -1 || i - first <= 3) && count <= 0)
+  if (t > 2 * MIN_SECS)
     {
-      if (count)
-        es_fprintf (fp, ", ");
-      es_fprintf (fp, ngettext("%d~second", "%d~seconds", seconds), seconds);
+      long long int c = t / MIN_SECS;
+      return xtryasprintf (ngettext("%lld~minute", "%lld~minutes", c), c);
     }
-
-  es_fputc (0, fp);
-  if (es_fclose_snatch (fp, (void **) &str, NULL))
-    log_fatal ("error snatching memory stream\n");
-
-  return str;
+  return xtryasprintf (ngettext("%lld~second", "%lld~seconds", t), t);
 }
 
 
@@ -2507,64 +2913,76 @@ write_stats_status (estream_t fp,
                     unsigned long signature_count,
                     unsigned long signature_first_seen,
                     unsigned long signature_most_recent,
+                    unsigned long signature_days,
                     unsigned long encryption_count,
                     unsigned long encryption_first_done,
-                    unsigned long encryption_most_recent)
+                    unsigned long encryption_most_recent,
+                    unsigned long encryption_days)
 {
-  const char *validity;
-  unsigned long messages;
+  int summary;
+  int validity;
+  unsigned long days_sq;
 
   /* Use the euclidean distance (m = sqrt(a^2 + b^2)) rather then the
      sum of the magnitudes (m = a + b) to ensure a balance between
      verified signatures and encrypted messages.  */
-  messages = sqrtu32 (signature_count * signature_count
-                      + encryption_count * encryption_count);
-
-  if (messages < 1)
-    validity = "1"; /* Key without history.  */
-  else if (messages < 2 * BASIC_TRUST_THRESHOLD)
-    validity = "2"; /* Key with too little history.  */
-  else if (messages < 2 * FULL_TRUST_THRESHOLD)
-    validity = "3"; /* Key with enough history for basic trust.  */
+  days_sq = signature_days * signature_days + encryption_days * encryption_days;
+
+  if (days_sq < 1)
+    validity = 1; /* Key without history.  */
+  else if (days_sq < (2 * BASIC_TRUST_THRESHOLD) * (2 * BASIC_TRUST_THRESHOLD))
+    validity = 2; /* Key with too little history.  */
+  else if (days_sq < (2 * FULL_TRUST_THRESHOLD) * (2 * FULL_TRUST_THRESHOLD))
+    validity = 3; /* Key with enough history for basic trust.  */
   else
-    validity = "4"; /* Key with a lot of history.  */
+    validity = 4; /* Key with a lot of history.  */
+
+  if (policy == TOFU_POLICY_ASK)
+    summary = 0; /* Key requires attention.  */
+  else
+    summary = validity;
 
   if (fp)
     {
-      es_fprintf (fp, "tfs:1:%s:%lu:%lu:%s:%lu:%lu:%lu:%lu:\n",
-                  validity, signature_count, encryption_count,
+      es_fprintf (fp, "tfs:1:%d:%lu:%lu:%s:%lu:%lu:%lu:%lu:%d:%lu:%lu:\n",
+                  summary, signature_count, encryption_count,
                   tofu_policy_str (policy),
                   signature_first_seen, signature_most_recent,
-                  encryption_first_done, encryption_most_recent);
+                  encryption_first_done, encryption_most_recent,
+                  validity, signature_days, encryption_days);
     }
   else
     {
       write_status_printf (STATUS_TOFU_STATS,
-                           "%s %lu %lu %s %lu %lu %lu %lu",
-                           validity,
+                           "%d %lu %lu %s %lu %lu %lu %lu %d %lu %lu",
+                           summary,
                            signature_count,
                            encryption_count,
                            tofu_policy_str (policy),
                            signature_first_seen,
                            signature_most_recent,
                            encryption_first_done,
-                           encryption_most_recent);
+                           encryption_most_recent,
+                           validity,
+                           signature_days, encryption_days);
     }
 }
 
 /* Note: If OUTFP is not NULL, this function merely prints a "tfs" record
- * to OUTFP.  In this case USER_ID is not required.
+ * to OUTFP.
  *
- * Returns whether the caller should call show_warning after iterating
- * over all user ids.
+ * POLICY is the key's policy (as returned by get_policy).
+ *
+ * Returns 0 if ONLY_STATUS_FD is set.  Otherwise, returns whether
+ * the caller should call show_warning after iterating over all user
+ * ids.
  */
 static int
-show_statistics (tofu_dbs_t dbs, const char *fingerprint,
-                const char *email, const char *user_id,
-                estream_t outfp, time_t now)
+show_statistics (tofu_dbs_t dbs,
+                 const char *fingerprint, const char *email,
+                 enum tofu_policy policy,
+                estream_t outfp, int only_status_fd, time_t now)
 {
-  enum tofu_policy policy = get_policy (dbs, fingerprint, email, NULL);
-
   char *fingerprint_pp;
   int rc;
   strlist_t strlist = NULL;
@@ -2573,20 +2991,24 @@ show_statistics (tofu_dbs_t dbs, const char *fingerprint,
   unsigned long signature_first_seen = 0;
   unsigned long signature_most_recent = 0;
   unsigned long signature_count = 0;
+  unsigned long signature_days = 0;
   unsigned long encryption_first_done = 0;
   unsigned long encryption_most_recent = 0;
   unsigned long encryption_count = 0;
+  unsigned long encryption_days = 0;
 
   int show_warning = 0;
 
-  (void) user_id;
+  if (only_status_fd && ! is_status_enabled ())
+    return 0;
 
   fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
 
   /* Get the signature stats.  */
   rc = gpgsql_exec_printf
     (dbs->db, strings_collect_cb, &strlist, &err,
-     "select count (*), min (signatures.time), max (signatures.time)\n"
+     "select count (*), coalesce (min (signatures.time), 0),\n"
+     "  coalesce (max (signatures.time), 0)\n"
      " from signatures\n"
      " left join bindings on signatures.binding = bindings.oid\n"
      " where fingerprint = %Q and email = %Q;",
@@ -2594,21 +3016,43 @@ show_statistics (tofu_dbs_t dbs, const char *fingerprint,
   if (rc)
     {
       log_error (_("error reading TOFU database: %s\n"), err);
-      print_further_info ("getting statistics");
+      print_further_info ("getting signature statistics");
       sqlite3_free (err);
+      rc = gpg_error (GPG_ERR_GENERAL);
+      goto out;
+    }
+  rc = gpgsql_exec_printf
+    (dbs->db, strings_collect_cb, &strlist, &err,
+     "select count (*) from\n"
+     "  (select round(signatures.time / (24 * 60 * 60)) day\n"
+     "    from signatures\n"
+     "    left join bindings on signatures.binding = bindings.oid\n"
+     "    where fingerprint = %Q and email = %Q\n"
+     "    group by day);",
+     fingerprint, email);
+  if (rc)
+    {
+      log_error (_("error reading TOFU database: %s\n"), err);
+      print_further_info ("getting signature statistics (by day)");
+      sqlite3_free (err);
+      rc = gpg_error (GPG_ERR_GENERAL);
       goto out;
     }
 
   if (strlist)
     {
+      /* We expect exactly 4 elements.  */
       log_assert (strlist->next);
       log_assert (strlist->next->next);
-      log_assert (! strlist->next->next->next);
+      log_assert (strlist->next->next->next);
+      log_assert (! strlist->next->next->next->next);
 
-      string_to_ulong (&signature_count, strlist->d, -1, __LINE__);
-      string_to_ulong (&signature_first_seen, strlist->next->d, -1, __LINE__);
-      string_to_ulong (&signature_most_recent,
+      string_to_ulong (&signature_days, strlist->d, -1, __LINE__);
+      string_to_ulong (&signature_count, strlist->next->d, -1, __LINE__);
+      string_to_ulong (&signature_first_seen,
                        strlist->next->next->d, -1, __LINE__);
+      string_to_ulong (&signature_most_recent,
+                       strlist->next->next->next->d, -1, __LINE__);
 
       free_strlist (strlist);
       strlist = NULL;
@@ -2617,7 +3061,8 @@ show_statistics (tofu_dbs_t dbs, const char *fingerprint,
   /* Get the encryption stats.  */
   rc = gpgsql_exec_printf
     (dbs->db, strings_collect_cb, &strlist, &err,
-     "select count (*), min (encryptions.time), max (encryptions.time)\n"
+     "select count (*), coalesce (min (encryptions.time), 0),\n"
+     "  coalesce (max (encryptions.time), 0)\n"
      " from encryptions\n"
      " left join bindings on encryptions.binding = bindings.oid\n"
      " where fingerprint = %Q and email = %Q;",
@@ -2625,21 +3070,43 @@ show_statistics (tofu_dbs_t dbs, const char *fingerprint,
   if (rc)
     {
       log_error (_("error reading TOFU database: %s\n"), err);
-      print_further_info ("getting statistics");
+      print_further_info ("getting encryption statistics");
       sqlite3_free (err);
+      rc = gpg_error (GPG_ERR_GENERAL);
+      goto out;
+    }
+  rc = gpgsql_exec_printf
+    (dbs->db, strings_collect_cb, &strlist, &err,
+     "select count (*) from\n"
+     "  (select round(encryptions.time / (24 * 60 * 60)) day\n"
+     "    from encryptions\n"
+     "    left join bindings on encryptions.binding = bindings.oid\n"
+     "    where fingerprint = %Q and email = %Q\n"
+     "    group by day);",
+     fingerprint, email);
+  if (rc)
+    {
+      log_error (_("error reading TOFU database: %s\n"), err);
+      print_further_info ("getting encryption statistics (by day)");
+      sqlite3_free (err);
+      rc = gpg_error (GPG_ERR_GENERAL);
       goto out;
     }
 
   if (strlist)
     {
+      /* We expect exactly 4 elements.  */
       log_assert (strlist->next);
       log_assert (strlist->next->next);
-      log_assert (! strlist->next->next->next);
+      log_assert (strlist->next->next->next);
+      log_assert (! strlist->next->next->next->next);
 
-      string_to_ulong (&encryption_count, strlist->d, -1, __LINE__);
-      string_to_ulong (&encryption_first_done, strlist->next->d, -1, __LINE__);
-      string_to_ulong (&encryption_most_recent,
+      string_to_ulong (&encryption_days, strlist->d, -1, __LINE__);
+      string_to_ulong (&encryption_count, strlist->next->d, -1, __LINE__);
+      string_to_ulong (&encryption_first_done,
                        strlist->next->next->d, -1, __LINE__);
+      string_to_ulong (&encryption_most_recent,
+                       strlist->next->next->next->d, -1, __LINE__);
 
       free_strlist (strlist);
       strlist = NULL;
@@ -2653,11 +3120,13 @@ show_statistics (tofu_dbs_t dbs, const char *fingerprint,
                       signature_count,
                       signature_first_seen,
                       signature_most_recent,
+                      signature_days,
                       encryption_count,
                       encryption_first_done,
-                      encryption_most_recent);
+                      encryption_most_recent,
+                      encryption_days);
 
-  if (!outfp)
+  if (!outfp && !only_status_fd)
     {
       estream_t fp;
       char *msg;
@@ -2667,56 +3136,55 @@ show_statistics (tofu_dbs_t dbs, const char *fingerprint,
         log_fatal ("error creating memory stream: %s\n",
                    gpg_strerror (gpg_error_from_syserror()));
 
-      es_fprintf (fp, _("%s: "), email);
-
-      if (signature_count == 0)
-        {
-          es_fprintf (fp, _("Verified %ld signatures"), 0L);
-          es_fputc ('\n', fp);
-        }
-      else
+      if (signature_count == 0 && encryption_count == 0)
         {
-          char *first_seen_ago_str = time_ago_str (now - signature_first_seen);
-
-          /* TRANSLATORS: The final %s is replaced by a string like
-             "7 months, 1 day, 5 minutes, 0 seconds". */
           es_fprintf (fp,
-                      ngettext("Verified %ld signature in the past %s",
-                               "Verified %ld signatures in the past %s",
-                               signature_count),
-                      signature_count, first_seen_ago_str);
-
-          xfree (first_seen_ago_str);
-        }
-
-      if (encryption_count == 0)
-        {
-          es_fprintf (fp, _(", and encrypted %ld messages"), 0L);
+                      _("%s: Verified 0~signatures and encrypted 0~messages."),
+                      email);
         }
       else
         {
-          char *first_done_ago_str = time_ago_str (now - encryption_first_done);
+          if (signature_count == 0)
+            es_fprintf (fp, _("%s: Verified 0 signatures."), email);
+          else
+            {
+              /* TRANSLATORS: The final %s is replaced by a string like
+                 "7~months". */
+              char *ago_str = time_ago_str (now - signature_first_seen);
+              es_fprintf
+                (fp,
+                 ngettext("%s: Verified %ld~signature in the past %s.",
+                          "%s: Verified %ld~signatures in the past %s.",
+                          signature_count),
+                 email, signature_count, ago_str);
+              xfree (ago_str);
+            }
 
-          /* TRANSLATORS: The final %s is replaced by a string like
-             "7 months, 1 day, 5 minutes, 0 seconds". */
-          es_fprintf (fp,
-                      ngettext(", and encrypted %ld message in the past %s",
-                               ", and encrypted %ld messages in the past %s",
-                               encryption_count),
-                      encryption_count, first_done_ago_str);
+          es_fputs ("  ", fp);
 
-          xfree (first_done_ago_str);
+          if (encryption_count == 0)
+            es_fprintf (fp, _("Encrypted 0 messages."));
+          else
+            {
+              char *ago_str = time_ago_str (now - encryption_first_done);
+
+              /* TRANSLATORS: The final %s is replaced by a string like
+                 "7~months". */
+              es_fprintf (fp,
+                          ngettext("Encrypted %ld~message in the past %s.",
+                                   "Encrypted %ld~messages in the past %s.",
+                                   encryption_count),
+                          encryption_count, ago_str);
+              xfree (ago_str);
+            }
         }
 
       if (opt.verbose)
         {
           es_fputs ("  ", fp);
-          es_fputc ('(', fp);
-          es_fprintf (fp, _("policy: %s"), tofu_policy_str (policy));
-          es_fputs (").\n", fp);
+          es_fprintf (fp, _("(policy: %s)"), tofu_policy_str (policy));
         }
-      else
-        es_fputs (".\n", fp);
+      es_fputs ("\n", fp);
 
 
       {
@@ -2724,7 +3192,10 @@ show_statistics (tofu_dbs_t dbs, const char *fingerprint,
         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);
+        msg = format_text (tmpmsg, 72, 80);
+        if (!msg) /* FIXME: Return the error all the way up.  */
+          log_fatal ("format failed: %s\n",
+                     gpg_strerror (gpg_error_from_syserror()));
         es_free (tmpmsg);
 
         /* Print a status line but suppress the trailing LF.
@@ -2753,15 +3224,15 @@ show_statistics (tofu_dbs_t dbs, const char *fingerprint,
 
           if (encryption_count == 0)
             log_info (_("Warning: you have yet to encrypt"
-                        " a message to this key and user id!\n"));
+                        " a message to this key!\n"));
           else if (encryption_count == 1)
             log_info (_("Warning: you have only encrypted"
-                        " one message to this key and user id!\n"));
+                        " one message to this key!\n"));
 
           /* Cf. write_stats_status  */
-          if (sqrtu32 (encryption_count * encryption_count
-                       + signature_count * signature_count)
-              < 2 * BASIC_TRUST_THRESHOLD)
+          if ((encryption_count * encryption_count
+              + signature_count * signature_count)
+             < ((2 * BASIC_TRUST_THRESHOLD) * (2 * BASIC_TRUST_THRESHOLD)))
             show_warning = 1;
         }
     }
@@ -2799,7 +3270,10 @@ show_warning (const char *fingerprint, strlist_t user_id_list)
       strlist_length (user_id_list)),
      set_policy_command);
 
-  text = format_text (tmpmsg, 0, 72, 80);
+  text = format_text (tmpmsg, 72, 80);
+  if (!text) /* FIXME: Return the error all the way up.  */
+    log_fatal ("format failed: %s\n",
+               gpg_strerror (gpg_error_from_syserror()));
   xfree (tmpmsg);
   log_string (GPGRT_LOG_INFO, text);
   xfree (text);
@@ -2845,7 +3319,7 @@ email_from_user_id (const char *user_id)
    TOFU_POLICY_ASK.
 
    This function returns 0 on success and an error code if an error
-   occured.  */
+   occurred.  */
 gpg_error_t
 tofu_register_signature (ctrl_t ctrl,
                          PKT_public_key *pk, strlist_t user_id_list,
@@ -2877,7 +3351,7 @@ tofu_register_signature (ctrl_t ctrl,
   if (rc)
     return rc;
 
-  log_assert (keyid_cmp (pk_keyid (pk), pk->main_keyid) == 0);
+  log_assert (pk_is_primary (pk));
 
   sig_digest = make_radix64_string (sig_digest_bin, sig_digest_bin_len);
   fingerprint = hexfingerprint (pk, NULL, 0);
@@ -2897,7 +3371,8 @@ tofu_register_signature (ctrl_t ctrl,
 
       /* Make sure the binding exists and record any TOFU
          conflicts.  */
-      if (get_trust (ctrl, pk, fingerprint, email, user_id->d, 0, now)
+      if (get_trust (ctrl, pk, fingerprint, email, user_id->d,
+                     0, NULL, NULL, now)
           == _tofu_GET_TRUST_ERROR)
         {
           rc = gpg_error (GPG_ERR_GENERAL);
@@ -2924,13 +3399,14 @@ tofu_register_signature (ctrl_t ctrl,
           log_error (_("error reading TOFU database: %s\n"), err);
           print_further_info ("checking existence");
           sqlite3_free (err);
+          rc = gpg_error (GPG_ERR_GENERAL);
         }
       else if (c > 1)
         /* Duplicates!  This should not happen.  In particular,
            because <fingerprint, email, sig_time, sig_digest> is the
            primary key!  */
         log_debug ("SIGNATURES DB contains duplicate records"
-                   " <key: %s, fingerprint: %s, time: 0x%lx, sig: %s,"
+                   " <key: %s, email: %s, time: 0x%lx, sig: %s,"
                    " origin: %s>."
                    "  Please report.\n",
                    fingerprint, email, (unsigned long) sig_time,
@@ -2939,7 +3415,7 @@ tofu_register_signature (ctrl_t ctrl,
         {
           if (DBG_TRUST)
             log_debug ("Already observed the signature and binding"
-                       " <key: %s, user id: %s, time: 0x%lx, sig: %s,"
+                       " <key: %s, email: %s, time: 0x%lx, sig: %s,"
                        " origin: %s>\n",
                        fingerprint, email, (unsigned long) sig_time,
                        sig_digest, origin);
@@ -2960,7 +3436,7 @@ tofu_register_signature (ctrl_t ctrl,
           log_assert (c == 0);
 
           rc = gpgsql_stepx
-            (dbs->db, &dbs->s.register_insert, NULL, NULL, &err,
+            (dbs->db, &dbs->s.register_signature, NULL, NULL, &err,
              "insert into signatures\n"
              " (binding, sig_digest, origin, sig_time, time)\n"
              " values\n"
@@ -2977,6 +3453,7 @@ tofu_register_signature (ctrl_t ctrl,
               log_error (_("error updating TOFU database: %s\n"), err);
               print_further_info ("insert signatures");
               sqlite3_free (err);
+              rc = gpg_error (GPG_ERR_GENERAL);
             }
         }
 
@@ -3020,12 +3497,14 @@ tofu_register_encryption (ctrl_t ctrl,
       return rc;
     }
 
-  /* Make sure PK is a primary key.  */
-  if (keyid_cmp (pk_keyid (pk), pk->main_keyid) != 0
-      || user_id_list)
-    kb = get_pubkeyblock (pk->keyid);
+  if (/* We need the key block to find the primary key.  */
+      ! pk_is_primary (pk)
+      /* We need the key block to find all user ids.  */
+      || ! user_id_list)
+    kb = get_pubkeyblock (ctrl, pk->keyid);
 
-  if (keyid_cmp (pk_keyid (pk), pk->main_keyid) != 0)
+  /* Make sure PK is a primary key.  */
+  if (! pk_is_primary (pk))
     pk = kb->pkt->pkt.public_key;
 
   if (! user_id_list)
@@ -3037,7 +3516,7 @@ tofu_register_encryption (ctrl_t ctrl,
         {
          PKT_user_id *uid = n->pkt->pkt.user_id;
 
-          if (uid->is_revoked)
+          if (uid->flags.revoked)
             continue;
 
           add_to_strlist (&user_id_list, uid->name);
@@ -3046,8 +3525,8 @@ tofu_register_encryption (ctrl_t ctrl,
       free_user_id_list = 1;
 
       if (! user_id_list)
-        log_info ("WARNING: Encrypting to %s, which has no"
-                  "non-revoked user ids.\n",
+        log_info (_("WARNING: Encrypting to %s, which has no "
+                    "non-revoked user ids\n"),
                   keystr (pk->keyid));
     }
 
@@ -3059,20 +3538,45 @@ tofu_register_encryption (ctrl_t ctrl,
   for (user_id = user_id_list; user_id; user_id = user_id->next)
     {
       char *email = email_from_user_id (user_id->d);
+      strlist_t conflict_set = NULL;
+      enum tofu_policy policy;
 
       /* Make sure the binding exists and that we recognize any
          conflicts.  */
       int tl = get_trust (ctrl, pk, fingerprint, email, user_id->d,
-                          may_ask, now);
+                          may_ask, &policy, &conflict_set, now);
       if (tl == _tofu_GET_TRUST_ERROR)
         {
           /* An error.  */
+          rc = gpg_error (GPG_ERR_GENERAL);
           xfree (email);
           goto die;
         }
 
+
+      /* If there is a conflict and MAY_ASK is true, we need to show
+       * the TOFU statistics for the current binding and the
+       * conflicting bindings.  But, if we are not in batch mode, then
+       * they have already been printed (this is required to make sure
+       * the information is available to the caller before cpr_get is
+       * called).  */
+      if (policy == TOFU_POLICY_ASK && may_ask && opt.batch)
+        {
+          strlist_t iter;
+
+          /* The conflict set should contain at least the current
+           * key.  */
+          log_assert (conflict_set);
+
+          for (iter = conflict_set; iter; iter = iter->next)
+            show_statistics (dbs, iter->d, email,
+                             TOFU_POLICY_ASK, NULL, 1, now);
+        }
+
+      free_strlist (conflict_set);
+
       rc = gpgsql_stepx
-        (dbs->db, &dbs->s.register_insert, NULL, NULL, &err,
+        (dbs->db, &dbs->s.register_encryption, NULL, NULL, &err,
          "insert into encryptions\n"
          " (binding, time)\n"
          " values\n"
@@ -3087,6 +3591,7 @@ tofu_register_encryption (ctrl_t ctrl,
           log_error (_("error updating TOFU database: %s\n"), err);
           print_further_info ("insert encryption");
           sqlite3_free (err);
+          rc = gpg_error (GPG_ERR_GENERAL);
         }
 
       xfree (email);
@@ -3180,6 +3685,7 @@ tofu_write_tfs_record (ctrl_t ctrl, estream_t fp,
   tofu_dbs_t dbs;
   char *fingerprint;
   char *email;
+  enum tofu_policy policy;
 
   if (!*user_id)
     return 0;  /* No TOFU stats possible for an empty ID.  */
@@ -3194,8 +3700,9 @@ tofu_write_tfs_record (ctrl_t ctrl, estream_t fp,
 
   fingerprint = hexfingerprint (pk, NULL, 0);
   email = email_from_user_id (user_id);
+  policy = get_policy (ctrl, dbs, pk, fingerprint, user_id, email, NULL, now);
 
-  show_statistics (dbs, fingerprint, email, user_id, fp, now);
+  show_statistics (dbs, fingerprint, email, policy, fp, 0, now);
 
   xfree (email);
   xfree (fingerprint);
@@ -3226,6 +3733,7 @@ tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
   int bindings = 0;
   int bindings_valid = 0;
   int need_warning = 0;
+  int had_conflict = 0;
 
   dbs = opendbs (ctrl);
   if (! dbs)
@@ -3238,16 +3746,19 @@ tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
   fingerprint = hexfingerprint (pk, NULL, 0);
 
   tofu_begin_batch_update (ctrl);
+  /* Start the batch transaction now.  */
   tofu_resume_batch_transaction (ctrl);
 
   for (user_id = user_id_list; user_id; user_id = user_id->next, bindings ++)
     {
       char *email = email_from_user_id (user_id->d);
+      strlist_t conflict_set = NULL;
+      enum tofu_policy policy;
 
       /* Always call get_trust to make sure the binding is
          registered.  */
       int tl = get_trust (ctrl, pk, fingerprint, email, user_id->d,
-                          may_ask, now);
+                          may_ask, &policy, &conflict_set, now);
       if (tl == _tofu_GET_TRUST_ERROR)
         {
           /* An error.  */
@@ -3269,8 +3780,36 @@ tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
         bindings_valid ++;
 
       if (may_ask && tl != TRUST_ULTIMATE && tl != TRUST_EXPIRED)
-        need_warning |=
-          show_statistics (dbs, fingerprint, email, user_id->d, NULL, now);
+        {
+          /* If policy is ask, then we already printed out the
+           * conflict information in ask_about_binding or will do so
+           * in a moment.  */
+          if (policy != TOFU_POLICY_ASK)
+            need_warning |=
+              show_statistics (dbs, fingerprint, email, policy, NULL, 0, now);
+
+          /* If there is a conflict and MAY_ASK is true, we need to
+           * show the TOFU statistics for the current binding and the
+           * conflicting bindings.  But, if we are not in batch mode,
+           * then they have already been printed (this is required to
+           * make sure the information is available to the caller
+           * before cpr_get is called).  */
+          if (policy == TOFU_POLICY_ASK && opt.batch)
+            {
+              strlist_t iter;
+
+              /* The conflict set should contain at least the current
+               * key.  */
+              log_assert (conflict_set);
+
+              had_conflict = 1;
+              for (iter = conflict_set; iter; iter = iter->next)
+                show_statistics (dbs, iter->d, email,
+                                 TOFU_POLICY_ASK, NULL, 1, now);
+            }
+        }
+
+      free_strlist (conflict_set);
 
       if (tl == TRUST_NEVER)
         trust_level = TRUST_NEVER;
@@ -3296,7 +3835,7 @@ tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
       xfree (email);
     }
 
-  if (need_warning)
+  if (need_warning && ! had_conflict)
     show_warning (fingerprint, user_id_list);
 
  die:
@@ -3326,6 +3865,7 @@ tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
 gpg_error_t
 tofu_set_policy (ctrl_t ctrl, kbnode_t kb, enum tofu_policy policy)
 {
+  gpg_error_t err = 0;
   time_t now = gnupg_get_time ();
   tofu_dbs_t dbs;
   PKT_public_key *pk;
@@ -3345,8 +3885,7 @@ tofu_set_policy (ctrl_t ctrl, kbnode_t kb, enum tofu_policy policy)
   if (DBG_TRUST)
     log_debug ("Setting TOFU policy for %s to %s\n",
               keystr (pk->keyid), tofu_policy_str (policy));
-  if (! (pk->main_keyid[0] == pk->keyid[0]
-        && pk->main_keyid[1] == pk->keyid[1]))
+  if (! pk_is_primary (pk))
     log_bug ("%s: Passed a subkey, but expecting a primary key.\n", __func__);
 
   fingerprint = hexfingerprint (pk, NULL, 0);
@@ -3362,39 +3901,33 @@ tofu_set_policy (ctrl_t ctrl, kbnode_t kb, enum tofu_policy policy)
        continue;
 
       user_id = kb->pkt->pkt.user_id;
-      if (user_id->is_revoked)
+      if (user_id->flags.revoked)
        /* Skip revoked user ids.  (Don't skip expired user ids, the
           expiry can be changed.)  */
        continue;
 
       email = email_from_user_id (user_id->name);
 
-      record_binding (dbs, fingerprint, email, user_id->name, policy, 1, now);
+      err = record_binding (dbs, fingerprint, email, user_id->name,
+                            policy, TOFU_POLICY_NONE, NULL, 0, 1, now);
+      if (err)
+        {
+          log_error (_("error setting policy for key %s, user id \"%s\": %s"),
+                     fingerprint, email, gpg_strerror (err));
+          xfree (email);
+          break;
+        }
 
       xfree (email);
     }
 
-  end_transaction (ctrl, 0);
+  if (err)
+    rollback_transaction (ctrl);
+  else
+    end_transaction (ctrl, 0);
 
   xfree (fingerprint);
-  return 0;
-}
-
-/* Set the TOFU policy for all non-revoked user ids in the KEY with
-   the key id KEYID to POLICY.
-
-   If no key is available with the specified key id, then this
-   function returns GPG_ERR_NO_PUBKEY.
-
-   Returns 0 on success and an error code otherwise.  */
-gpg_error_t
-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 (ctrl, keyblock, policy);
+  return err;
 }
 
 /* Return the TOFU policy for the specified binding in *POLICY.  If no
@@ -3408,13 +3941,13 @@ gpg_error_t
 tofu_get_policy (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *user_id,
                 enum tofu_policy *policy)
 {
+  time_t now = gnupg_get_time ();
   tofu_dbs_t dbs;
   char *fingerprint;
   char *email;
 
   /* Make sure PK is a primary key.  */
-  log_assert (pk->main_keyid[0] == pk->keyid[0]
-              && pk->main_keyid[1] == pk->keyid[1]);
+  log_assert (pk_is_primary (pk));
 
   dbs = opendbs (ctrl);
   if (! dbs)
@@ -3428,7 +3961,8 @@ tofu_get_policy (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *user_id,
 
   email = email_from_user_id (user_id->name);
 
-  *policy = get_policy (dbs, fingerprint, email, NULL);
+  *policy = get_policy (ctrl, dbs, pk, fingerprint,
+                        user_id->name, email, NULL, now);
 
   xfree (email);
   xfree (fingerprint);
@@ -3436,3 +3970,40 @@ tofu_get_policy (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *user_id,
     return gpg_error (GPG_ERR_GENERAL);
   return 0;
 }
+
+gpg_error_t
+tofu_notice_key_changed (ctrl_t ctrl, kbnode_t kb)
+{
+  tofu_dbs_t dbs;
+  PKT_public_key *pk;
+  char *fingerprint;
+  char *sqlerr = NULL;
+  int rc;
+
+  /* Make sure PK is a primary key.  */
+  setup_main_keyids (kb);
+  pk = kb->pkt->pkt.public_key;
+  log_assert (pk_is_primary (pk));
+
+  dbs = opendbs (ctrl);
+  if (! dbs)
+    {
+      log_error (_("error opening TOFU database: %s\n"),
+                 gpg_strerror (GPG_ERR_GENERAL));
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  fingerprint = hexfingerprint (pk, NULL, 0);
+
+  rc = gpgsql_stepx (dbs->db, NULL, NULL, NULL, &sqlerr,
+                     "update bindings set effective_policy = ?"
+                     " where fingerprint = ?;",
+                     GPGSQL_ARG_INT, (int) TOFU_POLICY_NONE,
+                     GPGSQL_ARG_STRING, fingerprint,
+                     GPGSQL_ARG_END);
+  xfree (fingerprint);
+
+  if (rc == _tofu_GET_POLICY_ERROR)
+    return gpg_error (GPG_ERR_GENERAL);
+  return 0;
+}