1
0
mirror of git://git.gnupg.org/gnupg.git synced 2024-06-14 00:19:50 +02:00

gpg: Split tofu's get_trust function into several smaller ones.

* g10/tofu.c (get_trust): Factor code out to ...
(format_conflict_msg_part1): new and to ...
(ask_about_binding): new.
--

Signed-off-by: Werner Koch <wk@gnupg.org>
This commit is contained in:
Werner Koch 2016-06-14 12:02:22 +02:00
parent 5ddccf4fc6
commit 1affdf1efc
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B

View File

@ -1602,6 +1602,411 @@ get_policy (tofu_dbs_t dbs, const char *fingerprint, const char *email,
return policy;
}
/* Format the first part of a conflict message and return that as a
* malloced string. */
static char *
format_conflict_msg_part1 (int policy, const char *conflict,
const char *fingerprint, const char *email)
{
estream_t fp;
char *binding;
int binding_shown = 0;
char *tmpstr, *text;
binding = xasprintf ("<%s, %s>", fingerprint, email);
fp = es_fopenmem (0, "rw,samethread");
if (!fp)
log_fatal ("error creating memory stream: %s\n",
gpg_strerror (gpg_error_from_syserror()));
if (policy == TOFU_POLICY_NONE)
{
es_fprintf (fp, _("The binding %s is NOT known."), binding);
es_fputs (" ", fp);
binding_shown = 1;
}
else if (policy == TOFU_POLICY_ASK
/* If there the conflict is with itself, then don't
* display this message. */
&& conflict && strcmp (conflict, fingerprint))
{
es_fprintf (fp,
_("The key with fingerprint %s raised a conflict "
"with the binding %s."
" Since this binding's policy was 'auto', it was "
"changed to 'ask'."),
conflict, binding);
es_fputs (" ", fp);
binding_shown = 1;
}
/* TRANSLATORS: The %s%s is replaced by either a fingerprint and a
blank or by two empty strings. */
es_fprintf (fp,
_("Please indicate whether you believe the binding %s%s"
"is legitimate (the key belongs to the stated owner) "
"or a forgery (bad)."),
binding_shown ? "" : binding,
binding_shown ? "" : " ");
es_fputc ('\n', fp);
xfree (binding);
es_fputc (0, fp);
if (es_fclose_snatch (fp, (void **)&tmpstr, NULL))
log_fatal ("error snatching memory stream\n");
text = format_text (tmpstr, 0, 72, 80);
es_free (tmpstr);
return text;
}
/* Ask the user about the binding. There are three ways we could end
* up here:
*
* - This is a new binding and there is a conflict
* (policy == TOFU_POLICY_NONE && bindings_with_this_email_count > 0),
*
* - This is a new binding and opt.tofu_default_policy is set to
* ask. (policy == TOFU_POLICY_NONE && opt.tofu_default_policy ==
* TOFU_POLICY_ASK), or,
*
* - The policy is ask (the user deferred last time) (policy ==
* TOFU_POLICY_ASK).
*/
static void
ask_about_binding (tofu_dbs_t dbs,
struct db *db,
enum tofu_policy *policy,
int *trust_level,
int bindings_with_this_email_count,
strlist_t bindings_with_this_email,
char *conflict,
const char *fingerprint,
const char *email,
const char *user_id)
{
char *sqerr = NULL;
int rc;
estream_t fp;
strlist_t other_user_ids = NULL;
struct signature_stats *stats = NULL;
struct signature_stats *stats_iter = NULL;
char *prompt;
char *choices;
struct db *db_key;
fp = es_fopenmem (0, "rw,samethread");
if (!fp)
log_fatal ("error creating memory stream: %s\n",
gpg_strerror (gpg_error_from_syserror()));
{
char *text = format_conflict_msg_part1 (*policy, conflict,
fingerprint, email);
es_fputs (text, fp);
es_fputc ('\n', fp);
xfree (text);
}
/* Find other user ids associated with this key and whether the
* bindings are marked as good or bad. */
if (opt.tofu_db_format == TOFU_DB_SPLIT)
{
/* In the split format, we need to search in the fingerprint DB
* for all the emails associated with this key, not the email DB. */
db_key = getdb (dbs, fingerprint, DB_KEY);
}
else
db_key = db;
if (db_key)
{
rc = gpgsql_stepx
(db_key->db, &db_key->s.get_trust_gather_other_user_ids,
strings_collect_cb2, &other_user_ids, &sqerr,
opt.tofu_db_format == TOFU_DB_SPLIT
? "select user_id, email from bindings where fingerprint = ?;"
: "select user_id, policy from bindings where fingerprint = ?;",
SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_END);
if (rc)
{
log_error (_("error gathering other user IDs: %s\n"), sqerr);
sqlite3_free (sqerr);
sqerr = 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_thing;
enum tofu_policy other_policy;
log_assert (strlist_iter->next);
strlist_iter = strlist_iter->next;
other_thing = strlist_iter->d;
if (opt.tofu_db_format == TOFU_DB_SPLIT)
other_policy = get_policy (dbs, fingerprint, other_thing, NULL);
else
other_policy = atoi (other_thing);
es_fprintf (fp, " %s (", other_user_id);
es_fprintf (fp, _("policy: %s"), tofu_policy_str (other_policy));
es_fprintf (fp, ")\n");
}
es_fprintf (fp, "\n");
free_strlist (other_user_ids);
}
/* Find other keys associated with this email address. */
/* FIXME: When generating the statistics, do we want the time
embedded in the signature (column 'sig_time') or the time that
we first verified the signature (column 'time'). */
rc = gpgsql_stepx
(db->db, &db->s.get_trust_gather_other_keys,
signature_stats_collect_cb, &stats, &sqerr,
"select fingerprint, policy, time_ago, count(*)\n"
" from (select bindings.*,\n"
" case\n"
/* From the future (but if its just a couple of hours in the
* future don't turn it into a warning)? Or should we use
* small, medium or large units? (Note: whatever we do, we
* keep the value in seconds. Then when we group, everything
* that rounds to the same number of seconds is grouped.) */
" when delta < -("STRINGIFY (TIME_AGO_FUTURE_IGNORE)") then -1\n"
" when delta < ("STRINGIFY (TIME_AGO_MEDIUM_THRESHOLD)")\n"
" then max(0,\n"
" round(delta / ("STRINGIFY (TIME_AGO_UNIT_SMALL)"))\n"
" * ("STRINGIFY (TIME_AGO_UNIT_SMALL)"))\n"
" when delta < ("STRINGIFY (TIME_AGO_LARGE_THRESHOLD)")\n"
" then round(delta / ("STRINGIFY (TIME_AGO_UNIT_MEDIUM)"))\n"
" * ("STRINGIFY (TIME_AGO_UNIT_MEDIUM)")\n"
" else round(delta / ("STRINGIFY (TIME_AGO_UNIT_LARGE)"))\n"
" * ("STRINGIFY (TIME_AGO_UNIT_LARGE)")\n"
" end time_ago,\n"
" delta time_ago_raw\n"
" from bindings\n"
" left join\n"
" (select *,\n"
" cast(strftime('%s','now') - sig_time as real) delta\n"
" from signatures) ss\n"
" on ss.binding = bindings.oid)\n"
" where email = ?\n"
" group by fingerprint, time_ago\n"
/* Make sure the current key is first. */
" order by fingerprint = ? asc, fingerprint desc, time_ago desc;\n",
SQLITE_ARG_STRING, email, SQLITE_ARG_STRING, fingerprint,
SQLITE_ARG_END);
if (rc)
{
strlist_t strlist_iter;
log_error (_("error gathering signature stats: %s\n"), sqerr);
sqlite3_free (sqerr);
sqerr = NULL;
es_fprintf (fp, ngettext("The email address \"%s\" is"
" associated with %d key:\n",
"The email address \"%s\" is"
" associated with %d keys:\n",
bindings_with_this_email_count),
email, bindings_with_this_email_count);
for (strlist_iter = bindings_with_this_email;
strlist_iter;
strlist_iter = strlist_iter->next)
es_fprintf (fp, " %s\n", strlist_iter->d);
}
else
{
char *key = NULL;
if (! stats || strcmp (stats->fingerprint, fingerprint))
{
/* If we have already added this key to the DB, then it will
* be first (see the above select). Since the first key on
* the list is not this key, we must not yet have verified any
* messages signed by this key. Add a dummy entry. */
signature_stats_prepend (&stats, fingerprint, TOFU_POLICY_AUTO, 0, 0);
}
es_fprintf (fp, _("Statistics for keys with the email address \"%s\":\n"),
email);
for (stats_iter = stats; stats_iter; stats_iter = stats_iter->next)
{
if (! key || strcmp (key, stats_iter->fingerprint))
{
int this_key;
char *key_pp;
key = stats_iter->fingerprint;
this_key = strcmp (key, fingerprint) == 0;
key_pp = format_hexfingerprint (key, NULL, 0);
es_fprintf (fp, " %s (", key_pp);
if (this_key)
es_fprintf (fp, _("this key"));
else
es_fprintf (fp, _("policy: %s"),
tofu_policy_str (stats_iter->policy));
es_fputs ("):\n", fp);
xfree (key_pp);
}
es_fputs (" ", fp);
if (stats_iter->time_ago == -1)
es_fprintf (fp, ngettext("%ld message signed in the future.",
"%ld messages signed in the future.",
stats_iter->count), stats_iter->count);
else
{
long t_scaled = time_ago_scale (stats_iter->time_ago);
/* TANSLATORS: This string is concatenated with one of
* the day/week/month strings to form one sentence. */
es_fprintf (fp, ngettext("%ld message signed",
"%ld messages signed",
stats_iter->count), stats_iter->count);
if (!stats_iter->count)
es_fputs (".", fp);
else if (stats_iter->time_ago < TIME_AGO_UNIT_MEDIUM)
es_fprintf (fp, ngettext(" over the past %ld day.",
" over the past %ld days.",
t_scaled), t_scaled);
else if (stats_iter->time_ago < TIME_AGO_UNIT_LARGE)
es_fprintf (fp, ngettext(" over the past %ld week.",
" over the past %ld weeks.",
t_scaled), t_scaled);
else
es_fprintf (fp, ngettext(" over the past %ld month.",
" over the past %ld months.",
t_scaled), t_scaled);
}
es_fputs ("\n", fp);
}
}
if ((*policy == TOFU_POLICY_NONE && bindings_with_this_email_count > 0)
|| (*policy == TOFU_POLICY_ASK && conflict))
{
/* This is a conflict. */
/* TRANSLATORS: Please translate the text found in the source
* file below. We don't directly internationalize that text so
* that we can tweak it without breaking translations. */
char *text = _("TOFU detected a binding conflict");
char *textbuf;
if (!strcmp (text, "TOFU detected a binding conflict"))
{
/* No translation. Use the English text. */
text =
"Normally, there is only a single key associated with an email "
"address. However, people sometimes generate a new key if "
"their key is too old or they think it might be compromised. "
"Alternatively, a new key may indicate a man-in-the-middle "
"attack! Before accepting this key, you should talk to or "
"call the person to make sure this new key is legitimate.";
}
textbuf = format_text (text, 0, 72, 80);
es_fprintf (fp, "\n%s\n", text);
xfree (textbuf);
}
es_fputc ('\n', fp);
/* Add a NUL terminator. */
es_fputc (0, fp);
if (es_fclose_snatch (fp, (void **) &prompt, NULL))
log_fatal ("error snatching memory stream\n");
/* I think showing the large message once is sufficient. If we
* would move it right before the cpr_get many lines will scroll
* away and the user might not realize that he merely entered a
* wrong choise (because he does not see that either). As a small
* benefit we allow C-L to redisplay everything. */
tty_printf ("%s", prompt);
while (1)
{
char *response;
/* TRANSLATORS: Two letters (normally the lower and upper case
* version of the hotkey) for each of the five choices. If
* there is only one choice in your language, repeat it. */
choices = _("gG" "aA" "uU" "rR" "bB");
if (strlen (choices) != 10)
log_bug ("Bad TOFU conflict translation! Please report.");
response = cpr_get
("tofu.conflict",
_("(G)ood, (A)ccept once, (U)nknown, (R)eject once, (B)ad? "));
trim_spaces (response);
cpr_kill_prompt ();
if (*response == CONTROL_L)
tty_printf ("%s", prompt);
else if (strlen (response) == 1)
{
char *choice = strchr (choices, *response);
if (choice)
{
int c = ((size_t) choice - (size_t) choices) / 2;
switch (c)
{
case 0: /* Good. */
*policy = TOFU_POLICY_GOOD;
*trust_level = tofu_policy_to_trust_level (*policy);
break;
case 1: /* Accept once. */
*policy = TOFU_POLICY_ASK;
*trust_level = tofu_policy_to_trust_level (TOFU_POLICY_GOOD);
break;
case 2: /* Unknown. */
*policy = TOFU_POLICY_UNKNOWN;
*trust_level = tofu_policy_to_trust_level (*policy);
break;
case 3: /* Reject once. */
*policy = TOFU_POLICY_ASK;
*trust_level = tofu_policy_to_trust_level (TOFU_POLICY_BAD);
break;
case 4: /* Bad. */
*policy = TOFU_POLICY_BAD;
*trust_level = tofu_policy_to_trust_level (*policy);
break;
default:
log_bug ("c should be between 0 and 4 but it is %d!", c);
}
if (record_binding (dbs, fingerprint, email, user_id,
*policy, 0))
{
/* If there's an error registering the
* binding, don't save the signature. */
*trust_level = _tofu_GET_TRUST_ERROR;
}
break;
}
}
xfree (response);
}
xfree (prompt);
signature_stats_free (stats);
}
/* Return the trust level (TRUST_NEVER, etc.) for the binding
* <FINGERPRINT, EMAIL> (email is already normalized). If no policy
* is registered, returns TOFU_POLICY_NONE. If an error occurs,
@ -1621,12 +2026,11 @@ get_trust (tofu_dbs_t dbs, PKT_public_key *pk,
const char *fingerprint, const char *email,
const char *user_id, int may_ask)
{
char *fingerprint_pp;
struct db *db;
enum tofu_policy policy;
char *conflict = NULL;
int rc;
char *err = NULL;
char *sqerr = NULL;
strlist_t bindings_with_this_email = NULL;
int bindings_with_this_email_count;
int change_conflicting_to_ask = 0;
@ -1649,8 +2053,6 @@ get_trust (tofu_dbs_t dbs, PKT_public_key *pk,
if (! db)
return _tofu_GET_TRUST_ERROR;
fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
policy = get_policy (dbs, fingerprint, email, &conflict);
if (policy == TOFU_POLICY_AUTO || policy == TOFU_POLICY_NONE)
{ /* See if the key is ultimately trusted. If so, we're done. */
@ -1692,7 +2094,7 @@ get_trust (tofu_dbs_t dbs, PKT_public_key *pk,
case TOFU_POLICY_UNKNOWN:
case TOFU_POLICY_BAD:
/* The saved judgement is auto -> auto, good, unknown or bad.
We don't need to ask the user anything. */
* We don't need to ask the user anything. */
if (DBG_TRUST)
log_debug ("TOFU: Known binding <%s, %s>'s policy: %s\n",
fingerprint, email, tofu_policy_str (policy));
@ -1711,7 +2113,7 @@ get_trust (tofu_dbs_t dbs, PKT_public_key *pk,
case TOFU_POLICY_NONE:
/* The binding is new, we need to check for conflicts. Case #3
below. */
* below. */
break;
case _tofu_GET_POLICY_ERROR:
@ -1724,49 +2126,51 @@ get_trust (tofu_dbs_t dbs, PKT_public_key *pk,
/* We get here if:
1. The saved policy is auto and the default policy is ask
(get_policy() == TOFU_POLICY_AUTO
&& opt.tofu_default_policy == TOFU_POLICY_ASK)
2. The saved policy is ask (either last time the user selected
accept once or reject once or there was a conflict and this
binding's policy was changed from auto to ask)
(policy == TOFU_POLICY_ASK), or,
3. We don't have a saved policy (policy == TOFU_POLICY_NONE)
(need to check for a conflict).
*
* 1. The saved policy is auto and the default policy is ask
* (get_policy() == TOFU_POLICY_AUTO
* && opt.tofu_default_policy == TOFU_POLICY_ASK)
*
* 2. The saved policy is ask (either last time the user selected
* accept once or reject once or there was a conflict and this
* binding's policy was changed from auto to ask)
* (policy == TOFU_POLICY_ASK), or,
*
* 3. We don't have a saved policy (policy == TOFU_POLICY_NONE)
* (need to check for a conflict).
*/
/* Look for conflicts. This is needed in all 3 cases.
Get the fingerprints of any bindings that share the email
address. Note: if the binding in question is in the DB, it will
also be returned. Thus, if the result set is empty, then this is
a new binding. */
*
* Get the fingerprints of any bindings that share the email
* address. Note: if the binding in question is in the DB, it will
* also be returned. Thus, if the result set is empty, then this is
* a new binding. */
rc = gpgsql_stepx
(db->db, &db->s.get_trust_bindings_with_this_email,
strings_collect_cb2, &bindings_with_this_email, &err,
strings_collect_cb2, &bindings_with_this_email, &sqerr,
"select distinct fingerprint from bindings where email = ?;",
SQLITE_ARG_STRING, email, SQLITE_ARG_END);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
log_error (_("error reading TOFU database: %s\n"), sqerr);
print_further_info ("listing fingerprints");
sqlite3_free (err);
sqlite3_free (sqerr);
goto out;
}
bindings_with_this_email_count = strlist_length (bindings_with_this_email);
if (bindings_with_this_email_count == 0
&& opt.tofu_default_policy != TOFU_POLICY_ASK)
/* New binding with no conflict and a concrete default policy.
We've never observed a binding with this email address
(BINDINGS_WITH_THIS_EMAIL_COUNT is 0 and the above query would return
the current binding if it were in the DB) and we have a default
policy, which is not to ask the user. */
{
/* New binding with no conflict and a concrete default policy.
*
* We've never observed a binding with this email address
* BINDINGS_WITH_THIS_EMAIL_COUNT is 0 and the above query would
* return the current binding if it were in the DB) and we have
* a default policy, which is not to ask the user.
*/
/* If we've seen this binding, then we've seen this email and
policy couldn't possibly be TOFU_POLICY_NONE. */
log_assert (policy == TOFU_POLICY_NONE);
@ -1789,18 +2193,20 @@ get_trust (tofu_dbs_t dbs, PKT_public_key *pk,
}
if (policy == TOFU_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. Note: we delay this until after we ask
for confirmation so that when the current policy is printed, it
is correct. */
change_conflicting_to_ask = 1;
{
/* This is a new binding and we have a conflict. Mark any
* conflicting bindings that have an automatic policy as now
* requiring confirmation. Note: we delay this until after we
* ask for confirmation so that when the current policy is
* printed, it is correct. */
change_conflicting_to_ask = 1;
}
if (! may_ask)
/* We can only get here in the third case (no saved policy) and if
there is a conflict. (If the policy was ask (cases #1 and #2)
and we weren't allowed to ask, we'd have already exited). */
{
/* We can only get here in the third case (no saved policy) and
* if there is a conflict. (If the policy was ask (cases #1 and
* #2) and we weren't allowed to ask, we'd have already exited). */
log_assert (policy == TOFU_POLICY_NONE);
if (record_binding (dbs, fingerprint, email, user_id,
@ -1812,412 +2218,52 @@ get_trust (tofu_dbs_t dbs, PKT_public_key *pk,
goto out;
}
/* 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 == TOFU_POLICY_NONE && bindings_with_this_email_count > 0),
- This is a new binding and opt.tofu_default_policy is set to
ask. (policy == TOFU_POLICY_NONE && opt.tofu_default_policy ==
TOFU_POLICY_ASK), or,
- The policy is ask (the user deferred last time) (policy ==
TOFU_POLICY_ASK).
*/
{
int is_conflict =
((policy == TOFU_POLICY_NONE && bindings_with_this_email_count > 0)
|| (policy == TOFU_POLICY_ASK && conflict));
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: %s\n",
gpg_strerror (gpg_error_from_syserror()));
/* Format the first part of the message. */
{
estream_t fp1;
char *binding = xasprintf ("<%s, %s>", fingerprint, email);
int binding_shown = 0;
char *tmpstr, *text;
fp1 = es_fopenmem (0, "rw,samethread");
if (!fp1)
log_fatal ("error creating memory stream: %s\n",
gpg_strerror (gpg_error_from_syserror()));
if (policy == TOFU_POLICY_NONE)
{
es_fprintf (fp1, _("The binding %s is NOT known."), binding);
es_fputs (" ", fp1);
binding_shown = 1;
}
else if (policy == TOFU_POLICY_ASK
/* If there the conflict is with itself, then don't
display this message. */
&& conflict && strcmp (conflict, fingerprint) != 0)
{
es_fprintf (fp1,
_("The key with fingerprint %s raised a conflict "
"with the binding %s."
" Since this binding's policy was 'auto', it was "
"changed to 'ask'."),
conflict, binding);
es_fputs (" ", fp1);
binding_shown = 1;
}
/* TRANSLATORS: The %s%s is replaced by either a fingerprint and a
blank or by two empty strings. */
es_fprintf (fp1,
_("Please indicate whether you believe the binding %s%s"
"is legitimate (the key belongs to the stated owner) "
"or a forgery (bad)."),
binding_shown ? "" : binding,
binding_shown ? "" : " ");
es_fputc ('\n', fp1);
xfree (binding);
es_fputc (0, fp1);
if (es_fclose_snatch (fp1, (void **)&tmpstr, NULL))
log_fatal ("error snatching memory stream\n");
text = format_text (tmpstr, 0, 72, 80);
es_free (tmpstr);
es_fputs (text, fp);
xfree (text);
}
es_fputc ('\n', fp);
/* Find other user ids associated with this key and whether the
bindings are marked as good or bad. */
{
struct db *db_key;
if (opt.tofu_db_format == TOFU_DB_SPLIT)
/* In the split format, we need to search in the fingerprint
DB for all the emails associated with this key, not the
email DB. */
db_key = getdb (dbs, fingerprint, DB_KEY);
else
db_key = db;
if (db_key)
{
rc = gpgsql_stepx
(db_key->db, &db_key->s.get_trust_gather_other_user_ids,
strings_collect_cb2, &other_user_ids, &err,
opt.tofu_db_format == TOFU_DB_SPLIT
? "select user_id, email from bindings where fingerprint = ?;"
: "select user_id, policy from bindings where fingerprint = ?;",
SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_END);
if (rc)
{
log_error (_("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_thing;
enum tofu_policy other_policy;
log_assert (strlist_iter->next);
strlist_iter = strlist_iter->next;
other_thing = strlist_iter->d;
if (opt.tofu_db_format == TOFU_DB_SPLIT)
other_policy = get_policy (dbs, fingerprint, other_thing, NULL);
else
other_policy = atoi (other_thing);
es_fprintf (fp, " %s (", other_user_id);
es_fprintf (fp, _("policy: %s"), tofu_policy_str (other_policy));
es_fprintf (fp, ")\n");
}
es_fprintf (fp, "\n");
free_strlist (other_user_ids);
}
/* Find other keys associated with this email address. */
/* XXX: When generating the statistics, do we want the time
embedded in the signature (column 'sig_time') or the time that
we first verified the signature (column 'time'). */
rc = gpgsql_stepx
(db->db, &db->s.get_trust_gather_other_keys,
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 < -("STRINGIFY (TIME_AGO_FUTURE_IGNORE)") then -1\n"
" when delta < ("STRINGIFY (TIME_AGO_MEDIUM_THRESHOLD)")\n"
" then max(0,\n"
" round(delta / ("STRINGIFY (TIME_AGO_UNIT_SMALL)"))\n"
" * ("STRINGIFY (TIME_AGO_UNIT_SMALL)"))\n"
" when delta < ("STRINGIFY (TIME_AGO_LARGE_THRESHOLD)")\n"
" then round(delta / ("STRINGIFY (TIME_AGO_UNIT_MEDIUM)"))\n"
" * ("STRINGIFY (TIME_AGO_UNIT_MEDIUM)")\n"
" else round(delta / ("STRINGIFY (TIME_AGO_UNIT_LARGE)"))\n"
" * ("STRINGIFY (TIME_AGO_UNIT_LARGE)")\n"
" end time_ago,\n"
" delta time_ago_raw\n"
" from bindings\n"
" left join\n"
" (select *,\n"
" cast(strftime('%s','now') - sig_time as real) delta\n"
" from signatures) ss\n"
" on ss.binding = bindings.oid)\n"
" where email = ?\n"
" group by fingerprint, time_ago\n"
/* Make sure the current key is first. */
" order by fingerprint = ? asc, fingerprint desc, time_ago desc;\n",
SQLITE_ARG_STRING, email, SQLITE_ARG_STRING, fingerprint,
SQLITE_ARG_END);
if (rc)
{
strlist_t strlist_iter;
log_error (_("error gathering signature stats: %s\n"), err);
sqlite3_free (err);
err = NULL;
es_fprintf (fp, ngettext("The email address \"%s\" is"
" associated with %d key:\n",
"The email address \"%s\" is"
" associated with %d keys:\n",
bindings_with_this_email_count),
email, bindings_with_this_email_count);
for (strlist_iter = bindings_with_this_email;
strlist_iter;
strlist_iter = strlist_iter->next)
es_fprintf (fp, " %s\n", strlist_iter->d);
}
else
{
char *key = NULL;
if (! stats || strcmp (stats->fingerprint, fingerprint) != 0)
/* If we have already added this key to the DB, then it will
be first (see the above select). Since the first key on
the list is not this key, we must not yet have verified
any messages signed by this key. Add a dummy entry. */
signature_stats_prepend (&stats, fingerprint, TOFU_POLICY_AUTO, 0, 0);
es_fprintf
(fp, _("Statistics for keys with the email address \"%s\":\n"),
email);
for (stats_iter = stats; stats_iter; stats_iter = stats_iter->next)
{
if (! key || strcmp (key, stats_iter->fingerprint) != 0)
{
int this_key;
char *key_pp;
key = stats_iter->fingerprint;
this_key = strcmp (key, fingerprint) == 0;
key_pp = format_hexfingerprint (key, NULL, 0);
es_fprintf (fp, " %s (", key_pp);
if (this_key)
es_fprintf (fp, _("this key"));
else
es_fprintf (fp, _("policy: %s"),
tofu_policy_str (stats_iter->policy));
es_fputs ("):\n", fp);
xfree (key_pp);
}
es_fputs (" ", fp);
if (stats_iter->time_ago == -1)
es_fprintf (fp, ngettext("%ld message signed in the future.",
"%ld messages signed in the future.",
stats_iter->count), stats_iter->count);
else
{
long t_scaled = time_ago_scale (stats_iter->time_ago);
/* TANSLATORS: This string is concatenated with one of
* the day/week/month strings to form one sentence. */
es_fprintf (fp, ngettext("%ld message signed",
"%ld messages signed",
stats_iter->count), stats_iter->count);
if (!stats_iter->count)
es_fputs (".", fp);
else if (stats_iter->time_ago < TIME_AGO_UNIT_MEDIUM)
es_fprintf (fp, ngettext(" over the past %ld day.",
" over the past %ld days.",
t_scaled), t_scaled);
else if (stats_iter->time_ago < TIME_AGO_UNIT_LARGE)
es_fprintf (fp, ngettext(" over the past %ld week.",
" over the past %ld weeks.",
t_scaled), t_scaled);
else
es_fprintf (fp, ngettext(" over the past %ld month.",
" over the past %ld months.",
t_scaled), t_scaled);
}
es_fputs ("\n", fp);
}
}
if (is_conflict)
{
/* TRANSLATORS: Please translate the text found in the source
file below. We don't directly internationalize that text
so that we can tweak it without breaking translations. */
char *text = _("TOFU detected a binding conflict");
char *textbuf;
if (strcmp (text, "TOFU detected a binding conflict") == 0)
/* No translation. Use the English text. */
text =
"Normally, there is only a single key associated with an email "
"address. However, people sometimes generate a new key if "
"their key is too old or they think it might be compromised. "
"Alternatively, a new key may indicate a man-in-the-middle "
"attack! Before accepting this key, you should talk to or "
"call the person to make sure this new key is legitimate.";
textbuf = format_text (text, 0, 72, 80);
es_fprintf (fp, "\n%s\n", text);
xfree (textbuf);
}
es_fputc ('\n', fp);
/* Add a NUL terminator. */
es_fputc (0, fp);
if (es_fclose_snatch (fp, (void **) &prompt, NULL))
log_fatal ("error snatching memory stream\n");
/* I think showing the large message once is sufficient. If we
would move it right before the cpr_get many lines will scroll
away and the user might not realize that he merely entered a
wrong choise (because he does not see that either). As a small
benefit we allow C-L to redisplay everything. */
tty_printf ("%s", prompt);
while (1)
{
char *response;
/* TRANSLATORS: Two letters (normally the lower and upper case
version of the hotkey) for each of the five choices. If
there is only one choice in your language, repeat it. */
choices = _("gG" "aA" "uU" "rR" "bB");
if (strlen (choices) != 10)
log_bug ("Bad TOFU conflict translation! Please report.");
response = cpr_get
("tofu.conflict",
_("(G)ood, (A)ccept once, (U)nknown, (R)eject once, (B)ad? "));
trim_spaces (response);
cpr_kill_prompt ();
if (*response == CONTROL_L)
tty_printf ("%s", prompt);
else if (strlen (response) == 1)
{
char *choice = strchr (choices, *response);
if (choice)
{
int c = ((size_t) choice - (size_t) choices) / 2;
switch (c)
{
case 0: /* Good. */
policy = TOFU_POLICY_GOOD;
trust_level = tofu_policy_to_trust_level (policy);
break;
case 1: /* Accept once. */
policy = TOFU_POLICY_ASK;
trust_level =
tofu_policy_to_trust_level (TOFU_POLICY_GOOD);
break;
case 2: /* Unknown. */
policy = TOFU_POLICY_UNKNOWN;
trust_level = tofu_policy_to_trust_level (policy);
break;
case 3: /* Reject once. */
policy = TOFU_POLICY_ASK;
trust_level =
tofu_policy_to_trust_level (TOFU_POLICY_BAD);
break;
case 4: /* Bad. */
policy = TOFU_POLICY_BAD;
trust_level = tofu_policy_to_trust_level (policy);
break;
default:
log_bug ("c should be between 0 and 4 but it is %d!", c);
}
if (record_binding (dbs, fingerprint, email, user_id,
policy, 0) != 0)
/* If there's an error registering the
binding, don't save the signature. */
trust_level = _tofu_GET_TRUST_ERROR;
break;
}
}
xfree (response);
}
xfree (prompt);
signature_stats_free (stats);
}
/* If we get here, we need to ask the user about the binding. */
ask_about_binding (dbs, db,
&policy,
&trust_level,
bindings_with_this_email_count,
bindings_with_this_email,
conflict,
fingerprint,
email,
user_id);
out:
if (change_conflicting_to_ask)
{
if (! may_ask)
/* If we weren't allowed to ask, also update this key as
conflicting with itself. */
rc = gpgsql_exec_printf
(db->db, NULL, NULL, &err,
"update bindings set policy = %d, conflict = %Q"
" where email = %Q"
" and (policy = %d or (policy = %d and fingerprint = %Q));",
TOFU_POLICY_ASK, fingerprint, email, TOFU_POLICY_AUTO,
TOFU_POLICY_ASK, fingerprint);
{
/* If we weren't allowed to ask, also update this key as
conflicting with itself. */
rc = gpgsql_exec_printf
(db->db, NULL, NULL, &sqerr,
"update bindings set policy = %d, conflict = %Q"
" where email = %Q"
" and (policy = %d or (policy = %d and fingerprint = %Q));",
TOFU_POLICY_ASK, fingerprint, email, TOFU_POLICY_AUTO,
TOFU_POLICY_ASK, fingerprint);
}
else
rc = gpgsql_exec_printf
(db->db, NULL, NULL, &err,
"update bindings set policy = %d, conflict = %Q"
" where email = %Q and fingerprint != %Q and policy = %d;",
TOFU_POLICY_ASK, fingerprint, email, fingerprint, TOFU_POLICY_AUTO);
{
rc = gpgsql_exec_printf
(db->db, NULL, NULL, &sqerr,
"update bindings set policy = %d, conflict = %Q"
" where email = %Q and fingerprint != %Q and policy = %d;",
TOFU_POLICY_ASK, fingerprint, email, fingerprint,
TOFU_POLICY_AUTO);
}
if (rc)
{
log_error (_("error changing TOFU policy: %s\n"), err);
sqlite3_free (err);
goto out;
log_error (_("error changing TOFU policy: %s\n"), sqerr);
sqlite3_free (sqerr);
goto out; /* FIXME */
}
}
xfree (conflict);
free_strlist (bindings_with_this_email);
xfree (fingerprint_pp);
return trust_level;
}