gpg: Fix type mismatch resulting in a buffer overflow.
[gnupg.git] / g10 / tofu.c
index 4eab487..2433b7b 100644 (file)
@@ -40,6 +40,7 @@
 #include "i18n.h"
 #include "trustdb.h"
 #include "mkdir_p.h"
+#include "sqlite.h"
 
 #include "tofu.h"
 
@@ -167,7 +168,7 @@ tofu_cache_dump (struct db *db)
 #  define TIME_AGO_UNIT_LARGE_NAME_PLURAL _("months")
 #endif
 
-
+\f
 
 const char *
 tofu_policy_str (enum tofu_policy policy)
@@ -213,214 +214,6 @@ tofu_policy_to_trust_level (enum tofu_policy policy)
       return 0;
     }
 }
-
-/* This is a convenience function that combines sqlite3_mprintf and
-   sqlite3_exec.  */
-static int
-sqlite3_exec_printf (sqlite3 *db,
-                    int (*callback)(void*,int,char**,char**), void *cookie,
-                    char **errmsg,
-                    const char *sql, ...)
-{
-  va_list ap;
-  int rc;
-  char *sql2;
-
-  va_start (ap, sql);
-  sql2 = sqlite3_vmprintf (sql, ap);
-  va_end (ap);
-
-#if 0
-  log_debug ("tofo db: executing: '%s'\n", sql2);
-#endif
-
-  rc = sqlite3_exec (db, sql2, callback, cookie, errmsg);
-
-  sqlite3_free (sql2);
-
-  return rc;
-}
-
-enum sqlite_arg_type
-  {
-    SQLITE_ARG_END = 0xdead001,
-    SQLITE_ARG_INT,
-    SQLITE_ARG_LONG_LONG,
-    SQLITE_ARG_STRING
-  };
-
-static int
-sqlite3_stepx (sqlite3 *db,
-               sqlite3_stmt **stmtp,
-               int (*callback) (void*,int,char**,char**),
-               void *cookie,
-               char **errmsg,
-               const char *sql, ...)
-{
-  int rc;
-  int err = 0;
-  sqlite3_stmt *stmt = NULL;
-
-  va_list va;
-  int args;
-  enum sqlite_arg_type t;
-  int i;
-
-  int cols;
-  /* Names of the columns.  We initialize this lazily to avoid the
-     overhead in case the query doesn't return any results.  */
-  const char **azColName = 0;
-  int callback_initialized = 0;
-
-  const char **azVals = 0;
-
-  callback_initialized = 0;
-
-  if (stmtp && *stmtp)
-    {
-      stmt = *stmtp;
-
-      /* Make sure this statement is associated with the supplied db.  */
-      assert (db == sqlite3_db_handle (stmt));
-
-#if DEBUG_TOFU_CACHE
-      prepares_saved ++;
-#endif
-    }
-  else
-    {
-      rc = sqlite3_prepare_v2 (db, sql, -1, &stmt, NULL);
-      if (rc)
-        log_fatal ("failed to prepare SQL: %s", sql);
-
-      if (stmtp)
-        *stmtp = stmt;
-    }
-
-#if DEBUG_TOFU_CACHE
-  queries ++;
-#endif
-
-  args = sqlite3_bind_parameter_count (stmt);
-  va_start (va, sql);
-  if (args)
-    {
-      for (i = 1; i <= args; i ++)
-        {
-          t = va_arg (va, enum sqlite_arg_type);
-          switch (t)
-            {
-            case SQLITE_ARG_INT:
-              {
-                int value = va_arg (va, int);
-                err = sqlite3_bind_int (stmt, i, value);
-                break;
-              }
-            case SQLITE_ARG_LONG_LONG:
-              {
-                long long value = va_arg (va, long long);
-                err = sqlite3_bind_int64 (stmt, i, value);
-                break;
-              }
-            case SQLITE_ARG_STRING:
-              {
-                char *text = va_arg (va, char *);
-                err = sqlite3_bind_text (stmt, i, text, -1, SQLITE_STATIC);
-                break;
-              }
-            default:
-              /* Internal error.  Likely corruption.  */
-              log_fatal ("Bad value for parameter type %d.\n", t);
-            }
-
-          if (err)
-            {
-              log_fatal ("Error binding parameter %d\n", i);
-              goto out;
-            }
-        }
-
-    }
-  t = va_arg (va, enum sqlite_arg_type);
-  assert (t == SQLITE_ARG_END);
-  va_end (va);
-
-  for (;;)
-    {
-      rc = sqlite3_step (stmt);
-
-      if (rc != SQLITE_ROW)
-        /* No more data (SQLITE_DONE) or an error occured.  */
-        break;
-
-      if (! callback)
-        continue;
-
-      if (! callback_initialized)
-        {
-          cols = sqlite3_column_count (stmt);
-          azColName = xmalloc (2 * cols * sizeof (const char *) + 1);
-
-          for (i = 0; i < cols; i ++)
-            azColName[i] = sqlite3_column_name (stmt, i);
-
-          callback_initialized = 1;
-        }
-
-      azVals = &azColName[cols];
-      for (i = 0; i < cols; i ++)
-        {
-          azVals[i] = sqlite3_column_text (stmt, i);
-          if (! azVals[i] && sqlite3_column_type (stmt, i) != SQLITE_NULL)
-            /* Out of memory.  */
-            {
-              err = SQLITE_NOMEM;
-              break;
-            }
-        }
-
-      if (callback (cookie, cols, (char **) azVals, (char **) azColName))
-        /* A non-zero result means to abort.  */
-        {
-          err = SQLITE_ABORT;
-          break;
-        }
-    }
-
- out:
-  xfree (azColName);
-
-  if (stmtp)
-    rc = sqlite3_reset (stmt);
-  else
-    rc = sqlite3_finalize (stmt);
-  if (rc == SQLITE_OK && err)
-    /* Local error.  */
-    {
-      rc = err;
-      if (errmsg)
-        {
-          const char *e = sqlite3_errstr (err);
-          size_t l = strlen (e) + 1;
-          *errmsg = sqlite3_malloc (l);
-          if (! *errmsg)
-            log_fatal ("Out of memory.\n");
-          memcpy (*errmsg, e, l);
-        }
-    }
-  else if (rc != SQLITE_OK && errmsg)
-    /* Error reported by sqlite.  */
-    {
-      const char * e = sqlite3_errmsg (db);
-      size_t l = strlen (e) + 1;
-      *errmsg = sqlite3_malloc (l);
-      if (! *errmsg)
-        log_fatal ("Out of memory.\n");
-      memcpy (*errmsg, e, l);
-    }
-
-  return rc;
-}
 \f
 static int batch_update;
 static time_t batch_update_started;
@@ -625,6 +418,14 @@ get_single_unsigned_long_cb (void *cookie, int argc, char **argv,
   return 0;
 }
 
+static int
+get_single_unsigned_long_cb2 (void *cookie, int argc, char **argv,
+                            char **azColName, sqlite3_stmt *stmt)
+{
+  (void) stmt;
+  return get_single_unsigned_long_cb (cookie, argc, argv, azColName);
+}
+
 /* We expect a single integer column whose name is "version".  COOKIE
    must point to an int.  This function always aborts.  On error or a
    if the version is bad, sets *VERSION to -1.  */
@@ -772,7 +573,7 @@ initdb (sqlite3 *db, enum db_type type)
          latter binding, we warn the user about the conflict and ask
          for a policy decision about the new binding.  We also change
          the old binding's policy to ask if it was auto.  So that we
-         know why this occured, we also set conflict to 0xbaddecaf.
+         know why this occurred, we also set conflict to 0xbaddecaf.
   */
   if (type == DB_EMAIL || type == DB_COMBINED)
     rc = sqlite3_exec_printf
@@ -1147,7 +948,8 @@ opendbs (void)
 
       if (have_tofu_db && have_tofu_d)
        {
-         log_info (_("Warning: Home directory contains both tofu.db and tofu.d.  Using split format for TOFU DB.\n"));
+         log_info (_("Warning: Home directory contains both tofu.db"
+                      " and tofu.d.  Using split format for TOFU DB.\n"));
          opt.tofu_db_format = TOFU_DB_SPLIT;
        }
       else if (have_tofu_db)
@@ -1164,9 +966,9 @@ opendbs (void)
        }
       else
        {
-         opt.tofu_db_format = TOFU_DB_SPLIT;
+         opt.tofu_db_format = TOFU_DB_FLAT;
          if (DBG_TRUST)
-           log_debug ("Using split format for TOFU DB.\n");
+           log_debug ("Using flat format for TOFU DB.\n");
        }
     }
 
@@ -1257,6 +1059,13 @@ get_single_long_cb (void *cookie, int argc, char **argv, char **azColName)
   return 0;
 }
 
+static int
+get_single_long_cb2 (void *cookie, int argc, char **argv, char **azColName,
+                     sqlite3_stmt *stmt)
+{
+  (void) stmt;
+  return get_single_long_cb (cookie, argc, argv, azColName);
+}
 
 /* Record (or update) a trust policy about a (possibly new)
    binding.
@@ -1266,10 +1075,14 @@ static gpg_error_t
 record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
                const char *user_id, enum tofu_policy policy, int show_old)
 {
+  char *fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
   struct db *db_email = NULL, *db_key = NULL;
   int rc;
   char *err = NULL;
-  enum tofu_policy policy_old = TOFU_POLICY_NONE;
+  /* policy_old needs to be a long and not an enum tofu_policy,
+     because we pass it by reference to get_single_long_cb2, which
+     expects a long.  */
+  long policy_old = TOFU_POLICY_NONE;
 
   if (! (policy == TOFU_POLICY_AUTO
         || policy == TOFU_POLICY_GOOD
@@ -1316,7 +1129,7 @@ record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
     {
       rc = sqlite3_stepx
        (db_email->db, &db_email->s.record_binding_get_old_policy,
-         get_single_long_cb, &policy_old, &err,
+         get_single_long_cb2, &policy_old, &err,
         "select policy from bindings where fingerprint = ? and email = ?",
         SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
          SQLITE_ARG_END);
@@ -1324,7 +1137,7 @@ record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
        {
          log_debug ("TOFU: Error reading from binding database"
                     " (reading policy for <%s, %s>): %s\n",
-                    fingerprint, email, err);
+                    fingerprint_pp, email, err);
          sqlite3_free (err);
        }
     }
@@ -1334,12 +1147,12 @@ record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
       if (policy_old != TOFU_POLICY_NONE)
        log_debug ("Changing TOFU trust policy for binding <%s, %s>"
                   " from %s to %s.\n",
-                  fingerprint, email,
+                  fingerprint_pp, email,
                   tofu_policy_str (policy_old),
                   tofu_policy_str (policy));
       else
        log_debug ("Set TOFU trust policy for binding <%s, %s> to %s.\n",
-                  fingerprint, email,
+                  fingerprint_pp, email,
                   tofu_policy_str (policy));
     }
 
@@ -1365,7 +1178,7 @@ record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
     {
       log_error (_("error updating TOFU binding database"
                   " (inserting <%s, %s> = %s): %s\n"),
-                fingerprint, email, tofu_policy_str (policy),
+                fingerprint_pp, email, tofu_policy_str (policy),
                 err);
       sqlite3_free (err);
       goto out;
@@ -1393,7 +1206,7 @@ record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
        {
          log_error (_("error updating TOFU binding database"
                       " (inserting <%s, %s>): %s\n"),
-                    fingerprint, email, err);
+                    fingerprint_pp, email, err);
          sqlite3_free (err);
          goto out;
        }
@@ -1431,6 +1244,8 @@ record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
        }
     }
 
+  xfree (fingerprint_pp);
+
   if (rc)
     return gpg_error (GPG_ERR_GENERAL);
   return 0;
@@ -1468,6 +1283,15 @@ strings_collect_cb (void *cookie, int argc, char **argv, char **azColName)
   return 0;
 }
 
+static int
+strings_collect_cb2 (void *cookie, int argc, char **argv, char **azColName,
+                     sqlite3_stmt *stmt)
+{
+  (void) stmt;
+  return strings_collect_cb (cookie, argc, argv, azColName);
+
+}
+
 /* Auxiliary data structure to collect statistics about
    signatures.  */
 struct signature_stats
@@ -1523,7 +1347,7 @@ signature_stats_prepend (struct signature_stats **statsp,
      <fingerprint, policy, time ago, count>.  */
 static int
 signature_stats_collect_cb (void *cookie, int argc, char **argv,
-                           char **azColName)
+                           char **azColName, sqlite3_stmt *stmt)
 {
   struct signature_stats **statsp = cookie;
   char *tail;
@@ -1533,6 +1357,7 @@ signature_stats_collect_cb (void *cookie, int argc, char **argv,
   unsigned long count;
 
   (void) azColName;
+  (void) stmt;
 
   i ++;
 
@@ -1654,7 +1479,7 @@ get_policy (struct dbs *dbs, const char *fingerprint, const char *email,
      still TOFU_POLICY_NONE after executing the query, then the
      result set was empty.)  */
   rc = sqlite3_stepx (db->db, &db->s.get_policy_select_policy_and_conflict,
-                      strings_collect_cb, &strlist, &err,
+                      strings_collect_cb2, &strlist, &err,
                       "select policy, conflict from bindings\n"
                       " where fingerprint = ? and email = ?",
                       SQLITE_ARG_STRING, fingerprint,
@@ -1750,6 +1575,7 @@ static enum tofu_policy
 get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
           const char *user_id, int may_ask)
 {
+  char *fingerprint_pp;
   struct db *db;
   enum tofu_policy policy;
   char *conflict = NULL;
@@ -1777,49 +1603,56 @@ get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
   if (! db)
     return _tofu_GET_TRUST_ERROR;
 
+  fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
+
   policy = get_policy (dbs, fingerprint, email, &conflict);
   if (policy == TOFU_POLICY_AUTO || policy == TOFU_POLICY_NONE)
     /* See if the key is ultimately trusted.  If so, we're done.  */
     {
-      int i, j;
-      char keyid[17];
-      KEYDB_SEARCH_DESC desc;
-
-      /* We need to convert the fingerprint as a string to a long
-         keyid.
-
-         FINGERPRINT has the form:
+      PKT_public_key *pk;
+      u32 kid[2];
+      char fpr_bin[MAX_FINGERPRINT_LEN+1];
+      size_t fpr_bin_len;
 
-           362D 3527 F53A AD19 71AA  FDE6 5885 9975 EE37 CF96
-                                          -------------------
-
-         The last 16 characters are the long keyid.
-      */
-      assert (strlen (fingerprint) > 4 * 4 + 3);
-      for (i = strlen (fingerprint) - (4 * 4 + 3), j = 0; j < 16; i ++, j ++)
+      if (!hex2str (fingerprint, fpr_bin, sizeof fpr_bin, &fpr_bin_len))
         {
-          if (fingerprint[i] == ' ')
-            i ++;
-          keyid[j] = fingerprint[i];
+          log_error ("error converting fingerprint: %s\n",
+                     gpg_strerror (gpg_error_from_syserror ()));
+          return _tofu_GET_TRUST_ERROR;
         }
-      keyid[j] = 0;
 
-      rc = classify_user_id (keyid, &desc, 1);
-      if (rc || desc.mode != KEYDB_SEARCH_MODE_LONG_KID)
+      /* We need to lookup the key by fingerprint again so that we can
+         properly extract the keyid.  Extracting direct from the
+         fingerprint works only for v4 keys and would assume that
+         there is no collision in the low 64 bit.  We can't guarantee
+         the latter in case the Tofu DB is used with a different
+         keyring.  In any case the UTK stuff needs to be changed to
+         use only fingerprints.  */
+      pk = xtrycalloc (1, sizeof *pk);
+      if (!pk)
+         {
+           log_error (_("out of core\n"));
+           return _tofu_GET_TRUST_ERROR;
+         }
+      rc = get_pubkey_byfprint_fast (pk, fpr_bin, fpr_bin_len);
+      if (rc)
         {
-          log_error (_("'%s' is not a valid long keyID\n"), keyid);
+          log_error (_("public key %s not found: %s\n"),
+                     fingerprint, gpg_strerror (rc));
           return _tofu_GET_TRUST_ERROR;
         }
+      keyid_from_pk (pk, kid);
+      free_public_key (pk);
 
-      if (tdb_keyid_is_utk (desc.u.kid))
+      if (tdb_keyid_is_utk (kid))
         {
           if (policy == TOFU_POLICY_NONE)
             {
               if (record_binding (dbs, fingerprint, email, user_id,
                                   TOFU_POLICY_AUTO, 0) != 0)
                 {
-                  log_error (_("error setting TOFU binding's trust level to %s\n"),
-                             "auto");
+                  log_error (_("error setting TOFU binding's trust level"
+                               " to %s\n"), "auto");
                   trust_level = _tofu_GET_TRUST_ERROR;
                   goto out;
                 }
@@ -1835,7 +1668,7 @@ get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
       policy = opt.tofu_default_policy;
       if (DBG_TRUST)
        log_debug ("TOFU: binding <%s, %s>'s policy is auto (default: %s).\n",
-                  fingerprint, email,
+                  fingerprint_pp, email,
                   tofu_policy_str (opt.tofu_default_policy));
     }
   switch (policy)
@@ -1848,7 +1681,7 @@ get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
         We don't need to ask the user anything.  */
       if (DBG_TRUST)
        log_debug ("TOFU: Known binding <%s, %s>'s policy: %s\n",
-                  fingerprint, email, tofu_policy_str (policy));
+                  fingerprint_pp, email, tofu_policy_str (policy));
       trust_level = tofu_policy_to_trust_level (policy);
       goto out;
 
@@ -1899,7 +1732,7 @@ get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
      a new binding.  */
   rc = sqlite3_stepx
     (db->db, &db->s.get_trust_bindings_with_this_email,
-     strings_collect_cb, &bindings_with_this_email, &err,
+     strings_collect_cb2, &bindings_with_this_email, &err,
      "select distinct fingerprint from bindings where email = ?;",
      SQLITE_ARG_STRING, email, SQLITE_ARG_END);
   if (rc)
@@ -1927,7 +1760,7 @@ get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
 
       if (DBG_TRUST)
        log_debug ("TOFU: New binding <%s, %s>, no conflict.\n",
-                  email, fingerprint);
+                  email, fingerprint_pp);
 
       if (record_binding (dbs, fingerprint, email, user_id,
                          TOFU_POLICY_AUTO, 0) != 0)
@@ -1996,7 +1829,7 @@ get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
     if (! fp)
       log_fatal ("Error creating memory stream\n");
 
-    binding = xasprintf ("<%s, %s>", fingerprint, email);
+    binding = xasprintf ("<%s, %s>", fingerprint_pp, email);
     binding_shown = 0;
 
     if (policy == TOFU_POLICY_NONE)
@@ -2009,11 +1842,13 @@ get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
                display this message.  */
             && conflict && strcmp (conflict, fingerprint) != 0)
       {
+        char *conflict_pp = format_hexfingerprint (conflict, NULL, 0);
        es_fprintf (fp,
                    _("The key %s raised a conflict with this binding (%s)."
-                      "  Since this binding's policy was 'auto', it was"
+                      "  Since this binding's policy was 'auto', it was "
                       "changed to 'ask'.  "),
-                   conflict, binding);
+                   conflict_pp, binding);
+        xfree (conflict_pp);
        binding_shown = 1;
       }
     es_fprintf (fp,
@@ -2042,7 +1877,7 @@ get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
        {
          rc = sqlite3_stepx
            (db_key->db, &db_key->s.get_trust_gather_other_user_ids,
-             strings_collect_cb, &other_user_ids, &err,
+             strings_collect_cb2, &other_user_ids, &err,
              opt.tofu_db_format == TOFU_DB_SPLIT
             ? "select user_id, email from bindings where fingerprint = ?;"
             : "select user_id, policy from bindings where fingerprint = ?;",
@@ -2161,13 +1996,16 @@ get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
            if (! key || strcmp (key, stats_iter->fingerprint) != 0)
              {
                int this_key;
+                char *key_pp;
                key = stats_iter->fingerprint;
                this_key = strcmp (key, fingerprint) == 0;
+                key_pp = format_hexfingerprint (key, NULL, 0);
                if (this_key)
-                 es_fprintf (fp, _("  %s (this key):"), key);
+                 es_fprintf (fp, _("  %s (this key):"), key_pp);
                else
                  es_fprintf (fp, _("  %s (policy: %s):"),
-                             key, tofu_policy_str (stats_iter->policy));
+                             key_pp, tofu_policy_str (stats_iter->policy));
+                xfree (key_pp);
                es_fprintf (fp, "\n");
              }
 
@@ -2200,10 +2038,12 @@ get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
            "Normally, there is only a single key associated with an email "
            "address.  However, people sometimes generate a new key if "
            "their key is too old or they think it might be compromised.  "
-           "Alternatively, a new key may indicate a man-in-the-middle"
+           "Alternatively, a new key may indicate a man-in-the-middle "
            "attack!  Before accepting this key, you should talk to or "
            "call the person to make sure this new key is legitimate.";
+        text = format_text (text, 0, 72, 80);
        es_fprintf (fp, "\n%s\n", text);
+        xfree (text);
       }
 
     es_fputc ('\n', fp);
@@ -2310,16 +2150,165 @@ get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
 
   xfree (conflict);
   free_strlist (bindings_with_this_email);
+  xfree (fingerprint_pp);
 
   return trust_level;
 }
 
+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 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 (years > 1)
+        es_fprintf (fp, _("%d years"), years);
+      else
+        es_fprintf (fp, _("%d year"), years);
+      count ++;
+      first = i;
+    }
+  i ++;
+  if ((first == -1 || i - first <= 3) && months)
+    {
+      if (count)
+        es_fprintf (fp, ", ");
+
+      if (months > 1)
+        es_fprintf (fp, _("%d months"), months);
+      else
+        es_fprintf (fp, _("%d month"), months);
+      count ++;
+      first = i;
+    }
+  i ++;
+  if ((first == -1 || i - first <= 3) && count < 2 && days)
+    {
+      if (count)
+        es_fprintf (fp, ", ");
+
+      if (days > 1)
+        es_fprintf (fp, _("%d days"), days);
+      else
+        es_fprintf (fp, _("%d day"), days);
+      count ++;
+      first = i;
+    }
+  i ++;
+  if ((first == -1 || i - first <= 3) && count < 2 && hours)
+    {
+      if (count)
+        es_fprintf (fp, ", ");
+
+      if (hours > 1)
+        es_fprintf (fp, _("%d hours"), hours);
+      else
+        es_fprintf (fp, _("%d hour"), hours);
+      count ++;
+      first = i;
+    }
+  i ++;
+  if ((first == -1 || i - first <= 3) && count < 2 && minutes)
+    {
+      if (count)
+        es_fprintf (fp, ", ");
+
+      if (minutes > 1)
+        es_fprintf (fp, _("%d minutes"), minutes);
+      else
+        es_fprintf (fp, _("%d minute"), minutes);
+      count ++;
+      first = i;
+    }
+  i ++;
+  if ((first == -1 || i - first <= 3) && count < 2)
+    {
+      if (count)
+        es_fprintf (fp, ", ");
+
+      if (seconds > 1)
+        es_fprintf (fp, _("%d seconds"), seconds);
+      else
+        es_fprintf (fp, _("%d second"), seconds);
+    }
+
+  es_fputc (0, fp);
+  if (es_fclose_snatch (fp, (void **) &str, NULL))
+    log_fatal ("error snatching memory stream\n");
+
+  return str;
+}
+
 static void
 show_statistics (struct dbs *dbs, const char *fingerprint,
                 const char *email, const char *user_id,
                 const char *sig_exclude)
 {
   struct db *db;
+  char *fingerprint_pp;
   int rc;
   strlist_t strlist = NULL;
   char *err = NULL;
@@ -2328,9 +2317,12 @@ show_statistics (struct dbs *dbs, const char *fingerprint,
   if (! db)
     return;
 
+  fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
+
   rc = sqlite3_exec_printf
     (db->db, strings_collect_cb, &strlist, &err,
-     "select count (*), strftime('%%s','now') - min (signatures.time)\n"
+     "select count (*), strftime('%%s','now') - min (signatures.time),\n"
+     "  strftime('%%s','now') - max (signatures.time)\n"
      " from signatures\n"
      " left join bindings on signatures.binding = bindings.oid\n"
      " where fingerprint = %Q and email = %Q and sig_digest %s%s%s;",
@@ -2351,14 +2343,15 @@ show_statistics (struct dbs *dbs, const char *fingerprint,
 
   if (! strlist)
     log_info (_("Have never verified a message signed by key %s!\n"),
-             fingerprint);
+              fingerprint_pp);
   else
     {
       char *tail = NULL;
       signed long messages;
       signed long first_seen_ago;
+      signed long most_recent_seen_ago;
 
-      assert (strlist_length (strlist) == 2);
+      assert (strlist_length (strlist) == 3);
 
       errno = 0;
       messages = strtol (strlist->d, &tail, 0);
@@ -2372,7 +2365,10 @@ show_statistics (struct dbs *dbs, const char *fingerprint,
 
       if (messages == 0 && *strlist->next->d == '\0')
        /* min(NULL) => NULL => "".  */
-       first_seen_ago = -1;
+        {
+          first_seen_ago = -1;
+          most_recent_seen_ago = -1;
+        }
       else
        {
          errno = 0;
@@ -2380,16 +2376,28 @@ show_statistics (struct dbs *dbs, const char *fingerprint,
          if (errno || *tail != '\0')
            /* Abort.  */
            {
-             log_debug ("%s:%d: Cound't convert %s (first_seen) to an int: %s.\n",
+             log_debug ("%s:%d: Couldn't convert %s (first_seen) to an int: %s.\n",
                         __func__, __LINE__,
                         strlist->next->d, strerror (errno));
              first_seen_ago = 0;
            }
+
+         errno = 0;
+         most_recent_seen_ago = strtol (strlist->next->next->d, &tail, 0);
+         if (errno || *tail != '\0')
+           /* Abort.  */
+           {
+             log_debug ("%s:%d: Couldn't convert %s (most_recent_seen) to an int: %s.\n",
+                        __func__, __LINE__,
+                        strlist->next->next->d, strerror (errno));
+             most_recent_seen_ago = 0;
+           }
        }
 
       if (messages == -1 || first_seen_ago == 0)
-       log_info (_("Failed to collect signature statistics for \"%s\" (key %s)\n"),
-                 user_id, fingerprint);
+        log_info (_("Failed to collect signature statistics"
+                    " for \"%s\" (key %s)\n"),
+                  user_id, fingerprint_pp);
       else
        {
          enum tofu_policy policy = get_policy (dbs, fingerprint, email, NULL);
@@ -2401,165 +2409,51 @@ show_statistics (struct dbs *dbs, const char *fingerprint,
            log_fatal ("error creating memory stream\n");
 
          if (messages == 0)
-           es_fprintf (fp,
-                       _("Verified 0 messages signed by \"%s\""
-                         " (key: %s, policy %s)."),
-                       user_id, fingerprint, tofu_policy_str (policy));
+            es_fprintf (fp,
+                        _("Verified 0 messages signed by \"%s\""
+                          " (key: %s, policy %s)."),
+                        user_id, fingerprint_pp, tofu_policy_str (policy));
          else
            {
-             int years = 0;
-             int months = 0;
-             int days = 0;
-             int hours = 0;
-             int minutes = 0;
-             int seconds = 0;
-
-             /* The number of units that we've printed so far.  */
-             int count = 0;
-             /* The first unit that we printed (year = 0, month = 1,
-                etc.).  */
-             int first = -1;
-             /* The current unit.  */
-             int i = 0;
+              char *first_seen_ago_str = time_ago_str (first_seen_ago);
+              char *most_recent_seen_ago_str =
+                time_ago_str (most_recent_seen_ago);
 
              es_fprintf (fp,
                          _("Verified %ld messages signed by \"%s\""
-                           " (key: %s, policy: %s) in the past "),
+                           " (key: %s, policy: %s) in the past %s."),
                          messages, user_id,
-                         fingerprint, tofu_policy_str (policy));
+                         fingerprint_pp, tofu_policy_str (policy),
+                          first_seen_ago_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 MONTH_SECS (30 * DAY_SECS)
-#define YEAR_SECS (365 * DAY_SECS)
+              if (messages > 1)
+                es_fprintf (fp,
+                            _("  The most recent message was verified %s ago."),
+                            most_recent_seen_ago_str);
 
-             if (first_seen_ago > YEAR_SECS)
-               {
-                 years = first_seen_ago / YEAR_SECS;
-                 first_seen_ago -= years * YEAR_SECS;
-               }
-             if (first_seen_ago > MONTH_SECS)
-               {
-                 months = first_seen_ago / MONTH_SECS;
-                 first_seen_ago -= months * MONTH_SECS;
-               }
-             if (first_seen_ago > DAY_SECS)
-               {
-                 days = first_seen_ago / DAY_SECS;
-                 first_seen_ago -= days * DAY_SECS;
-               }
-             if (first_seen_ago > HOUR_SECS)
-               {
-                 hours = first_seen_ago / HOUR_SECS;
-                 first_seen_ago -= hours * HOUR_SECS;
-               }
-             if (first_seen_ago > MIN_SECS)
-               {
-                 minutes = first_seen_ago / MIN_SECS;
-                 first_seen_ago -= minutes * MIN_SECS;
-               }
-             seconds = first_seen_ago;
-
-#undef MIN_SECS
-#undef HOUR_SECS
-#undef DAY_SECS
-#undef MONTH_SECS
-#undef YEAR_SECS
-
-             if (years)
-               {
-                 if (years > 1)
-                   es_fprintf (fp, _("%d years"), years);
-                 else
-                   es_fprintf (fp, _("%d year"), years);
-                 count ++;
-                 first = i;
-               }
-             i ++;
-             if ((first == -1 || i - first <= 3) && months)
-               {
-                 if (count)
-                   es_fprintf (fp, _(", "));
-
-                 if (months > 1)
-                   es_fprintf (fp, _("%d months"), months);
-                 else
-                   es_fprintf (fp, _("%d month"), months);
-                 count ++;
-                 first = i;
-               }
-             i ++;
-             if ((first == -1 || i - first <= 3) && count < 2 && days)
-               {
-                 if (count)
-                   es_fprintf (fp, _(", "));
-
-                 if (days > 1)
-                   es_fprintf (fp, _("%d days"), days);
-                 else
-                   es_fprintf (fp, _("%d day"), days);
-                 count ++;
-                 first = i;
-               }
-             i ++;
-             if ((first == -1 || i - first <= 3) && count < 2 && hours)
-               {
-                 if (count)
-                   es_fprintf (fp, _(", "));
-
-                 if (hours > 1)
-                   es_fprintf (fp, _("%d hours"), hours);
-                 else
-                   es_fprintf (fp, _("%d hour"), hours);
-                 count ++;
-                 first = i;
-               }
-             i ++;
-             if ((first == -1 || i - first <= 3) && count < 2 && minutes)
-               {
-                 if (count)
-                   es_fprintf (fp, _(", "));
-
-                 if (minutes > 1)
-                   es_fprintf (fp, _("%d minutes"), minutes);
-                 else
-                   es_fprintf (fp, _("%d minute"), minutes);
-                 count ++;
-                 first = i;
-               }
-             i ++;
-             if ((first == -1 || i - first <= 3) && count < 2)
-               {
-                 if (count)
-                   es_fprintf (fp, _(", "));
-
-                 if (seconds > 1)
-                   es_fprintf (fp, _("%d seconds"), seconds);
-                 else
-                   es_fprintf (fp, _("%d second"), seconds);
-               }
-
-             es_fprintf (fp, _("."));
-           }
+              xfree (first_seen_ago_str);
+              xfree (most_recent_seen_ago_str);
+            }
 
          es_fputc (0, fp);
          if (es_fclose_snatch (fp, (void **) &msg, NULL))
            log_fatal ("error snatching memory stream\n");
 
          log_info ("%s\n", msg);
+          xfree (msg);
 
          if (policy == TOFU_POLICY_AUTO && messages < 10)
            {
              char *set_policy_command;
-             const char *text;
+             char *text;
+              char *tmp;
 
              if (messages == 0)
-               log_info (_("Warning: we've have yet to see a message signed by this key!\n"));
+               log_info (_("Warning: we've have yet to see"
+                            " a message signed by this key!\n"));
              else if (messages == 1)
-               log_info (_("Warning: we've only seen a single message signed by this key!\n"));
+               log_info (_("Warning: we've only seen a"
+                            " single message signed by this key!\n"));
 
              set_policy_command =
                xasprintf ("gpg --tofu-policy bad \"%s\"", fingerprint);
@@ -2574,9 +2468,14 @@ show_statistics (struct dbs *dbs, const char *fingerprint,
                  "Carefully examine the email address for small variations "
                  "(e.g., additional white space).  If the key is suspect, "
                  "then use '%s' to mark it as being bad.\n";
-             log_info (text,
-                       messages, messages == 1 ? _("message") : _("message"),
-                       set_policy_command);
+              tmp = xasprintf
+                (text,
+                 messages, messages == 1 ? _("message") : _("message"),
+                 set_policy_command);
+              text = format_text (tmp, 0, 72, 80);
+              xfree (tmp);
+             log_info ("%s", text);
+              xfree (text);
              free (set_policy_command);
            }
        }
@@ -2584,6 +2483,7 @@ show_statistics (struct dbs *dbs, const char *fingerprint,
 
  out:
   free_strlist (strlist);
+  xfree (fingerprint_pp);
 
   return;
 }
@@ -2596,52 +2496,18 @@ email_from_user_id (const char *user_id)
 {
   char *email = mailbox_from_userid (user_id);
   if (! email)
-    /* Hmm, no email address was provided.  Just take the lower-case
-       version of the whole user id.  It could be a hostname, for
-       instance.  */
-    email = ascii_strlwr (xstrdup (user_id));
-
-  return email;
-}
-
-/* Pretty print a MAX_FINGERPRINT_LEN-byte binary fingerprint into a
-   malloc'd string.  */
-static char *
-fingerprint_pp (const byte *fingerprint_bin)
-{
-  char fingerprint[MAX_FINGERPRINT_LEN * 2 + 1];
-  char *fingerprint_pretty;
-  int space = (/* The characters and the NUL.  */
-              sizeof (fingerprint)
-              /* After every fourth character, we add a space (except
-                 the last).  */
-              + (sizeof (fingerprint) - 1) / 4 - 1
-              /* Half way through we add a second space.  */
-              + 1);
-  int i;
-  int j;
-
-  bin2hex (fingerprint_bin, MAX_FINGERPRINT_LEN, fingerprint);
-
-  fingerprint_pretty = xmalloc (space);
-
-  for (i = 0, j = 0; i < MAX_FINGERPRINT_LEN * 2; i ++)
     {
-      if (i && i % 4 == 0)
-       fingerprint_pretty[j ++] = ' ';
-      if (i == MAX_FINGERPRINT_LEN * 2 / 2)
-       fingerprint_pretty[j ++] = ' ';
-
-      fingerprint_pretty[j ++] = fingerprint[i];
+      /* Hmm, no email address was provided or we are out of core.  Just
+         take the lower-case version of the whole user id.  It could be
+         a hostname, for instance.  */
+      email = ascii_strlwr (xstrdup (user_id));
     }
-  fingerprint_pretty[j ++] = 0;
-  assert (j == space);
 
-  return fingerprint_pretty;
+  return email;
 }
 
-/* Register the signature with the binding <FINGERPRINT_BIN, USER_ID>.
-   FINGERPRINT must be MAX_FINGERPRINT_LEN bytes long.
+/* Register the signature with the binding <fingerprint, USER_ID>.
+   The fingerprint is taken from the primary key packet PK.
 
    SIG_DIGEST_BIN is the binary representation of the message's
    digest.  SIG_DIGEST_BIN_LEN is its length.
@@ -2660,13 +2526,14 @@ fingerprint_pp (const byte *fingerprint_bin)
    This function returns the binding's trust level on return.  If an
    error occurs, this function returns TRUST_UNKNOWN.  */
 int
-tofu_register (const byte *fingerprint_bin, const char *user_id,
+tofu_register (PKT_public_key *pk, const char *user_id,
               const byte *sig_digest_bin, int sig_digest_bin_len,
               time_t sig_time, const char *origin, int may_ask)
 {
   struct dbs *dbs;
   struct db *db;
   char *fingerprint = NULL;
+  char *fingerprint_pp = NULL;
   char *email = NULL;
   char *err = NULL;
   int rc;
@@ -2684,7 +2551,8 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
       goto die;
     }
 
-  fingerprint = fingerprint_pp (fingerprint_bin);
+  fingerprint = hexfingerprint (pk, NULL, 0);
+  fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
 
   if (! *user_id)
     {
@@ -2726,7 +2594,7 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
      it again.  */
   rc = sqlite3_stepx
     (db->db, &db->s.register_already_seen,
-     get_single_unsigned_long_cb, &c, &err,
+     get_single_unsigned_long_cb2, &c, &err,
      "select count (*)\n"
      " from signatures left join bindings\n"
      "  on signatures.binding = bindings.oid\n"
@@ -2750,7 +2618,7 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
     log_debug ("SIGNATURES DB contains duplicate records"
               " <key: %s, %s, time: 0x%lx, sig: %s, %s>."
               "  Please report.\n",
-              fingerprint, email, (unsigned long) sig_time,
+              fingerprint_pp, email, (unsigned long) sig_time,
               sig_digest, origin);
   else if (c == 1)
     {
@@ -2758,7 +2626,7 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
       if (DBG_TRUST)
        log_debug ("Already observed the signature"
                   " <key: %s, %s, time: 0x%lx, sig: %s, %s>\n",
-                  fingerprint, email, (unsigned long) sig_time,
+                  fingerprint_pp, email, (unsigned long) sig_time,
                   sig_digest, origin);
     }
   else
@@ -2767,7 +2635,7 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
     {
       if (DBG_TRUST)
        log_debug ("TOFU: Saving signature <%s, %s, %s>\n",
-                  fingerprint, email, sig_digest);
+                  fingerprint_pp, email, sig_digest);
 
       assert (c == 0);
 
@@ -2813,6 +2681,7 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
                     already_verified ? NULL : sig_digest);
 
   xfree (email);
+  xfree (fingerprint_pp);
   xfree (fingerprint);
   if (dbs)
     closedbs (dbs);
@@ -2874,7 +2743,7 @@ tofu_wot_trust_combine (int tofu_base, int wot_base)
 /* Return the validity (TRUST_NEVER, etc.) of the binding
    <FINGERPRINT, USER_ID>.
 
-   FINGERPRINT must be a MAX_FINGERPRINT_LEN-byte fingerprint.
+   PK is the primary key packet.
 
    If MAY_ASK is 1 and the policy is TOFU_POLICY_ASK, then the user
    will be prompted to choose a different policy.  If MAY_ASK is 0 and
@@ -2882,7 +2751,7 @@ tofu_wot_trust_combine (int tofu_base, int wot_base)
 
    Returns TRUST_UNDEFINED if an error occurs.  */
 int
-tofu_get_validity (const byte *fingerprint_bin, const char *user_id,
+tofu_get_validity (PKT_public_key *pk, const char *user_id,
                   int may_ask)
 {
   struct dbs *dbs;
@@ -2897,11 +2766,12 @@ tofu_get_validity (const byte *fingerprint_bin, const char *user_id,
       goto die;
     }
 
-  fingerprint = fingerprint_pp (fingerprint_bin);
+  fingerprint = hexfingerprint (pk, NULL, 0);
 
   if (! *user_id)
     {
-      log_debug ("user id is empty.  Can't get TOFU validity for this binding.\n");
+      log_debug ("user id is empty."
+                 "  Can't get TOFU validity for this binding.\n");
       goto die;
     }
 
@@ -2936,8 +2806,6 @@ tofu_set_policy (kbnode_t kb, enum tofu_policy policy)
 {
   struct dbs *dbs;
   PKT_public_key *pk;
-  char fingerprint_bin[MAX_FINGERPRINT_LEN];
-  size_t fingerprint_bin_len = sizeof (fingerprint_bin);
   char *fingerprint = NULL;
 
   assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
@@ -2957,10 +2825,7 @@ tofu_set_policy (kbnode_t kb, enum tofu_policy policy)
         && pk->main_keyid[1] == pk->keyid[1]))
     log_bug ("%s: Passed a subkey, but expecting a primary key.\n", __func__);
 
-  fingerprint_from_pk (pk, fingerprint_bin, &fingerprint_bin_len);
-  assert (fingerprint_bin_len == sizeof (fingerprint_bin));
-
-  fingerprint = fingerprint_pp (fingerprint_bin);
+  fingerprint = hexfingerprint (pk, NULL, 0);
 
   for (; kb; kb = kb->next)
     {
@@ -3018,8 +2883,6 @@ tofu_get_policy (PKT_public_key *pk, PKT_user_id *user_id,
                 enum tofu_policy *policy)
 {
   struct dbs *dbs;
-  char fingerprint_bin[MAX_FINGERPRINT_LEN];
-  size_t fingerprint_bin_len = sizeof (fingerprint_bin);
   char *fingerprint;
   char *email;
 
@@ -3034,10 +2897,7 @@ tofu_get_policy (PKT_public_key *pk, PKT_user_id *user_id,
       return gpg_error (GPG_ERR_GENERAL);
     }
 
-  fingerprint_from_pk (pk, fingerprint_bin, &fingerprint_bin_len);
-  assert (fingerprint_bin_len == sizeof (fingerprint_bin));
-
-  fingerprint = fingerprint_pp (fingerprint_bin);
+  fingerprint = hexfingerprint (pk, NULL, 0);
 
   email = email_from_user_id (user_id->name);