gpg: Print a warning on Tor problems.
[gnupg.git] / g10 / tofu.c
index f84609e..8d535fa 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:
@@ -28,6 +28,7 @@
 #include <sys/stat.h>
 #include <stdarg.h>
 #include <sqlite3.h>
+#include <time.h>
 
 #include "gpg.h"
 #include "types.h"
 #include "mkdir_p.h"
 #include "gpgsql.h"
 #include "status.h"
+#include "sqrtu32.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
   {
@@ -72,16 +75,19 @@ 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_other_keys;
+    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;
+  int in_transaction;
+  time_t batch_update_started;
 };
 
 
@@ -93,24 +99,23 @@ struct tofu_dbs_s
 /* If a message is signed a couple of hours in the future, just assume
    some clock skew.  */
 #define TIME_AGO_FUTURE_IGNORE (2 * 60 * 60)
-#if 0
-#  define TIME_AGO_UNIT_SMALL 60
-#  define TIME_AGO_MEDIUM_THRESHOLD (60 * TIME_AGO_UNIT_SMALL)
-#  define TIME_AGO_UNIT_MEDIUM (60 * 60)
-#  define TIME_AGO_LARGE_THRESHOLD (24 * 60 * TIME_AGO_UNIT_SMALL)
-#  define TIME_AGO_UNIT_LARGE (24 * 60 * 60)
-#else
-#  define TIME_AGO_UNIT_SMALL (24 * 60 * 60)
-#  define TIME_AGO_MEDIUM_THRESHOLD (4 * TIME_AGO_UNIT_SMALL)
-#  define TIME_AGO_UNIT_MEDIUM (7 * 24 * 60 * 60)
-#  define TIME_AGO_LARGE_THRESHOLD (28 * TIME_AGO_UNIT_SMALL)
-#  define TIME_AGO_UNIT_LARGE (30 * 24 * 60 * 60)
-#endif
+/* Days.  */
+#define TIME_AGO_UNIT_SMALL (24 * 60 * 60)
+#define TIME_AGO_SMALL_THRESHOLD (7 * TIME_AGO_UNIT_SMALL)
+/* Months.  */
+#define TIME_AGO_UNIT_MEDIUM (30 * 24 * 60 * 60)
+#define TIME_AGO_MEDIUM_THRESHOLD (2 * TIME_AGO_UNIT_MEDIUM)
+/* Years.  */
+#define TIME_AGO_UNIT_LARGE (365 * 24 * 60 * 60)
+#define TIME_AGO_LARGE_THRESHOLD (2 * TIME_AGO_UNIT_LARGE)
 
 /* 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)
@@ -159,7 +164,9 @@ tofu_policy_to_trust_level (enum tofu_policy policy)
 
 
 \f
-/* Start a transaction on DB.  */
+/* Start a transaction on DB.  If ONLY_BATCH is set, then this will
+   start a batch transaction if we haven't started a batch transaction
+   and one has been requested.  */
 static gpg_error_t
 begin_transaction (ctrl_t ctrl, int only_batch)
 {
@@ -173,38 +180,53 @@ 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.  */
-      ctrl->tofu.in_transaction == 0
+      dbs->in_transaction == 0
       /* There is an open batch transaction.  */
       && dbs->in_batch_transaction
       /* And some time has gone by since it was started.  */
-      && ctrl->tofu.batch_update_started != gnupg_get_time ())
+      && 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.  */
-      log_assert (ctrl->tofu.in_transaction == 0);
+      log_assert (dbs->in_transaction == 0);
 
       rc = gpgsql_stepx (dbs->db, &dbs->s.savepoint_batch,
                           NULL, NULL, &err,
-                          "savepoint batch;", SQLITE_ARG_END);
+                          "begin immediate transaction;", GPGSQL_ARG_END);
       if (rc)
         {
           log_error (_("error beginning transaction on TOFU database: %s\n"),
@@ -214,18 +236,21 @@ begin_transaction (ctrl_t ctrl, int only_batch)
         }
 
       dbs->in_batch_transaction = 1;
-      ctrl->tofu.batch_update_started = gnupg_get_time ();
+      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(ctrl->tofu.in_transaction >= 0);
-  ctrl->tofu.in_transaction ++;
+  log_assert (dbs->in_transaction >= 0);
+  dbs->in_transaction ++;
 
   rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err,
                            "savepoint inner%d;",
-                           ctrl->tofu.in_transaction);
+                           dbs->in_transaction);
   if (rc)
     {
       log_error (_("error beginning transaction on TOFU database: %s\n"),
@@ -240,8 +265,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)
 {
@@ -249,14 +274,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 (ctrl->tofu.in_transaction == 0);
+      if (only_batch)
+        log_assert (dbs->in_transaction == 0);
 
       if (/* Batch mode disabled?  */
           (!ctrl->tofu.batch_updated_wanted || only_batch == 2)
@@ -266,10 +292,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;", SQLITE_ARG_END);
+                             "commit transaction;", GPGSQL_ARG_END);
           if (rc)
             {
               log_error (_("error committing transaction on TOFU database: %s\n"),
@@ -281,14 +308,18 @@ end_transaction (ctrl_t ctrl, int only_batch)
           return 0;
         }
 
-      return 0;
+      if (only_batch)
+        return 0;
     }
 
   log_assert (dbs);
-  log_assert (ctrl->tofu.in_transaction > 0);
+  log_assert (dbs->in_transaction > 0);
 
   rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err,
-                           "release inner%d;", ctrl->tofu.in_transaction);
+                           "release inner%d;", dbs->in_transaction);
+
+  dbs->in_transaction --;
+
   if (rc)
     {
       log_error (_("error committing transaction on TOFU database: %s\n"),
@@ -297,8 +328,6 @@ end_transaction (ctrl_t ctrl, int only_batch)
       return gpg_error (GPG_ERR_GENERAL);
     }
 
-  ctrl->tofu.in_transaction --;
-
   return 0;
 }
 
@@ -311,15 +340,15 @@ rollback_transaction (ctrl_t ctrl)
   char *err = NULL;
 
   log_assert (dbs);
-  log_assert (ctrl->tofu.in_transaction > 0);
+  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;",
-                           ctrl->tofu.in_transaction);
+                           dbs->in_transaction);
 
-  ctrl->tofu.in_transaction --;
+  dbs->in_transaction --;
 
   if (rc)
     {
@@ -343,11 +372,28 @@ tofu_end_batch_update (ctrl_t ctrl)
 {
   log_assert (ctrl->tofu.batch_updated_wanted > 0);
   ctrl->tofu.batch_updated_wanted --;
+  end_transaction (ctrl, 1);
+}
 
-  if (!ctrl->tofu.batch_updated_wanted)
-    end_transaction (ctrl, 1);
+/* Suspend any extant batch transaction (it is safe to call this even
+   no batch transaction has been started).  Note: you cannot suspend a
+   batch transaction if you are in a normal transaction.  The batch
+   transaction can be resumed explicitly by calling
+   tofu_resume_batch_transaction or implicitly by starting a normal
+   transaction.  */
+static void
+tofu_suspend_batch_transaction (ctrl_t ctrl)
+{
+  end_transaction (ctrl, 2);
 }
 
+/* Resume a batch transaction if there is no extant batch transaction
+   and one has been requested using tofu_begin_batch_transaction.  */
+static void
+tofu_resume_batch_transaction (ctrl_t ctrl)
+{
+  begin_transaction (ctrl, 1);
+}
 
 
 \f
@@ -366,9 +412,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
@@ -394,9 +443,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
@@ -458,6 +510,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.
@@ -585,7 +783,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"
@@ -630,6 +828,58 @@ initdb (sqlite3 *db)
     }
 
  out:
+  if (! rc)
+    {
+      /* Early version of the v1 format did not include the encryption
+         table.  Add it.  */
+      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);
@@ -655,6 +905,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
@@ -679,12 +959,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))
         {
@@ -696,7 +978,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);
@@ -712,12 +997,12 @@ tofu_closedbs (ctrl_t ctrl)
   tofu_dbs_t dbs;
   sqlite3_stmt **statements;
 
-  log_assert (ctrl->tofu.in_transaction == 0);
-
   dbs = ctrl->tofu.dbs;
   if (!dbs)
     return;  /* Not initialized.  */
 
+  log_assert (dbs->in_transaction == 0);
+
   end_transaction (ctrl, 2);
 
   /* Arghh, that is a surprising use of the struct.  */
@@ -727,6 +1012,7 @@ tofu_closedbs (ctrl_t ctrl)
     sqlite3_finalize (*statements);
 
   sqlite3_close (dbs->db);
+  xfree (dbs->want_lock_file);
   xfree (dbs);
   ctrl->tofu.dbs = NULL;
 }
@@ -763,15 +1049,14 @@ 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)
+               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;
   char *err = NULL;
-  /* policy_old needs to be a long and not an enum tofu_policy,
-     because we pass it by reference to get_single_long_cb2, which
-     expects a long.  */
-  long policy_old = TOFU_POLICY_NONE;
 
   if (! (policy == TOFU_POLICY_AUTO
         || policy == TOFU_POLICY_GOOD
@@ -781,17 +1066,23 @@ record_binding (tofu_dbs_t dbs, const char *fingerprint, const char *email,
     log_bug ("%s: Bad value for policy (%d)!\n", __func__, policy);
 
 
-  if (show_old)
+  if (DBG_TRUST || show_old)
     {
       /* Get the old policy.  Since this is just for informational
        * purposes, there is no need to start a transaction or to die
        * if there is a failure.  */
+
+      /* policy_old needs to be a long and not an enum tofu_policy,
+         because we pass it by reference to get_single_long_cb2, which
+         expects a long.  */
+      long policy_old = TOFU_POLICY_NONE;
+
       rc = gpgsql_stepx
        (dbs->db, &dbs->s.record_binding_get_old_policy,
          get_single_long_cb2, &policy_old, &err,
         "select policy from bindings where fingerprint = ? and email = ?",
-        SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
-         SQLITE_ARG_END);
+        GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
+         GPGSQL_ARG_END);
       if (rc)
        {
          log_debug ("TOFU: Error reading from binding database"
@@ -800,26 +1091,19 @@ record_binding (tofu_dbs_t dbs, const char *fingerprint, const char *email,
          sqlite3_free (err);
        }
 
-      if (DBG_TRUST)
-        {
-          if (policy_old != TOFU_POLICY_NONE)
-            log_debug ("Changing TOFU trust policy for binding"
-                       " <key: %s, user id: %s> from %s to %s.\n",
-                       fingerprint, email,
-                       tofu_policy_str (policy_old),
-                       tofu_policy_str (policy));
-          else
-            log_debug ("Setting TOFU trust policy for new binding"
-                       " <key: %s, user id: %s> to %s.\n",
-                       fingerprint, email,
-                       tofu_policy_str (policy));
-        }
-    }
-
-  if (policy_old == policy)
-    {
-      rc = 0;
-      goto leave; /* Nothing to do.  */
+      if (policy_old != TOFU_POLICY_NONE)
+        (show_old ? log_info : log_debug)
+          ("Changing TOFU trust policy for binding"
+           " <key: %s, user id: %s> from %s to %s.\n",
+           fingerprint, show_old ? user_id : email,
+           tofu_policy_str (policy_old),
+           tofu_policy_str (policy));
+      else
+        (show_old ? log_info : log_debug)
+          ("Setting TOFU trust policy for new binding"
+           " <key: %s, user id: %s> to %s.\n",
+           fingerprint, show_old ? user_id : email,
+           tofu_policy_str (policy));
     }
 
   if (opt.dry_run)
@@ -832,17 +1116,34 @@ 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"
-     "  ?, ?, ?, strftime('%s','now'), ?);",
-     SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
-     SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
-     SQLITE_ARG_STRING, user_id, SQLITE_ARG_INT, (int) policy,
-     SQLITE_ARG_END);
+     "  ?, ?, ?, ?, ?,"
+     /* If 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)
     {
       log_error (_("error updating TOFU database: %s\n"), err);
@@ -858,7 +1159,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
@@ -913,6 +1214,10 @@ struct signature_stats
   /* Number of signatures during this time.  */
   unsigned long count;
 
+  /* If the corresponding key/user id has been expired / revoked.  */
+  int is_expired;
+  int is_revoked;
+
   /* The key that generated this signature.  */
   char fingerprint[1];
 };
@@ -936,7 +1241,7 @@ signature_stats_prepend (struct signature_stats **statsp,
                         unsigned long count)
 {
   struct signature_stats *stats =
-    xmalloc (sizeof (*stats) + strlen (fingerprint));
+    xmalloc_clear (sizeof (*stats) + strlen (fingerprint));
 
   stats->next = *statsp;
   *statsp = stats;
@@ -999,134 +1304,18 @@ 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 = ?",
-                      SQLITE_ARG_STRING, fingerprint,
-                      SQLITE_ARG_STRING, email,
-                      SQLITE_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.  */
 static char *
-format_conflict_msg_part1 (int policy, const char *conflict,
-                           const char *fingerprint, const char *email)
+format_conflict_msg_part1 (int policy, strlist_t conflict_set,
+                           const char *email)
 {
   estream_t fp;
-  char *binding;
-  int binding_shown = 0;
+  char *fingerprint;
   char *tmpstr, *text;
 
-  binding = xasprintf ("<%s, %s>", fingerprint, email);
+  log_assert (conflict_set);
+  fingerprint = conflict_set->d;
 
   fp = es_fopenmem (0, "rw,samethread");
   if (!fp)
@@ -1135,37 +1324,34 @@ format_conflict_msg_part1 (int policy, const char *conflict,
 
   if (policy == TOFU_POLICY_NONE)
     {
-      es_fprintf (fp, _("The binding %s is NOT known."), binding);
+      es_fprintf (fp,
+                  _("This is the first time the email address \"%s\" is "
+                    "being used with key %s."),
+                  email, fingerprint);
       es_fputs ("  ", fp);
-      binding_shown = 1;
     }
-  else if (policy == TOFU_POLICY_ASK
-           /* If there the conflict is with itself, then don't
-            * display this message.  */
-           && conflict && strcmp (conflict, fingerprint))
-    {
-      es_fprintf (fp,
-                  _("The key with fingerprint %s raised a conflict "
-                    "with the binding %s."
-                    "  Since this binding's policy was 'auto', it was "
-                    "changed to 'ask'."),
-                  conflict, binding);
+  else if (policy == TOFU_POLICY_ASK && conflict_set->next)
+    {
+      int conflicts = strlist_length (conflict_set);
+      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 "
+                      "changed to 'ask'."));
       es_fputs ("  ", fp);
-      binding_shown = 1;
     }
 
-  /* TRANSLATORS: The %s%s is replaced by either a fingerprint and a
-     blank or by two empty strings.  */
   es_fprintf (fp,
-              _("Please indicate whether you believe the binding %s%s"
-                "is legitimate (the key belongs to the stated owner) "
-                "or a forgery (bad)."),
-              binding_shown ? "" : binding,
-              binding_shown ? "" : " ");
+              _("Please indicate whether this email address should"
+                " be associated with key %s or whether you think someone"
+                " is impersonating \"%s\"."),
+              fingerprint, email);
   es_fputc ('\n', fp);
 
-  xfree (binding);
-
   es_fputc (0, fp);
   if (es_fclose_snatch (fp, (void **)&tmpstr, NULL))
     log_fatal ("error snatching memory stream\n");
@@ -1176,71 +1362,257 @@ format_conflict_msg_part1 (int policy, const char *conflict,
 }
 
 
-/* Ask the user about the binding.  There are three ways we could end
- * up here:
- *
- *   - This is a new binding and there is a conflict
- *     (policy == TOFU_POLICY_NONE && bindings_with_this_email_count > 0),
- *
- *   - This is a new binding and opt.tofu_default_policy is set to
- *     ask.  (policy == TOFU_POLICY_NONE && opt.tofu_default_policy ==
- *     TOFU_POLICY_ASK), or,
- *
- *   - The policy is ask (the user deferred last time) (policy ==
- *     TOFU_POLICY_ASK).
- */
-static void
-ask_about_binding (tofu_dbs_t dbs,
-                   enum tofu_policy *policy,
-                   int *trust_level,
-                   int bindings_with_this_email_count,
-                   strlist_t bindings_with_this_email,
-                   char *conflict,
-                   const char *fingerprint,
-                   const char *email,
-                   const char *user_id)
+/* Return 1 if A signed B and B signed A.  */
+static int
+cross_sigs (const char *email, kbnode_t a, kbnode_t b)
 {
-  char *sqerr = NULL;
-  int rc;
-  estream_t fp;
-  strlist_t other_user_ids = NULL;
-  struct signature_stats *stats = NULL;
-  struct signature_stats *stats_iter = NULL;
-  char *prompt;
-  char *choices;
+  int i;
 
-  fp = es_fopenmem (0, "rw,samethread");
-  if (!fp)
-    log_fatal ("error creating memory stream: %s\n",
-               gpg_strerror (gpg_error_from_syserror()));
+  PKT_public_key *a_pk = a->pkt->pkt.public_key;
+  PKT_public_key *b_pk = b->pkt->pkt.public_key;
 
-  {
-    char *text = format_conflict_msg_part1 (*policy, conflict,
-                                            fingerprint, email);
-    es_fputs (text, fp);
-    es_fputc ('\n', fp);
-    xfree (text);
-  }
+  char a_keyid[33];
+  char b_keyid[33];
 
-  /* Find other user ids associated with this key and whether the
-   * bindings are marked as good or bad.  */
-  rc = gpgsql_stepx
-    (dbs->db, &dbs->s.get_trust_gather_other_user_ids,
-     strings_collect_cb2, &other_user_ids, &sqerr,
-     "select user_id, policy from bindings where fingerprint = ?;",
-     SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_END);
-  if (rc)
+  if (DBG_TRUST)
     {
-      log_error (_("error gathering other user IDs: %s\n"), sqerr);
-      sqlite3_free (sqerr);
-      sqerr = NULL;
+      format_keyid (pk_main_keyid (a_pk),
+                    KF_LONG, a_keyid, sizeof (a_keyid));
+      format_keyid (pk_main_keyid (b_pk),
+                    KF_LONG, b_keyid, sizeof (b_keyid));
+    }
+
+  for (i = 0; i < 2; i ++)
+    {
+      /* See if SIGNER signed SIGNEE.  */
+
+      kbnode_t signer = i == 0 ? a : b;
+      kbnode_t signee = i == 0 ? b : a;
+
+      PKT_public_key *signer_pk = signer->pkt->pkt.public_key;
+      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;
+
+          sig = n->pkt->pkt.signature;
+
+          if (! (sig->sig_class == 0x10
+                 || sig->sig_class == 0x11
+                 || sig->sig_class == 0x12
+                 || sig->sig_class == 0x13))
+            /* Not a signature over a user id.  */
+            continue;
+
+          /* SIG is on SIGNEE's keyblock.  If SIG was generated by the
+             signer, then it's a match.  */
+          if (keyid_cmp (sig->keyid, signer_kid) == 0)
+            /* Match!  */
+            break;
+        }
+      if (! n)
+        /* We didn't find a signature from signer over signee.  */
+        {
+          if (DBG_TRUST)
+            log_debug ("No cross sig between %s and %s\n",
+                       a_keyid, b_keyid);
+          return 0;
+        }
+    }
+
+  /* A signed B and B signed A.  */
+  if (DBG_TRUST)
+    log_debug ("Cross sig between %s and %s\n",
+               a_keyid, b_keyid);
+
+  return 1;
+}
+
+/* Return whether the key was signed by an ultimately trusted key.  */
+static int
+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;
+
+      sig = n->pkt->pkt.signature;
+
+      if (! (sig->sig_class == 0x10
+             || sig->sig_class == 0x11
+             || sig->sig_class == 0x12
+             || sig->sig_class == 0x13))
+        /* Not a signature over a user id.  */
+        continue;
+
+      /* SIG is on SIGNEE's keyblock.  If SIG was generated by the
+         signer, then it's a match.  */
+      if (tdb_keyid_is_utk (sig->keyid))
+        {
+          /* Match!  */
+          if (DBG_TRUST)
+            log_debug ("TOFU: %s is signed by an ultimately trusted key.\n",
+                       pk_keyid_str (a->pkt->pkt.public_key));
+
+          return 1;
+        }
+    }
+
+  if (DBG_TRUST)
+    log_debug ("TOFU: %s is NOT signed by an ultimately trusted key.\n",
+               pk_keyid_str (a->pkt->pkt.public_key));
+
+  return 0;
+}
+
+
+enum
+  {
+    BINDING_NEW = 1 << 0,
+    BINDING_CONFLICT = 1 << 1,
+    BINDING_EXPIRED = 1 << 2,
+    BINDING_REVOKED = 1 << 3
+  };
+
+
+/* Ask the user about the binding.  There are three ways we could end
+ * up here:
+ *
+ *   - This is a new binding and there is a conflict
+ *     (policy == TOFU_POLICY_NONE && conflict_set_count > 1),
+ *
+ *   - This is a new binding and opt.tofu_default_policy is set to
+ *     ask.  (policy == TOFU_POLICY_NONE && opt.tofu_default_policy ==
+ *     TOFU_POLICY_ASK), or,
+ *
+ *   - The policy is ask (the user deferred last time) (policy ==
+ *     TOFU_POLICY_ASK).
+ *
+ * Note: this function must not be called while in a transaction!
+ *
+ * CONFLICT_SET includes all of the conflicting bindings
+ * with FINGERPRINT first.  FLAGS is a bit-wise or of
+ * BINDING_NEW, etc.
+ */
+static void
+ask_about_binding (ctrl_t ctrl,
+                   enum tofu_policy *policy,
+                   int *trust_level,
+                   strlist_t conflict_set,
+                   const char *fingerprint,
+                   const char *email,
+                   const char *user_id,
+                   time_t now)
+{
+  tofu_dbs_t dbs;
+  strlist_t iter;
+  int conflict_set_count = strlist_length (conflict_set);
+  char *sqerr = NULL;
+  int rc;
+  estream_t fp;
+  strlist_t other_user_ids = NULL;
+  struct signature_stats *stats = NULL;
+  struct signature_stats *stats_iter = NULL;
+  char *prompt = NULL;
+  char *choices;
+
+  dbs = ctrl->tofu.dbs;
+  log_assert (dbs);
+  log_assert (dbs->in_transaction == 0);
+
+  fp = es_fopenmem (0, "rw,samethread");
+  if (!fp)
+    log_fatal ("error creating memory stream: %s\n",
+               gpg_strerror (gpg_error_from_syserror()));
+
+  {
+    char *text = format_conflict_msg_part1 (*policy, conflict_set, email);
+    es_fputs (text, fp);
+    es_fputc ('\n', fp);
+    xfree (text);
+  }
+
+  begin_transaction (ctrl, 0);
+
+  /* Find other user ids associated with this key and whether the
+   * bindings are marked as good or bad.  */
+  rc = gpgsql_stepx
+    (dbs->db, &dbs->s.get_trust_gather_other_user_ids,
+     strings_collect_cb2, &other_user_ids, &sqerr,
+     "select user_id, policy from bindings where fingerprint = ?;",
+     GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_END);
+  if (rc)
+    {
+      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)
     {
       strlist_t strlist_iter;
 
-      es_fprintf (fp, _("Known user IDs associated with this key:\n"));
+      es_fprintf (fp, _("This key's user IDs:\n"));
       for (strlist_iter = other_user_ids;
            strlist_iter;
            strlist_iter = strlist_iter->next)
@@ -1264,45 +1636,85 @@ ask_about_binding (tofu_dbs_t dbs,
       free_strlist (other_user_ids);
     }
 
-  /* Find other keys associated with this email address.  */
-  /* FIXME: When generating the statistics, do we want the time
-     embedded in the signature (column 'sig_time') or the time that
-     we first verified the signature (column 'time').  */
-  rc = gpgsql_stepx
-    (dbs->db, &dbs->s.get_trust_gather_other_keys,
-     signature_stats_collect_cb, &stats, &sqerr,
-     "select fingerprint, policy, time_ago, count(*)\n"
-     " from (select bindings.*,\n"
-     "        case\n"
-     /* From the future (but if its just a couple of hours in the
-      * future don't turn it into a warning)?  Or should we use
-      * small, medium or large units?  (Note: whatever we do, we
-      * keep the value in seconds.  Then when we group, everything
-      * that rounds to the same number of seconds is grouped.)  */
-     "         when delta < -("STRINGIFY (TIME_AGO_FUTURE_IGNORE)") then -1\n"
-     "         when delta < ("STRINGIFY (TIME_AGO_MEDIUM_THRESHOLD)")\n"
-     "          then max(0,\n"
-     "                   round(delta / ("STRINGIFY (TIME_AGO_UNIT_SMALL)"))\n"
-     "               * ("STRINGIFY (TIME_AGO_UNIT_SMALL)"))\n"
-     "         when delta < ("STRINGIFY (TIME_AGO_LARGE_THRESHOLD)")\n"
-     "          then round(delta / ("STRINGIFY (TIME_AGO_UNIT_MEDIUM)"))\n"
-     "               * ("STRINGIFY (TIME_AGO_UNIT_MEDIUM)")\n"
-     "         else round(delta / ("STRINGIFY (TIME_AGO_UNIT_LARGE)"))\n"
-     "              * ("STRINGIFY (TIME_AGO_UNIT_LARGE)")\n"
-     "        end time_ago,\n"
-     "        delta time_ago_raw\n"
-     "       from bindings\n"
-     "       left join\n"
-     "         (select *,\n"
-     "            cast(strftime('%s','now') - sig_time as real) delta\n"
-     "           from signatures) ss\n"
-     "        on ss.binding = bindings.oid)\n"
-     " where email = ?\n"
-     " group by fingerprint, time_ago\n"
-     /* Make sure the current key is first.  */
-     " order by fingerprint = ? asc, fingerprint desc, time_ago desc;\n",
-     SQLITE_ARG_STRING, email, SQLITE_ARG_STRING, fingerprint,
-     SQLITE_ARG_END);
+  /* Get the stats for all the keys in CONFLICT_SET.  */
+  strlist_rev (&conflict_set);
+  for (iter = conflict_set; iter && ! rc; iter = iter->next)
+    {
+#define STATS_SQL(table, time, sign)                         \
+         "select fingerprint, policy, time_ago, count(*)\n" \
+         " from\n" \
+         "  (select bindings.*,\n" \
+         "     "sign" case\n" \
+         "       when delta ISNULL then 1\n" \
+         /* From the future (but if its just a couple of hours in the \
+          * future don't turn it into a warning)?  Or should we use \
+          * small, medium or large units?  (Note: whatever we do, we \
+          * keep the value in seconds.  Then when we group, everything \
+          * that rounds to the same number of seconds is grouped.)  */ \
+         "      when delta < -("STRINGIFY (TIME_AGO_FUTURE_IGNORE)") then 2\n" \
+         "      when delta < ("STRINGIFY (TIME_AGO_SMALL_THRESHOLD)")\n" \
+         "       then 3\n" \
+         "      when delta < ("STRINGIFY (TIME_AGO_MEDIUM_THRESHOLD)")\n" \
+         "       then 4\n" \
+         "      when delta < ("STRINGIFY (TIME_AGO_LARGE_THRESHOLD)")\n" \
+         "       then 5\n" \
+         "      else 6\n" \
+         "     end time_ago,\n" \
+         "    delta time_ago_raw\n" \
+         "   from bindings\n" \
+         "   left join\n" \
+         "     (select *,\n" \
+         "        cast(? - " time " as real) delta\n" \
+         "       from " table ") ss\n" \
+         "    on ss.binding = bindings.oid)\n" \
+         " where email = ? and fingerprint = ?\n" \
+         " group by time_ago\n" \
+         /* Make sure the current key is first.  */ \
+         " order by time_ago desc;\n"
+
+      /* Use the time when we saw the signature, not when the
+         signature was created as that can be forged.  */
+      rc = gpgsql_stepx
+        (dbs->db, &dbs->s.get_trust_gather_signature_stats,
+         signature_stats_collect_cb, &stats, &sqerr,
+         STATS_SQL ("signatures", "time", ""),
+         GPGSQL_ARG_LONG_LONG, (long long) now,
+         GPGSQL_ARG_STRING, email,
+         GPGSQL_ARG_STRING, iter->d,
+         GPGSQL_ARG_END);
+      if (rc)
+        {
+          rc = gpg_error (GPG_ERR_GENERAL);
+          break;
+        }
+
+      if (!stats || strcmp (iter->d, stats->fingerprint) != 0)
+        /* No stats for this binding.  Add a dummy entry.  */
+        signature_stats_prepend (&stats, iter->d, TOFU_POLICY_AUTO, 1, 1);
+
+      rc = gpgsql_stepx
+        (dbs->db, &dbs->s.get_trust_gather_encryption_stats,
+         signature_stats_collect_cb, &stats, &sqerr,
+         STATS_SQL ("encryptions", "time", "-"),
+         GPGSQL_ARG_LONG_LONG, (long long) now,
+         GPGSQL_ARG_STRING, email,
+         GPGSQL_ARG_STRING, iter->d,
+         GPGSQL_ARG_END);
+      if (rc)
+        {
+          rc = gpg_error (GPG_ERR_GENERAL);
+          break;
+        }
+
+#undef STATS_SQL
+
+      if (!stats || strcmp (iter->d, stats->fingerprint) != 0
+          || stats->time_ago > 0)
+        /* No stats for this binding.  Add a dummy entry.  */
+        signature_stats_prepend (&stats, iter->d, TOFU_POLICY_AUTO, -1, 1);
+    }
+  end_transaction (ctrl, 0);
+  strlist_rev (&conflict_set);
   if (rc)
     {
       strlist_t strlist_iter;
@@ -1315,9 +1727,9 @@ ask_about_binding (tofu_dbs_t dbs,
                                " associated with %d key:\n",
                                "The email address \"%s\" is"
                                " associated with %d keys:\n",
-                               bindings_with_this_email_count),
-                  email, bindings_with_this_email_count);
-      for (strlist_iter = bindings_with_this_email;
+                               conflict_set_count),
+                  email, conflict_set_count);
+      for (strlist_iter = conflict_set;
            strlist_iter;
            strlist_iter = strlist_iter->next)
         es_fprintf (fp, "  %s\n", strlist_iter->d);
@@ -1325,20 +1737,30 @@ ask_about_binding (tofu_dbs_t dbs,
   else
     {
       char *key = NULL;
+      strlist_t binding;
+      int seen_in_past = 0;
+      int encrypted = 1;
 
-      if (! stats || strcmp (stats->fingerprint, fingerprint))
-        {
-          /* If we have already added this key to the DB, then it will
-           * be first (see the above select).  Since the first key on
-           * the list is not this key, we must not yet have verified any
-           * messages signed by this key.  Add a dummy entry.  */
-          signature_stats_prepend (&stats, fingerprint, TOFU_POLICY_AUTO, 0, 0);
-        }
-
-      es_fprintf (fp, _("Statistics for keys with the email address \"%s\":\n"),
+      es_fprintf (fp, _("Statistics for keys"
+                        " with the email address \"%s\":\n"),
                   email);
       for (stats_iter = stats; stats_iter; stats_iter = stats_iter->next)
         {
+#if 0
+          log_debug ("%s: time_ago: %ld; count: %ld\n",
+                     stats_iter->fingerprint,
+                     stats_iter->time_ago,
+                     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;
@@ -1348,6 +1770,26 @@ ask_about_binding (tofu_dbs_t dbs,
               this_key = strcmp (key, fingerprint) == 0;
               key_pp = format_hexfingerprint (key, NULL, 0);
               es_fprintf (fp, "  %s (", key_pp);
+
+              /* Find the associated binding.  */
+              for (binding = conflict_set;
+                   binding;
+                   binding = binding->next)
+                if (strcmp (key, binding->d) == 0)
+                  break;
+              log_assert (binding);
+
+              if ((binding->flags & BINDING_REVOKED))
+                {
+                  es_fprintf (fp, _("revoked"));
+                  es_fprintf (fp, _(", "));
+                }
+              else if ((binding->flags & BINDING_EXPIRED))
+                {
+                  es_fprintf (fp, _("expired"));
+                  es_fprintf (fp, _(", "));
+                }
+
               if (this_key)
                 es_fprintf (fp, _("this key"));
               else
@@ -1355,45 +1797,116 @@ ask_about_binding (tofu_dbs_t dbs,
                             tofu_policy_str (stats_iter->policy));
               es_fputs ("):\n", fp);
               xfree (key_pp);
+
+              seen_in_past = 0;
+
+              show_statistics (dbs, stats_iter->fingerprint, email,
+                               TOFU_POLICY_ASK, NULL, 1, now);
             }
 
+          if (labs(stats_iter->time_ago) == 1)
+            {
+              /* The 1 in this case is the NULL entry.  */
+              log_assert (stats_iter->count == 1);
+              stats_iter->count = 0;
+            }
+          seen_in_past += stats_iter->count;
+
           es_fputs ("    ", fp);
-          if (stats_iter->time_ago == -1)
-            es_fprintf (fp, ngettext("%ld message signed in the future.",
-                                     "%ld messages signed in the future.",
-                                     stats_iter->count), stats_iter->count);
+
+          if (!stats_iter->count)
+            {
+              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
             {
-              long t_scaled = time_ago_scale (stats_iter->time_ago);
-
-              /* TANSLATORS: This string is concatenated with one of
-               * the day/week/month strings to form one sentence.  */
-              es_fprintf (fp, ngettext("%ld message signed",
-                                       "%ld messages signed",
-                                       stats_iter->count), stats_iter->count);
-              if (!stats_iter->count)
-                es_fputs (".", fp);
-              else if (stats_iter->time_ago < TIME_AGO_UNIT_MEDIUM)
-                es_fprintf (fp, ngettext(" over the past %ld day.",
-                                         " over the past %ld days.",
-                                         t_scaled), t_scaled);
-              else if (stats_iter->time_ago < TIME_AGO_UNIT_LARGE)
-                es_fprintf (fp, ngettext(" over the past %ld week.",
-                                         " over the past %ld weeks.",
-                                         t_scaled), t_scaled);
+              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
-                es_fprintf (fp, ngettext(" over the past %ld month.",
-                                         " over the past %ld months.",
-                                         t_scaled), t_scaled);
+                log_assert (! "Broken SQL.\n");
             }
           es_fputs ("\n", fp);
         }
     }
 
-
-  if ((*policy == TOFU_POLICY_NONE && bindings_with_this_email_count > 0)
-      || (*policy == TOFU_POLICY_ASK
-          && (conflict || bindings_with_this_email_count > 0)))
+  if (conflict_set_count > 1 || (conflict_set->flags & BINDING_CONFLICT))
     {
       /* This is a conflict.  */
 
@@ -1406,11 +1919,11 @@ ask_about_binding (tofu_dbs_t dbs,
         {
           /* No translation.  Use the English text.  */
           text =
-            "Normally, there is only a single key associated with an email "
-            "address.  However, people sometimes generate a new key if "
+            "Normally, an email address is associated with a single key.  "
+            "However, people sometimes generate a new key if "
             "their key is too old or they think it might be compromised.  "
             "Alternatively, a new key may indicate a man-in-the-middle "
-            "attack!  Before accepting this key, you should talk to or "
+            "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);
@@ -1431,6 +1944,10 @@ ask_about_binding (tofu_dbs_t dbs,
    * wrong choise (because he does not see that either).  As a small
    * benefit we allow C-L to redisplay everything.  */
   tty_printf ("%s", prompt);
+
+  /* Suspend any transaction: it could take a while until the user
+     responds.  */
+  tofu_suspend_batch_transaction (ctrl);
   while (1)
     {
       char *response;
@@ -1449,9 +1966,17 @@ ask_about_binding (tofu_dbs_t dbs,
       cpr_kill_prompt ();
       if (*response == CONTROL_L)
         tty_printf ("%s", prompt);
-      else if (strlen (response) == 1)
+      else if (!response[0])
+        /* Default to unknown.  Don't save it.  */
+        {
+          tty_printf (_("Defaulting to unknown."));
+          *policy = TOFU_POLICY_UNKNOWN;
+          break;
+        }
+      else if (!response[1])
         {
           char *choice = strchr (choices, *response);
+
           if (choice)
             {
               int c = ((size_t) choice - (size_t) choices) / 2;
@@ -1483,7 +2008,7 @@ ask_about_binding (tofu_dbs_t dbs,
                 }
 
               if (record_binding (dbs, fingerprint, email, user_id,
-                                  *policy, 0))
+                                  *policy, TOFU_POLICY_NONE, NULL, 0, 0, now))
                 {
                   /* If there's an error registering the
                    * binding, don't save the signature.  */
@@ -1495,11 +2020,611 @@ ask_about_binding (tofu_dbs_t dbs,
       xfree (response);
     }
 
+  tofu_resume_batch_transaction (ctrl);
+
   xfree (prompt);
 
   signature_stats_free (stats);
 }
 
+/* Return the set of keys that conflict with the binding <fingerprint,
+   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,
+                    PKT_public_key *pk, const char *fingerprint,
+                    const char *email)
+{
+  gpg_error_t rc;
+  char *sqerr;
+  strlist_t conflict_set = NULL;
+  int conflict_set_count;
+  strlist_t iter;
+  kbnode_t *kb_all;
+  KEYDB_HANDLE hd;
+  int i;
+
+  /* Get the fingerprints of any bindings that share the email address
+   * and whether the bindings have a known conflict.
+   *
+   * Note: if the binding in question is in the DB, it will also be
+   * returned.  Thus, if the result set is empty, then <email,
+   * fingerprint> is a new binding.  */
+  rc = gpgsql_stepx
+    (dbs->db, &dbs->s.get_trust_bindings_with_this_email,
+     strings_collect_cb2, &conflict_set, &sqerr,
+     "select"
+     /* A binding should only appear once, but try not to break in the
+      * case of corruption.  */
+     "  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
+        it is present).  */
+     "  order by fingerprint = ? asc, fingerprint desc;",
+     GPGSQL_ARG_STRING, email,
+     GPGSQL_ARG_STRING, fingerprint,
+     GPGSQL_ARG_END);
+  if (rc)
+    {
+      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;
+    }
+
+  /* Set BINDING_CONFLICT if the binding has a known conflict.  This
+   * allows us to distinguish between bindings where the user
+   * explicitly set the policy to ask and bindings where we set the
+   * policy to ask due to a conflict.  */
+  for (iter = conflict_set; iter; iter = iter->next)
+    {
+      int l = strlen (iter->d);
+      if (!(l == 2 * MAX_FINGERPRINT_LEN
+            || l == 2 * MAX_FINGERPRINT_LEN + 1))
+        {
+          log_error (_("TOFU db corruption detected.\n"));
+          print_further_info ("fingerprint '%s' is not %d characters long",
+                              iter->d, 2 * MAX_FINGERPRINT_LEN);
+        }
+
+      if (l >= 1 && iter->d[l - 1] == '!')
+        {
+          iter->flags |= BINDING_CONFLICT;
+          /* Remove the !.  */
+          iter->d[l - 1] = 0;
+        }
+    }
+
+  /* If the current binding has not yet been recorded, add it to the
+   * list.  (The order by above ensures that if it is present, it will
+   * be first.)  */
+  if (! (conflict_set && strcmp (conflict_set->d, fingerprint) == 0))
+    {
+      add_to_strlist (&conflict_set, fingerprint);
+      conflict_set->flags |= BINDING_NEW;
+    }
+
+  conflict_set_count = strlist_length (conflict_set);
+
+  /* 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);
+  hd = keydb_new ();
+  for (i = 0, iter = conflict_set;
+       i < conflict_set_count;
+       i ++, iter = iter->next)
+    {
+      char *fp = iter->d;
+      KEYDB_SEARCH_DESC desc;
+      kbnode_t kb;
+      PKT_public_key *binding_pk;
+      kbnode_t n;
+      int found_user_id;
+
+      rc = keydb_search_reset (hd);
+      if (rc)
+        {
+          log_error (_("resetting keydb: %s\n"),
+                     gpg_strerror (rc));
+          continue;
+        }
+
+      rc = classify_user_id (fp, &desc, 0);
+      if (rc)
+        {
+          log_error (_("error parsing key specification '%s': %s\n"),
+                     fp, gpg_strerror (rc));
+          continue;
+        }
+
+      rc = keydb_search (hd, &desc, 1, NULL);
+      if (rc)
+        {
+          /* Note: it is entirely possible that we don't have the key
+             corresponding to an entry in the TOFU DB.  This can
+             happen if we merge two TOFU DBs, but not the key
+             rings.  */
+          log_info (_("key \"%s\" not found: %s\n"),
+                    fp, gpg_strerror (rc));
+          continue;
+        }
+
+      rc = keydb_get_keyblock (hd, &kb);
+      if (rc)
+        {
+          log_error (_("error reading keyblock: %s\n"),
+                     gpg_strerror (rc));
+          print_further_info ("fingerprint: %s", fp);
+          continue;
+        }
+
+      merge_keys_and_selfsig (kb);
+
+      log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
+
+      kb_all[i] = kb;
+
+      /* Since we have the key block, use this opportunity to figure
+       * out if the binding is expired or revoked.  */
+      binding_pk = kb->pkt->pkt.public_key;
+
+      /* The binding is always expired/revoked if the key is
+       * expired/revoked.  */
+      if (binding_pk->has_expired)
+        iter->flags |= BINDING_EXPIRED;
+      if (binding_pk->flags.revoked)
+        iter->flags |= BINDING_REVOKED;
+
+      /* The binding is also expired/revoked if the user id is
+       * expired/revoked.  */
+      n = kb;
+      found_user_id = 0;
+      while ((n = find_next_kbnode (n, PKT_USER_ID)) && ! found_user_id)
+        {
+          PKT_user_id *user_id2 = n->pkt->pkt.user_id;
+          char *email2;
+
+          if (user_id2->attrib_data)
+            continue;
+
+          email2 = email_from_user_id (user_id2->name);
+
+          if (strcmp (email, email2) == 0)
+            {
+              found_user_id = 1;
+
+              if (user_id2->is_revoked)
+                iter->flags |= BINDING_REVOKED;
+              if (user_id2->is_expired)
+                iter->flags |= BINDING_EXPIRED;
+            }
+
+          xfree (email2);
+        }
+
+      if (! found_user_id)
+        {
+          log_info (_("TOFU db corruption detected.\n"));
+          print_further_info ("user id '%s' not on key block '%s'",
+                              email, fingerprint);
+        }
+    }
+  keydb_release (hd);
+
+  /* Now that we have the key blocks, check for cross sigs.  */
+  {
+    int j;
+    strlist_t *prevp;
+    strlist_t iter_next;
+    int *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 fucntion return an error.  */
+      }
+
+    for (i = 0; i < conflict_set_count; i ++)
+      {
+        /* Look for cross sigs between this key (i == 0) or a key
+         * that has cross sigs with i == 0 (i.e., transitively) */
+        if (! (i == 0 || die[i]))
+          continue;
+
+        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 (email, kb_all[i], kb_all[j]))
+            die[j] = 1;
+      }
+
+    /* Free unconflicting bindings (and all of the key blocks).  */
+    for (iter = conflict_set, prevp = &conflict_set, i = 0;
+         iter;
+         iter = iter_next, i ++)
+      {
+        iter_next = iter->next;
+
+        release_kbnode (kb_all[i]);
+
+        if (die[i])
+          {
+            *prevp = iter_next;
+            iter->next = NULL;
+            free_strlist (iter);
+            conflict_set_count --;
+          }
+        else
+          {
+            prevp = &iter->next;
+          }
+      }
+
+    /* 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)
+    {
+      log_debug ("binding <key: %s, email: %s> conflicts:\n",
+                 fingerprint, email);
+      for (iter = conflict_set; iter; iter = iter->next)
+        {
+          log_debug ("  %s:%s%s%s%s\n",
+                     iter->d,
+                     (iter->flags & BINDING_NEW) ? " new" : "",
+                     (iter->flags & BINDING_CONFLICT) ? " known_conflict" : "",
+                     (iter->flags & BINDING_EXPIRED) ? " expired" : "",
+                     (iter->flags & BINDING_REVOKED) ? " revoked" : "");
+        }
+    }
+
+  return conflict_set;
+}
+
+
+/* Return the effective policy for the binding <FINGERPRINT, EMAIL>
+ * (email has already been normalized) and any conflict information in
+ * *CONFLICT_SETP, if CONFLICT_SETP is not NULL.  Returns
+ * _tofu_GET_POLICY_ERROR if an error occurs.  */
+static enum tofu_policy
+get_policy (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 (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 (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 (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
@@ -1514,24 +2639,32 @@ ask_about_binding (tofu_dbs_t dbs,
  * necessary if there is a conflict or the binding's policy is
  * TOFU_POLICY_ASK.  In the case of a conflict, we set the new
  * conflicting binding's policy to TOFU_POLICY_ASK.  In either case,
- * we return TRUST_UNDEFINED.  */
+ * we return TRUST_UNDEFINED.  Note: if MAY_ASK is set, then this
+ * function must not be called while in a transaction!  */
 static enum tofu_policy
-get_trust (tofu_dbs_t dbs, PKT_public_key *pk,
+get_trust (ctrl_t ctrl, PKT_public_key *pk,
            const char *fingerprint, const char *email,
-          const char *user_id, int may_ask)
+          const char *user_id, int may_ask, time_t now)
 {
+  tofu_dbs_t dbs = ctrl->tofu.dbs;
+  int in_transaction = 0;
   enum tofu_policy policy;
-  char *conflict = NULL;
   int rc;
   char *sqerr = NULL;
-  strlist_t bindings_with_this_email = NULL;
-  int bindings_with_this_email_count;
-  int change_conflicting_to_ask = 0;
+  strlist_t conflict_set = NULL;
   int trust_level = TRUST_UNKNOWN;
+  strlist_t iter;
+
+  log_assert (dbs);
+
+  if (may_ask)
+    log_assert (dbs->in_transaction == 0);
 
   if (opt.batch)
     may_ask = 0;
 
+  log_assert (pk_is_primary (pk));
+
   /* Make sure _tofu_GET_TRUST_ERROR isn't equal to any of the trust
      levels.  */
   log_assert (_tofu_GET_TRUST_ERROR != TRUST_UNKNOWN
@@ -1542,37 +2675,27 @@ get_trust (tofu_dbs_t dbs, PKT_public_key *pk,
               && _tofu_GET_TRUST_ERROR != TRUST_FULLY
               && _tofu_GET_TRUST_ERROR != TRUST_ULTIMATE);
 
-  policy = get_policy (dbs, fingerprint, email, &conflict);
-  if (policy == TOFU_POLICY_AUTO || policy == TOFU_POLICY_NONE)
-    { /* See if the key is ultimately trusted.  If so, we're done.  */
-      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_AUTO, 0) != 0)
-                {
-                  log_error (_("error setting TOFU binding's trust level"
-                               " to %s\n"), "auto");
-                  trust_level = _tofu_GET_TRUST_ERROR;
-                  goto out;
-                }
-            }
+  /* If the key is ultimately trusted, there is nothing to do.  */
+  {
+    u32 kid[2];
+
+    keyid_from_pk (pk, kid);
+    if (tdb_keyid_is_utk (kid))
+      {
+        trust_level = TRUST_ULTIMATE;
+        goto out;
+      }
+  }
 
-          trust_level = TRUST_ULTIMATE;
-          goto out;
-        }
-    }
+  begin_transaction (ctrl, 0);
+  in_transaction = 1;
 
+  policy = get_policy (dbs, pk, fingerprint, user_id, email, &conflict_set, now);
   if (policy == TOFU_POLICY_AUTO)
     {
       policy = opt.tofu_default_policy;
       if (DBG_TRUST)
-       log_debug ("TOFU: binding <key: %s, user id: %s>'s policy is "
+       log_debug ("TOFU: binding <key: %s, user id: %s>'s policy is"
                    " auto (default: %s).\n",
                   fingerprint, email,
                   tofu_policy_str (opt.tofu_default_policy));
@@ -1592,18 +2715,7 @@ get_trust (tofu_dbs_t dbs, 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:
@@ -1624,143 +2736,85 @@ get_trust (tofu_dbs_t dbs, 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).
+   *      (policy == TOFU_POLICY_ASK).
    */
+  log_assert (policy == TOFU_POLICY_ASK);
 
-  /* Look for conflicts.  This is needed in all 3 cases.
-   *
-   * Get the fingerprints of any bindings that share the email
-   * address.  Note: if the binding in question is in the DB, it will
-   * also be returned.  Thus, if the result set is empty, then this is
-   * a new binding.  */
-  rc = gpgsql_stepx
-    (dbs->db, &dbs->s.get_trust_bindings_with_this_email,
-     strings_collect_cb2, &bindings_with_this_email, &sqerr,
-     "select distinct fingerprint from bindings where email = ?;",
-     SQLITE_ARG_STRING, email, SQLITE_ARG_END);
-  if (rc)
-    {
-      log_error (_("error reading TOFU database: %s\n"), sqerr);
-      print_further_info ("listing fingerprints");
-      sqlite3_free (sqerr);
-      goto out;
-    }
-
-  bindings_with_this_email_count = strlist_length (bindings_with_this_email);
-  if (bindings_with_this_email_count == 0
-      && opt.tofu_default_policy != TOFU_POLICY_ASK)
+  if (may_ask)
     {
-      /* New binding with no conflict and a concrete default policy.
-       *
-       * We've never observed a binding with this email address
-       * BINDINGS_WITH_THIS_EMAIL_COUNT is 0 and the above query would
-       * return the current binding if it were in the DB) and we have
-       * a default policy, which is not to ask the user.
-       */
+      /* We can't be in a normal transaction in ask_about_binding.  */
+      end_transaction (ctrl, 0);
+      in_transaction = 0;
 
-      /* 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);
-
-      if (record_binding (dbs, fingerprint, email, user_id,
-                         TOFU_POLICY_AUTO, 0) != 0)
-       {
-         log_error (_("error setting TOFU binding's trust level to %s\n"),
-                      "auto");
-         trust_level = _tofu_GET_TRUST_ERROR;
-         goto out;
-       }
-
-      trust_level = 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 (policy == TOFU_POLICY_NONE)
+  else
     {
-      /* This is a new binding and we have a conflict.  Mark any
-       * conflicting bindings that have an automatic policy as now
-       * requiring confirmation.  Note: we delay this until after we
-       * ask for confirmation so that when the current policy is
-       * printed, it is correct.  */
-      change_conflicting_to_ask = 1;
+      for (iter = conflict_set; iter; iter = iter->next)
+        show_statistics (dbs, iter->d, email,
+                         TOFU_POLICY_ASK, NULL, 1, now);
+
+      trust_level = TRUST_UNDEFINED;
     }
 
-  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) != 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;
     }
 
-  /* If we get here, we need to ask the user about the binding.  */
-  ask_about_binding (dbs,
-                     &policy,
-                     &trust_level,
-                     bindings_with_this_email_count,
-                     bindings_with_this_email,
-                     conflict,
-                     fingerprint,
-                     email,
-                     user_id);
+  /* The conflict set should always contain at least one element:
+   * the current key.  */
+  log_assert (conflict_set);
 
- out:
-  if (change_conflicting_to_ask)
+  for (iter = conflict_set->next; iter; iter = iter->next)
     {
-      if (! may_ask)
-        {
-          /* If we weren't allowed to ask, also update this key as
-             conflicting with itself.  */
-          rc = gpgsql_exec_printf
-            (dbs->db, NULL, NULL, &sqerr,
-             "update bindings set policy = %d, conflict = %Q"
-             " where email = %Q"
-             "  and (policy = %d or (policy = %d and fingerprint = %Q));",
-             TOFU_POLICY_ASK, fingerprint, email, TOFU_POLICY_AUTO,
-             TOFU_POLICY_ASK, fingerprint);
-        }
-      else
-        {
-          rc = 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, fingerprint,
-             TOFU_POLICY_AUTO);
-        }
-
+      /* 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)
-       {
-         log_error (_("error changing TOFU policy: %s\n"), sqerr);
-         sqlite3_free (sqerr);
+        {
+          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);
     }
 
-  xfree (conflict);
-  free_strlist (bindings_with_this_email);
+ out:
+  if (in_transaction)
+    end_transaction (ctrl, 0);
+
+  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.
  *
@@ -1770,402 +2824,419 @@ get_trust (tofu_dbs_t dbs, 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) && 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 < 2 && 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 < 2 && 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 < 2 && 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 < 2)
+  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);
 }
 
 
 /* If FP is NULL, write TOFU_STATS status line.  If FP is not NULL
  * write a "tfs" record to that stream. */
 static void
-write_stats_status (estream_t fp, long messages, enum tofu_policy policy,
-                    unsigned long first_seen,
-                    unsigned long most_recent_seen)
+write_stats_status (estream_t fp,
+                    enum tofu_policy policy,
+                    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_days)
 {
-  const char *validity;
-
-  if (messages < 1)
-    validity = "1"; /* Key without history.  */
-  else if (messages < BASIC_TRUST_THRESHOLD)
-    validity = "2"; /* Key with too little history.  */
-  else if (messages < FULL_TRUST_THRESHOLD)
-    validity = "3"; /* Key with enough history for basic trust.  */
+  int summary;
+  int validity;
+  unsigned long days;
+
+  /* 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.  */
+  days = sqrtu32 (signature_days * signature_days
+                  + encryption_days * encryption_days);
+
+  if (days < 1)
+    validity = 1; /* Key without history.  */
+  else if (days < 2 * BASIC_TRUST_THRESHOLD)
+    validity = 2; /* Key with too little history.  */
+  else if (days < 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:%ld:0:%s:%lu:%lu:\n",
-                  validity, messages,
+      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),
-                  first_seen, most_recent_seen);
+                  signature_first_seen, signature_most_recent,
+                  encryption_first_done, encryption_most_recent,
+                  validity, signature_days, encryption_days);
     }
   else
     {
-      char numbuf1[35];
-      char numbuf2[35];
-      char numbuf3[35];
-
-      snprintf (numbuf1, sizeof numbuf1, " %ld", messages);
-      *numbuf2 = *numbuf3 = 0;
-      if (first_seen && most_recent_seen)
-        {
-          snprintf (numbuf2, sizeof numbuf2, " %lu", first_seen);
-          snprintf (numbuf3, sizeof numbuf3, " %lu", most_recent_seen);
-        }
-
-      write_status_strings (STATUS_TOFU_STATS,
-                            validity, numbuf1, " 0",
-                            " ", tofu_policy_str (policy),
-                            numbuf2, numbuf3,
-                            NULL);
+      write_status_printf (STATUS_TOFU_STATS,
+                           "%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,
+                           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.  */
-static void
-show_statistics (tofu_dbs_t dbs, const char *fingerprint,
-                const char *email, const char *user_id,
-                const char *sig_exclude, estream_t outfp)
+ * to OUTFP.
+ *
+ * POLICY is the key's policy (as returned by get_policy).
+ *
+ * Returns 0 if 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,
+                 enum tofu_policy policy,
+                estream_t outfp, int only_status_fd, time_t now)
 {
   char *fingerprint_pp;
   int rc;
   strlist_t strlist = NULL;
   char *err = NULL;
 
+  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;
+
+  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"
      " from signatures\n"
      " left join bindings on signatures.binding = bindings.oid\n"
-     " where fingerprint = %Q and email = %Q and sig_digest %s%s%s;",
-     fingerprint, email,
-     /* We want either: sig_digest != 'SIG_EXCLUDE' or sig_digest is
-       not NULL.  */
-     sig_exclude ? "!= '" : "is not NULL",
-     sig_exclude ? sig_exclude : "",
-     sig_exclude ? "'" : "");
+     " where fingerprint = %Q and email = %Q;",
+     fingerprint, email);
+  if (rc)
+    {
+      log_error (_("error reading TOFU database: %s\n"), err);
+      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 statistics");
+      print_further_info ("getting signature statistics (by day)");
       sqlite3_free (err);
+      rc = gpg_error (GPG_ERR_GENERAL);
       goto out;
     }
 
-  if (!outfp)
-    write_status_text_and_buffer (STATUS_TOFU_USER, fingerprint,
-                                  email, strlen (email), 0);
+  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->next);
+
+      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;
+    }
 
-  if (! strlist)
+  /* Get the encryption stats.  */
+  rc = gpgsql_exec_printf
+    (dbs->db, strings_collect_cb, &strlist, &err,
+     "select count (*), min (encryptions.time), max (encryptions.time)\n"
+     " from encryptions\n"
+     " left join bindings on encryptions.binding = bindings.oid\n"
+     " where fingerprint = %Q and email = %Q;",
+     fingerprint, email);
+  if (rc)
     {
-      if (!outfp)
-        log_info (_("Have never verified a message signed by key %s!\n"),
-                  fingerprint_pp);
-      write_stats_status (outfp, 0, TOFU_POLICY_NONE, 0, 0);
+      log_error (_("error reading TOFU database: %s\n"), err);
+      print_further_info ("getting encryption statistics");
+      sqlite3_free (err);
+      rc = gpg_error (GPG_ERR_GENERAL);
+      goto out;
     }
-  else
+  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)
     {
-      unsigned long now = gnupg_get_time ();
-      signed long messages;
-      unsigned long first_seen;
-      unsigned long most_recent_seen;
+      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->next);
+
+      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__);
 
-      log_assert (strlist_length (strlist) == 3);
+      free_strlist (strlist);
+      strlist = NULL;
+    }
+
+  if (!outfp)
+    write_status_text_and_buffer (STATUS_TOFU_USER, fingerprint,
+                                  email, strlen (email), 0);
+
+  write_stats_status (outfp, policy,
+                      signature_count,
+                      signature_first_seen,
+                      signature_most_recent,
+                      signature_days,
+                      encryption_count,
+                      encryption_first_done,
+                      encryption_most_recent,
+                      encryption_days);
+
+  if (!outfp && !only_status_fd)
+    {
+      estream_t fp;
+      char *msg;
 
-      string_to_long (&messages, strlist->d, -1, __LINE__);
+      fp = es_fopenmem (0, "rw,samethread");
+      if (! fp)
+        log_fatal ("error creating memory stream: %s\n",
+                   gpg_strerror (gpg_error_from_syserror()));
 
-      if (messages == 0 && *strlist->next->d == '\0')
-        { /* min(NULL) => NULL => "".  */
-          first_seen = 0;
-          most_recent_seen = 0;
+      if (signature_count == 0 && encryption_count == 0)
+        {
+          es_fprintf (fp,
+                      _("%s: Verified 0~signatures and encrypted 0~messages."),
+                      email);
         }
       else
-       {
-          string_to_ulong (&first_seen, strlist->next->d, -1, __LINE__);
-          if (first_seen > now)
-            {
-              log_debug ("time-warp - tofu DB has a future value (%lu, %lu)\n",
-                         first_seen, now);
-              first_seen = now;
-            }
-         string_to_ulong (&most_recent_seen, strlist->next->next->d, -1,
-                           __LINE__);
-          if (most_recent_seen > now)
+        {
+          if (signature_count == 0)
+            es_fprintf (fp, _("%s: Verified 0 signatures."), email);
+          else
             {
-              log_debug ("time-warp - tofu DB has a future value (%lu, %lu)\n",
-                         most_recent_seen, now);
-              most_recent_seen = now;
+              /* 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);
             }
 
-       }
+          es_fputs ("  ", fp);
 
-      if (messages == -1 || !first_seen)
-        {
-          write_stats_status (outfp, 0, TOFU_POLICY_NONE, 0, 0);
-          if (!outfp)
-            log_info (_("Failed to collect signature statistics for \"%s\"\n"
-                        "(key %s)\n"),
-                      user_id, fingerprint_pp);
+          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);
+            }
         }
-      else if (outfp)
+
+      if (opt.verbose)
         {
-          write_stats_status (outfp, messages,
-                              get_policy (dbs, fingerprint, email, NULL),
-                              first_seen, most_recent_seen);
+          es_fputs ("  ", fp);
+          es_fprintf (fp, _("(policy: %s)"), tofu_policy_str (policy));
         }
-      else
-       {
-         enum tofu_policy policy = get_policy (dbs, fingerprint, email, NULL);
-         estream_t fp;
-         char *msg;
-
-          write_stats_status (NULL, messages,
-                              policy,
-                              first_seen, most_recent_seen);
+      es_fputs ("\n", fp);
 
-         fp = es_fopenmem (0, "rw,samethread");
-         if (! fp)
-            log_fatal ("error creating memory stream: %s\n",
-                       gpg_strerror (gpg_error_from_syserror()));
-
-         if (messages == 0)
-            {
-              es_fprintf (fp, _("Verified %ld messages signed by \"%s\"."),
-                          0L, user_id);
-              es_fputc ('\n', fp);
-            }
-         else
-           {
-              char *first_seen_ago_str = time_ago_str (now - 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 message signed by \"%s\"\n"
-                                   "in the past %s.",
-                                   "Verified %ld messages signed by \"%s\"\n"
-                                   "in the past %s.",
-                                   messages),
-                         messages, user_id, first_seen_ago_str);
-
-              if (messages > 1)
-                {
-                  char *tmpstr = time_ago_str (now - most_recent_seen);
-                  es_fputs ("  ", fp);
-                  es_fprintf (fp, _("The most recent message was"
-                                    " verified %s ago."), tmpstr);
-                  xfree (tmpstr);
-                }
-              xfree (first_seen_ago_str);
+      {
+        char *tmpmsg, *p;
+        es_fputc (0, fp);
+        if (es_fclose_snatch (fp, (void **) &tmpmsg, NULL))
+          log_fatal ("error snatching memory stream\n");
+        msg = format_text (tmpmsg, 0, 72, 80);
+        es_free (tmpmsg);
 
-              if (opt.verbose)
-                {
-                  es_fputs ("  ", fp);
-                  es_fputc ('(', fp);
-                  es_fprintf (fp, _("policy: %s"), tofu_policy_str (policy));
-                  es_fputs (")\n", fp);
-                }
-              else
-                es_fputs ("\n", fp);
-            }
+        /* Print a status line but suppress the trailing LF.
+         * Spaces are not percent escaped. */
+        if (*msg)
+          write_status_buffer (STATUS_TOFU_STATS_LONG,
+                               msg, strlen (msg)-1, -1);
 
-          {
-            char *tmpmsg, *p;
-            es_fputc (0, fp);
-            if (es_fclose_snatch (fp, (void **) &tmpmsg, NULL))
-              log_fatal ("error snatching memory stream\n");
-            msg = format_text (tmpmsg, 0, 72, 80);
-            es_free (tmpmsg);
-
-            /* Print a status line but suppress the trailing LF.
-             * Spaces are not percent escaped. */
-            if (*msg)
-              write_status_buffer (STATUS_TOFU_STATS_LONG,
-                                   msg, strlen (msg)-1, -1);
-
-            /* Remove the non-breaking space markers.  */
-            for (p=msg; *p; p++)
-              if (*p == '~')
-                *p = ' ';
+        /* Remove the non-breaking space markers.  */
+        for (p=msg; *p; p++)
+          if (*p == '~')
+            *p = ' ';
+      }
 
-          }
+      log_string (GPGRT_LOG_INFO, msg);
+      xfree (msg);
 
-         log_string (GPGRT_LOG_INFO, msg);
-          xfree (msg);
-
-         if (policy == TOFU_POLICY_AUTO && messages < BASIC_TRUST_THRESHOLD)
-           {
-             char *set_policy_command;
-             char *text;
-              char *tmpmsg;
-
-             if (messages == 0)
-               log_info (_("Warning: we've have yet to see"
-                            " a message signed by this key!\n"));
-             else if (messages == 1)
-               log_info (_("Warning: we've only seen a"
-                            " single message signed by this key!\n"));
-
-             set_policy_command =
-               xasprintf ("gpg --tofu-policy bad %s", fingerprint);
-
-              tmpmsg = xasprintf
-                (ngettext
-                 ("Warning: if you think you've seen more than %ld message "
-                  "signed by this key, then this key might be a forgery!  "
-                  "Carefully examine the email address for small "
-                  "variations.  If the key is suspect, then use\n"
-                  "  %s\n"
-                  "to mark it as being bad.\n",
-                  "Warning: if you think you've seen more than %ld messages "
-                  "signed by this key, then this key might be a forgery!  "
-                      "Carefully examine the email address for small "
-                  "variations.  If the key is suspect, then use\n"
-                  "  %s\n"
-                  "to mark it as being bad.\n",
-                  messages),
-                  messages, set_policy_command);
-              text = format_text (tmpmsg, 0, 72, 80);
-              xfree (tmpmsg);
-             log_string (GPGRT_LOG_INFO, text);
-              xfree (text);
-
-             es_free (set_policy_command);
-           }
-       }
+      if (policy == TOFU_POLICY_AUTO)
+        {
+          if (signature_count == 0)
+            log_info (_("Warning: we have yet to see"
+                        " a message signed using this key and user id!\n"));
+          else if (signature_count == 1)
+            log_info (_("Warning: we've only seen one message"
+                        " signed using this key and user id!\n"));
+
+          if (encryption_count == 0)
+            log_info (_("Warning: you have yet to encrypt"
+                        " a message to this key!\n"));
+          else if (encryption_count == 1)
+            log_info (_("Warning: you have only encrypted"
+                        " one message to this key!\n"));
+
+          /* Cf. write_stats_status  */
+          if (sqrtu32 (encryption_count * encryption_count
+                       + signature_count * signature_count)
+              < 2 * BASIC_TRUST_THRESHOLD)
+            show_warning = 1;
+        }
     }
 
  out:
-  free_strlist (strlist);
   xfree (fingerprint_pp);
 
-  return;
+  return show_warning;
+}
+
+static void
+show_warning (const char *fingerprint, strlist_t user_id_list)
+{
+  char *set_policy_command;
+  char *text;
+  char *tmpmsg;
+
+  set_policy_command =
+    xasprintf ("gpg --tofu-policy bad %s", fingerprint);
+
+  tmpmsg = xasprintf
+    (ngettext
+     ("Warning: if you think you've seen more signatures "
+      "by this key and user id, then this key might be a "
+      "forgery!  Carefully examine the email address for small "
+      "variations.  If the key is suspect, then use\n"
+      "  %s\n"
+      "to mark it as being bad.\n",
+      "Warning: if you think you've seen more signatures "
+      "by this key and these user ids, then this key might be a "
+      "forgery!  Carefully examine the email addresses for small "
+      "variations.  If the key is suspect, then use\n"
+      "  %s\n"
+      "to mark it as being bad.\n",
+      strlist_length (user_id_list)),
+     set_policy_command);
+
+  text = format_text (tmpmsg, 0, 72, 80);
+  xfree (tmpmsg);
+  log_string (GPGRT_LOG_INFO, text);
+  xfree (text);
+
+  es_free (set_policy_command);
 }
 
+
 /* Extract the email address from a user id and normalize it.  If the
    user id doesn't contain an email address, then we use the whole
    user_id and normalize that.  The returned string must be freed.  */
@@ -2203,12 +3274,14 @@ 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 (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
-              const byte *sig_digest_bin, int sig_digest_bin_len,
-              time_t sig_time, const char *origin)
+tofu_register_signature (ctrl_t ctrl,
+                         PKT_public_key *pk, strlist_t user_id_list,
+                         const byte *sig_digest_bin, int sig_digest_bin_len,
+                         time_t sig_time, const char *origin)
 {
+  time_t now = gnupg_get_time ();
   gpg_error_t rc;
   tofu_dbs_t dbs;
   char *fingerprint = NULL;
@@ -2233,6 +3306,8 @@ tofu_register (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
   if (rc)
     return rc;
 
+  log_assert (pk_is_primary (pk));
+
   sig_digest = make_radix64_string (sig_digest_bin, sig_digest_bin_len);
   fingerprint = hexfingerprint (pk, NULL, 0);
 
@@ -2251,7 +3326,7 @@ tofu_register (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
 
       /* Make sure the binding exists and record any TOFU
          conflicts.  */
-      if (get_trust (dbs, pk, fingerprint, email, user_id->d, 0)
+      if (get_trust (ctrl, pk, fingerprint, email, user_id->d, 0, now)
           == _tofu_GET_TRUST_ERROR)
         {
           rc = gpg_error (GPG_ERR_GENERAL);
@@ -2269,22 +3344,23 @@ tofu_register (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
          "  on signatures.binding = bindings.oid\n"
          " where fingerprint = ? and email = ? and sig_time = ?\n"
          "  and sig_digest = ?",
-         SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
-         SQLITE_ARG_LONG_LONG, (long long) sig_time,
-         SQLITE_ARG_STRING, sig_digest,
-         SQLITE_ARG_END);
+         GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
+         GPGSQL_ARG_LONG_LONG, (long long) sig_time,
+         GPGSQL_ARG_STRING, sig_digest,
+         GPGSQL_ARG_END);
       if (rc)
         {
           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,
@@ -2293,7 +3369,7 @@ tofu_register (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
         {
           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);
@@ -2314,22 +3390,24 @@ tofu_register (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
           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"
              " ((select oid from bindings\n"
              "    where fingerprint = ? and email = ?),\n"
-             "  ?, ?, ?, strftime('%s', 'now'));",
-             SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
-             SQLITE_ARG_STRING, sig_digest, SQLITE_ARG_STRING, origin,
-             SQLITE_ARG_LONG_LONG, (long long) sig_time,
-             SQLITE_ARG_END);
+             "  ?, ?, ?, ?);",
+             GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
+             GPGSQL_ARG_STRING, sig_digest, GPGSQL_ARG_STRING, origin,
+             GPGSQL_ARG_LONG_LONG, (long long) sig_time,
+             GPGSQL_ARG_LONG_LONG, (long long) now,
+             GPGSQL_ARG_END);
           if (rc)
             {
               log_error (_("error updating TOFU database: %s\n"), err);
               print_further_info ("insert signatures");
               sqlite3_free (err);
+              rc = gpg_error (GPG_ERR_GENERAL);
             }
         }
 
@@ -2350,6 +3428,120 @@ tofu_register (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
   return rc;
 }
 
+gpg_error_t
+tofu_register_encryption (ctrl_t ctrl,
+                          PKT_public_key *pk, strlist_t user_id_list,
+                          int may_ask)
+{
+  time_t now = gnupg_get_time ();
+  gpg_error_t rc = 0;
+  tofu_dbs_t dbs;
+  kbnode_t kb = NULL;
+  int free_user_id_list = 0;
+  char *fingerprint = NULL;
+  strlist_t user_id;
+  char *err = NULL;
+
+  dbs = opendbs (ctrl);
+  if (! dbs)
+    {
+      rc = gpg_error (GPG_ERR_GENERAL);
+      log_error (_("error opening TOFU database: %s\n"),
+                 gpg_strerror (rc));
+      return rc;
+    }
+
+  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 (pk->keyid);
+
+  /* Make sure PK is a primary key.  */
+  if (! pk_is_primary (pk))
+    pk = kb->pkt->pkt.public_key;
+
+  if (! user_id_list)
+    {
+      /* Use all non-revoked user ids.  Do use expired user ids.  */
+      kbnode_t n = kb;
+
+      while ((n = find_next_kbnode (n, PKT_USER_ID)))
+        {
+         PKT_user_id *uid = n->pkt->pkt.user_id;
+
+          if (uid->is_revoked)
+            continue;
+
+          add_to_strlist (&user_id_list, uid->name);
+        }
+
+      free_user_id_list = 1;
+
+      if (! user_id_list)
+        log_info (_("WARNING: Encrypting to %s, which has no "
+                    "non-revoked user ids.\n"),
+                  keystr (pk->keyid));
+    }
+
+  fingerprint = hexfingerprint (pk, NULL, 0);
+
+  tofu_begin_batch_update (ctrl);
+  tofu_resume_batch_transaction (ctrl);
+
+  for (user_id = user_id_list; user_id; user_id = user_id->next)
+    {
+      char *email = email_from_user_id (user_id->d);
+
+      /* 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);
+      if (tl == _tofu_GET_TRUST_ERROR)
+        {
+          /* An error.  */
+          rc = gpg_error (GPG_ERR_GENERAL);
+          xfree (email);
+          goto die;
+        }
+
+      rc = gpgsql_stepx
+        (dbs->db, &dbs->s.register_encryption, NULL, NULL, &err,
+         "insert into encryptions\n"
+         " (binding, time)\n"
+         " values\n"
+         " ((select oid from bindings\n"
+         "    where fingerprint = ? and email = ?),\n"
+         "  ?);",
+         GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
+         GPGSQL_ARG_LONG_LONG, (long long) now,
+         GPGSQL_ARG_END);
+      if (rc)
+        {
+          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);
+    }
+
+ die:
+  tofu_end_batch_update (ctrl);
+
+  if (kb)
+    release_kbnode (kb);
+
+  if (free_user_id_list)
+    free_strlist (user_id_list);
+
+  xfree (fingerprint);
+
+  return rc;
+}
+
+
 /* Combine a trust level returned from the TOFU trust model with a
    trust level returned by the PGP trust model.  This is primarily of
    interest when the trust model is tofu+pgp (TM_TOFU_PGP).
@@ -2418,10 +3610,12 @@ gpg_error_t
 tofu_write_tfs_record (ctrl_t ctrl, estream_t fp,
                        PKT_public_key *pk, const char *user_id)
 {
+  time_t now = gnupg_get_time ();
   gpg_error_t err;
   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.  */
@@ -2436,8 +3630,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 (dbs, pk, fingerprint, user_id, email, NULL, now);
 
-  show_statistics (dbs, fingerprint, email, user_id, NULL, fp);
+  show_statistics (dbs, fingerprint, email, policy, fp, 0, now);
 
   xfree (email);
   xfree (fingerprint);
@@ -2452,20 +3647,22 @@ tofu_write_tfs_record (ctrl_t ctrl, estream_t fp,
    PK is the primary key packet.
 
    If MAY_ASK is 1 and the policy is TOFU_POLICY_ASK, then the user
-   will be prompted to choose a different policy.  If MAY_ASK is 0 and
-   the policy is TOFU_POLICY_ASK, then TRUST_UNKNOWN is returned.
+   will be prompted to choose a policy.  If MAY_ASK is 0 and the
+   policy is TOFU_POLICY_ASK, then TRUST_UNKNOWN is returned.
 
    Returns TRUST_UNDEFINED if an error occurs.  */
 int
 tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
                   int may_ask)
 {
+  time_t now = gnupg_get_time ();
   tofu_dbs_t dbs;
   char *fingerprint = NULL;
   strlist_t user_id;
   int trust_level = TRUST_UNKNOWN;
   int bindings = 0;
   int bindings_valid = 0;
+  int need_warning = 0;
 
   dbs = opendbs (ctrl);
   if (! dbs)
@@ -2477,7 +3674,9 @@ tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
 
   fingerprint = hexfingerprint (pk, NULL, 0);
 
-  begin_transaction (ctrl, 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 ++)
     {
@@ -2485,7 +3684,8 @@ tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
 
       /* Always call get_trust to make sure the binding is
          registered.  */
-      int tl = get_trust (dbs, pk, fingerprint, email, user_id->d, may_ask);
+      int tl = get_trust (ctrl, pk, fingerprint, email, user_id->d,
+                          may_ask, now);
       if (tl == _tofu_GET_TRUST_ERROR)
         {
           /* An error.  */
@@ -2507,7 +3707,13 @@ 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)
-        show_statistics (dbs, fingerprint, email, user_id->d, NULL, NULL);
+        {
+          enum tofu_policy policy =
+            get_policy (dbs, pk, fingerprint, user_id->d, email, NULL, now);
+
+          need_warning |=
+            show_statistics (dbs, fingerprint, email, policy, NULL, 0, now);
+        }
 
       if (tl == TRUST_NEVER)
         trust_level = TRUST_NEVER;
@@ -2533,8 +3739,11 @@ tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
       xfree (email);
     }
 
+  if (need_warning)
+    show_warning (fingerprint, user_id_list);
+
  die:
-  end_transaction (ctrl, 0);
+  tofu_end_batch_update (ctrl);
 
   xfree (fingerprint);
 
@@ -2560,6 +3769,8 @@ 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;
+  time_t now = gnupg_get_time ();
   tofu_dbs_t dbs;
   PKT_public_key *pk;
   char *fingerprint = NULL;
@@ -2578,12 +3789,13 @@ 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);
 
+  begin_transaction (ctrl, 0);
+
   for (; kb; kb = kb->next)
     {
       PKT_user_id *user_id;
@@ -2600,30 +3812,26 @@ tofu_set_policy (ctrl_t ctrl, kbnode_t kb, enum tofu_policy policy)
 
       email = email_from_user_id (user_id->name);
 
-      record_binding (dbs, fingerprint, email, user_id->name, policy, 1);
+      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);
     }
 
-  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);
+  if (err)
+    rollback_transaction (ctrl);
+  else
+    end_transaction (ctrl, 0);
 
-  return tofu_set_policy (ctrl, keyblock, policy);
+  xfree (fingerprint);
+  return err;
 }
 
 /* Return the TOFU policy for the specified binding in *POLICY.  If no
@@ -2637,13 +3845,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)
@@ -2657,7 +3865,7 @@ 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 (dbs, pk, fingerprint, user_id->name, email, NULL, now);
 
   xfree (email);
   xfree (fingerprint);
@@ -2665,3 +3873,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;
+}