[git] GnuPG - branch, neal/next, updated. gnupg-2.1.8-51-gf97d39d

by Neal H. Walfield cvs at cvs.gnupg.org
Tue Sep 29 11:40:10 CEST 2015


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "The GNU Privacy Guard".

The branch, neal/next has been updated
  discards  e806e4dc85a7f5b3d7928853037280b84246c2d0 (commit)
       via  f97d39dc5d2a3e420f67e2930fbd6ce504d936bd (commit)
       via  8ab63e4b5018044ecfb0b9910412487066886826 (commit)
       via  12443eafa6e19b94a8b554126423e2a5ccc2dd7e (commit)
       via  5c067d54d349fdfb3243634789c8841515d2c28f (commit)
       via  cfbe6ba9cf1414e9aa4977e2bbaecaa43154b2ae (commit)
       via  f1effdc5ecd0cc52a28db7ae28a5c28f33486542 (commit)
       via  348acbe18adc895ee9d6b7a501969ea378d0a2bf (commit)
       via  83811e3f1f0c615b2b63bafdb49a35a0fc198088 (commit)
       via  6bb7206e357acfd7276a8f1334c0f0c9cc6ed100 (commit)

This update added new revisions after undoing existing revisions.  That is
to say, the old revision is not a strict subset of the new revision.  This
situation occurs when you --force push a change and generate a repository
containing something like this:

 * -- * -- B -- O -- O -- O (e806e4dc85a7f5b3d7928853037280b84246c2d0)
            \
             N -- N -- N (f97d39dc5d2a3e420f67e2930fbd6ce504d936bd)

When this happens we assume that you've already had alert emails for all
of the O revisions, and so we here report only the revisions in the N
branch from the common base, B.

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit f97d39dc5d2a3e420f67e2930fbd6ce504d936bd
Author: Neal H. Walfield <neal at g10code.com>
Date:   Sat Sep 19 01:32:51 2015 +0200

    TOFU stuff.

diff --git a/configure.ac b/configure.ac
index 27fc9a9..bee69ec 100644
--- a/configure.ac
+++ b/configure.ac
@@ -780,6 +780,12 @@ DL_LIBS=$LIBS
 AC_SUBST(DL_LIBS)
 LIBS="$gnupg_dlopen_save_libs"
 
+# Checks for g10
+
+PKG_CHECK_MODULES(SQLITE3, sqlite3)
+AC_SUBST(SQLITE3_CFLAGS)
+AC_SUBST(SQLITE3_LIBS)
+
 # Checks for g13
 
 AC_PATH_PROG(ENCFS, encfs, /usr/bin/encfs)
diff --git a/g10/Makefile.am b/g10/Makefile.am
index 2fd52b3..cf5a19f 100644
--- a/g10/Makefile.am
+++ b/g10/Makefile.am
@@ -25,7 +25,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/common
 
 include $(top_srcdir)/am/cmacros.am
 
-AM_CFLAGS = $(LIBGCRYPT_CFLAGS) \
+AM_CFLAGS = $(SQLITE3_CFLAGS) $(LIBGCRYPT_CFLAGS) \
             $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS)
 
 needed_libs = ../kbx/libkeybox.a $(libcommon)
@@ -125,7 +125,8 @@ gpg2_SOURCES  = gpg.c		\
 	      call-agent.c call-agent.h \
 	      trust.c $(trust_source) \
 	      $(card_source) \
-	      exec.c exec.h
+	      exec.c exec.h \
+	      tofu.h tofu.c
 
 gpgv2_SOURCES = gpgv.c           \
 	      $(common_source)  \
@@ -140,7 +141,7 @@ gpgv2_SOURCES = gpgv.c           \
 
 LDADD =  $(needed_libs) ../common/libgpgrl.a \
          $(ZLIBS) $(LIBINTL) $(CAPLIBS) $(NETLIBS)
-gpg2_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(LIBREADLINE) \
+gpg2_LDADD = $(LDADD) $(SQLITE3_LIBS) $(LIBGCRYPT_LIBS) $(LIBREADLINE) \
              $(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \
 	     $(LIBICONV) $(resource_objs) $(extra_sys_libs)
 gpg2_LDFLAGS = $(extra_bin_ldflags)
diff --git a/g10/gpg.c b/g10/gpg.c
index 9454b53..ec2292b 100644
--- a/g10/gpg.c
+++ b/g10/gpg.c
@@ -59,6 +59,7 @@
 #include "gc-opt-flags.h"
 #include "asshelp.h"
 #include "call-dirmngr.h"
+#include "tofu.h"
 #include "../common/init.h"
 #include "../common/shareddefs.h"
 
@@ -162,6 +163,7 @@ enum cmd_and_opt_values
     aChangePIN,
     aPasswd,
     aServer,
+    aTOFUPolicy,
 
     oTextmode,
     oNoTextmode,
@@ -384,6 +386,7 @@ enum cmd_and_opt_values
     oFakedSystemTime,
     oNoAutostart,
     oPrintPKARecords,
+    oTOFUDefaultPolicy,
 
     oNoop
   };
@@ -474,6 +477,8 @@ static ARGPARSE_OPTS opts[] = {
   ARGPARSE_c (aPrimegen, "gen-prime", "@" ),
   ARGPARSE_c (aGenRandom,"gen-random", "@" ),
   ARGPARSE_c (aServer,   "server",  N_("run in server mode")),
+  ARGPARSE_c (aTOFUPolicy, "tofu-policy",
+	      N_("|VALUE|set the TOFU policy for a key (good, bad, ask, auto)")),
 
   ARGPARSE_group (301, N_("@\nOptions:\n ")),
 
@@ -669,6 +674,7 @@ static ARGPARSE_OPTS opts[] = {
   ARGPARSE_s_i (oDefCertLevel, "default-cert-check-level", "@"), /* old */
   ARGPARSE_s_n (oAlwaysTrust, "always-trust", "@"),
   ARGPARSE_s_s (oTrustModel, "trust-model", "@"),
+  ARGPARSE_s_s (oTOFUDefaultPolicy, "tofu-default-policy", "@"),
   ARGPARSE_s_s (oSetFilename, "set-filename", "@"),
   ARGPARSE_s_n (oForYourEyesOnly, "for-your-eyes-only", "@"),
   ARGPARSE_s_n (oNoForYourEyesOnly, "no-for-your-eyes-only", "@"),
@@ -1937,6 +1943,12 @@ parse_trust_model(const char *model)
     opt.trust_model=TM_ALWAYS;
   else if(ascii_strcasecmp(model,"direct")==0)
     opt.trust_model=TM_DIRECT;
+  else if(ascii_strcasecmp(model,"tofu")==0)
+    opt.trust_model=TM_TOFU;
+  else if(ascii_strcasecmp(model,"tofu+pgp")==0)
+    opt.trust_model=TM_TOFU_PGP;
+  else if(ascii_strcasecmp(model,"tofu+wot")==0)
+    opt.trust_model=TM_TOFU_PGP;
   else if(ascii_strcasecmp(model,"auto")==0)
     opt.trust_model=TM_AUTO;
   else
@@ -1944,6 +1956,24 @@ parse_trust_model(const char *model)
 }
 #endif /*NO_TRUST_MODELS*/
 
+static int
+parse_tofu_policy (const char *policy)
+{
+  if (ascii_strcasecmp (policy, "bad") == 0)
+    return TOFU_BINDING_BAD;
+  else if (ascii_strcasecmp (policy, "ask") == 0)
+    return TOFU_BINDING_ASK;
+  else if (ascii_strcasecmp (policy, "auto") == 0)
+    return TOFU_BINDING_AUTO;
+  else if (ascii_strcasecmp (policy, "good") == 0)
+    return TOFU_BINDING_GOOD;
+  else
+    log_fatal("unknown TOFU policy '%s'"
+	      " (expected 'bad', 'ask', 'auto' or 'good')\n",
+	      policy);
+
+  BUG();
+}
 
 /* This fucntion called to initialized a new control object.  It is
    assumed that this object has been zeroed out before calling this
@@ -2148,6 +2178,7 @@ main (int argc, char **argv)
 #else
     opt.trust_model = TM_AUTO;
 #endif
+    opt.tofu_default_policy = TOFU_BINDING_AUTO;
     opt.mangle_dos_filenames = 0;
     opt.min_cert_level = 2;
     set_screen_dimensions ();
@@ -2370,6 +2401,10 @@ main (int argc, char **argv)
             opt.batch = 1;
             break;
 
+          case aTOFUPolicy:
+            set_cmd (&cmd, pargs.r_opt);
+            break;
+
 	  case oArmor: opt.armor = 1; opt.no_armor=0; break;
 	  case oOutput: opt.outfile = pargs.r.ret_str; break;
 	  case oMaxOutput: opt.max_output = pargs.r.ret_ulong; break;
@@ -2551,6 +2586,9 @@ main (int argc, char **argv)
 	    parse_trust_model(pargs.r.ret_str);
 	    break;
 #endif /*!NO_TRUST_MODELS*/
+	  case oTOFUDefaultPolicy:
+	    opt.tofu_default_policy = parse_tofu_policy (pargs.r.ret_str);
+	    break;
 
 	  case oForceOwnertrust:
 	    log_info(_("Note: %s is not for normal use!\n"),
@@ -4348,6 +4386,66 @@ main (int argc, char **argv)
         gcry_control (GCRYCTL_PRINT_CONFIG, stdout);
         break;
 
+      case aTOFUPolicy:
+	{
+	  int policy;
+	  int i;
+	  KEYDB_HANDLE hd;
+
+	  if (argc < 2)
+	    wrong_args("--tofu-policy POLICY KEYID [KEYID...]");
+
+	  policy = parse_tofu_policy (argv[0]);
+
+	  hd = keydb_new ();
+	  if (! hd)
+	    log_fatal (_("Failed to open the keyring DB.\n"));
+
+	  for (i = 1; i < argc; i ++)
+	    {
+	      KEYDB_SEARCH_DESC desc;
+	      kbnode_t kb;
+
+	      rc = classify_user_id (argv[i], &desc, 0);
+	      if (rc)
+		log_fatal (_("Failed to parse `%s'.\n"), argv[i]);
+
+	      if (! (desc.mode == KEYDB_SEARCH_MODE_SHORT_KID
+		     || desc.mode == KEYDB_SEARCH_MODE_LONG_KID
+		     || desc.mode == KEYDB_SEARCH_MODE_FPR16
+		     || desc.mode == KEYDB_SEARCH_MODE_FPR20
+		     || desc.mode == KEYDB_SEARCH_MODE_FPR
+		     || desc.mode == KEYDB_SEARCH_MODE_KEYGRIP))
+		log_fatal (_("`%s' does not appear to be a valid"
+			     " key id, fingerprint or key grip.\n"),
+			   argv[i]);
+
+	      rc = keydb_search_reset (hd);
+	      if (rc)
+		log_fatal (_("Failed to reset keyring handle.\n"));
+
+	      rc = keydb_search (hd, &desc, 1, NULL);
+	      if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY)
+		log_fatal (_("Key `%s' is not available\n"), argv[i]);
+	      else if (rc)
+		log_fatal (_("Failed to find key `%s'\n"), argv[i]);
+
+	      rc = keydb_get_keyblock (hd, &kb);
+	      if (rc)
+		log_fatal (_("Failed to read key `%s' from the keyring\n"),
+			   argv[i]);
+
+	      merge_keys_and_selfsig (kb);
+
+	      if (tofu_set_policy (kb, policy))
+		g10_exit (1);
+	    }
+
+	  keydb_release (hd);
+
+	}
+	break;
+
       case aListPackets:
 	opt.list_packets=2;
       default:
diff --git a/g10/gpgv.c b/g10/gpgv.c
index 412f4be..15f8908 100644
--- a/g10/gpgv.c
+++ b/g10/gpgv.c
@@ -285,10 +285,13 @@ get_validity_info (PKT_public_key *pk, PKT_user_id *uid)
 }
 
 unsigned int
-get_validity (PKT_public_key *pk, PKT_user_id *uid)
+get_validity (PKT_public_key *pk, PKT_user_id *uid, PKT_signature *sig,
+	      int may_ask)
 {
   (void)pk;
   (void)uid;
+  (void)sig;
+  (void)may_ask;
   return 0;
 }
 
diff --git a/g10/keyedit.c b/g10/keyedit.c
index 61aceb2..730952d 100644
--- a/g10/keyedit.c
+++ b/g10/keyedit.c
@@ -3006,7 +3006,8 @@ show_key_with_all_names (ctrl_t ctrl, estream_t fp,
 
 	      /* Show a warning once */
 	      if (!did_warn
-		  && (get_validity (pk, NULL) & TRUST_FLAG_PENDING_CHECK))
+		  && (get_validity (pk, NULL, NULL, 0)
+		      & TRUST_FLAG_PENDING_CHECK))
 		{
 		  did_warn = 1;
 		  do_warn = 1;
@@ -5298,7 +5299,7 @@ menu_revuid (KBNODE pub_keyblock)
 		/* If the trustdb has an entry for this key+uid then the
 		   trustdb needs an update. */
 		if (!update_trust
-		    && (get_validity (pk, uid) & TRUST_MASK) >=
+		    && (get_validity (pk, uid, NULL, 0) & TRUST_MASK) >=
 		    TRUST_UNDEFINED)
 		  update_trust = 1;
 #endif /*!NO_TRUST_MODELS*/
diff --git a/g10/keylist.c b/g10/keylist.c
index bfc6d49..7ee5387 100644
--- a/g10/keylist.c
+++ b/g10/keylist.c
@@ -986,7 +986,7 @@ list_keyblock_print (KBNODE keyblock, int secret, int fpr,
      include, but it looks sort of confusing in the listing... */
   if (opt.list_options & LIST_SHOW_VALIDITY)
     {
-      int validity = get_validity (pk, NULL);
+      int validity = get_validity (pk, NULL, NULL, 0);
       es_fprintf (es_stdout, " [%s]", trust_value_to_string (validity));
     }
 #endif
diff --git a/g10/mainproc.c b/g10/mainproc.c
index f7b7c6b..3ff757b 100644
--- a/g10/mainproc.c
+++ b/g10/mainproc.c
@@ -835,6 +835,7 @@ do_check_sig (CTX c, kbnode_t node, int *is_selfsig,
   PKT_signature *sig;
   gcry_md_hd_t md = NULL;
   gcry_md_hd_t md2 = NULL;
+  gcry_md_hd_t md_good = NULL;
   int algo, rc;
 
   assert (node->pkt->pkttype == PKT_SIGNATURE);
@@ -910,8 +911,21 @@ do_check_sig (CTX c, kbnode_t node, int *is_selfsig,
     return GPG_ERR_SIG_CLASS;
 
   rc = signature_check2 (sig, md, NULL, is_expkey, is_revkey, NULL);
-  if (gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE && md2)
-    rc = signature_check2 (sig, md2, NULL, is_expkey, is_revkey, NULL);
+  if (! rc)
+    md_good = md;
+  else if (gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE && md2)
+    {
+      rc = signature_check2 (sig, md2, NULL, is_expkey, is_revkey, NULL);
+      if (! rc)
+	md_good = md2;
+    }
+
+  if (md_good)
+    {
+      unsigned char *buffer = gcry_md_read (md_good, 0);
+      sig->digest_len = gcry_md_get_algo_dlen (map_md_openpgp_to_gcry (algo));
+      memcpy (sig->digest, buffer, sig->digest_len);
+    }
 
   gcry_md_close (md);
   gcry_md_close (md2);
@@ -1832,9 +1846,9 @@ check_sig_and_print (CTX c, kbnode_t node)
 
           assert (pk);
 
-          /* Get it before we print anything to avoid interrupting the
-             output with the "please do a --check-trustdb" line. */
-          valid = get_validity (pk, un->pkt->pkt.user_id);
+	  /* Since this is just informational, don't actually ask the
+	     user to update any trust information.  */
+          valid = get_validity (pk, un->pkt->pkt.user_id, sig, 0);
 
           keyid_str[17] = 0; /* cut off the "[uncertain]" part */
 
@@ -1923,8 +1937,11 @@ check_sig_and_print (CTX c, kbnode_t node)
                   else if (un->pkt->pkt.user_id->is_expired)
                     valid = _("expired");
                   else
+		    /* Since this is just informational, don't
+		       actually ask the user to update any trust
+		       information.  */
                     valid = (trust_value_to_string
-                             (get_validity (pk, un->pkt->pkt.user_id)));
+                             (get_validity (pk, un->pkt->pkt.user_id, sig, 0)));
                   log_printf (" [%s]\n",valid);
                 }
               else
diff --git a/g10/options.h b/g10/options.h
index fd2f4a2..f3b6594 100644
--- a/g10/options.h
+++ b/g10/options.h
@@ -117,8 +117,12 @@ struct
      we started storing the trust model inside the trustdb. */
   enum
     {
-      TM_CLASSIC=0, TM_PGP=1, TM_EXTERNAL=2, TM_ALWAYS, TM_DIRECT, TM_AUTO
+      TM_CLASSIC=0, TM_PGP=1, TM_EXTERNAL=2,
+      TM_ALWAYS, TM_DIRECT, TM_AUTO, TM_TOFU, TM_TOFU_PGP
     } trust_model;
+  /* TOFU_BINDING_BAD, TOFU_BINDING_ASK, TOFU_BINDING_AUTO, or
+     TOFU_BINDING_GOOD.  */
+  int tofu_default_policy;
   int force_ownertrust;
   enum
     {
diff --git a/g10/packet.h b/g10/packet.h
index 1906ec5..1aafbb2 100644
--- a/g10/packet.h
+++ b/g10/packet.h
@@ -175,6 +175,11 @@ typedef struct
   subpktarea_t *unhashed;    /* Ditto for unhashed data. */
   byte digest_start[2];      /* First 2 bytes of the digest. */
   gcry_mpi_t  data[PUBKEY_MAX_NSIG];
+  /* The message digest and its length (in bytes).  Note the maximum
+     digest length is 512 bits (64 bytes).  If DIGEST_LEN is 0, then
+     the digest's value has not been saved here.  */
+  byte digest[512 / 8];
+  int digest_len;
 } PKT_signature;
 
 #define ATTRIB_IMAGE 1
diff --git a/g10/pkclist.c b/g10/pkclist.c
index 9996d18..06ba86e 100644
--- a/g10/pkclist.c
+++ b/g10/pkclist.c
@@ -37,6 +37,7 @@
 #include "status.h"
 #include "photoid.h"
 #include "i18n.h"
+#include "tofu.h"
 
 #define CONTROL_D ('D' - 'A' + 1)
 
@@ -507,13 +508,13 @@ do_we_trust_pre( PKT_public_key *pk, unsigned int trustlevel )
 
 /****************
  * Check whether we can trust this signature.
- * Returns: Error if we shall not trust this signatures.
+ * Returns an error code if we should not trust this signature.
  */
 int
 check_signatures_trust( PKT_signature *sig )
 {
   PKT_public_key *pk = xmalloc_clear( sizeof *pk );
-  unsigned int trustlevel;
+  unsigned int trustlevel = TRUST_UNKNOWN;
   int rc=0;
 
   rc = get_pubkey( pk, sig->keyid );
@@ -537,7 +538,7 @@ check_signatures_trust( PKT_signature *sig )
     log_info(_("WARNING: this key might be revoked (revocation key"
 	       " not present)\n"));
 
-  trustlevel = get_validity (pk, NULL);
+  trustlevel = get_validity (pk, NULL, sig, 1);
 
   if ( (trustlevel & TRUST_FLAG_REVOKED) )
     {
@@ -829,7 +830,7 @@ find_and_check_key (ctrl_t ctrl, const char *name, unsigned int use,
     }
 
   /* Key found and usable.  Check validity. */
-  trustlevel = get_validity (pk, pk->user_id);
+  trustlevel = get_validity (pk, pk->user_id, NULL, 1);
   if ( (trustlevel & TRUST_FLAG_DISABLED) )
     {
       /* Key has been disabled. */
@@ -1114,7 +1115,7 @@ build_pk_list (ctrl_t ctrl,
                 { /* Check validity of this key. */
                   int trustlevel;
 
-                  trustlevel = get_validity (pk, pk->user_id);
+                  trustlevel = get_validity (pk, pk->user_id, NULL, 1);
                   if ( (trustlevel & TRUST_FLAG_DISABLED) )
                     {
                       tty_printf (_("Public key is disabled.\n") );
diff --git a/g10/test-stubs.c b/g10/test-stubs.c
index c6f6d68..2de9253 100644
--- a/g10/test-stubs.c
+++ b/g10/test-stubs.c
@@ -104,10 +104,13 @@ get_validity_info (PKT_public_key *pk, PKT_user_id *uid)
 }
 
 unsigned int
-get_validity (PKT_public_key *pk, PKT_user_id *uid)
+get_validity (PKT_public_key *pk, PKT_user_id *uid, PKT_signature *sig,
+	      int may_ask)
 {
   (void)pk;
   (void)uid;
+  (void)sig;
+  (void)may_ask;
   return 0;
 }
 
diff --git a/g10/tofu.c b/g10/tofu.c
new file mode 100644
index 0000000..30f7835
--- /dev/null
+++ b/g10/tofu.c
@@ -0,0 +1,1327 @@
+/* XXX:
+
+   - Add indices.
+
+   - Add support for multiple DBs?
+
+   - Add some test cases.
+
+   - Make sure encryption works.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <assert.h>
+#include <sqlite3.h>
+
+#include "gpg.h"
+#include "types.h"
+#include "logging.h"
+#include "stringhelp.h"
+#include "options.h"
+#include "mbox-util.h"
+#include "i18n.h"
+#include "trustdb.h"
+
+#include "tofu.h"
+
+#define POLICY_NONE -2
+
+const char *
+tofu_binding_policy_str (enum tofu_binding_policy policy)
+{
+  switch (policy)
+    {
+    case TOFU_BINDING_BAD: return "bad";
+    case TOFU_BINDING_ASK: return "ask";
+    case TOFU_BINDING_AUTO: return "automatic";
+    case TOFU_BINDING_GOOD: return "good";
+    default: return "unknown";
+    }
+}
+
+int
+tofu_binding_policy_to_trust (enum tofu_binding_policy policy)
+{
+  if (policy == TOFU_BINDING_AUTO)
+    /* If POLICY is AUTO, fallback to OPT.TOFU_DEFAULT_POLICY.  */
+    policy = opt.tofu_default_policy;
+
+  switch (policy)
+    {
+    case TOFU_BINDING_BAD:
+      return TRUST_NEVER;
+    case TOFU_BINDING_ASK:
+      return TRUST_ASK;
+    case TOFU_BINDING_AUTO:
+      /* If OPT.TOFU_DEFAULT_POLICY is also AUTO, then trust fully.  */
+      return TRUST_FULLY;
+    case TOFU_BINDING_GOOD:
+      return TRUST_FULLY;
+    default:
+      log_bug ("Bad value for trust policy: %d\n",
+	       opt.tofu_default_policy);
+      return 0;
+    }
+}
+
+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;
+}
+
+static void
+closedb (sqlite3 *db)
+{
+  if (db)
+    sqlite3_close (db);
+}
+
+static int
+version_check_cb (void *cookie, int argc, char **argv, char **azColName)
+{
+  int *version = cookie;
+
+  if (argc != 1)
+    *version = 0;
+  if (strcmp (azColName[0], "version") != 0)
+    *version = 0;
+
+  if (strcmp (argv[0], "1") == 0)
+    *version = 1;
+  else
+    log_error (_("TOFU: unsupported database version: %s\n"), argv[0]);
+
+  /* Don't run again.  */
+  return 1;
+}
+
+/* Return 1 if the database is okay and 0 otherwise.  */
+static int
+initdb (sqlite3 *db)
+{
+  char *err = NULL;
+  int rc;
+  int version = -1;
+
+  rc = sqlite3_exec (db, "select version from version;", version_check_cb,
+		     &version, &err);
+  if (rc == SQLITE_ABORT && version == 1)
+    /* Happy, happy, joy, joy.  */
+    {
+      sqlite3_free (err);
+      return 1;
+    }
+
+  rc = sqlite3_exec (db, "begin transaction;", NULL, NULL, &err);
+  if (rc)
+    {
+      log_error (_("Error beginning transaction on TOFU database: %s\n"),
+		 err);
+      sqlite3_free (err);
+      return 0;
+    }
+
+  /* If the version table doesn't exist, then this might be a new
+     file and we need to initialize the whole sheband.  */
+  rc = sqlite3_exec (db,
+		     "create table version (version INTEGER);",
+		     NULL, NULL, &err);
+  if (rc)
+    {
+      log_error (_("Error initializing TOFU database (%s): %s\n"),
+		 "version", err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+  rc = sqlite3_exec (db,
+		     "insert into version values (1);",
+		     NULL, NULL, &err);
+  if (rc)
+    {
+      log_error (_("Error initializing TOFU database (%s): %s\n"),
+		 "version, init", err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+  /* The list of <fingerprint, email> bindings and auxiliary data.
+
+       OID is a unique ID identifying this binding (and used by the
+         signatures table, see below).  Note: OIDs will never be
+         reused.
+
+       FINGERPRINT: The key's fingerprint.
+
+       EMAIL: The normalized email address.
+
+       USER_ID: The unmodified user id from which EMAIL was extracted.
+
+       TIME: The time this binding was first observed.
+
+       POLICY: The trust policy (-1, 0, 1, or 2; see the
+         documentation for TOFU_BINDING_BAD, etc. above).  */
+  rc = sqlite3_exec_printf
+    (db, NULL, NULL, &err,
+     "create table bindings\n"
+     " (oid INTEGER PRIMARY KEY AUTOINCREMENT,\n"
+     "  fingerprint TEXT, email TEXT, user_id TEXT, time INTEGER,"
+     "  policy BOOLEAN CHECK (policy in (%d, %d, %d, %d)),\n"
+     "  unique (fingerprint, email));\n"
+     "create index bindings_fingerprint_email\n"
+     " on bindings (fingerprint, email);",
+     TOFU_BINDING_BAD, TOFU_BINDING_ASK, TOFU_BINDING_AUTO, TOFU_BINDING_GOOD);
+  if (rc)
+    {
+      log_error (_("Error initializing TOFU database (%s): %s\n"),
+		 "bindings", err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+  /* The signatures that we have observed.
+
+     BINDING refers to a record in the bindings table, which describes
+       the binding.
+
+     SIG_DIGEST is the digest stored in the signature.
+
+     SIG_TIME is the timestamp stored in the signature.
+
+     ORIGIN is a free-form string that describes who fed this
+       signature to GnuPG (e.g., email:claws).
+
+     TIME is the time this signature was registered.  */
+  rc = sqlite3_exec (db,
+		     "create table signatures "
+		     " (binding INTEGER, sig_digest TEXT, origin TEXT,"
+		     "  sig_time INTEGER, time INTEGER,"
+		     "  primary key (binding, sig_digest, origin));",
+		     NULL, NULL, &err);
+  if (rc)
+    {
+      log_error (_("Error initializing TOFU database (%s): %s\n"),
+		 "signatures", err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+ out:
+  if (rc)
+    {
+      rc = sqlite3_exec (db, "abort transaction;", NULL, NULL, &err);
+      if (rc)
+	{
+	  log_error (_("%s: Error aborting transaction: %s\n"),
+		     __func__, err);
+	  sqlite3_free (err);
+	}
+      return 0;
+    }
+  else
+    {
+      rc = sqlite3_exec (db, "commit transaction;", NULL, NULL, &err);
+      if (rc)
+	{
+	  log_error (_("%s: Error commit transaction: %s\n"),
+		     __func__, err);
+	  sqlite3_free (err);
+	  return 0;
+	}
+      return 1;
+    }
+}
+
+/* Opens and initializes the TOFU database.  Return's NULL on
+   failure.  */
+static sqlite3 *
+opendb (void)
+{
+  char *filename;
+  sqlite3 *db;
+  int rc = 0;
+
+  filename = make_filename (opt.homedir, "tofu.db", NULL);
+  rc = sqlite3_open (filename, &db);
+  if (rc)
+    {
+      log_error (_("Can't open TOFU database ('%s'): %s\n"),
+                 filename, sqlite3_errmsg (db));
+      sqlite3_close (db);
+      db = NULL;
+    }
+
+  xfree (filename);
+
+  if (db && initdb (db) == 0)
+    {
+      closedb (db);
+      db = NULL;
+    }
+
+  return db;
+}
+
+
+/* Collect results of a select min (foo) ...; style query.  Aborts if
+   the argument is not a valid integer (or real of the form X.0).  */
+static long
+get_single_long_cb (void *cookie, int argc, char **argv, char **azColName)
+{
+  long *count = cookie;
+  char *tail = NULL;
+
+  (void) azColName;
+
+  assert (argc == 1);
+
+  errno = 0;
+  *count = strtol (argv[0], &tail, 0);
+  if (errno || *tail != '\0')
+    /* Abort.  */
+    return 1;
+  return 0;
+}
+
+/* Record (or update) a trust policy about a (possibly new)
+   binding.  */
+static gpg_error_t
+record_binding (sqlite3 *db, const char *fingerprint, const char *email,
+		const char *user_id, int policy, int show_old)
+{
+  int rc;
+  char *err = NULL;
+  long policy_old = POLICY_NONE;
+
+  if (! (policy == TOFU_BINDING_BAD
+	 || policy == TOFU_BINDING_ASK
+	 || policy == TOFU_BINDING_AUTO
+	 || policy == TOFU_BINDING_GOOD))
+    log_bug ("TOFU: %s: Bad value for policy (%d)!\n", __func__, policy);
+
+  if (show_old)
+    /* Get the old policy.  Since this is just for informational
+       purposes, there is not need to start a transaction.  */
+    {
+      rc = sqlite3_exec_printf
+	(db, get_single_long_cb, &policy_old, &err,
+	 "select policy from bindings"
+	 " where fingerprint = %Q and email = %Q",
+	 fingerprint, email);
+      if (rc)
+	{
+	  log_debug ("TOFU: Error reading from binding database"
+		     " (reading policy for <%s, %s>): %s\n",
+		     fingerprint, email, err);
+	  sqlite3_free (err);
+	}
+    }
+
+  if (policy_old != POLICY_NONE)
+    log_info (_("TOFU: Changing trust policy for binding <%s, %s>"
+		" from %s to %s.\n"),
+	      fingerprint, email,
+	      tofu_binding_policy_str (policy_old),
+	      tofu_binding_policy_str (policy));
+  else
+    log_info (_("TOFU: Set trust policy for binding <%s, %s> to %s.\n"),
+	      fingerprint, email,
+	      tofu_binding_policy_str (policy));
+
+  if (policy_old == policy)
+    /* Nothing to do.  */
+    return 0;
+
+  rc = sqlite3_exec_printf
+    (db, NULL, NULL, &err,
+     "insert or replace into bindings\n"
+     " (oid, fingerprint, email, user_id, time, policy)\n"
+     " values (\n"
+     /* If we don't explicitly reuse the OID, then SQLite will
+	reallocate a new one.  We just need to search for the OID
+	based on the fingerprint and email since they are unique.  */
+     "  (select oid from bindings where fingerprint = %Q and email = %Q),\n"
+     "  %Q, %Q, %Q, strftime('%%s','now'), %d);",
+     fingerprint, email, fingerprint, email, user_id, policy);
+  if (rc)
+    {
+      log_error ("TOFU: Error updating binding database"
+		 " (inserting <%s, %s, %s>): %s\n",
+		 fingerprint, email, tofu_binding_policy_str (policy),
+		 err);
+      sqlite3_free (err);
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  return 0;
+}
+
+
+/* Collect results of a select count (*) ...; style query.  Aborts if
+   the argument is not a valid integer (or real of the form X.0).  */
+static int
+get_single_unsigned_long_cb (void *cookie, int argc, char **argv,
+			     char **azColName)
+{
+  unsigned long int *count = cookie;
+  char *tail = NULL;
+
+  (void) azColName;
+
+  assert (argc == 1);
+
+  errno = 0;
+  *count = strtoul (argv[0], &tail, 0);
+  if (errno || *tail != '\0')
+    /* Abort.  */
+    return 1;
+  return 0;
+}
+
+/* Collect the strings returned by a query in a simply 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
+   results are added to the list as follows (the value is parentheses
+   is the 1-based index in the final list):
+
+     row 1, col 2 (6)
+     row 1, col 1 (5)
+     row 2, col 2 (4)
+     row 2, col 1 (3)
+     row 3, col 2 (2)
+     row 3, col 1 (1)
+
+   This is because add_to_strlist pushes the results onto the front of
+   the list.  The end result is that the rows are backwards, but the
+   columns are in the expected order.  */
+static int
+strings_collect_cb (void *cookie, int argc, char **argv, char **azColName)
+{
+  int i;
+  strlist_t *strlist = cookie;
+
+  (void) azColName;
+
+  for (i = argc - 1; i >= 0; i --)
+    add_to_strlist (strlist, argv[i] ? argv[i] : "");
+
+  return 0;
+}
+
+/* Auxiliary data structure to collect statistics about
+   signatures.  */
+struct signature_stats
+{
+  struct signature_stats *next;
+
+  /* The user-assigned policy for this binding.  */
+  enum tofu_binding_policy policy;
+
+  /* How long ago the signature was created (rounded to a multiple of
+     TIME_AGO_UNIT_SMALL, etc.).  */
+  long time_ago;
+  /* Number of signatures during this time.  */
+  unsigned long count;
+
+  /* The key that generated this signature.  */
+  char fingerprint[1];
+};
+
+static void
+signature_stats_free (struct signature_stats *stats)
+{
+  while (stats)
+    {
+      struct signature_stats *next = stats->next;
+      xfree (stats);
+      stats = next;
+    }
+}
+
+/* Process rows that contain the four columns:
+
+     <fingerprint, policy, time ago, count>.  */
+static int
+signature_stats_collect_cb (void *cookie, int argc, char **argv,
+			    char **azColName)
+{
+  struct signature_stats **statsp = cookie;
+  struct signature_stats *stats = xmalloc (sizeof (*stats) + strlen (argv[0]));
+  char *tail;
+  int i = 0;
+
+  (void) azColName;
+
+  stats->next = *statsp;
+  *statsp = stats;
+
+  strcpy (stats->fingerprint, argv[i]);
+  i ++;
+
+  tail = NULL;
+  errno = 0;
+  stats->policy = strtol (argv[i], &tail, 0);
+  if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0'))
+    {
+      /* Abort.  */
+      log_error ("%s: Error converting %s to an integer (tail = `%s')\n",
+		 __func__, argv[i], tail);
+      return 1;
+    }
+  i ++;
+
+  tail = NULL;
+  errno = 0;
+  stats->time_ago = strtol (argv[i], &tail, 0);
+  if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0'))
+    {
+      /* Abort.  */
+      log_error ("%s: Error converting %s to an integer (tail = `%s')\n",
+		 __func__, argv[i], tail);
+      return 1;
+    }
+  i ++;
+
+  tail = NULL;
+  errno = 0;
+  stats->count = strtoul (argv[i], &tail, 0);
+  if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0'))
+    {
+      /* Abort.  */
+      log_error ("%s: Error converting %s to an integer (tail = `%s')\n",
+		 __func__, argv[i], tail);
+      return 1;
+    }
+  i ++;
+
+  assert (argc == i);
+
+  return 0;
+}
+
+/* The grouping parameters when collecting signature statistics.  */
+
+/* If a message is signed a couple of hours in the future, just assume
+   some clock skew.  */
+#define TIME_AGO_FUTURE_IGNORE (2 * 60 * 60)
+#if 1
+#  define TIME_AGO_UNIT_SMALL 60
+#  define TIME_AGO_UNIT_SMALL_NAME _("minute")
+#  define TIME_AGO_UNIT_SMALL_NAME_PLURAL _("minutes")
+#  define TIME_AGO_MEDIUM_THRESHOLD (60 * TIME_AGO_UNIT_SMALL)
+#  define TIME_AGO_UNIT_MEDIUM (60 * 60)
+#  define TIME_AGO_UNIT_MEDIUM_NAME _("hour")
+#  define TIME_AGO_UNIT_MEDIUM_NAME_PLURAL _("hours")
+#  define TIME_AGO_LARGE_THRESHOLD (24 * 60 * TIME_AGO_UNIT_SMALL)
+#  define TIME_AGO_UNIT_LARGE (24 * 60 * 60)
+#  define TIME_AGO_UNIT_LARGE_NAME _("day")
+#  define TIME_AGO_UNIT_LARGE_NAME_PLURAL _("days")
+#else
+#  define TIME_AGO_UNIT_SMALL (24 * 60 * 60)
+#  define TIME_AGO_UNIT_SMALL_NAME _("day")
+#  define TIME_AGO_UNIT_SMALL_NAME_PLURAL _("days")
+#  define TIME_AGO_MEDIUM_THRESHOLD (4 * TIME_AGO_UNIT_SMALL)
+#  define TIME_AGO_UNIT_MEDIUM (7 * 24 * 60 * 60)
+#  define TIME_AGO_UNIT_MEDIUM_NAME _("week")
+#  define TIME_AGO_UNIT_MEDIUM_NAME_PLURAL _("weeks")
+#  define TIME_AGO_LARGE_THRESHOLD (28 * TIME_AGO_UNIT_SMALL)
+#  define TIME_AGO_UNIT_LARGE (30 * 24 * 60 * 60)
+#  define TIME_AGO_UNIT_LARGE_NAME _("month")
+#  define TIME_AGO_UNIT_LARGE_NAME_PLURAL _("months")
+#endif
+
+/* Convert from seconds to time units.
+
+   Note: T should already be a multiple of TIME_AGO_UNIT_SMALL or
+   TIME_AGO_UNIT_MEDIUM or TIME_AGO_UNIT_LARGE.  */
+signed long
+time_ago_scale (signed long t)
+{
+  if (t < TIME_AGO_UNIT_MEDIUM)
+    return t / TIME_AGO_UNIT_SMALL;
+  if (t < TIME_AGO_UNIT_LARGE)
+    return t / TIME_AGO_UNIT_MEDIUM;
+  return t / TIME_AGO_UNIT_LARGE;
+}
+
+/* Return the appropriate unit (respecting whether it is plural or
+   singular).  */
+const char *
+time_ago_unit (signed long t)
+{
+  signed long t_scaled = time_ago_scale (t);
+
+  if (t < TIME_AGO_UNIT_MEDIUM)
+    {
+      if (t_scaled == 1)
+	return TIME_AGO_UNIT_SMALL_NAME;
+      return TIME_AGO_UNIT_SMALL_NAME_PLURAL;
+    }
+  if (t < TIME_AGO_UNIT_LARGE)
+    {
+      if (t_scaled == 1)
+	return TIME_AGO_UNIT_MEDIUM_NAME;
+      return TIME_AGO_UNIT_MEDIUM_NAME_PLURAL;
+    }
+  if (t_scaled == 1)
+    return TIME_AGO_UNIT_LARGE_NAME;
+  return TIME_AGO_UNIT_LARGE_NAME_PLURAL;
+}
+
+
+#define GET_TRUST_ERROR -1
+
+/* Return the trust policy (TRUST_NEVER, etc.) for the binding
+   <fingerprint, email>.  If we don't have a policy, returns
+   POLICY_NONE.  If an error occurs, returns GET_TRUST_ERROR.  */
+static int
+get_trust (sqlite3 *db, const char *fingerprint, const char *email,
+	   const char *user_id, int may_ask)
+{
+  strlist_t fingerprints = NULL;
+  int policy;
+  int rc;
+  char *err = NULL;
+  int trust_level = TRUST_UNKNOWN;
+  int count;
+
+  assert (GET_TRUST_ERROR != TRUST_UNKNOWN &&
+	  GET_TRUST_ERROR != TRUST_EXPIRED &&
+	  GET_TRUST_ERROR != TRUST_UNDEFINED &&
+	  GET_TRUST_ERROR != TRUST_NEVER &&
+	  GET_TRUST_ERROR != TRUST_MARGINAL &&
+	  GET_TRUST_ERROR != TRUST_FULLY &&
+	  GET_TRUST_ERROR != TRUST_ULTIMATE);
+
+  /* Check if the <FINGERPRINT, EMAIL> binding is known (POLICY_NONE
+     cannot appear in the DB.  Thus, if POLICY is still
+     POLICY_NONE after executing the query, then the result set was
+     empty.)  */
+  policy = POLICY_NONE;
+  assert (policy != TOFU_BINDING_BAD
+	  && policy != TOFU_BINDING_ASK
+	  && policy != TOFU_BINDING_AUTO
+	  && policy != TOFU_BINDING_GOOD);
+  rc = sqlite3_exec_printf
+    (db, get_single_unsigned_long_cb, &policy, &err,
+     "select policy from bindings where fingerprint = %Q and email = %Q",
+     fingerprint, email);
+  if (rc)
+    {
+      log_error ("Error reading from TOFU database"
+		 " (checking for existing bad bindings): %s (%d)\n",
+		 err, rc);
+      sqlite3_free (err);
+      policy = GET_TRUST_ERROR;
+      goto out;
+    }
+
+  switch (policy)
+    {
+    case TOFU_BINDING_BAD:
+      /* The saved judgement is bad.  We don't need to ask the user
+	 anything.  */
+      log_info ("TOFO: Bad binding: <%s, %s>\n",
+		fingerprint, email);
+      trust_level = tofu_binding_policy_to_trust (policy);
+      goto out;
+
+    case TOFU_BINDING_GOOD:
+      /* The saved judgement is good.  We don't need to ask the user
+	 anything.  */
+      log_info ("TOFO: Good binding: <%s, %s>\n",
+		fingerprint, email);
+      trust_level = tofu_binding_policy_to_trust (policy);
+      goto out;
+
+    case TOFU_BINDING_AUTO:
+      /* The saved judgement is auto.  If opt.tofu_default_policy is
+	 not ask, we don't have to ask the user anything.  */
+      log_info ("TOFO: auto (default trust: %s) binding: <%s, %s>\n",
+		tofu_binding_policy_str (opt.tofu_default_policy),
+		fingerprint, email);
+      if (opt.tofu_default_policy == TOFU_BINDING_ASK)
+	/* We need to ask the user what to do.  Case #1 below.  */
+	{
+	  if (! may_ask)
+	    {
+	      trust_level = TRUST_UNDEFINED;
+	      goto out;
+	    }
+	}
+      else
+	{
+	  assert (opt.tofu_default_policy == TOFU_BINDING_BAD
+		  || opt.tofu_default_policy == TOFU_BINDING_AUTO
+		  || opt.tofu_default_policy == TOFU_BINDING_GOOD);
+	  trust_level = tofu_binding_policy_to_trust (policy);
+	  goto out;
+	}
+
+      break;
+
+    case TOFU_BINDING_ASK:
+      /* We need to ask the user what to do.  Case #2 below.  */
+      if (! may_ask)
+	{
+	  trust_level = TRUST_UNDEFINED;
+	  goto out;
+	}
+
+      break;
+
+    case POLICY_NONE:
+      /* The binding is new, we need to check for conflicts.  Case #3
+	 below.  */
+      break;
+
+    default:
+      log_bug ("%s: Impossible value for policy (%d)\n", __func__, policy);
+    }
+
+
+  /* We get here if:
+
+       1. The saved policy is auto and the default trust policy is
+          ask (need to ask),
+
+       2. The saved trust judgement is undefined (last time, the user
+          selected accept once or reject once) (need to ask), or,
+
+       3. We don't yet have a saved trust policy (policy == POLICY_NONE)
+          (need to check for a conflict).
+   */
+
+  if (! may_ask)
+    /* We can only get here in the third case in which case we have a
+       conflict.  */
+    {
+      assert (policy == POLICY_NONE);
+
+      trust_level = TRUST_UNKNOWN;
+      goto out;
+    }
+
+  /* Look for conflicts.  This is need in all 3 cases.
+
+     Get the fingerprints of any bindings that share the email
+     address.  Note: if the binding in question is in the DB, it will
+     also be returned.  Thus, if the result set is empty, then this is
+     a new binding.  */
+  rc = sqlite3_exec_printf
+    (db, strings_collect_cb, &fingerprints, &err,
+     "select distinct fingerprint from bindings where email = %Q;",
+     email);
+  if (rc)
+    {
+      log_error ("TOFU: Error reading from database"
+		 " (listing fingerprints): %s\n",
+		 err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+  count = strlist_length (fingerprints);
+  if (count == 0 && opt.tofu_default_policy != TOFU_BINDING_ASK)
+    /* New binding with no conflict and a concrete default policy.
+
+       We've never observed a binding with this email address (COUNT
+       is 0 and the above query would return the current binding if it
+       were in the DB) and we have a default policy, which is not to
+       ask the user.  */
+    {
+      /* If we've seen this binding, then we've seen this email and
+	 policy couldn't possibly be POLICY_NONE.  */
+      assert (policy == POLICY_NONE);
+
+      log_info ("TOFU: New binding <%s, %s>, no conflict.\n",
+		email, fingerprint);
+
+      if (record_binding (db, fingerprint, email, user_id,
+			  TOFU_BINDING_AUTO, 0) != 0)
+	{
+	  log_error ("TOFU: Error setting binding's trust level to auto\n");
+	  trust_level = GET_TRUST_ERROR;
+	  goto out;
+	}
+
+      trust_level = tofu_binding_policy_to_trust (TOFU_BINDING_AUTO);
+      goto out;
+    }
+
+#if 1
+  /* XXX: Is this behavior desirable?  */
+  if (policy == POLICY_NONE)
+    /* This is a new binding and we have a conflict.  Mark any
+       conflicting bindings that have an automatic policy as now
+       requiring confirmation.  */
+    {
+      rc = sqlite3_exec_printf
+	(db, NULL, NULL, &err,
+	 "update bindings set policy = %d"
+	 " where email = %Q and policy = %d;",
+	 TOFU_BINDING_ASK, email, TOFU_BINDING_AUTO);
+      if (rc)
+	{
+	  log_error ("TOFU: Error changing judgement: %s\n", err);
+	  sqlite3_free (err);
+	  goto out;
+	}
+    }
+#endif
+
+  /* If we get here, we need to ask the user about the binding.  There
+     are three ways we could end up here:
+
+       - This is a new binding and there is a conflict
+         (policy == POLICY_NONE && length(fingerprints) > 0),
+
+       - This is a new binding and opt.tofu_default_policy is set to
+         ask.  (policy == POLICY_NONE && opt.tofu_default_policy ==
+         TOFU_BINDING_ASK), or,
+
+       - The trust policy is ask (the user deferred last time)
+         (policy == TOFU_BINDING_ASK).
+   */
+  {
+    estream_t fp;
+    strlist_t other_user_ids = NULL;
+    struct signature_stats *stats = NULL;
+    struct signature_stats *stats_iter = NULL;
+    char *prompt;
+    char *choices;
+
+    fp = es_fopenmem (0, "rw,samethread");
+    if (! fp)
+      log_fatal ("Error creating memory stream\n");
+
+    if (policy == POLICY_NONE)
+      es_fprintf (fp, "The binding <%s, %s> is NOT known.  ",
+		  email, fingerprint);
+    es_fprintf (fp, "Please indicate whether you believe the binding ");
+    if (policy != POLICY_NONE)
+      es_fprintf (fp, "<%s, %s> ", email, fingerprint);
+    es_fprintf (fp, "is legitimate (good) or a forgery (bad).\n\n");
+
+
+    /* Find other user ids associated with this key and whether the
+       bindings are marked as good or bad.  */
+    rc = sqlite3_exec_printf
+      (db, strings_collect_cb, &other_user_ids, &err,
+       "select user_id, policy from bindings where fingerprint = %Q;",
+       fingerprint);
+    if (rc)
+      {
+	log_error ("TOFU: Error gathering other user ids: %s.\n", err);
+	sqlite3_free (err);
+	err = NULL;
+      }
+
+    if (other_user_ids)
+      {
+	strlist_t strlist_iter;
+
+	es_fprintf (fp, "Known user ids associated with this key:\n");
+	for (strlist_iter = other_user_ids;
+	     strlist_iter;
+	     strlist_iter = strlist_iter->next)
+	  {
+	    char *other_user_id = strlist_iter->d;
+	    char *other_policy;
+
+	    assert (strlist_iter->next);
+	    strlist_iter = strlist_iter->next;
+	    other_policy = strlist_iter->d;
+
+	    es_fprintf (fp, "  %s (%s)\n",
+			other_user_id,
+			tofu_binding_policy_str (atoi (other_policy)));
+	  }
+	es_fprintf (fp, "\n");
+
+	free_strlist (other_user_ids);
+      }
+
+    /* Find other keys associated with this email address.  */
+    rc = sqlite3_exec_printf
+      (db, signature_stats_collect_cb, &stats, &err,
+       "select fingerprint, policy, time_ago, count(*)\n"
+       " from (select bindings.*,\n"
+       "        case\n"
+       /* From the future (but if its just a couple of hours in the
+	  future don't turn it into a warning)?  Or should we use
+	  small, medium or large units?  (Note: whatever we do, we
+	  keep the value in seconds.  Then when we group, everything
+	  that rounds to the same number of seconds is grouped.)  */
+       "         when delta < -%d then -1\n"
+       "         when delta < %d then max(0, round(delta / %d) * %d)\n"
+       "         when delta < %d then round(delta / %d) * %d\n"
+       "         else round(delta / %d) * %d\n"
+       "        end time_ago,\n"
+       "        delta time_ago_raw\n"
+       "       from (select *,\n"
+       "              cast(strftime('%%s','now') - sig_time as real) delta\n"
+       "             from signatures) ss\n"
+       "       left join bindings on ss.binding = bindings.oid)\n"
+       " where email = %Q\n"
+       " group by fingerprint, time_ago\n"
+       " order by fingerprint = %Q asc, fingerprint desc, time_ago desc;\n",
+       TIME_AGO_FUTURE_IGNORE,
+       TIME_AGO_MEDIUM_THRESHOLD, TIME_AGO_UNIT_SMALL, TIME_AGO_UNIT_SMALL,
+       TIME_AGO_LARGE_THRESHOLD, TIME_AGO_UNIT_MEDIUM, TIME_AGO_UNIT_MEDIUM,
+       TIME_AGO_UNIT_LARGE, TIME_AGO_UNIT_LARGE,
+       email, fingerprint);
+    if (rc)
+      {
+	strlist_t strlist_iter;
+
+	log_error ("TOFU: Error gathering signature stats: %s.\n",
+		   err);
+	sqlite3_free (err);
+	err = NULL;
+
+	es_fprintf
+	  (fp, "The email address (%s) is associated with %d keys:\n",
+	   email, count);
+	for (strlist_iter = fingerprints;
+	     strlist_iter;
+	     strlist_iter = strlist_iter->next)
+	  es_fprintf (fp, "  %s\n", strlist_iter->d);
+      }
+    else
+      {
+	char *key = NULL;
+
+	es_fprintf (fp, "Statistics for keys with the email `%s':\n", email);
+	for (stats_iter = stats; stats_iter; stats_iter = stats_iter->next)
+	  {
+	    if (! key || strcmp (key, stats_iter->fingerprint) != 0)
+	      {
+		int this_key;
+		key = stats_iter->fingerprint;
+		this_key = strcmp (key, fingerprint) == 0;
+		es_fprintf (fp, "  %s", key);
+		if (this_key)
+		  es_fprintf (fp, " (this key):");
+		else
+		  es_fprintf (fp, ", which was judged to be %s:",
+			      tofu_binding_policy_str (stats_iter->policy));
+		es_fprintf (fp, "\n");
+	      }
+
+	    if (stats_iter->time_ago == -1)
+	      es_fprintf (fp, "    %ld %s signed in the future.\n",
+			  stats_iter->count,
+			  stats_iter->count == 1 ? "message" : "messages");
+	    else
+	      es_fprintf (fp, "    %ld %s %ld %s ago.\n",
+			  stats_iter->count,
+			  stats_iter->count == 1 ? "message" : "messages",
+			  time_ago_scale (stats_iter->time_ago),
+			  time_ago_unit (stats_iter->time_ago));
+	  }
+      }
+
+    es_fputc ('\n', fp);
+    es_fprintf
+      (fp,
+       _("Normally, there is only a single key associated with an email\n"
+	 "address.  However, people sometimes generate a new key if\n"
+	 "their key is too old or they think it might be compromised.\n"
+	 "Alternatively, a new key may indicate a man-in-the-middle attack!\n"
+	 "Before accepting this key, you should talk to or call the person\n"
+	 "to make sure this new key is legitimate.\n"));
+
+    es_fputc ('\n', fp);
+    choices = _("gGaArRbB");
+    es_fprintf (fp, _("(G)ood/(A)ccept once/(R)eject once/(B)ad? "));
+
+    /* Add a NUL terminator.  */
+    es_fputc (0, fp);
+    if (es_fclose_snatch (fp, (void **) &prompt, NULL))
+      log_fatal ("error snatching memory stream\n");
+
+    while (1)
+      {
+	char *response;
+
+	if (strlen (choices) != 8)
+	  log_bug ("Bad TOFU conflict translation!  Please report.");
+
+	response = cpr_get ("tofu conflict", prompt);
+	trim_spaces(response);
+	cpr_kill_prompt();
+	if (strlen (response) == 1)
+	  {
+	    char *choice = strchr (choices, *response);
+	    if (choice)
+	      {
+		int c = ((size_t) choice - (size_t) choices) / 2;
+		assert (0 <= c && c <= 3);
+
+		switch (c)
+		  {
+		  case 0: /* Good.  */
+		    policy = TOFU_BINDING_GOOD;
+		    trust_level = tofu_binding_policy_to_trust (policy);
+		    if (record_binding (db, fingerprint, email, user_id,
+					policy, 0) != 0)
+		      /* If there's an error registering the
+			 binding, don't save the signature.  */
+		      trust_level = GET_TRUST_ERROR;
+		    break;
+		  case 1: /* Accept once.  */
+		    policy = TOFU_BINDING_ASK;
+		    trust_level =
+		      tofu_binding_policy_to_trust (TOFU_BINDING_GOOD);
+		    if (record_binding (db, fingerprint, email, user_id,
+					policy, 0) != 0)
+		      trust_level = GET_TRUST_ERROR;
+		    break;
+		  case 2: /* Reject once.  */
+		    policy = TOFU_BINDING_ASK;
+		    trust_level =
+		      tofu_binding_policy_to_trust (TOFU_BINDING_BAD);
+		    if (record_binding (db, fingerprint, email, user_id,
+					policy, 0) != 0)
+		      trust_level = GET_TRUST_ERROR;
+		    break;
+		  case 3: /* Bad.  */
+		    policy = TOFU_BINDING_BAD;
+		    trust_level = tofu_binding_policy_to_trust (policy);
+		    if (record_binding (db, fingerprint, email, user_id,
+					policy, 0) != 0)
+		      trust_level = GET_TRUST_ERROR;
+		    break;
+		  default:
+		    log_bug ("c should be between 0 and 3 but it is %d!", c);
+		  }
+
+		break;
+	      }
+	  }
+	xfree (response);
+      }
+
+    xfree (prompt);
+
+    signature_stats_free (stats);
+  }
+
+ out:
+  free_strlist (fingerprints);
+
+  return trust_level;
+}
+
+
+int
+tofu_register (const byte *fingerprint_bin, const char *user_id,
+	       const byte *sig_digest_bin, int sig_digest_bin_len,
+	       time_t sig_time, const char *origin, int may_ask)
+{
+  sqlite3 *db;
+  char fingerprint[MAX_FINGERPRINT_LEN * 2 + 1];
+  char *email = NULL;
+  char *err = NULL;
+  int rc;
+  int trust_level = TRUST_UNKNOWN;
+  char *sig_digest;
+  unsigned long c;
+
+  db = opendb ();
+  if (! db)
+    {
+      log_info ("TOFU: Error opening DB.\n");
+      goto die;
+    }
+
+  bin2hex (fingerprint_bin, MAX_FINGERPRINT_LEN, fingerprint);
+
+  if (! *user_id)
+    {
+      log_info ("TOFU: user id is empty.  Can't continue.\n");
+      goto die;
+    }
+
+  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));
+
+  if (! origin)
+    /* The default origin is simply "unknown".  */
+    origin = "unknown";
+
+  trust_level = get_trust (db, fingerprint, email, user_id, may_ask);
+  if (trust_level == GET_TRUST_ERROR)
+    /* An error.  */
+    {
+      trust_level = TRUST_UNKNOWN;
+      goto die;
+    }
+
+  /* Save the observed signature in the DB.  */
+  sig_digest = make_radix64_string (sig_digest_bin, sig_digest_bin_len);
+
+  /* We do a query and then an insert.  Make sure they are atomic
+     by wrapping them in a transaction.  */
+  rc = sqlite3_exec (db, "begin transaction;", NULL, NULL, &err);
+  if (rc)
+    {
+      log_error ("Error beginning transaction on TOFU database: %s\n", err);
+      sqlite3_free (err);
+      goto die;
+    }
+
+  /* If we've already seen this signature before, then don't add
+     it again.  */
+  rc = sqlite3_exec_printf
+    (db, get_single_unsigned_long_cb, &c, &err,
+     "select count (*)\n"
+     " from signatures left join bindings\n"
+     "  on signatures.binding = bindings.oid\n"
+     " where fingerprint = %Q and email = %Q and sig_time = 0x%lx\n"
+     "  and sig_digest = %Q",
+     fingerprint, email, (unsigned long) sig_time, sig_digest);
+  if (rc)
+    {
+      log_error ("Error reading from SIGNATURES database"
+		 " (checking existence): %s\n",
+		 err);
+      sqlite3_free (err);
+    }
+  else if (c > 1)
+    /* Duplicates!  This should not happen.  In particular,
+       because <fingerprint, email, sig_time, sig_digest> is the
+       primary key!  */
+    log_debug ("SIGNATURES DB contains duplicate records"
+	       " <key: %s, %s, time: 0x%lx, sig: %s, %s>."
+	       "  Please report.\n",
+	       fingerprint, email, (unsigned long) sig_time,
+	       sig_digest, origin);
+  else if (c == 1)
+    log_debug ("Already observed the signature"
+	       " <key: %s, %s, time: 0x%lx, sig: %s, %s>\n",
+	       fingerprint, email, (unsigned long) sig_time,
+	       sig_digest, origin);
+  else
+    /* This is the first time that we've seen this signature.
+       Record it.  */
+    {
+      log_debug ("TOFU: Saving signature <%s, %s, %s>\n",
+		 fingerprint, email, sig_digest);
+
+      assert (c == 0);
+
+      rc = sqlite3_exec_printf
+	(db, NULL, NULL, &err,
+	 "insert into signatures\n"
+	 " (binding, sig_digest, origin, sig_time, time)\n"
+	 " values\n"
+	 " ((select oid from bindings\n"
+	 "    where fingerprint = %Q and email = %Q),\n"
+	 "  %Q, %Q, 0x%lx, strftime('%%s', 'now'));",
+	 fingerprint, email, sig_digest, origin, (unsigned long) sig_time);
+      if (rc)
+	{
+	  log_error ("TOFU: Error updating DB"
+		     " (inserting into signatures table): %s\n",
+		     err);
+	  sqlite3_free (err);
+	}
+    }
+
+  /* It only matters whether we abort or commit the transaction
+     (so long as we do something) if we execute the insert.  */
+  if (rc)
+    rc = sqlite3_exec (db, "abort transaction;", NULL, NULL, &err);
+  else
+    rc = sqlite3_exec (db, "commit transaction;", NULL, NULL, &err);
+  if (rc)
+    {
+      log_error ("Error ending transaction on TOFU database: %s\n", err);
+      sqlite3_free (err);
+      goto die;
+    }
+
+ die:
+  xfree (email);
+  closedb (db);
+
+  return trust_level;
+}
+
+int
+tofu_wot_trust_combine (int tofu_base, int wot_base)
+{
+  int tofu = tofu_base & TRUST_MASK;
+  int wot = wot_base & TRUST_MASK;
+  int upper = (tofu_base & ~TRUST_MASK) | (wot_base & ~TRUST_MASK);
+
+  assert (tofu == TRUST_UNKNOWN
+	  || tofu == TRUST_EXPIRED
+	  || tofu == TRUST_UNDEFINED
+	  || tofu == TRUST_NEVER
+	  || tofu == TRUST_MARGINAL
+	  || tofu == TRUST_FULLY
+	  || tofu == TRUST_ULTIMATE);
+  assert (wot == TRUST_UNKNOWN
+	  || wot == TRUST_EXPIRED
+	  || wot == TRUST_UNDEFINED
+	  || wot == TRUST_NEVER
+	  || wot == TRUST_MARGINAL
+	  || wot == TRUST_FULLY
+	  || wot == TRUST_ULTIMATE);
+
+  /* We first consider negative trust policys.  These trump positive
+     trust policies.  */
+  if (tofu == TRUST_NEVER || wot == TRUST_NEVER)
+    /* TRUST_NEVER trumps everything else.  */
+    return upper | TRUST_NEVER;
+  if (tofu == TRUST_EXPIRED || wot == TRUST_EXPIRED)
+    /* TRUST_EXPIRED trumps everything but TRUST_NEVER.  */
+    return upper | TRUST_EXPIRED;
+
+  /* Now we only have positive or neutral trust policies.  We take
+     the max.  */
+  if (tofu == TRUST_ULTIMATE || wot == TRUST_ULTIMATE)
+    return upper | TRUST_ULTIMATE;
+  if (tofu == TRUST_FULLY || wot == TRUST_FULLY)
+    return upper | TRUST_FULLY;
+  if (tofu == TRUST_MARGINAL || wot == TRUST_MARGINAL)
+    return upper | TRUST_MARGINAL;
+  if (tofu == TRUST_UNDEFINED || wot == TRUST_UNDEFINED)
+    return upper | TRUST_UNDEFINED;
+  return upper | TRUST_UNKNOWN;
+}
+
+int
+tofu_get_validity (const byte *fingerprint_bin, const char *user_id,
+		   int may_ask)
+{
+  sqlite3 *db;
+  char fingerprint[MAX_FINGERPRINT_LEN * 2 + 1];
+  char *email = NULL;
+  int trust_level = TRUST_UNDEFINED;
+
+  db = opendb ();
+  if (! db)
+    {
+      log_info ("TOFU: Error opening DB.\n");
+      goto die;
+    }
+
+  bin2hex (fingerprint_bin, MAX_FINGERPRINT_LEN, fingerprint);
+
+  if (! *user_id)
+    {
+      log_info ("TOFU: user id is empty.  Can't continue.\n");
+      goto die;
+    }
+
+  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));
+
+  trust_level = get_trust (db, fingerprint, email, user_id, may_ask);
+  if (trust_level == GET_TRUST_ERROR)
+    /* An error.  */
+    trust_level = TRUST_UNKNOWN;
+
+ die:
+  xfree (email);
+  closedb (db);
+
+  return trust_level;
+}
+
+int
+tofu_set_policy (kbnode_t kb, enum tofu_binding_policy policy)
+{
+  sqlite3 *db = opendb ();
+  PKT_public_key *pk;
+  char fingerprint_bin[MAX_FINGERPRINT_LEN];
+  size_t fingerprint_bin_len = sizeof (fingerprint_bin);
+  char fingerprint[MAX_FINGERPRINT_LEN * 2 + 1];
+
+  assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
+  pk = kb->pkt->pkt.public_key;
+
+  log_info ("Setting TOFU policy for %s to %s\n",
+	    keystr (pk->keyid), tofu_binding_policy_str (policy));
+  if (! (pk->main_keyid[0] == pk->keyid[0]
+	 && 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));
+
+  bin2hex (fingerprint_bin, MAX_FINGERPRINT_LEN, fingerprint);
+
+  for (; kb; kb = kb->next)
+    {
+      PKT_user_id *user_id;
+      char *email;
+
+      if (kb->pkt->pkttype != PKT_USER_ID)
+	continue;
+
+      user_id = kb->pkt->pkt.user_id;
+      if (user_id->is_revoked || user_id->is_expired)
+	/* Skip revoked or expired user ids.  */
+	continue;
+
+      email = mailbox_from_userid (user_id->name);
+      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->name));
+
+      record_binding (db, fingerprint, email, user_id->name, policy, 1);
+
+      xfree (email);
+    }
+
+  closedb (db);
+
+  return 0;
+}
+
+int
+tofu_set_policy_by_keyid (u32 *keyid, enum tofu_binding_policy policy)
+{
+  kbnode_t keyblock = get_pubkeyblock (keyid);
+  if (! keyblock)
+    return GPG_ERR_NO_PUBKEY;
+
+  return tofu_set_policy (keyblock, policy);
+}
diff --git a/g10/tofu.h b/g10/tofu.h
new file mode 100644
index 0000000..846add2
--- /dev/null
+++ b/g10/tofu.h
@@ -0,0 +1,64 @@
+#ifndef G10_TOFU_H
+#define G10_TOFU_H
+
+#include <config.h>
+
+/* For each binding, we have a trust policy.  There are four
+   possible trust policies.  */
+enum tofu_binding_policy
+  {
+    /* The user explicitly marked the binding as bad.  In this case,
+       we always return TRUST_NEVER.  */
+    TOFU_BINDING_BAD = -1,
+
+    /* The user deferred a definitive policy decision about the
+       binding (by selecting accept once or reject once).  The next
+       time we see this binding, we should ask the user what to
+       do.  */
+    TOFU_BINDING_ASK = 0,
+
+    /* We made a default policy decision.  This is only done if there
+       is no conflict with another binding (that is, the email address
+       is not part of another known key).  The default policy is
+       configurable (and specified using: --tofu-default-policy).
+
+       Note: when using the default policy, we save TOFU_BINDING_AUTO
+       with the binding, not the policy that was in effect.  This way,
+       if the user invokes gpg again, but with a different value for
+       --tofu-default-policy, a different decision is made.  */
+    TOFU_BINDING_AUTO = 1,
+
+    /* The user explicitly marked the binding as good.  In this case,
+       we return TRUST_FULLY.  */
+    TOFU_BINDING_GOOD = 2
+  };
+
+/* Return a string representation of a trust policy.  Returns
+   "unknown" if POLICY is not valid.  */
+const char *tofu_binding_policy_str (enum tofu_binding_policy policy);
+
+/* Convert a trust policy (e.g., TOFU_BINDING_BAD) to a trust value
+   (e.g., TRUST_BAD) in light of the current configuration.  */
+int tofu_binding_policy_to_trust (enum tofu_binding_policy policy);
+
+
+/* PRIMARY_KEY_FINGERPRINT should be MAX_FINGERPRINT_LEN bytes
+   long.  */
+int tofu_register (const byte *primary_key_fingerprint, const char *name,
+		   const byte *sigs_digest, int sigs_digest_len,
+		   time_t sig_time, const char *origin, int may_ask);
+
+/* Determine the validity (TRUST_NEVER, etc.) of a given binding.  */
+int tofu_get_validity (const byte *fingerprint_bin, const char *user_id,
+		       int may_ask);
+
+int tofu_set_policy (kbnode_t kb, enum tofu_binding_policy policy);
+
+int tofu_set_policy_by_keyid (u32 *keyid, enum tofu_binding_policy policy);
+
+
+/* When in PGP+WOT mode, we need to combine TOFU and WOT trust
+   policies.  */
+int tofu_wot_trust_combine (int tofu, int wot);
+
+#endif
diff --git a/g10/trust.c b/g10/trust.c
index 316fe2f..38d957e 100644
--- a/g10/trust.c
+++ b/g10/trust.c
@@ -152,7 +152,7 @@ uid_trust_string_fixed (PKT_public_key *key, PKT_user_id *uid)
     return                         _("[ expired]");
   else if(key)
     {
-      switch (get_validity(key,uid)&TRUST_MASK)
+      switch (get_validity (key, uid, NULL, 0) & TRUST_MASK)
         {
         case TRUST_UNKNOWN:   return _("[ unknown]");
         case TRUST_EXPIRED:   return _("[ expired]");
@@ -298,7 +298,8 @@ check_or_update_trustdb (void)
  * otherwise, a reasonable value for the entire key is returned.
  */
 unsigned int
-get_validity (PKT_public_key *pk, PKT_user_id *uid)
+get_validity (PKT_public_key *pk, PKT_user_id *uid, PKT_signature *sig,
+	      int may_ask)
 {
   int rc;
   unsigned int validity;
@@ -330,7 +331,7 @@ get_validity (PKT_public_key *pk, PKT_user_id *uid)
 #ifdef NO_TRUST_MODELS
   validity = TRUST_UNKNOWN;
 #else
-  validity = tdb_get_validity_core (pk, uid, main_pk);
+  validity = tdb_get_validity_core (pk, uid, main_pk, sig, may_ask);
 #endif
 
  leave:
@@ -359,7 +360,7 @@ get_validity_info (PKT_public_key *pk, PKT_user_id *uid)
   if (!pk)
     return '?';  /* Just in case a NULL PK is passed.  */
 
-  trustlevel = get_validity (pk, uid);
+  trustlevel = get_validity (pk, uid, NULL, 0);
   if ((trustlevel & TRUST_FLAG_REVOKED))
     return 'r';
   return trust_letter (trustlevel);
@@ -374,7 +375,7 @@ get_validity_string (PKT_public_key *pk, PKT_user_id *uid)
   if (!pk)
     return "err";  /* Just in case a NULL PK is passed.  */
 
-  trustlevel = get_validity (pk, uid);
+  trustlevel = get_validity (pk, uid, NULL, 0);
   if ((trustlevel & TRUST_FLAG_REVOKED))
     return _("revoked");
   return trust_value_to_string (trustlevel);
diff --git a/g10/trustdb.c b/g10/trustdb.c
index b16682d..eb72ecf 100644
--- a/g10/trustdb.c
+++ b/g10/trustdb.c
@@ -40,6 +40,7 @@
 #include "i18n.h"
 #include "tdbio.h"
 #include "trustdb.h"
+#include "tofu.h"
 
 
 typedef struct key_item **KeyHashTable; /* see new_key_hash_table() */
@@ -379,6 +380,8 @@ trust_model_string(void)
     case TM_CLASSIC:  return "classic";
     case TM_PGP:      return "PGP";
     case TM_EXTERNAL: return "external";
+    case TM_TOFU:     return "TOFU";
+    case TM_TOFU_PGP: return "TOFU+PGP";
     case TM_ALWAYS:   return "always";
     case TM_DIRECT:   return "direct";
     default:          return "unknown";
@@ -963,16 +966,21 @@ tdb_check_trustdb_stale (void)
 
 /*
  * Return the validity information for PK.  This is the core of
- * get_validity.
+ * get_validity.  If SIG is not NULL, then the trust is being
+ * evaluated in the context of the provided signature.  This is used
+ * by the TOFU code to record statistics.
  */
 unsigned int
 tdb_get_validity_core (PKT_public_key *pk, PKT_user_id *uid,
-                       PKT_public_key *main_pk)
+                       PKT_public_key *main_pk,
+		       PKT_signature *sig,
+		       int may_ask)
 {
   TRUSTREC trec, vrec;
   gpg_error_t err;
   ulong recno;
-  unsigned int validity;
+  unsigned int tofu_validity = TRUST_UNKNOWN;
+  unsigned int validity = TRUST_UNKNOWN;
 
   init_trustdb ();
 
@@ -993,60 +1001,146 @@ tdb_get_validity_core (PKT_public_key *pk, PKT_user_id *uid,
       goto leave;
     }
 
-  err = read_trust_record (main_pk, &trec);
-  if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
-    {
-      tdbio_invalid ();
-      return 0;
-    }
-  if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
+  if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP)
     {
-      /* No record found.  */
-      validity = TRUST_UNKNOWN;
-      goto leave;
-    }
+      kbnode_t user_id_node;
+      int user_ids = 0;
+      int user_ids_expired = 0;
 
-  /* Loop over all user IDs */
-  recno = trec.r.trust.validlist;
-  validity = 0;
-  while (recno)
-    {
-      read_record (recno, &vrec, RECTYPE_VALID);
+      char fingerprint[MAX_FINGERPRINT_LEN];
+      size_t fingerprint_len = sizeof (fingerprint);
+
+      fingerprint_from_pk (pk, fingerprint, &fingerprint_len);
+      assert (fingerprint_len == sizeof (fingerprint));
 
-      if(uid)
+      /* If the caller didn't supply a user id then iterate over all
+	 uids.  */
+      if (! uid)
+	user_id_node = get_pubkeyblock (pk->main_keyid);
+
+      while (uid
+	     || (user_id_node = find_next_kbnode (user_id_node, PKT_USER_ID)))
 	{
-	  /* If a user ID is given we return the validity for that
-	     user ID ONLY.  If the namehash is not found, then there
-	     is no validity at all (i.e. the user ID wasn't
-	     signed). */
-	  if(memcmp(vrec.r.valid.namehash,uid->namehash,20)==0)
+	  unsigned int tl;
+	  PKT_user_id *user_id;
+
+	  if (uid)
+	    user_id = uid;
+	  else
+	    user_id = user_id_node->pkt->pkt.user_id;
+
+	  if (user_id->is_revoked || user_id->is_expired)
+	    /* If the user id is revoked or expired, then skip it.  */
 	    {
-	      validity=(vrec.r.valid.validity & TRUST_MASK);
-	      break;
+	      char *s;
+	      if (user_id->is_revoked && user_id->is_expired)
+		s = "revoked and expired";
+	      else if (user_id->is_revoked)
+		s = "revoked";
+	      else
+		s = "expire";
+
+	      log_info ("TOFU: Ignoring %s user id (%s)\n", s, user_id->name);
+
+	      continue;
 	    }
+
+	  user_ids ++;
+
+	  if (sig)
+	    tl = tofu_register (fingerprint, user_id->name,
+				sig->digest, sig->digest_len,
+				sig->timestamp, "unknown",
+				may_ask);
+	  else
+	    tl = tofu_get_validity (fingerprint, user_id->name, may_ask);
+
+	  if (tl == TRUST_EXPIRED)
+	    user_ids_expired ++;
+	  else if (tl == TRUST_UNDEFINED || tl == TRUST_UNKNOWN)
+	    ;
+	  else if (tl == TRUST_NEVER)
+	    tofu_validity = TRUST_NEVER;
+	  else
+	    {
+	      assert (tl == TRUST_MARGINAL
+		      || tl == TRUST_FULLY
+		      || tl == TRUST_ULTIMATE);
+
+	      if (tl > tofu_validity)
+		/* XXX: We we really want the max?  */
+		tofu_validity = tl;
+	    }
+
+	  if (uid)
+	    /* If the caller specified a user id, then we stop
+	       now.  */
+	    break;
 	}
-      else
+    }
+
+  if (opt.trust_model == TM_TOFU_PGP
+      || opt.trust_model == TM_CLASSIC
+      || opt.trust_model == TM_PGP)
+    {
+      err = read_trust_record (main_pk, &trec);
+      if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
 	{
-	  /* If no namehash is given, we take the maximum validity
-	     over all user IDs */
-	  if ( validity < (vrec.r.valid.validity & TRUST_MASK) )
-	    validity = (vrec.r.valid.validity & TRUST_MASK);
+	  tdbio_invalid ();
+	  return 0;
+	}
+      if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
+	{
+	  /* No record found.  */
+	  validity = TRUST_UNKNOWN;
+	  goto leave;
 	}
 
-      recno = vrec.r.valid.next;
-    }
+      /* Loop over all user IDs */
+      recno = trec.r.trust.validlist;
+      validity = 0;
+      while (recno)
+	{
+	  read_record (recno, &vrec, RECTYPE_VALID);
 
-  if ( (trec.r.trust.ownertrust & TRUST_FLAG_DISABLED) )
-    {
-      validity |= TRUST_FLAG_DISABLED;
-      pk->flags.disabled = 1;
+	  if(uid)
+	    {
+	      /* If a user ID is given we return the validity for that
+		 user ID ONLY.  If the namehash is not found, then
+		 there is no validity at all (i.e. the user ID wasn't
+		 signed). */
+	      if(memcmp(vrec.r.valid.namehash,uid->namehash,20)==0)
+		{
+		  validity=(vrec.r.valid.validity & TRUST_MASK);
+		  break;
+		}
+	    }
+	  else
+	    {
+	      /* If no user ID is given, we take the maximum validity
+		 over all user IDs */
+	      if (validity < (vrec.r.valid.validity & TRUST_MASK))
+		validity = (vrec.r.valid.validity & TRUST_MASK);
+	    }
+
+	  recno = vrec.r.valid.next;
+	}
+
+      if ((trec.r.trust.ownertrust & TRUST_FLAG_DISABLED))
+	{
+	  validity |= TRUST_FLAG_DISABLED;
+	  pk->flags.disabled = 1;
+	}
+      else
+	pk->flags.disabled = 0;
+      pk->flags.disabled_valid = 1;
     }
-  else
-    pk->flags.disabled = 0;
-  pk->flags.disabled_valid = 1;
 
  leave:
-  if (pending_check_trustdb)
+  validity = tofu_wot_trust_combine (tofu_validity, validity);
+
+  if (opt.trust_model != TM_TOFU
+      && pending_check_trustdb)
     validity |= TRUST_FLAG_PENDING_CHECK;
 
   return validity;
diff --git a/g10/trustdb.h b/g10/trustdb.h
index 771a821..0a0fbec 100644
--- a/g10/trustdb.h
+++ b/g10/trustdb.h
@@ -30,6 +30,7 @@
 #define TRUST_MARGINAL	  4  /* m: marginally trusted */
 #define TRUST_FULLY	  5  /* f: fully trusted      */
 #define TRUST_ULTIMATE	  6  /* u: ultimately trusted */
+#define TRUST_ASK	 31  /* This is only used for TOFU.  */
 /* trust values not covered by the mask */
 #define TRUST_FLAG_REVOKED 32 /* r: revoked */
 #define TRUST_FLAG_SUB_REVOKED 64 /* r: revoked but for subkeys */
@@ -86,7 +87,8 @@ void revalidation_mark (void);
 void check_trustdb_stale (void);
 void check_or_update_trustdb (void);
 
-unsigned int get_validity (PKT_public_key *pk, PKT_user_id *uid);
+unsigned int get_validity (PKT_public_key *pk, PKT_user_id *uid,
+			   PKT_signature *sig, int may_ask);
 int get_validity_info (PKT_public_key *pk, PKT_user_id *uid);
 const char *get_validity_string (PKT_public_key *pk, PKT_user_id *uid);
 
@@ -120,7 +122,8 @@ void tdb_check_or_update (void);
 int tdb_cache_disabled_value (PKT_public_key *pk);
 
 unsigned int tdb_get_validity_core (PKT_public_key *pk, PKT_user_id *uid,
-                                    PKT_public_key *main_pk);
+                                    PKT_public_key *main_pk,
+				    PKT_signature *sig, int may_askx);
 
 void list_trust_path( const char *username );
 int enum_cert_paths( void **context, ulong *lid,

-----------------------------------------------------------------------

Summary of changes:
 agent/pksign.c          |  42 ++++-
 common/exechelp-posix.c |  62 ++++++--
 common/exechelp-w32.c   |  76 +++++----
 common/exechelp-w32ce.c |   2 +-
 common/exechelp.h       |  40 +++--
 common/ssh-utils.c      |   2 +
 common/t-ssh-utils.c    |  19 +++
 common/util.h           |   4 +
 doc/mkdefsinc.c         |  10 +-
 g10/gpg.c               | 103 ++++++++++---
 g10/gpgv.c              |   5 +-
 g10/keyedit.c           |   5 +-
 g10/keylist.c           |   2 +-
 g10/mainproc.c          |  11 +-
 g10/options.h           |   4 +-
 g10/pkclist.c           |  84 +---------
 g10/sig-check.c         |  30 ++--
 g10/sign.c              |  30 ----
 g10/test-stubs.c        |   5 +-
 g10/tofu.c              | 398 +++++++++++++++++++++++++++++++++---------------
 g10/tofu.h              |  51 ++++---
 g10/trust.c             |  11 +-
 g10/trustdb.c           | 111 ++++++++------
 g10/trustdb.h           |   6 +-
 scd/apdu.c              |  10 +-
 25 files changed, 705 insertions(+), 418 deletions(-)


hooks/post-receive
-- 
The GNU Privacy Guard
http://git.gnupg.org




More information about the Gnupg-commits mailing list