gpg: Make function mk_datestr public.
[gnupg.git] / g10 / tofu.c
index 8575947..c183fc6 100644 (file)
 #include <time.h>
 
 #include "gpg.h"
-#include "types.h"
-#include "logging.h"
-#include "stringhelp.h"
+#include "../common/types.h"
+#include "../common/logging.h"
+#include "../common/stringhelp.h"
 #include "options.h"
-#include "mbox-util.h"
-#include "i18n.h"
-#include "ttyio.h"
+#include "../common/mbox-util.h"
+#include "../common/i18n.h"
+#include "../common/ttyio.h"
 #include "trustdb.h"
-#include "mkdir_p.h"
+#include "../common/mkdir_p.h"
 #include "gpgsql.h"
-#include "status.h"
-#include "sqrtu32.h"
+#include "../common/status.h"
 
 #include "tofu.h"
 
 
 #define CONTROL_L ('L' - 'A' + 1)
 
-/* Number of signed messages required to indicate that enough history
- * is available for basic trust.  */
-#define BASIC_TRUST_THRESHOLD  10
-/* Number of signed messages required to indicate that a lot of
- * history is available.  */
-#define FULL_TRUST_THRESHOLD  100
+/* Number of days with signed / ecnrypted messages required to
+ * indicate that enough history is available for basic trust.  */
+#define BASIC_TRUST_THRESHOLD  4
+/* Number of days with signed / encrypted messages required to
+ * indicate that a lot of history is available.  */
+#define FULL_TRUST_THRESHOLD  21
 
 
-/* An struct with data pertaining to the tofu DB.
-
-   To initialize this data structure, call opendbs().  Cleanup is done
-   when the CTRL object is released.  To get a handle to a database,
-   use the getdb() function.  This will either return an existing
-   handle or open a new DB connection, as appropriate.  */
+/* A struct with data pertaining to the tofu DB.  There is one such
+   struct per session and it is cached in session's ctrl structure.
+   To initialize this or get the current singleton, call opendbs().
+   There is no need to explicitly release it; cleanup is done when the
+   CTRL object is released.  */
 struct tofu_dbs_s
 {
   sqlite3 *db;
@@ -113,8 +111,10 @@ struct tofu_dbs_s
 /* Local prototypes.  */
 static gpg_error_t end_transaction (ctrl_t ctrl, int only_batch);
 static char *email_from_user_id (const char *user_id);
-
-
+static int show_statistics (tofu_dbs_t dbs,
+                            const char *fingerprint, const char *email,
+                            enum tofu_policy policy,
+                            estream_t outfp, int only_status_fd, time_t now);
 \f
 const char *
 tofu_policy_str (enum tofu_policy policy)
@@ -179,8 +179,8 @@ begin_transaction (ctrl_t ctrl, int only_batch)
    * than 500 ms), to prevent starving other gpg processes, we drop
    * and retake the batch lock.
    *
-   * Note: if we wanted higher resolution, we could use
-   * npth_clock_gettime.  */
+   * Note: gnupg_get_time has a one second resolution, if we wanted a
+   * higher resolution, we could use npth_clock_gettime.  */
   if (/* No real transactions.  */
       dbs->in_transaction == 0
       /* There is an open batch transaction.  */
@@ -264,8 +264,8 @@ begin_transaction (ctrl_t ctrl, int only_batch)
 
 /* Commit a transaction.  If ONLY_BATCH is 1, then this only ends the
  * batch transaction if we have left batch mode.  If ONLY_BATCH is 2,
- * this ends any open batch transaction even if we are still in batch
- * mode.  */
+ * this commits any open batch transaction even if we are still in
+ * batch mode.  */
 static gpg_error_t
 end_transaction (ctrl_t ctrl, int only_batch)
 {
@@ -341,7 +341,7 @@ rollback_transaction (ctrl_t ctrl)
   log_assert (dbs);
   log_assert (dbs->in_transaction > 0);
 
-  /* Be careful to not any progress made by closed transactions in
+  /* Be careful to not undo any progress made by closed transactions in
      batch mode.  */
   rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err,
                            "rollback to inner%d;",
@@ -411,9 +411,12 @@ string_to_long (long *r_value, const char *string, long fallback, int line)
   if (errno || !(!strcmp (tail, ".0") || !*tail))
     {
       err = errno? gpg_error_from_errno (errno) : gpg_error (GPG_ERR_BAD_DATA);
-      log_debug ("%s:%d: "
-                 "strtol failed for DB returned string (tail=%.10s): %s\n",
-                 __FILE__, line, tail, gpg_strerror (err));
+      log_debug ("%s:%d: strtol failed for TOFU DB data; returned string"
+                 " (string='%.10s%s'; tail='%.10s%s'): %s\n",
+                 __FILE__, line,
+                 string, string && strlen(string) > 10 ? "..." : "",
+                 tail, tail && strlen(tail) > 10 ? "..." : "",
+                 gpg_strerror (err));
       *r_value = fallback;
     }
   else
@@ -439,9 +442,12 @@ string_to_ulong (unsigned long *r_value, const char *string,
   if (errno || !(!strcmp (tail, ".0") || !*tail))
     {
       err = errno? gpg_error_from_errno (errno) : gpg_error (GPG_ERR_BAD_DATA);
-      log_debug ("%s:%d: "
-                 "strtoul failed for DB returned string (tail=%.10s): %s\n",
-                 __FILE__, line, tail, gpg_strerror (err));
+      log_debug ("%s:%d: strtoul failed for TOFU DB data; returned string"
+                 " (string='%.10s%s'; tail='%.10s%s'): %s\n",
+                 __FILE__, line,
+                 string, string && strlen(string) > 10 ? "..." : "",
+                 tail, tail && strlen(tail) > 10 ? "..." : "",
+                 gpg_strerror (err));
       *r_value = fallback;
     }
   else
@@ -1152,7 +1158,7 @@ record_binding (tofu_dbs_t dbs, const char *fingerprint, const char *email,
 }
 
 
-/* Collect the strings returned by a query in a simply string list.
+/* Collect the strings returned by a query in a simple string list.
    Any NULL values are converted to the empty string.
 
    If a result has 3 rows and each row contains two columns, then the
@@ -1297,23 +1303,8 @@ signature_stats_collect_cb (void *cookie, int argc, char **argv,
   return 0;
 }
 
-/* Convert from seconds to time units.
-
-   Note: T should already be a multiple of TIME_AGO_UNIT_SMALL or
-   TIME_AGO_UNIT_MEDIUM or TIME_AGO_UNIT_LARGE.  */
-signed long
-time_ago_scale (signed long t)
-{
-  if (t < TIME_AGO_UNIT_MEDIUM)
-    return t / TIME_AGO_UNIT_SMALL;
-  if (t < TIME_AGO_UNIT_LARGE)
-    return t / TIME_AGO_UNIT_MEDIUM;
-  return t / TIME_AGO_UNIT_LARGE;
-}
-
-
 /* Format the first part of a conflict message and return that as a
- * malloced string.  */
+ * malloced string. Returns NULL on error. */
 static char *
 format_conflict_msg_part1 (int policy, strlist_t conflict_set,
                            const char *email)
@@ -1341,8 +1332,11 @@ format_conflict_msg_part1 (int policy, strlist_t conflict_set,
   else if (policy == TOFU_POLICY_ASK && conflict_set->next)
     {
       int conflicts = strlist_length (conflict_set);
-      es_fprintf (fp, _("The email address \"%s\" is associated with %d keys!"),
-                  email, conflicts);
+      es_fprintf
+        (fp, ngettext("The email address \"%s\" is associated with %d key!",
+                      "The email address \"%s\" is associated with %d keys!",
+                      conflicts),
+         email, conflicts);
       if (opt.verbose)
         es_fprintf (fp,
                     _("  Since this binding's policy was 'auto', it has been "
@@ -1360,7 +1354,7 @@ format_conflict_msg_part1 (int policy, strlist_t conflict_set,
   es_fputc (0, fp);
   if (es_fclose_snatch (fp, (void **)&tmpstr, NULL))
     log_fatal ("error snatching memory stream\n");
-  text = format_text (tmpstr, 0, 72, 80);
+  text = format_text (tmpstr, 72, 80);
   es_free (tmpstr);
 
   return text;
@@ -1578,7 +1572,7 @@ ask_about_binding (ctrl_t ctrl,
   struct signature_stats *stats = NULL;
   struct signature_stats *stats_iter = NULL;
   char *prompt = NULL;
-  char *choices;
+  const char *choices;
 
   dbs = ctrl->tofu.dbs;
   log_assert (dbs);
@@ -1591,6 +1585,10 @@ ask_about_binding (ctrl_t ctrl,
 
   {
     char *text = format_conflict_msg_part1 (*policy, conflict_set, email);
+    if (!text) /* FIXME: Return the error all the way up.  */
+      log_fatal ("format failed: %s\n",
+                 gpg_strerror (gpg_error_from_syserror()));
+
     es_fputs (text, fp);
     es_fputc ('\n', fp);
     xfree (text);
@@ -1744,6 +1742,7 @@ ask_about_binding (ctrl_t ctrl,
       char *key = NULL;
       strlist_t binding;
       int seen_in_past = 0;
+      int encrypted = 1;
 
       es_fprintf (fp, _("Statistics for keys"
                         " with the email address \"%s\":\n"),
@@ -1757,6 +1756,14 @@ ask_about_binding (ctrl_t ctrl,
                      stats_iter->count);
 #endif
 
+          if (stats_iter->time_ago > 0 && encrypted)
+            {
+              /* We've change from the encrypted stats to the verified
+               * stats.  Reset SEEN_IN_PAST.  */
+              encrypted = 0;
+              seen_in_past = 0;
+            }
+
           if (! key || strcmp (key, stats_iter->fingerprint))
             {
               int this_key;
@@ -1778,12 +1785,12 @@ ask_about_binding (ctrl_t ctrl,
               if ((binding->flags & BINDING_REVOKED))
                 {
                   es_fprintf (fp, _("revoked"));
-                  es_fprintf (fp, _(", "));
+                  es_fprintf (fp, ", ");
                 }
               else if ((binding->flags & BINDING_EXPIRED))
                 {
                   es_fprintf (fp, _("expired"));
-                  es_fprintf (fp, _(", "));
+                  es_fprintf (fp, ", ");
                 }
 
               if (this_key)
@@ -1795,6 +1802,9 @@ ask_about_binding (ctrl_t ctrl,
               xfree (key_pp);
 
               seen_in_past = 0;
+
+              show_statistics (dbs, stats_iter->fingerprint, email,
+                               TOFU_POLICY_ASK, NULL, 1, now);
             }
 
           if (labs(stats_iter->time_ago) == 1)
@@ -1806,50 +1816,92 @@ ask_about_binding (ctrl_t ctrl,
           seen_in_past += stats_iter->count;
 
           es_fputs ("    ", fp);
-          /* TANSLATORS: This string is concatenated with one of
-           * the day/week/month strings to form one sentence.  */
-          if (stats_iter->time_ago > 0)
-            es_fprintf (fp, ngettext("Verified %d message",
-                                     "Verified %d messages",
-                                     seen_in_past), seen_in_past);
-          else
-            es_fprintf (fp, ngettext("Encrypted %d message",
-                                     "Encrypted %d messages",
-                                     seen_in_past), seen_in_past);
 
           if (!stats_iter->count)
-            es_fputs (".", fp);
+            {
+              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)
             {
-              es_fprintf (fp, "in the future.");
+              if (stats_iter->time_ago > 0)
+                es_fprintf (fp, ngettext("Verified %d message in the future.",
+                                         "Verified %d messages in the future.",
+                                         seen_in_past), seen_in_past);
+              else
+                es_fprintf (fp, ngettext("Encrypted %d message in the future.",
+                                         "Encrypted %d messages in the future.",
+                                         seen_in_past), seen_in_past);
               /* Reset it.  */
               seen_in_past = 0;
             }
           else
             {
               if (labs(stats_iter->time_ago) == 3)
-                es_fprintf (fp, ngettext(" over the past day.",
-                                         " over the past %d days.",
-                                         TIME_AGO_SMALL_THRESHOLD
-                                         / TIME_AGO_UNIT_SMALL),
-                            TIME_AGO_SMALL_THRESHOLD
-                            / TIME_AGO_UNIT_SMALL);
+                {
+                  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)
-                es_fprintf (fp, ngettext(" over the past month.",
-                                         " over the past %d months.",
-                                         TIME_AGO_MEDIUM_THRESHOLD
-                                         / TIME_AGO_UNIT_MEDIUM),
-                            TIME_AGO_MEDIUM_THRESHOLD
-                            / TIME_AGO_UNIT_MEDIUM);
+                {
+                  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)
-                es_fprintf (fp, ngettext(" over the past year.",
-                                         " over the past %d years.",
-                                         TIME_AGO_LARGE_THRESHOLD
-                                         / TIME_AGO_UNIT_LARGE),
-                            TIME_AGO_LARGE_THRESHOLD
-                            / TIME_AGO_UNIT_LARGE);
+                {
+                  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)
-                es_fprintf (fp, _(" in the past."));
+                {
+                  if (stats_iter->time_ago > 0)
+                    es_fprintf
+                      (fp, _("Messages verified in the past: %d."),
+                       seen_in_past);
+                  else
+                    es_fprintf
+                      (fp, _("Messages encrypted in the past: %d."),
+                       seen_in_past);
+                }
               else
                 log_assert (! "Broken SQL.\n");
             }
@@ -1864,7 +1916,7 @@ ask_about_binding (ctrl_t ctrl,
       /* TRANSLATORS: Please translate the text found in the source
        * file below.  We don't directly internationalize that text so
        * that we can tweak it without breaking translations.  */
-      char *text = _("TOFU detected a binding conflict");
+      const char *text = _("TOFU detected a binding conflict");
       char *textbuf;
       if (!strcmp (text, "TOFU detected a binding conflict"))
         {
@@ -1877,8 +1929,8 @@ ask_about_binding (ctrl_t ctrl,
             "attack!  Before accepting this association, you should talk to or "
             "call the person to make sure this new key is legitimate.";
         }
-      textbuf = format_text (text, 0, 72, 80);
-      es_fprintf (fp, "\n%s\n", textbuf);
+      textbuf = format_text (text, 72, 80);
+      es_fprintf (fp, "\n%s\n", textbuf? textbuf : "[OUT OF CORE!]");
       xfree (textbuf);
     }
 
@@ -1892,7 +1944,7 @@ ask_about_binding (ctrl_t ctrl,
   /* I think showing the large message once is sufficient.  If we
    * would move it right before the cpr_get many lines will scroll
    * away and the user might not realize that he merely entered a
-   * wrong choise (because he does not see that either).  As a small
+   * wrong choice (because he does not see that either).  As a small
    * benefit we allow C-L to redisplay everything.  */
   tty_printf ("%s", prompt);
 
@@ -1920,7 +1972,7 @@ ask_about_binding (ctrl_t ctrl,
       else if (!response[0])
         /* Default to unknown.  Don't save it.  */
         {
-          tty_printf (_("Defaulting to unknown."));
+          tty_printf (_("Defaulting to unknown.\n"));
           *policy = TOFU_POLICY_UNKNOWN;
           break;
         }
@@ -1982,7 +2034,7 @@ ask_about_binding (ctrl_t ctrl,
    email> (including the binding itself, which will be first in the
    list).  For each returned key also sets BINDING_NEW, etc.  */
 static strlist_t
-build_conflict_set (tofu_dbs_t dbs,
+build_conflict_set (ctrl_t ctrl, tofu_dbs_t dbs,
                     PKT_public_key *pk, const char *fingerprint,
                     const char *email)
 {
@@ -2125,7 +2177,7 @@ build_conflict_set (tofu_dbs_t dbs,
           continue;
         }
 
-      merge_keys_and_selfsig (kb);
+      merge_keys_and_selfsig (ctrl, kb);
 
       log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
 
@@ -2160,9 +2212,9 @@ build_conflict_set (tofu_dbs_t dbs,
             {
               found_user_id = 1;
 
-              if (user_id2->is_revoked)
+              if (user_id2->flags.revoked)
                 iter->flags |= BINDING_REVOKED;
-              if (user_id2->is_expired)
+              if (user_id2->flags.expired)
                 iter->flags |= BINDING_EXPIRED;
             }
 
@@ -2183,9 +2235,15 @@ build_conflict_set (tofu_dbs_t dbs,
     int j;
     strlist_t *prevp;
     strlist_t iter_next;
-    int die[conflict_set_count];
+    int *die;
 
-    memset (die, 0, sizeof (die));
+    log_assert (conflict_set_count > 0);
+    die = xtrycalloc (conflict_set_count, sizeof *die);
+    if (!die)
+      {
+        /*err = gpg_error_from_syserror ();*/
+        xoutofcore (); /* Fixme: Let the function return an error.  */
+      }
 
     for (i = 0; i < conflict_set_count; i ++)
       {
@@ -2225,6 +2283,7 @@ build_conflict_set (tofu_dbs_t dbs,
     /* We shouldn't have removed the head.  */
     log_assert (conflict_set);
     log_assert (conflict_set_count >= 1);
+    xfree (die);
   }
   xfree (kb_all);
 
@@ -2248,11 +2307,20 @@ build_conflict_set (tofu_dbs_t dbs,
 
 
 /* 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.  */
+ * (email has already been normalized).  Returns
+ * _tofu_GET_POLICY_ERROR if an error occurs.  Returns any conflict
+ * information in *CONFLICT_SETP if CONFLICT_SETP is not NULL and the
+ * returned policy is TOFU_POLICY_ASK (consequently, if there is a
+ * conflict, but the user set the policy to good *CONFLICT_SETP will
+ * empty).  Note: as per build_conflict_set, which is used to build
+ * the conflict information, the conflict information includes the
+ * current user id as the first element of the linked list.
+ *
+ * This function registers the binding in the bindings table if it has
+ * not yet been registered.
+ */
 static enum tofu_policy
-get_policy (tofu_dbs_t dbs, PKT_public_key *pk,
+get_policy (ctrl_t ctrl, tofu_dbs_t dbs, PKT_public_key *pk,
             const char *fingerprint, const char *user_id, const char *email,
            strlist_t *conflict_setp, time_t now)
 {
@@ -2401,16 +2469,16 @@ get_policy (tofu_dbs_t dbs, PKT_public_key *pk,
   /* See if the key is signed by an ultimately trusted key.  */
   {
     int fingerprint_raw_len = strlen (fingerprint) / 2;
-    char fingerprint_raw[fingerprint_raw_len];
+    char fingerprint_raw[20];
     int len = 0;
 
-    if (fingerprint_raw_len != 20
+    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: %zd, parsed: %d)\n",
+          log_debug ("TOFU: Bad fingerprint: %s (len: %zu, parsed: %d)\n",
                      fingerprint, strlen (fingerprint), len);
       }
     else
@@ -2418,7 +2486,7 @@ get_policy (tofu_dbs_t dbs, PKT_public_key *pk,
         int lookup_err;
         kbnode_t kb;
 
-        lookup_err = get_pubkey_byfprint (NULL, &kb,
+        lookup_err = get_pubkey_byfprint (ctrl, NULL, &kb,
                                           fingerprint_raw,
                                           fingerprint_raw_len);
         if (lookup_err)
@@ -2444,7 +2512,7 @@ get_policy (tofu_dbs_t dbs, PKT_public_key *pk,
    * 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 = build_conflict_set (ctrl, dbs, pk, fingerprint, email);
   conflict_set_count = strlist_length (conflict_set);
   if (conflict_set_count == 0)
     {
@@ -2475,11 +2543,12 @@ get_policy (tofu_dbs_t dbs, PKT_public_key *pk,
   if (conflict_set_count == 1
       && (conflict_set->flags & BINDING_CONFLICT))
     {
-      /* No known conflicts now, but there was a conflict.  That is,
-       * at somepoint there was a conflict, but it went away.  A
-       * conflict can go away if there is now a cross sig between the
-       * two keys.  In this case, we just silently clear the
-       * 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",
@@ -2549,7 +2618,7 @@ get_policy (tofu_dbs_t dbs, PKT_public_key *pk,
   if (effective_policy == TOFU_POLICY_ASK && conflict_setp)
     {
       if (! conflict_set)
-        conflict_set = build_conflict_set (dbs, pk, fingerprint, email);
+        conflict_set = build_conflict_set (ctrl, dbs, pk, fingerprint, email);
       *conflict_setp = conflict_set;
     }
   else
@@ -2587,7 +2656,9 @@ get_policy (tofu_dbs_t dbs, PKT_public_key *pk,
 static enum tofu_policy
 get_trust (ctrl_t ctrl, PKT_public_key *pk,
            const char *fingerprint, const char *email,
-          const char *user_id, int may_ask, time_t now)
+           const char *user_id, int may_ask,
+           enum tofu_policy *policyp, strlist_t *conflict_setp,
+           time_t now)
 {
   tofu_dbs_t dbs = ctrl->tofu.dbs;
   int in_transaction = 0;
@@ -2618,6 +2689,23 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
               && _tofu_GET_TRUST_ERROR != TRUST_FULLY
               && _tofu_GET_TRUST_ERROR != TRUST_ULTIMATE);
 
+  begin_transaction (ctrl, 0);
+  in_transaction = 1;
+
+  /* We need to call get_policy even if the key is ultimately trusted
+   * to make sure the binding has been registered.  */
+  policy = get_policy (ctrl, dbs, pk, fingerprint, user_id, email,
+                       &conflict_set, now);
+
+  if (policy == TOFU_POLICY_ASK)
+    /* The conflict set should always contain at least one element:
+     * the current key.  */
+    log_assert (conflict_set);
+  else
+    /* If the policy is not TOFU_POLICY_ASK, then conflict_set will be
+     * NULL.  */
+    log_assert (! conflict_set);
+
   /* If the key is ultimately trusted, there is nothing to do.  */
   {
     u32 kid[2];
@@ -2626,14 +2714,11 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
     if (tdb_keyid_is_utk (kid))
       {
         trust_level = TRUST_ULTIMATE;
+        policy = TOFU_POLICY_GOOD;
         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;
@@ -2642,6 +2727,14 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
                    " auto (default: %s).\n",
                   fingerprint, email,
                   tofu_policy_str (opt.tofu_default_policy));
+
+      if (policy == TOFU_POLICY_ASK)
+        /* The default policy is ASK, but there is no conflict (policy
+         * was 'auto').  In this case, we need to make sure the
+         * conflict set includes at least the current user id.  */
+        {
+          add_to_strlist (&conflict_set, fingerprint);
+        }
     }
   switch (policy)
     {
@@ -2700,7 +2793,9 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
                          now);
     }
   else
-    trust_level = TRUST_UNDEFINED;
+    {
+      trust_level = TRUST_UNDEFINED;
+    }
 
   /* Mark any conflicting bindings that have an automatic policy as
    * now requiring confirmation.  Note: we do this after we ask for
@@ -2744,14 +2839,20 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
   if (in_transaction)
     end_transaction (ctrl, 0);
 
-  free_strlist (conflict_set);
+  if (policyp)
+    *policyp = policy;
+
+  if (conflict_setp)
+    *conflict_setp = conflict_set;
+  else
+    free_strlist (conflict_set);
 
   return trust_level;
 }
 
 
 /* Return a malloced string of the form
- *    "7 months, 1 day, 5 minutes, 0 seconds"
+ *    "7~months"
  * The caller should replace all '~' in the returned string by a space
  * and also free the returned string.
  *
@@ -2761,127 +2862,46 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
 static char *
 time_ago_str (long long int t)
 {
-  estream_t fp;
-  int years = 0;
-  int months = 0;
-  int days = 0;
-  int hours = 0;
-  int minutes = 0;
-  int seconds = 0;
-
-  /* The number of units that we've printed so far.  */
-  int count = 0;
-  /* The first unit that we printed (year = 0, month = 1,
-     etc.).  */
-  int first = -1;
-  /* The current unit.  */
-  int i = 0;
-
-  char *str;
-
   /* It would be nice to use a macro to do this, but gettext
      works on the unpreprocessed code.  */
 #define MIN_SECS (60)
 #define HOUR_SECS (60 * MIN_SECS)
 #define DAY_SECS (24 * HOUR_SECS)
+#define WEEK_SECS (7 * DAY_SECS)
 #define MONTH_SECS (30 * DAY_SECS)
 #define YEAR_SECS (365 * DAY_SECS)
 
-  if (t > YEAR_SECS)
+  if (t > 2 * YEAR_SECS)
     {
-      years = t / YEAR_SECS;
-      t -= years * YEAR_SECS;
+      long long int c = t / YEAR_SECS;
+      return xtryasprintf (ngettext("%lld~year", "%lld~years", c), c);
     }
-  if (t > MONTH_SECS)
+  if (t > 2 * MONTH_SECS)
     {
-      months = t / MONTH_SECS;
-      t -= months * MONTH_SECS;
+      long long int c = t / MONTH_SECS;
+      return xtryasprintf (ngettext("%lld~month", "%lld~months", c), c);
     }
-  if (t > DAY_SECS)
+  if (t > 2 * WEEK_SECS)
     {
-      days = t / DAY_SECS;
-      t -= days * DAY_SECS;
+      long long int c = t / WEEK_SECS;
+      return xtryasprintf (ngettext("%lld~week", "%lld~weeks", c), c);
     }
-  if (t > HOUR_SECS)
+  if (t > 2 * DAY_SECS)
     {
-      hours = t / HOUR_SECS;
-      t -= hours * HOUR_SECS;
+      long long int c = t / DAY_SECS;
+      return xtryasprintf (ngettext("%lld~day", "%lld~days", c), c);
     }
-  if (t > MIN_SECS)
+  if (t > 2 * HOUR_SECS)
     {
-      minutes = t / MIN_SECS;
-      t -= minutes * MIN_SECS;
+      long long int c = t / HOUR_SECS;
+      return xtryasprintf (ngettext("%lld~hour", "%lld~hours", c), c);
     }
-  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 * MIN_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 / MIN_SECS;
+      return xtryasprintf (ngettext("%lld~minute", "%lld~minutes", c), c);
     }
-  i ++;
-  if ((first == -1 || i - first <= 3) && count <= 0 && months)
-    {
-      if (count)
-        es_fprintf (fp, ", ");
-      es_fprintf (fp, ngettext("%d~month", "%d~months", months), months);
-      count ++;
-      first = i;
-    }
-  i ++;
-  if ((first == -1 || i - first <= 3) && count <= 0 && days)
-    {
-      if (count)
-        es_fprintf (fp, ", ");
-      es_fprintf (fp, ngettext("%d~day", "%d~days", days), days);
-      count ++;
-      first = i;
-    }
-  i ++;
-  if ((first == -1 || i - first <= 3) && count <= 0 && hours)
-    {
-      if (count)
-        es_fprintf (fp, ", ");
-      es_fprintf (fp, ngettext("%d~hour", "%d~hours", hours), hours);
-      count ++;
-      first = i;
-    }
-  i ++;
-  if ((first == -1 || i - first <= 3) && count <= 0 && minutes)
-    {
-      if (count)
-        es_fprintf (fp, ", ");
-      es_fprintf (fp, ngettext("%d~minute", "%d~minutes", minutes), minutes);
-      count ++;
-      first = i;
-    }
-  i ++;
-  if ((first == -1 || i - first <= 3) && count <= 0)
-    {
-      if (count)
-        es_fprintf (fp, ", ");
-      es_fprintf (fp, ngettext("%d~second", "%d~seconds", seconds), seconds);
-    }
-
-  es_fputc (0, fp);
-  if (es_fclose_snatch (fp, (void **) &str, NULL))
-    log_fatal ("error snatching memory stream\n");
-
-  return str;
+  return xtryasprintf (ngettext("%lld~second", "%lld~seconds", t), t);
 }
 
 
@@ -2893,65 +2913,76 @@ write_stats_status (estream_t fp,
                     unsigned long signature_count,
                     unsigned long signature_first_seen,
                     unsigned long signature_most_recent,
+                    unsigned long signature_days,
                     unsigned long encryption_count,
                     unsigned long encryption_first_done,
-                    unsigned long encryption_most_recent)
+                    unsigned long encryption_most_recent,
+                    unsigned long encryption_days)
 {
-  const char *validity;
-  unsigned long messages;
+  int summary;
+  int validity;
+  unsigned long days_sq;
 
   /* Use the euclidean distance (m = sqrt(a^2 + b^2)) rather then the
      sum of the magnitudes (m = a + b) to ensure a balance between
      verified signatures and encrypted messages.  */
-  messages = sqrtu32 (signature_count * signature_count
-                      + encryption_count * encryption_count);
-
-  if (messages < 1)
-    validity = "1"; /* Key without history.  */
-  else if (messages < 2 * BASIC_TRUST_THRESHOLD)
-    validity = "2"; /* Key with too little history.  */
-  else if (messages < 2 * FULL_TRUST_THRESHOLD)
-    validity = "3"; /* Key with enough history for basic trust.  */
+  days_sq = signature_days * signature_days + encryption_days * encryption_days;
+
+  if (days_sq < 1)
+    validity = 1; /* Key without history.  */
+  else if (days_sq < (2 * BASIC_TRUST_THRESHOLD) * (2 * BASIC_TRUST_THRESHOLD))
+    validity = 2; /* Key with too little history.  */
+  else if (days_sq < (2 * FULL_TRUST_THRESHOLD) * (2 * FULL_TRUST_THRESHOLD))
+    validity = 3; /* Key with enough history for basic trust.  */
+  else
+    validity = 4; /* Key with a lot of history.  */
+
+  if (policy == TOFU_POLICY_ASK)
+    summary = 0; /* Key requires attention.  */
   else
-    validity = "4"; /* Key with a lot of history.  */
+    summary = validity;
 
   if (fp)
     {
-      es_fprintf (fp, "tfs:1:%s:%lu:%lu:%s:%lu:%lu:%lu:%lu:\n",
-                  validity, signature_count, encryption_count,
+      es_fprintf (fp, "tfs:1:%d:%lu:%lu:%s:%lu:%lu:%lu:%lu:%d:%lu:%lu:\n",
+                  summary, signature_count, encryption_count,
                   tofu_policy_str (policy),
                   signature_first_seen, signature_most_recent,
-                  encryption_first_done, encryption_most_recent);
+                  encryption_first_done, encryption_most_recent,
+                  validity, signature_days, encryption_days);
     }
   else
     {
       write_status_printf (STATUS_TOFU_STATS,
-                           "%s %lu %lu %s %lu %lu %lu %lu",
-                           validity,
+                           "%d %lu %lu %s %lu %lu %lu %lu %d %lu %lu",
+                           summary,
                            signature_count,
                            encryption_count,
                            tofu_policy_str (policy),
                            signature_first_seen,
                            signature_most_recent,
                            encryption_first_done,
-                           encryption_most_recent);
+                           encryption_most_recent,
+                           validity,
+                           signature_days, encryption_days);
     }
 }
 
 /* Note: If OUTFP is not NULL, this function merely prints a "tfs" record
  * to OUTFP.
  *
- * Returns whether the caller should call show_warning after iterating
- * over all user ids.
+ * POLICY is the key's policy (as returned by get_policy).
+ *
+ * Returns 0 if ONLY_STATUS_FD is set.  Otherwise, returns whether
+ * the caller should call show_warning after iterating over all user
+ * ids.
  */
 static int
-show_statistics (tofu_dbs_t dbs, PKT_public_key *pk, const char *fingerprint,
-                const char *email, const char *user_id,
-                estream_t outfp, time_t now)
+show_statistics (tofu_dbs_t dbs,
+                 const char *fingerprint, const char *email,
+                 enum tofu_policy policy,
+                estream_t outfp, int only_status_fd, time_t now)
 {
-  enum tofu_policy policy =
-    get_policy (dbs, pk, fingerprint, user_id, email, NULL, now);
-
   char *fingerprint_pp;
   int rc;
   strlist_t strlist = NULL;
@@ -2960,20 +2991,24 @@ show_statistics (tofu_dbs_t dbs, PKT_public_key *pk, const char *fingerprint,
   unsigned long signature_first_seen = 0;
   unsigned long signature_most_recent = 0;
   unsigned long signature_count = 0;
+  unsigned long signature_days = 0;
   unsigned long encryption_first_done = 0;
   unsigned long encryption_most_recent = 0;
   unsigned long encryption_count = 0;
+  unsigned long encryption_days = 0;
 
   int show_warning = 0;
 
-  (void) user_id;
+  if (only_status_fd && ! is_status_enabled ())
+    return 0;
 
   fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
 
   /* Get the signature stats.  */
   rc = gpgsql_exec_printf
     (dbs->db, strings_collect_cb, &strlist, &err,
-     "select count (*), min (signatures.time), max (signatures.time)\n"
+     "select count (*), coalesce (min (signatures.time), 0),\n"
+     "  coalesce (max (signatures.time), 0)\n"
      " from signatures\n"
      " left join bindings on signatures.binding = bindings.oid\n"
      " where fingerprint = %Q and email = %Q;",
@@ -2986,18 +3021,38 @@ show_statistics (tofu_dbs_t dbs, PKT_public_key *pk, const char *fingerprint,
       rc = gpg_error (GPG_ERR_GENERAL);
       goto out;
     }
+  rc = gpgsql_exec_printf
+    (dbs->db, strings_collect_cb, &strlist, &err,
+     "select count (*) from\n"
+     "  (select round(signatures.time / (24 * 60 * 60)) day\n"
+     "    from signatures\n"
+     "    left join bindings on signatures.binding = bindings.oid\n"
+     "    where fingerprint = %Q and email = %Q\n"
+     "    group by day);",
+     fingerprint, email);
+  if (rc)
+    {
+      log_error (_("error reading TOFU database: %s\n"), err);
+      print_further_info ("getting signature statistics (by day)");
+      sqlite3_free (err);
+      rc = gpg_error (GPG_ERR_GENERAL);
+      goto out;
+    }
 
   if (strlist)
     {
-      /* We expect exactly 3 elements.  */
+      /* We expect exactly 4 elements.  */
       log_assert (strlist->next);
       log_assert (strlist->next->next);
-      log_assert (! strlist->next->next->next);
+      log_assert (strlist->next->next->next);
+      log_assert (! strlist->next->next->next->next);
 
-      string_to_ulong (&signature_count, strlist->d, -1, __LINE__);
-      string_to_ulong (&signature_first_seen, strlist->next->d, -1, __LINE__);
-      string_to_ulong (&signature_most_recent,
+      string_to_ulong (&signature_days, strlist->d, -1, __LINE__);
+      string_to_ulong (&signature_count, strlist->next->d, -1, __LINE__);
+      string_to_ulong (&signature_first_seen,
                        strlist->next->next->d, -1, __LINE__);
+      string_to_ulong (&signature_most_recent,
+                       strlist->next->next->next->d, -1, __LINE__);
 
       free_strlist (strlist);
       strlist = NULL;
@@ -3006,7 +3061,8 @@ show_statistics (tofu_dbs_t dbs, PKT_public_key *pk, const char *fingerprint,
   /* Get the encryption stats.  */
   rc = gpgsql_exec_printf
     (dbs->db, strings_collect_cb, &strlist, &err,
-     "select count (*), min (encryptions.time), max (encryptions.time)\n"
+     "select count (*), coalesce (min (encryptions.time), 0),\n"
+     "  coalesce (max (encryptions.time), 0)\n"
      " from encryptions\n"
      " left join bindings on encryptions.binding = bindings.oid\n"
      " where fingerprint = %Q and email = %Q;",
@@ -3019,18 +3075,38 @@ show_statistics (tofu_dbs_t dbs, PKT_public_key *pk, const char *fingerprint,
       rc = gpg_error (GPG_ERR_GENERAL);
       goto out;
     }
+  rc = gpgsql_exec_printf
+    (dbs->db, strings_collect_cb, &strlist, &err,
+     "select count (*) from\n"
+     "  (select round(encryptions.time / (24 * 60 * 60)) day\n"
+     "    from encryptions\n"
+     "    left join bindings on encryptions.binding = bindings.oid\n"
+     "    where fingerprint = %Q and email = %Q\n"
+     "    group by day);",
+     fingerprint, email);
+  if (rc)
+    {
+      log_error (_("error reading TOFU database: %s\n"), err);
+      print_further_info ("getting encryption statistics (by day)");
+      sqlite3_free (err);
+      rc = gpg_error (GPG_ERR_GENERAL);
+      goto out;
+    }
 
   if (strlist)
     {
-      /* We expect exactly 3 elements.  */
+      /* We expect exactly 4 elements.  */
       log_assert (strlist->next);
       log_assert (strlist->next->next);
-      log_assert (! strlist->next->next->next);
+      log_assert (strlist->next->next->next);
+      log_assert (! strlist->next->next->next->next);
 
-      string_to_ulong (&encryption_count, strlist->d, -1, __LINE__);
-      string_to_ulong (&encryption_first_done, strlist->next->d, -1, __LINE__);
-      string_to_ulong (&encryption_most_recent,
+      string_to_ulong (&encryption_days, strlist->d, -1, __LINE__);
+      string_to_ulong (&encryption_count, strlist->next->d, -1, __LINE__);
+      string_to_ulong (&encryption_first_done,
                        strlist->next->next->d, -1, __LINE__);
+      string_to_ulong (&encryption_most_recent,
+                       strlist->next->next->next->d, -1, __LINE__);
 
       free_strlist (strlist);
       strlist = NULL;
@@ -3044,11 +3120,13 @@ show_statistics (tofu_dbs_t dbs, PKT_public_key *pk, const char *fingerprint,
                       signature_count,
                       signature_first_seen,
                       signature_most_recent,
+                      signature_days,
                       encryption_count,
                       encryption_first_done,
-                      encryption_most_recent);
+                      encryption_most_recent,
+                      encryption_days);
 
-  if (!outfp)
+  if (!outfp && !only_status_fd)
     {
       estream_t fp;
       char *msg;
@@ -3058,56 +3136,55 @@ show_statistics (tofu_dbs_t dbs, PKT_public_key *pk, const char *fingerprint,
         log_fatal ("error creating memory stream: %s\n",
                    gpg_strerror (gpg_error_from_syserror()));
 
-      es_fprintf (fp, _("%s: "), email);
-
-      if (signature_count == 0)
+      if (signature_count == 0 && encryption_count == 0)
         {
-          es_fprintf (fp, _("Verified %ld signatures"), 0L);
-          es_fputc ('\n', fp);
-        }
-      else
-        {
-          char *first_seen_ago_str = time_ago_str (now - signature_first_seen);
-
-          /* TRANSLATORS: The final %s is replaced by a string like
-             "7 months, 1 day, 5 minutes, 0 seconds". */
           es_fprintf (fp,
-                      ngettext("Verified %ld signature in the past %s",
-                               "Verified %ld signatures in the past %s",
-                               signature_count),
-                      signature_count, first_seen_ago_str);
-
-          xfree (first_seen_ago_str);
-        }
-
-      if (encryption_count == 0)
-        {
-          es_fprintf (fp, _(", and encrypted %ld messages"), 0L);
+                      _("%s: Verified 0~signatures and encrypted 0~messages."),
+                      email);
         }
       else
         {
-          char *first_done_ago_str = time_ago_str (now - encryption_first_done);
+          if (signature_count == 0)
+            es_fprintf (fp, _("%s: Verified 0 signatures."), email);
+          else
+            {
+              /* TRANSLATORS: The final %s is replaced by a string like
+                 "7~months". */
+              char *ago_str = time_ago_str (now - signature_first_seen);
+              es_fprintf
+                (fp,
+                 ngettext("%s: Verified %ld~signature in the past %s.",
+                          "%s: Verified %ld~signatures in the past %s.",
+                          signature_count),
+                 email, signature_count, ago_str);
+              xfree (ago_str);
+            }
 
-          /* TRANSLATORS: The final %s is replaced by a string like
-             "7 months, 1 day, 5 minutes, 0 seconds". */
-          es_fprintf (fp,
-                      ngettext(", and encrypted %ld message in the past %s",
-                               ", and encrypted %ld messages in the past %s",
-                               encryption_count),
-                      encryption_count, first_done_ago_str);
+          es_fputs ("  ", fp);
 
-          xfree (first_done_ago_str);
+          if (encryption_count == 0)
+            es_fprintf (fp, _("Encrypted 0 messages."));
+          else
+            {
+              char *ago_str = time_ago_str (now - encryption_first_done);
+
+              /* TRANSLATORS: The final %s is replaced by a string like
+                 "7~months". */
+              es_fprintf (fp,
+                          ngettext("Encrypted %ld~message in the past %s.",
+                                   "Encrypted %ld~messages in the past %s.",
+                                   encryption_count),
+                          encryption_count, ago_str);
+              xfree (ago_str);
+            }
         }
 
       if (opt.verbose)
         {
           es_fputs ("  ", fp);
-          es_fputc ('(', fp);
-          es_fprintf (fp, _("policy: %s"), tofu_policy_str (policy));
-          es_fputs (").\n", fp);
+          es_fprintf (fp, _("(policy: %s)"), tofu_policy_str (policy));
         }
-      else
-        es_fputs (".\n", fp);
+      es_fputs ("\n", fp);
 
 
       {
@@ -3115,7 +3192,10 @@ show_statistics (tofu_dbs_t dbs, PKT_public_key *pk, const char *fingerprint,
         es_fputc (0, fp);
         if (es_fclose_snatch (fp, (void **) &tmpmsg, NULL))
           log_fatal ("error snatching memory stream\n");
-        msg = format_text (tmpmsg, 0, 72, 80);
+        msg = format_text (tmpmsg, 72, 80);
+        if (!msg) /* FIXME: Return the error all the way up.  */
+          log_fatal ("format failed: %s\n",
+                     gpg_strerror (gpg_error_from_syserror()));
         es_free (tmpmsg);
 
         /* Print a status line but suppress the trailing LF.
@@ -3150,9 +3230,9 @@ show_statistics (tofu_dbs_t dbs, PKT_public_key *pk, const char *fingerprint,
                         " one message to this key!\n"));
 
           /* Cf. write_stats_status  */
-          if (sqrtu32 (encryption_count * encryption_count
-                       + signature_count * signature_count)
-              < 2 * BASIC_TRUST_THRESHOLD)
+          if ((encryption_count * encryption_count
+              + signature_count * signature_count)
+             < ((2 * BASIC_TRUST_THRESHOLD) * (2 * BASIC_TRUST_THRESHOLD)))
             show_warning = 1;
         }
     }
@@ -3190,7 +3270,10 @@ show_warning (const char *fingerprint, strlist_t user_id_list)
       strlist_length (user_id_list)),
      set_policy_command);
 
-  text = format_text (tmpmsg, 0, 72, 80);
+  text = format_text (tmpmsg, 72, 80);
+  if (!text) /* FIXME: Return the error all the way up.  */
+    log_fatal ("format failed: %s\n",
+               gpg_strerror (gpg_error_from_syserror()));
   xfree (tmpmsg);
   log_string (GPGRT_LOG_INFO, text);
   xfree (text);
@@ -3288,7 +3371,8 @@ tofu_register_signature (ctrl_t ctrl,
 
       /* Make sure the binding exists and record any TOFU
          conflicts.  */
-      if (get_trust (ctrl, pk, fingerprint, email, user_id->d, 0, now)
+      if (get_trust (ctrl, pk, fingerprint, email, user_id->d,
+                     0, NULL, NULL, now)
           == _tofu_GET_TRUST_ERROR)
         {
           rc = gpg_error (GPG_ERR_GENERAL);
@@ -3417,7 +3501,7 @@ tofu_register_encryption (ctrl_t ctrl,
       ! pk_is_primary (pk)
       /* We need the key block to find all user ids.  */
       || ! user_id_list)
-    kb = get_pubkeyblock (pk->keyid);
+    kb = get_pubkeyblock (ctrl, pk->keyid);
 
   /* Make sure PK is a primary key.  */
   if (! pk_is_primary (pk))
@@ -3432,7 +3516,7 @@ tofu_register_encryption (ctrl_t ctrl,
         {
          PKT_user_id *uid = n->pkt->pkt.user_id;
 
-          if (uid->is_revoked)
+          if (uid->flags.revoked)
             continue;
 
           add_to_strlist (&user_id_list, uid->name);
@@ -3441,8 +3525,8 @@ tofu_register_encryption (ctrl_t ctrl,
       free_user_id_list = 1;
 
       if (! user_id_list)
-        log_info (_("WARNING: Encrypting to %s, which has no"
-                    "non-revoked user ids.\n"),
+        log_info (_("WARNING: Encrypting to %s, which has no "
+                    "non-revoked user ids\n"),
                   keystr (pk->keyid));
     }
 
@@ -3454,11 +3538,13 @@ tofu_register_encryption (ctrl_t ctrl,
   for (user_id = user_id_list; user_id; user_id = user_id->next)
     {
       char *email = email_from_user_id (user_id->d);
+      strlist_t conflict_set = NULL;
+      enum tofu_policy policy;
 
       /* Make sure the binding exists and that we recognize any
          conflicts.  */
       int tl = get_trust (ctrl, pk, fingerprint, email, user_id->d,
-                          may_ask, now);
+                          may_ask, &policy, &conflict_set, now);
       if (tl == _tofu_GET_TRUST_ERROR)
         {
           /* An error.  */
@@ -3467,6 +3553,28 @@ tofu_register_encryption (ctrl_t ctrl,
           goto die;
         }
 
+
+      /* If there is a conflict and MAY_ASK is true, we need to show
+       * the TOFU statistics for the current binding and the
+       * conflicting bindings.  But, if we are not in batch mode, then
+       * they have already been printed (this is required to make sure
+       * the information is available to the caller before cpr_get is
+       * called).  */
+      if (policy == TOFU_POLICY_ASK && may_ask && opt.batch)
+        {
+          strlist_t iter;
+
+          /* The conflict set should contain at least the current
+           * key.  */
+          log_assert (conflict_set);
+
+          for (iter = conflict_set; iter; iter = iter->next)
+            show_statistics (dbs, iter->d, email,
+                             TOFU_POLICY_ASK, NULL, 1, now);
+        }
+
+      free_strlist (conflict_set);
+
       rc = gpgsql_stepx
         (dbs->db, &dbs->s.register_encryption, NULL, NULL, &err,
          "insert into encryptions\n"
@@ -3577,6 +3685,7 @@ tofu_write_tfs_record (ctrl_t ctrl, estream_t fp,
   tofu_dbs_t dbs;
   char *fingerprint;
   char *email;
+  enum tofu_policy policy;
 
   if (!*user_id)
     return 0;  /* No TOFU stats possible for an empty ID.  */
@@ -3591,8 +3700,9 @@ tofu_write_tfs_record (ctrl_t ctrl, estream_t fp,
 
   fingerprint = hexfingerprint (pk, NULL, 0);
   email = email_from_user_id (user_id);
+  policy = get_policy (ctrl, dbs, pk, fingerprint, user_id, email, NULL, now);
 
-  show_statistics (dbs, pk, fingerprint, email, user_id, fp, now);
+  show_statistics (dbs, fingerprint, email, policy, fp, 0, now);
 
   xfree (email);
   xfree (fingerprint);
@@ -3623,6 +3733,7 @@ tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
   int bindings = 0;
   int bindings_valid = 0;
   int need_warning = 0;
+  int had_conflict = 0;
 
   dbs = opendbs (ctrl);
   if (! dbs)
@@ -3641,11 +3752,13 @@ tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
   for (user_id = user_id_list; user_id; user_id = user_id->next, bindings ++)
     {
       char *email = email_from_user_id (user_id->d);
+      strlist_t conflict_set = NULL;
+      enum tofu_policy policy;
 
       /* Always call get_trust to make sure the binding is
          registered.  */
       int tl = get_trust (ctrl, pk, fingerprint, email, user_id->d,
-                          may_ask, now);
+                          may_ask, &policy, &conflict_set, now);
       if (tl == _tofu_GET_TRUST_ERROR)
         {
           /* An error.  */
@@ -3667,8 +3780,36 @@ tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
         bindings_valid ++;
 
       if (may_ask && tl != TRUST_ULTIMATE && tl != TRUST_EXPIRED)
-        need_warning |=
-          show_statistics (dbs, pk, fingerprint, email, user_id->d, NULL, now);
+        {
+          /* If policy is ask, then we already printed out the
+           * conflict information in ask_about_binding or will do so
+           * in a moment.  */
+          if (policy != TOFU_POLICY_ASK)
+            need_warning |=
+              show_statistics (dbs, fingerprint, email, policy, NULL, 0, now);
+
+          /* If there is a conflict and MAY_ASK is true, we need to
+           * show the TOFU statistics for the current binding and the
+           * conflicting bindings.  But, if we are not in batch mode,
+           * then they have already been printed (this is required to
+           * make sure the information is available to the caller
+           * before cpr_get is called).  */
+          if (policy == TOFU_POLICY_ASK && opt.batch)
+            {
+              strlist_t iter;
+
+              /* The conflict set should contain at least the current
+               * key.  */
+              log_assert (conflict_set);
+
+              had_conflict = 1;
+              for (iter = conflict_set; iter; iter = iter->next)
+                show_statistics (dbs, iter->d, email,
+                                 TOFU_POLICY_ASK, NULL, 1, now);
+            }
+        }
+
+      free_strlist (conflict_set);
 
       if (tl == TRUST_NEVER)
         trust_level = TRUST_NEVER;
@@ -3694,7 +3835,7 @@ tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
       xfree (email);
     }
 
-  if (need_warning)
+  if (need_warning && ! had_conflict)
     show_warning (fingerprint, user_id_list);
 
  die:
@@ -3724,7 +3865,7 @@ tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
 gpg_error_t
 tofu_set_policy (ctrl_t ctrl, kbnode_t kb, enum tofu_policy policy)
 {
-  gpg_error_t err;
+  gpg_error_t err = 0;
   time_t now = gnupg_get_time ();
   tofu_dbs_t dbs;
   PKT_public_key *pk;
@@ -3760,7 +3901,7 @@ tofu_set_policy (ctrl_t ctrl, kbnode_t kb, enum tofu_policy policy)
        continue;
 
       user_id = kb->pkt->pkt.user_id;
-      if (user_id->is_revoked)
+      if (user_id->flags.revoked)
        /* Skip revoked user ids.  (Don't skip expired user ids, the
           expiry can be changed.)  */
        continue;
@@ -3789,23 +3930,6 @@ tofu_set_policy (ctrl_t ctrl, kbnode_t kb, enum tofu_policy policy)
   return err;
 }
 
-/* Set the TOFU policy for all non-revoked user ids in the KEY with
-   the key id KEYID to POLICY.
-
-   If no key is available with the specified key id, then this
-   function returns GPG_ERR_NO_PUBKEY.
-
-   Returns 0 on success and an error code otherwise.  */
-gpg_error_t
-tofu_set_policy_by_keyid (ctrl_t ctrl, u32 *keyid, enum tofu_policy policy)
-{
-  kbnode_t keyblock = get_pubkeyblock (keyid);
-  if (! keyblock)
-    return gpg_error (GPG_ERR_NO_PUBKEY);
-
-  return tofu_set_policy (ctrl, keyblock, policy);
-}
-
 /* Return the TOFU policy for the specified binding in *POLICY.  If no
    policy has been set for the binding, sets *POLICY to
    TOFU_POLICY_NONE.
@@ -3837,7 +3961,8 @@ tofu_get_policy (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *user_id,
 
   email = email_from_user_id (user_id->name);
 
-  *policy = get_policy (dbs, pk, fingerprint, user_id->name, email, NULL, now);
+  *policy = get_policy (ctrl, dbs, pk, fingerprint,
+                        user_id->name, email, NULL, now);
 
   xfree (email);
   xfree (fingerprint);