g10: Record and show statistics for encrypted messages when using TOFU
authorNeal H. Walfield <neal@g10code.com>
Tue, 6 Sep 2016 13:45:38 +0000 (15:45 +0200)
committerNeal H. Walfield <neal@g10code.com>
Tue, 6 Sep 2016 19:37:48 +0000 (21:37 +0200)
* g10/tofu.c: Include "sqrtu32.h".
(struct tofu_dbs_s.s): Rename get_trust_gather_other_keys to
get_trust_gather_signature_stats.  Add new field
get_trust_gather_encryption_stats.
(initdb): Create the encryptions table.
(ask_about_binding): Show the encryption statistics too.
(tofu_register): Rename from this...
(tofu_register_signature): ... to this and update callers.
(tofu_register_encryption): New function.
(write_stats_status): Add parameters encryption_count,
encryption_first_done and encryption_most_recent.  Update callers.
Compute the trust using the euclidean distance of the signature and
signature count.  Compare with twice the threshold.  Include
encryption count information in the TFS and TOFU_STATS lines.
(show_statistics): Also get information about the encrypted messages.
* g10/trustdb.c (tdb_get_validity_core): Use it.

--
Signed-off-by: Neal H. Walfield <neal@g10code.com>
doc/DETAILS
g10/Makefile.am
g10/pkclist.c
g10/tofu.c
g10/tofu.h
g10/trustdb.c

index cf779d2..b5431d0 100644 (file)
@@ -238,8 +238,10 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB:
     - Field 4 :: signcount - The number of signatures seen.
     - Field 5 :: encrcount - The number of encryptions done.
     - Field 6 :: policy - A string with the policy
-    - Field 7 :: first-seen - a timestamp or 0 if not known.
-    - Field 8 :: most-recent-seen - a timestamp or 0 if not known.
+    - Field 7 :: signture-first-seen - a timestamp or 0 if not known.
+    - Field 8 :: signature-most-recent-seen - a timestamp or 0 if not known.
+    - Field 9 :: encryption-first-done - a timestamp or 0 if not known.
+    - Field 10 :: encryption-most-recent-done - a timestamp or 0 if not known.
 
 *** TRU - Trust database information
     Example for a "tru" trust base record:
@@ -715,7 +717,7 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB:
     userid encoded in UTF-8 and percent escaped.  The fingerprint is
     indentical for all TOFU_USER lines up to a NEWSIG line.
 
-*** TOFU_STATS <validity> <sign-count> 0 [<policy> [<tm1> <tm2>]]
+*** TOFU_STATS <validity> <sign-count> 0 [<policy> [<tm1> <tm2> <tm3> <tm4>]]
 
     Statistics for the current user id.
 
@@ -734,9 +736,11 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB:
     - ask     :: Policy is "ask"
     - unknown :: Policy is not known.
 
-    TM1 ist the time the first messages was verified.  TM2 is the time
-    the most recent message was verified.  Both may either be seconds
-    since Epoch or an ISO time string (yyyymmddThhmmss).
+    TM1 ist the time the first message was verified.  TM2 is the time
+    the most recent message was verified.  TM3 is the time the first
+    message was encrypted.  TM4 is the most recent encryption. All may
+    either be seconds since Epoch or an ISO time string
+    (yyyymmddThhmmss).
 
 *** TOFU_STATS_SHORT <long_string>
 
index fc33e83..7b87e6a 100644 (file)
@@ -74,7 +74,7 @@ trust_source = trustdb.c trustdb.h tdbdump.c tdbio.c tdbio.h
 endif
 
 if USE_TOFU
-tofu_source = tofu.h tofu.c gpgsql.c gpgsql.h
+tofu_source = tofu.h tofu.c gpgsql.c gpgsql.h sqrtu32.c sqrtu32.h
 else
 tofu_source =
 endif
index f7b2483..62f5b7f 100644 (file)
@@ -1314,6 +1314,29 @@ build_pk_list (ctrl_t ctrl, strlist_t rcpts, PK_LIST *ret_pk_list)
       rc = GPG_ERR_NO_USER_ID;
     }
 
+#ifdef USE_TOFU
+  if (! rc && (opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU))
+    {
+      PK_LIST iter;
+      for (iter = pk_list; iter; iter = iter->next)
+        {
+          int rc2;
+
+          /* Note: we already resolved any conflict when looking up
+             the key.  Don't annoy the user again if she selected
+             accept once.  */
+          rc2 = tofu_register_encryption (ctrl, iter->pk, NULL, 0);
+          if (rc2)
+            log_info ("WARNING: Failed to register encryption to %s"
+                      " with TOFU engine\n",
+                      keystr (pk_main_keyid (iter->pk)));
+          else if (DBG_TRUST)
+            log_debug ("Registered encryption to %s with TOFU DB.\n",
+                      keystr (pk_main_keyid (iter->pk)));
+        }
+    }
+#endif /*USE_TOFU*/
+
  fail:
 
   if ( rc )
index 083f5ef..defc54f 100644 (file)
@@ -41,6 +41,7 @@
 #include "mkdir_p.h"
 #include "gpgsql.h"
 #include "status.h"
+#include "sqrtu32.h"
 
 #include "tofu.h"
 
@@ -76,7 +77,8 @@ struct tofu_dbs_s
     sqlite3_stmt *get_policy_select_policy_and_conflict;
     sqlite3_stmt *get_trust_bindings_with_this_email;
     sqlite3_stmt *get_trust_gather_other_user_ids;
-    sqlite3_stmt *get_trust_gather_other_keys;
+    sqlite3_stmt *get_trust_gather_signature_stats;
+    sqlite3_stmt *get_trust_gather_encryption_stats;
     sqlite3_stmt *register_already_seen;
     sqlite3_stmt *register_insert;
   } s;
@@ -649,6 +651,19 @@ initdb (sqlite3 *db)
     }
 
  out:
+  if (! rc)
+    {
+      /* Early version of the v1 format did not include the encryption
+         table.  Add it.  */
+      sqlite3_exec (db,
+                    "create table if not exists encryptions"
+                    " (binding INTEGER NOT NULL,"
+                    "  time INTEGER);"
+                    "create index if not exists encryptions_binding"
+                    " on encryptions (binding);\n",
+                    NULL, NULL, &err);
+    }
+
   if (rc)
     {
       rc = sqlite3_exec (db, "rollback;", NULL, NULL, &err);
@@ -1384,39 +1399,42 @@ ask_about_binding (ctrl_t ctrl,
   strlist_rev (&conflict_set);
   for (iter = conflict_set; iter && ! rc; iter = iter->next)
     {
+#define STATS_SQL(table, time, sign)                         \
+         "select fingerprint, policy, time_ago, count(*)\n" \
+         " from\n" \
+         "  (select bindings.*,\n" \
+         "     "sign" case\n" \
+         "       when delta ISNULL then 1\n" \
+         /* From the future (but if its just a couple of hours in the \
+          * future don't turn it into a warning)?  Or should we use \
+          * small, medium or large units?  (Note: whatever we do, we \
+          * keep the value in seconds.  Then when we group, everything \
+          * that rounds to the same number of seconds is grouped.)  */ \
+         "      when delta < -("STRINGIFY (TIME_AGO_FUTURE_IGNORE)") then 2\n" \
+         "      when delta < ("STRINGIFY (TIME_AGO_SMALL_THRESHOLD)")\n" \
+         "       then 3\n" \
+         "      when delta < ("STRINGIFY (TIME_AGO_MEDIUM_THRESHOLD)")\n" \
+         "       then 4\n" \
+         "      when delta < ("STRINGIFY (TIME_AGO_LARGE_THRESHOLD)")\n" \
+         "       then 5\n" \
+         "      else 6\n" \
+         "     end time_ago,\n" \
+         "    delta time_ago_raw\n" \
+         "   from bindings\n" \
+         "   left join\n" \
+         "     (select *,\n" \
+         "        cast(strftime('%s','now') - " time " as real) delta\n" \
+         "       from " table ") ss\n" \
+         "    on ss.binding = bindings.oid)\n" \
+         " where email = ? and fingerprint = ?\n" \
+         " group by time_ago\n" \
+         /* Make sure the current key is first.  */ \
+         " order by time_ago desc;\n"
+
       rc = gpgsql_stepx
-        (dbs->db, &dbs->s.get_trust_gather_other_keys,
+        (dbs->db, &dbs->s.get_trust_gather_signature_stats,
          signature_stats_collect_cb, &stats, &sqerr,
-         "select fingerprint, policy, time_ago, count(*)\n"
-         " from\n"
-         "  (select bindings.*,\n"
-         "     case\n"
-         "       when delta ISNULL then 1\n"
-         /* From the future (but if its just a couple of hours in the
-          * future don't turn it into a warning)?  Or should we use
-          * small, medium or large units?  (Note: whatever we do, we
-          * keep the value in seconds.  Then when we group, everything
-          * that rounds to the same number of seconds is grouped.)  */
-         "      when delta < -("STRINGIFY (TIME_AGO_FUTURE_IGNORE)") then 2\n"
-         "      when delta < ("STRINGIFY (TIME_AGO_SMALL_THRESHOLD)")\n"
-         "       then 3\n"
-         "      when delta < ("STRINGIFY (TIME_AGO_MEDIUM_THRESHOLD)")\n"
-         "       then 4\n"
-         "      when delta < ("STRINGIFY (TIME_AGO_LARGE_THRESHOLD)")\n"
-         "       then 5\n"
-         "      else 6\n"
-         "     end time_ago,\n"
-         "    delta time_ago_raw\n"
-         "   from bindings\n"
-         "   left join\n"
-         "     (select *,\n"
-         "        cast(strftime('%s','now') - sig_time as real) delta\n"
-         "       from signatures) ss\n"
-         "    on ss.binding = bindings.oid)\n"
-         " where email = ? and fingerprint = ?\n"
-         " group by time_ago\n"
-         /* Make sure the current key is first.  */
-         " order by time_ago desc;\n",
+         STATS_SQL ("signatures", "sig_time", ""),
          GPGSQL_ARG_STRING, email,
          GPGSQL_ARG_STRING, iter->d,
          GPGSQL_ARG_END);
@@ -1426,6 +1444,23 @@ ask_about_binding (ctrl_t ctrl,
       if (!stats || strcmp (iter->d, stats->fingerprint) != 0)
         /* No stats for this binding.  Add a dummy entry.  */
         signature_stats_prepend (&stats, iter->d, TOFU_POLICY_AUTO, 1, 1);
+
+      rc = gpgsql_stepx
+        (dbs->db, &dbs->s.get_trust_gather_encryption_stats,
+         signature_stats_collect_cb, &stats, &sqerr,
+         STATS_SQL ("encryptions", "time", "-"),
+         GPGSQL_ARG_STRING, email,
+         GPGSQL_ARG_STRING, iter->d,
+         GPGSQL_ARG_END);
+      if (rc)
+        break;
+
+#undef STATS_SQL
+
+      if (!stats || strcmp (iter->d, stats->fingerprint) != 0
+          || stats->time_ago > 0)
+        /* No stats for this binding.  Add a dummy entry.  */
+        signature_stats_prepend (&stats, iter->d, TOFU_POLICY_AUTO, -1, 1);
     }
   end_transaction (ctrl, 0);
   strlist_rev (&conflict_set);
@@ -1459,6 +1494,13 @@ ask_about_binding (ctrl_t ctrl,
                   email);
       for (stats_iter = stats; stats_iter; stats_iter = stats_iter->next)
         {
+#if 0
+          log_debug ("%s: time_ago: %ld; count: %ld\n",
+                     stats_iter->fingerprint,
+                     stats_iter->time_ago,
+                     stats_iter->count);
+#endif
+
           if (! key || strcmp (key, stats_iter->fingerprint))
             {
               int this_key;
@@ -1499,7 +1541,7 @@ ask_about_binding (ctrl_t ctrl,
               seen_in_past = 0;
             }
 
-          if (stats_iter->time_ago == 1)
+          if (abs(stats_iter->time_ago) == 1)
             {
               /* The 1 in this case is the NULL entry.  */
               log_assert (stats_iter->count == 1);
@@ -1510,12 +1552,18 @@ ask_about_binding (ctrl_t ctrl,
           es_fputs ("    ", fp);
           /* TANSLATORS: This string is concatenated with one of
            * the day/week/month strings to form one sentence.  */
-          es_fprintf (fp, ngettext("Verified %d message",
-                                   "Verified %d messages",
-                                   seen_in_past), seen_in_past);
+          if (stats_iter->time_ago > 0)
+            es_fprintf (fp, ngettext("Verified %d message",
+                                     "Verified %d messages",
+                                     seen_in_past), seen_in_past);
+          else
+            es_fprintf (fp, ngettext("Encrypted %d message",
+                                     "Encrypted %d messages",
+                                     seen_in_past), seen_in_past);
+
           if (!stats_iter->count)
             es_fputs (".", fp);
-          else if (stats_iter->time_ago == 2)
+          else if (abs(stats_iter->time_ago) == 2)
             {
               es_fprintf (fp, "in the future.");
               /* Reset it.  */
@@ -1523,25 +1571,25 @@ ask_about_binding (ctrl_t ctrl,
             }
           else
             {
-              if (stats_iter->time_ago == 3)
+              if (abs(stats_iter->time_ago) == 3)
                 es_fprintf (fp, ngettext(" over the past days.",
                                          " over the past %d days.",
                                          seen_in_past),
                             TIME_AGO_SMALL_THRESHOLD
                             / TIME_AGO_UNIT_SMALL);
-              else if (stats_iter->time_ago == 4)
+              else if (abs(stats_iter->time_ago) == 4)
                 es_fprintf (fp, ngettext(" over the past month.",
                                          " over the past %d months.",
                                          seen_in_past),
                             TIME_AGO_MEDIUM_THRESHOLD
                             / TIME_AGO_UNIT_MEDIUM);
-              else if (stats_iter->time_ago == 5)
+              else if (abs(stats_iter->time_ago) == 5)
                 es_fprintf (fp, ngettext(" over the past year.",
                                          " over the past %d years.",
                                          seen_in_past),
                             TIME_AGO_LARGE_THRESHOLD
                             / TIME_AGO_UNIT_LARGE);
-              else if (stats_iter->time_ago == 6)
+              else if (abs(stats_iter->time_ago) == 6)
                 es_fprintf (fp, _(" in the past."));
               else
                 log_assert (! "Broken SQL.\n");
@@ -2349,46 +2397,59 @@ time_ago_str (long long int t)
 /* If FP is NULL, write TOFU_STATS status line.  If FP is not NULL
  * write a "tfs" record to that stream. */
 static void
-write_stats_status (estream_t fp, long messages, enum tofu_policy policy,
-                    unsigned long first_seen,
-                    unsigned long most_recent_seen)
+write_stats_status (estream_t fp,
+                    enum tofu_policy policy,
+                    unsigned long signature_count,
+                    unsigned long signature_first_seen,
+                    unsigned long signature_most_recent,
+                    unsigned long encryption_count,
+                    unsigned long encryption_first_done,
+                    unsigned long encryption_most_recent)
 {
   const char *validity;
 
+  /* Use the euclidean distance rather then the sum of the magnitudes
+     to ensure a balance between verified signatures and encrypted
+     messages.  */
+  float messages = sqrtu32 (signature_count) + sqrtu32 (encryption_count);
+
   if (messages < 1)
     validity = "1"; /* Key without history.  */
-  else if (messages < BASIC_TRUST_THRESHOLD)
+  else if (messages < sqrtu32 (2 * BASIC_TRUST_THRESHOLD))
     validity = "2"; /* Key with too little history.  */
-  else if (messages < FULL_TRUST_THRESHOLD)
+  else if (messages < sqrtu32 (2 * FULL_TRUST_THRESHOLD))
     validity = "3"; /* Key with enough history for basic trust.  */
   else
     validity = "4"; /* Key with a lot of history.  */
 
   if (fp)
     {
-      es_fprintf (fp, "tfs:1:%s:%ld:0:%s:%lu:%lu:\n",
-                  validity, messages,
+      es_fprintf (fp, "tfs:1:%s:%ld:%ld:%s:%lu:%lu:%lu:%lu:\n",
+                  validity, signature_count, encryption_count,
                   tofu_policy_str (policy),
-                  first_seen, most_recent_seen);
+                  signature_first_seen, signature_most_recent,
+                  encryption_first_done, encryption_most_recent);
     }
   else
     {
       char numbuf1[35];
       char numbuf2[35];
       char numbuf3[35];
+      char numbuf4[35];
+      char numbuf5[35];
+      char numbuf6[35];
 
-      snprintf (numbuf1, sizeof numbuf1, " %ld", messages);
-      *numbuf2 = *numbuf3 = 0;
-      if (first_seen && most_recent_seen)
-        {
-          snprintf (numbuf2, sizeof numbuf2, " %lu", first_seen);
-          snprintf (numbuf3, sizeof numbuf3, " %lu", most_recent_seen);
-        }
+      snprintf (numbuf1, sizeof numbuf1, " %ld", signature_count);
+      snprintf (numbuf2, sizeof numbuf2, " %ld", encryption_count);
+      snprintf (numbuf3, sizeof numbuf3, " %lu", signature_first_seen);
+      snprintf (numbuf4, sizeof numbuf4, " %lu", signature_most_recent);
+      snprintf (numbuf5, sizeof numbuf5, " %lu", encryption_first_done);
+      snprintf (numbuf6, sizeof numbuf6, " %lu", encryption_most_recent);
 
       write_status_strings (STATUS_TOFU_STATS,
-                            validity, numbuf1, " 0",
+                            validity, numbuf1, numbuf2,
                             " ", tofu_policy_str (policy),
-                            numbuf2, numbuf3,
+                            numbuf3, numbuf4, numbuf5, numbuf6,
                             NULL);
     }
 }
@@ -2401,13 +2462,24 @@ show_statistics (tofu_dbs_t dbs, const char *fingerprint,
                 const char *email, const char *user_id,
                 estream_t outfp)
 {
+  unsigned long now = gnupg_get_time ();
+  enum tofu_policy policy = get_policy (dbs, fingerprint, email, NULL);
+
   char *fingerprint_pp;
   int rc;
   strlist_t strlist = NULL;
   char *err = NULL;
 
+  unsigned long signature_first_seen = 0;
+  unsigned long signature_most_recent = 0;
+  unsigned long signature_count = 0;
+  unsigned long encryption_first_done = 0;
+  unsigned long encryption_most_recent = 0;
+  unsigned long encryption_count = 0;
+
   fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
 
+  /* Get the signature stats.  */
   rc = gpgsql_exec_printf
     (dbs->db, strings_collect_cb, &strlist, &err,
      "select count (*), min (signatures.time), max (signatures.time)\n"
@@ -2423,191 +2495,217 @@ show_statistics (tofu_dbs_t dbs, const char *fingerprint,
       goto out;
     }
 
-  if (!outfp)
-    write_status_text_and_buffer (STATUS_TOFU_USER, fingerprint,
-                                  email, strlen (email), 0);
+  if (strlist)
+    {
+      log_assert (strlist->next);
+      log_assert (strlist->next->next);
+      log_assert (! strlist->next->next->next);
+
+      string_to_long (&signature_count, strlist->d, -1, __LINE__);
+      string_to_long (&signature_first_seen, strlist->next->d, -1, __LINE__);
+      string_to_long (&signature_most_recent,
+                      strlist->next->next->d, -1, __LINE__);
+
+      free_strlist (strlist);
+      strlist = NULL;
+    }
 
-  if (! strlist)
+  /* Get the encryption stats.  */
+  rc = gpgsql_exec_printf
+    (dbs->db, strings_collect_cb, &strlist, &err,
+     "select count (*), min (encryptions.time), max (encryptions.time)\n"
+     " from encryptions\n"
+     " left join bindings on encryptions.binding = bindings.oid\n"
+     " where fingerprint = %Q and email = %Q;",
+     fingerprint, email);
+  if (rc)
     {
-      if (!outfp)
-        log_info (_("Have never verified a message signed by key %s!\n"),
-                  fingerprint_pp);
-      write_stats_status (outfp, 0, TOFU_POLICY_NONE, 0, 0);
+      log_error (_("error reading TOFU database: %s\n"), err);
+      print_further_info ("getting statistics");
+      sqlite3_free (err);
+      goto out;
     }
-  else
+
+  if (strlist)
     {
-      unsigned long now = gnupg_get_time ();
-      signed long messages;
-      unsigned long first_seen;
-      unsigned long most_recent_seen;
+      log_assert (strlist->next);
+      log_assert (strlist->next->next);
+      log_assert (! strlist->next->next->next);
+
+      string_to_long (&encryption_count, strlist->d, -1, __LINE__);
+      string_to_long (&encryption_first_done, strlist->next->d, -1, __LINE__);
+      string_to_long (&encryption_most_recent,
+                      strlist->next->next->d, -1, __LINE__);
+
+      free_strlist (strlist);
+      strlist = NULL;
+    }
 
-      log_assert (strlist_length (strlist) == 3);
+  if (!outfp)
+    write_status_text_and_buffer (STATUS_TOFU_USER, fingerprint,
+                                  email, strlen (email), 0);
 
-      string_to_long (&messages, strlist->d, -1, __LINE__);
+  write_stats_status (outfp, policy,
+                      signature_count,
+                      signature_first_seen,
+                      signature_most_recent,
+                      encryption_count,
+                      encryption_first_done,
+                      encryption_most_recent);
 
-      if (messages == 0 && *strlist->next->d == '\0')
-        { /* min(NULL) => NULL => "".  */
-          first_seen = 0;
-          most_recent_seen = 0;
+  if (!outfp)
+    {
+      estream_t fp;
+      char *msg;
+
+      fp = es_fopenmem (0, "rw,samethread");
+      if (! fp)
+        log_fatal ("error creating memory stream: %s\n",
+                   gpg_strerror (gpg_error_from_syserror()));
+
+      if (signature_count == 0)
+        {
+          es_fprintf (fp, _("Verified %ld messages signed by \"%s\"."),
+                      0L, user_id);
+          es_fputc ('\n', fp);
         }
       else
-       {
-          string_to_ulong (&first_seen, strlist->next->d, -1, __LINE__);
-          if (first_seen > now)
-            {
-              log_debug ("time-warp - tofu DB has a future value (%lu, %lu)\n",
-                         first_seen, now);
-              first_seen = now;
-            }
-         string_to_ulong (&most_recent_seen, strlist->next->next->d, -1,
-                           __LINE__);
-          if (most_recent_seen > now)
+        {
+          char *first_seen_ago_str = time_ago_str (now - signature_first_seen);
+
+          /* TRANSLATORS: The final %s is replaced by a string like
+             "7 months, 1 day, 5 minutes, 0 seconds". */
+          es_fprintf (fp,
+                      ngettext("Verified %ld message signed by \"%s\"\n"
+                               "in the past %s.",
+                               "Verified %ld messages signed by \"%s\"\n"
+                               "in the past %s.",
+                               signature_count),
+                      signature_count, user_id, first_seen_ago_str);
+
+          if (signature_count > 1)
             {
-              log_debug ("time-warp - tofu DB has a future value (%lu, %lu)\n",
-                         most_recent_seen, now);
-              most_recent_seen = now;
+              char *tmpstr = time_ago_str (now - signature_most_recent);
+              es_fputs ("  ", fp);
+              es_fprintf (fp, _("The most recent message was"
+                                " verified %s ago."), tmpstr);
+              xfree (tmpstr);
             }
+          xfree (first_seen_ago_str);
+        }
 
-       }
+      es_fprintf (fp, "  ");
 
-      if (messages == -1 || first_seen == -1)
+      if (encryption_count == 0)
         {
-          write_stats_status (outfp, 0, TOFU_POLICY_NONE, 0, 0);
-          if (!outfp)
-            log_info (_("Failed to collect signature statistics for \"%s\"\n"
-                        "(key %s)\n"),
-                      user_id, fingerprint_pp);
-        }
-      else if (outfp)
-        {
-          write_stats_status (outfp, messages,
-                              get_policy (dbs, fingerprint, email, NULL),
-                              first_seen, most_recent_seen);
+          es_fprintf (fp, _("Encrypted %ld messages to \"%s\"."),
+                      0L, user_id);
+          es_fputc ('\n', fp);
         }
       else
-       {
-         enum tofu_policy policy = get_policy (dbs, fingerprint, email, NULL);
-         estream_t fp;
-         char *msg;
-
-          write_stats_status (NULL, messages,
-                              policy,
-                              first_seen, most_recent_seen);
-
-         fp = es_fopenmem (0, "rw,samethread");
-         if (! fp)
-            log_fatal ("error creating memory stream: %s\n",
-                       gpg_strerror (gpg_error_from_syserror()));
-
-         if (messages == 0)
+        {
+          char *first_done_ago_str = time_ago_str (now - encryption_first_done);
+
+          /* TRANSLATORS: The final %s is replaced by a string like
+             "7 months, 1 day, 5 minutes, 0 seconds". */
+          es_fprintf (fp,
+                      ngettext("Encrypted %ld message to \"%s\"\n"
+                               "in the past %s.",
+                               "Encrypted %ld messages to \"%s\"\n"
+                               "in the past %s.",
+                               encryption_count),
+                      encryption_count, user_id, first_done_ago_str);
+
+          if (encryption_count > 1)
             {
-              es_fprintf (fp, _("Verified %ld messages signed by \"%s\"."),
-                          0L, user_id);
-              es_fputc ('\n', fp);
+              char *tmpstr = time_ago_str (now - encryption_most_recent);
+              es_fputs ("  ", fp);
+              es_fprintf (fp, _("The most recent message was"
+                                " verified %s ago."), tmpstr);
+              xfree (tmpstr);
             }
-         else
-           {
-              char *first_seen_ago_str = time_ago_str (now - first_seen);
-
-              /* TRANSLATORS: The final %s is replaced by a string like
-                 "7 months, 1 day, 5 minutes, 0 seconds". */
-             es_fprintf (fp,
-                          ngettext("Verified %ld message signed by \"%s\"\n"
-                                   "in the past %s.",
-                                   "Verified %ld messages signed by \"%s\"\n"
-                                   "in the past %s.",
-                                   messages),
-                         messages, user_id, first_seen_ago_str);
-
-              if (messages > 1)
-                {
-                  char *tmpstr = time_ago_str (now - most_recent_seen);
-                  es_fputs ("  ", fp);
-                  es_fprintf (fp, _("The most recent message was"
-                                    " verified %s ago."), tmpstr);
-                  xfree (tmpstr);
-                }
-              xfree (first_seen_ago_str);
+          xfree (first_done_ago_str);
+        }
 
-              if (opt.verbose)
-                {
-                  es_fputs ("  ", fp);
-                  es_fputc ('(', fp);
-                  es_fprintf (fp, _("policy: %s"), tofu_policy_str (policy));
-                  es_fputs (")\n", fp);
-                }
-              else
-                es_fputs ("\n", fp);
-            }
+      if (opt.verbose)
+        {
+          es_fputs ("  ", fp);
+          es_fputc ('(', fp);
+          es_fprintf (fp, _("policy: %s"), tofu_policy_str (policy));
+          es_fputs (")\n", fp);
+        }
+      else
+        es_fputs ("\n", fp);
 
-          {
-            char *tmpmsg, *p;
-            es_fputc (0, fp);
-            if (es_fclose_snatch (fp, (void **) &tmpmsg, NULL))
-              log_fatal ("error snatching memory stream\n");
-            msg = format_text (tmpmsg, 0, 72, 80);
-            es_free (tmpmsg);
-
-            /* Print a status line but suppress the trailing LF.
-             * Spaces are not percent escaped. */
-            if (*msg)
-              write_status_buffer (STATUS_TOFU_STATS_LONG,
-                                   msg, strlen (msg)-1, -1);
-
-            /* Remove the non-breaking space markers.  */
-            for (p=msg; *p; p++)
-              if (*p == '~')
-                *p = ' ';
 
-          }
+      {
+        char *tmpmsg, *p;
+        es_fputc (0, fp);
+        if (es_fclose_snatch (fp, (void **) &tmpmsg, NULL))
+          log_fatal ("error snatching memory stream\n");
+        msg = format_text (tmpmsg, 0, 72, 80);
+        es_free (tmpmsg);
+
+        /* Print a status line but suppress the trailing LF.
+         * Spaces are not percent escaped. */
+        if (*msg)
+          write_status_buffer (STATUS_TOFU_STATS_LONG,
+                               msg, strlen (msg)-1, -1);
+
+        /* Remove the non-breaking space markers.  */
+        for (p=msg; *p; p++)
+          if (*p == '~')
+            *p = ' ';
+      }
 
-         log_string (GPGRT_LOG_INFO, msg);
-          xfree (msg);
-
-         if (policy == TOFU_POLICY_AUTO && messages < BASIC_TRUST_THRESHOLD)
-           {
-             char *set_policy_command;
-             char *text;
-              char *tmpmsg;
-
-             if (messages == 0)
-               log_info (_("Warning: we have yet to see"
-                            " a message signed by this key and user id!\n"));
-             else if (messages == 1)
-               log_info (_("Warning: we've only seen a single message"
-                            " signed by this key and user id!\n"));
-
-             set_policy_command =
-               xasprintf ("gpg --tofu-policy bad %s", fingerprint);
-
-              tmpmsg = xasprintf
-                (ngettext
-                 ("Warning: if you think you've seen more than %ld message "
-                  "signed by this key and user id, then this key might be a "
-                  "forgery!  Carefully examine the email address for small "
-                  "variations.  If the key is suspect, then use\n"
-                  "  %s\n"
-                  "to mark it as being bad.\n",
-                  "Warning: if you think you've seen more than %ld messages "
-                  "signed by this key, then this key might be a forgery!  "
-                      "Carefully examine the email address for small "
-                  "variations.  If the key is suspect, then use\n"
-                  "  %s\n"
-                  "to mark it as being bad.\n",
-                  messages),
-                  messages, set_policy_command);
-              text = format_text (tmpmsg, 0, 72, 80);
-              xfree (tmpmsg);
-             log_string (GPGRT_LOG_INFO, text);
-              xfree (text);
-
-             es_free (set_policy_command);
-           }
-       }
+      log_string (GPGRT_LOG_INFO, msg);
+      xfree (msg);
+
+      if (policy == TOFU_POLICY_AUTO
+          /* Cf. write_stats_status  */
+          && (sqrtu32 (encryption_count) + sqrtu32 (signature_count)
+              < sqrtu32 (2 * BASIC_TRUST_THRESHOLD)))
+        {
+          char *set_policy_command;
+          char *text;
+          char *tmpmsg;
+
+          if (signature_count == 0)
+            log_info (_("Warning: we have yet to see"
+                        " a message signed by this key and user id!\n"));
+          else if (signature_count == 1)
+            log_info (_("Warning: we've only seen a single message"
+                        " signed by this key and user id!\n"));
+
+          set_policy_command =
+            xasprintf ("gpg --tofu-policy bad %s", fingerprint);
+
+          tmpmsg = xasprintf
+            (ngettext
+             ("Warning: if you think you've seen more than %ld message "
+              "signed by this key and user id, then this key might be a "
+              "forgery!  Carefully examine the email address for small "
+              "variations.  If the key is suspect, then use\n"
+              "  %s\n"
+              "to mark it as being bad.\n",
+              "Warning: if you think you've seen more than %ld messages "
+              "signed by this key, then this key might be a forgery!  "
+              "Carefully examine the email address for small "
+              "variations.  If the key is suspect, then use\n"
+              "  %s\n"
+              "to mark it as being bad.\n",
+              signature_count),
+             signature_count, set_policy_command);
+          text = format_text (tmpmsg, 0, 72, 80);
+          xfree (tmpmsg);
+          log_string (GPGRT_LOG_INFO, text);
+          xfree (text);
+
+          es_free (set_policy_command);
+        }
     }
 
  out:
-  free_strlist (strlist);
   xfree (fingerprint_pp);
 
   return;
@@ -2652,9 +2750,10 @@ email_from_user_id (const char *user_id)
    This function returns 0 on success and an error code if an error
    occured.  */
 gpg_error_t
-tofu_register (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
-              const byte *sig_digest_bin, int sig_digest_bin_len,
-              time_t sig_time, const char *origin)
+tofu_register_signature (ctrl_t ctrl,
+                         PKT_public_key *pk, strlist_t user_id_list,
+                         const byte *sig_digest_bin, int sig_digest_bin_len,
+                         time_t sig_time, const char *origin)
 {
   gpg_error_t rc;
   tofu_dbs_t dbs;
@@ -2797,6 +2896,114 @@ tofu_register (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
   return rc;
 }
 
+gpg_error_t
+tofu_register_encryption (ctrl_t ctrl,
+                          PKT_public_key *pk, strlist_t user_id_list,
+                          int may_ask)
+{
+  gpg_error_t rc = 0;
+  tofu_dbs_t dbs;
+  kbnode_t kb = NULL;
+  int free_user_id_list = 0;
+  char *fingerprint = NULL;
+  strlist_t user_id;
+  char *err = NULL;
+
+  dbs = opendbs (ctrl);
+  if (! dbs)
+    {
+      rc = gpg_error (GPG_ERR_GENERAL);
+      log_error (_("error opening TOFU database: %s\n"),
+                 gpg_strerror (rc));
+      return rc;
+    }
+
+  /* Make sure PK is a primary key.  */
+  if (keyid_cmp (pk_keyid (pk), pk->main_keyid) != 0
+      || user_id_list)
+    kb = get_pubkeyblock (pk->keyid);
+
+  if (keyid_cmp (pk_keyid (pk), pk->main_keyid) != 0)
+    pk = kb->pkt->pkt.public_key;
+
+  if (! user_id_list)
+    {
+      /* Use all non-revoked user ids.  Do use expired user ids.  */
+      kbnode_t n = kb;
+
+      while ((n = find_next_kbnode (n, PKT_USER_ID)))
+        {
+         PKT_user_id *uid = n->pkt->pkt.user_id;
+
+          if (uid->is_revoked)
+            continue;
+
+          add_to_strlist (&user_id_list, uid->name);
+        }
+
+      free_user_id_list = 1;
+
+      if (! user_id_list)
+        log_info ("WARNING: Encrypting to %s, which has no"
+                  "non-revoked user ids.\n",
+                  keystr (pk->keyid));
+    }
+
+  fingerprint = hexfingerprint (pk, NULL, 0);
+
+  tofu_begin_batch_update (ctrl);
+  tofu_resume_batch_transaction (ctrl);
+
+  for (user_id = user_id_list; user_id; user_id = user_id->next)
+    {
+      char *email = email_from_user_id (user_id->d);
+
+      /* Make sure the binding exists and that we recognize any
+         conflicts.  */
+      int tl = get_trust (ctrl, pk, fingerprint, email, user_id->d,
+                          may_ask);
+      if (tl == _tofu_GET_TRUST_ERROR)
+        {
+          /* An error.  */
+          xfree (email);
+          goto die;
+        }
+
+      rc = gpgsql_stepx
+        (dbs->db, &dbs->s.register_insert, NULL, NULL, &err,
+         "insert into encryptions\n"
+         " (binding, time)\n"
+         " values\n"
+         " ((select oid from bindings\n"
+         "    where fingerprint = ? and email = ?),\n"
+         "  strftime('%s', 'now'));",
+         GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
+         GPGSQL_ARG_END);
+      if (rc)
+        {
+          log_error (_("error updating TOFU database: %s\n"), err);
+          print_further_info ("insert encryption");
+          sqlite3_free (err);
+        }
+
+      xfree (email);
+    }
+
+ die:
+  tofu_end_batch_update (ctrl);
+
+  if (kb)
+    release_kbnode (kb);
+
+  if (free_user_id_list)
+    free_strlist (user_id_list);
+
+  xfree (fingerprint);
+
+  return rc;
+}
+
+
 /* Combine a trust level returned from the TOFU trust model with a
    trust level returned by the PGP trust model.  This is primarily of
    interest when the trust model is tofu+pgp (TM_TOFU_PGP).
index b9826c9..df69a7a 100644 (file)
@@ -78,13 +78,24 @@ int tofu_policy_to_trust_level (enum tofu_policy policy);
    data came from, e.g., "email:claws" (default: "unknown").  Note:
    this function does not interact with the user, If there is a
    conflict, or if the binding's policy is ask, the actual interaction
-   is deferred until tofu_get_validity is called..  Set the string
+   is deferred until tofu_get_validity is called.  Set the string
    list FLAG to indicate that a specified user id is expired.  This
    function returns 0 on success and an error code on failure.  */
-gpg_error_t tofu_register (ctrl_t ctrl, PKT_public_key *pk,
-                           strlist_t user_id_list,
-                           const byte *sigs_digest, int sigs_digest_len,
-                           time_t sig_time, const char *origin);
+gpg_error_t tofu_register_signature (ctrl_t ctrl, PKT_public_key *pk,
+                                     strlist_t user_id_list,
+                                     const byte *sigs_digest,
+                                     int sigs_digest_len,
+                                     time_t sig_time, const char *origin);
+
+/* Note that an encrypted mail was sent to <PK, USER_ID>, for each
+   USER_ID in USER_ID_LIST.  (If USER_ID_LIST is NULL, then all
+   non-revoked user ids associated with PK are used.)  If MAY_ASK is
+   set, then may interact with the user to resolve a TOFU
+   conflict.  */
+gpg_error_t tofu_register_encryption (ctrl_t ctrl,
+                                      PKT_public_key *pk,
+                                      strlist_t user_id_list,
+                                      int may_ask);
 
 /* Combine a trust level returned from the TOFU trust model with a
    trust level returned by the PGP trust model.  This is primarily of
index 6f63c34..5457ea1 100644 (file)
@@ -1090,9 +1090,9 @@ tdb_get_validity_core (ctrl_t ctrl,
          into account.  */
       if (sig)
         {
-          err = tofu_register (ctrl, main_pk, user_id_list,
-                               sig->digest, sig->digest_len,
-                               sig->timestamp, "unknown");
+          err = tofu_register_signature (ctrl, main_pk, user_id_list,
+                                         sig->digest, sig->digest_len,
+                                         sig->timestamp, "unknown");
           if (err)
             {
               log_error ("TOFU: error registering signature: %s\n",