1
0
mirror of git://git.gnupg.org/gnupg.git synced 2025-01-09 12:54:23 +01: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; 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 /* Return the trust level (TRUST_NEVER, etc.) for the binding
* <FINGERPRINT, EMAIL> (email is already normalized). If no policy * <FINGERPRINT, EMAIL> (email is already normalized). If no policy
* is registered, returns TOFU_POLICY_NONE. If an error occurs, * 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 *fingerprint, const char *email,
const char *user_id, int may_ask) const char *user_id, int may_ask)
{ {
char *fingerprint_pp;
struct db *db; struct db *db;
enum tofu_policy policy; enum tofu_policy policy;
char *conflict = NULL; char *conflict = NULL;
int rc; int rc;
char *err = NULL; char *sqerr = NULL;
strlist_t bindings_with_this_email = NULL; strlist_t bindings_with_this_email = NULL;
int bindings_with_this_email_count; int bindings_with_this_email_count;
int change_conflicting_to_ask = 0; int change_conflicting_to_ask = 0;
@ -1649,8 +2053,6 @@ get_trust (tofu_dbs_t dbs, PKT_public_key *pk,
if (! db) if (! db)
return _tofu_GET_TRUST_ERROR; return _tofu_GET_TRUST_ERROR;
fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
policy = get_policy (dbs, fingerprint, email, &conflict); policy = get_policy (dbs, fingerprint, email, &conflict);
if (policy == TOFU_POLICY_AUTO || policy == TOFU_POLICY_NONE) if (policy == TOFU_POLICY_AUTO || policy == TOFU_POLICY_NONE)
{ /* See if the key is ultimately trusted. If so, we're done. */ { /* 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_UNKNOWN:
case TOFU_POLICY_BAD: case TOFU_POLICY_BAD:
/* The saved judgement is auto -> auto, good, unknown or 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) if (DBG_TRUST)
log_debug ("TOFU: Known binding <%s, %s>'s policy: %s\n", log_debug ("TOFU: Known binding <%s, %s>'s policy: %s\n",
fingerprint, email, tofu_policy_str (policy)); fingerprint, email, tofu_policy_str (policy));
@ -1711,7 +2113,7 @@ get_trust (tofu_dbs_t dbs, PKT_public_key *pk,
case TOFU_POLICY_NONE: case TOFU_POLICY_NONE:
/* The binding is new, we need to check for conflicts. Case #3 /* The binding is new, we need to check for conflicts. Case #3
below. */ * below. */
break; break;
case _tofu_GET_POLICY_ERROR: case _tofu_GET_POLICY_ERROR:
@ -1724,49 +2126,51 @@ get_trust (tofu_dbs_t dbs, PKT_public_key *pk,
/* We get here if: /* We get here if:
*
1. The saved policy is auto and the default policy is ask * 1. The saved policy is auto and the default policy is ask
(get_policy() == TOFU_POLICY_AUTO * (get_policy() == TOFU_POLICY_AUTO
&& opt.tofu_default_policy == TOFU_POLICY_ASK) * && opt.tofu_default_policy == TOFU_POLICY_ASK)
*
2. The saved policy is ask (either last time the user selected * 2. The saved policy is ask (either last time the user selected
accept once or reject once or there was a conflict and this * accept once or reject once or there was a conflict and this
binding's policy was changed from auto to ask) * binding's policy was changed from auto to ask)
(policy == TOFU_POLICY_ASK), or, * (policy == TOFU_POLICY_ASK), or,
*
3. We don't have a saved policy (policy == TOFU_POLICY_NONE) * 3. We don't have a saved policy (policy == TOFU_POLICY_NONE)
(need to check for a conflict). * (need to check for a conflict).
*/ */
/* Look for conflicts. This is needed in all 3 cases. /* Look for conflicts. This is needed in all 3 cases.
*
Get the fingerprints of any bindings that share the email * Get the fingerprints of any bindings that share the email
address. Note: if the binding in question is in the DB, it will * 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 * also be returned. Thus, if the result set is empty, then this is
a new binding. */ * a new binding. */
rc = gpgsql_stepx rc = gpgsql_stepx
(db->db, &db->s.get_trust_bindings_with_this_email, (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 = ?;", "select distinct fingerprint from bindings where email = ?;",
SQLITE_ARG_STRING, email, SQLITE_ARG_END); SQLITE_ARG_STRING, email, SQLITE_ARG_END);
if (rc) 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"); print_further_info ("listing fingerprints");
sqlite3_free (err); sqlite3_free (sqerr);
goto out; goto out;
} }
bindings_with_this_email_count = strlist_length (bindings_with_this_email); bindings_with_this_email_count = strlist_length (bindings_with_this_email);
if (bindings_with_this_email_count == 0 if (bindings_with_this_email_count == 0
&& opt.tofu_default_policy != TOFU_POLICY_ASK) && 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 /* If we've seen this binding, then we've seen this email and
policy couldn't possibly be TOFU_POLICY_NONE. */ policy couldn't possibly be TOFU_POLICY_NONE. */
log_assert (policy == 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) 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 /* This is a new binding and we have a conflict. Mark any
requiring confirmation. Note: we delay this until after we ask * conflicting bindings that have an automatic policy as now
for confirmation so that when the current policy is printed, it * requiring confirmation. Note: we delay this until after we
is correct. */ * ask for confirmation so that when the current policy is
change_conflicting_to_ask = 1; * printed, it is correct. */
change_conflicting_to_ask = 1;
}
if (! may_ask) 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); log_assert (policy == TOFU_POLICY_NONE);
if (record_binding (dbs, fingerprint, email, user_id, if (record_binding (dbs, fingerprint, email, user_id,
@ -1812,412 +2218,52 @@ get_trust (tofu_dbs_t dbs, PKT_public_key *pk,
goto out; goto out;
} }
/* If we get here, we need to ask the user about the binding. There /* If we get here, we need to ask the user about the binding. */
are three ways we could end up here: ask_about_binding (dbs, db,
&policy,
- This is a new binding and there is a conflict &trust_level,
(policy == TOFU_POLICY_NONE && bindings_with_this_email_count > 0), bindings_with_this_email_count,
bindings_with_this_email,
- This is a new binding and opt.tofu_default_policy is set to conflict,
ask. (policy == TOFU_POLICY_NONE && opt.tofu_default_policy == fingerprint,
TOFU_POLICY_ASK), or, email,
user_id);
- 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);
}
out: out:
if (change_conflicting_to_ask) if (change_conflicting_to_ask)
{ {
if (! may_ask) if (! may_ask)
/* If we weren't allowed to ask, also update this key as {
conflicting with itself. */ /* If we weren't allowed to ask, also update this key as
rc = gpgsql_exec_printf conflicting with itself. */
(db->db, NULL, NULL, &err, rc = gpgsql_exec_printf
"update bindings set policy = %d, conflict = %Q" (db->db, NULL, NULL, &sqerr,
" where email = %Q" "update bindings set policy = %d, conflict = %Q"
" and (policy = %d or (policy = %d and fingerprint = %Q));", " where email = %Q"
TOFU_POLICY_ASK, fingerprint, email, TOFU_POLICY_AUTO, " and (policy = %d or (policy = %d and fingerprint = %Q));",
TOFU_POLICY_ASK, fingerprint); TOFU_POLICY_ASK, fingerprint, email, TOFU_POLICY_AUTO,
TOFU_POLICY_ASK, fingerprint);
}
else else
rc = gpgsql_exec_printf {
(db->db, NULL, NULL, &err, rc = gpgsql_exec_printf
"update bindings set policy = %d, conflict = %Q" (db->db, NULL, NULL, &sqerr,
" where email = %Q and fingerprint != %Q and policy = %d;", "update bindings set policy = %d, conflict = %Q"
TOFU_POLICY_ASK, fingerprint, email, fingerprint, TOFU_POLICY_AUTO); " where email = %Q and fingerprint != %Q and policy = %d;",
TOFU_POLICY_ASK, fingerprint, email, fingerprint,
TOFU_POLICY_AUTO);
}
if (rc) if (rc)
{ {
log_error (_("error changing TOFU policy: %s\n"), err); log_error (_("error changing TOFU policy: %s\n"), sqerr);
sqlite3_free (err); sqlite3_free (sqerr);
goto out; goto out; /* FIXME */
} }
} }
xfree (conflict); xfree (conflict);
free_strlist (bindings_with_this_email); free_strlist (bindings_with_this_email);
xfree (fingerprint_pp);
return trust_level; return trust_level;
} }