1
0
mirror of git://git.gnupg.org/gnupg.git synced 2025-04-17 15:44:34 +02:00

gpg: Cache prepared SQL queries and open DB connections.

* g10/tofu.c: Include <stdarg.h>.
(prepares_saved) [DEBUG_TOFU_CACHE]: New variable.
(queries) [DEBUG_TOFU_CACHE]: New variable.
(struct db): Add fields prevp, begin_transaction, end_transaction,
rollback, record_binding_get_old_policy, record_binding_update,
record_binding_update2, get_policy_select_policy_and_conflict,
get_trust_bindings_with_this_email, get_trust_gather_other_user_ids,
get_trust_gather_other_keys, register_already_seen, and
register_insert.
[DEBUG_TOFU_CACHE]: Add field hits.
(STRINGIFY): New macro.
(STRINGIFY2): New macro.
(enum sqlite_arg_type): New enum.
(sqlite3_stepx): New function.
(combined_db): Remove variable.
(opendb): Don't cache the combined db.
(struct dbs): New struct.  Update users to use this as the head of the
local DB list rather than overloading struct db.
(unlink_db): New function.
(link_db): New function.
(db_cache): New variable.
(db_cache_count): New variable.
(DB_CACHE_ENTRIES): Define.
(getdb): If the dbs specific cache doesn't include the DB, look at
DB_CACHE.  Only if that also doesn't include the DB open the
corresponding DB.
(closedb): New function.
(opendbs): Don't open the combined DB.  Just return an initialized
struct dbs.
(closedbs): Don't close the dbs specific dbs.  Attach them to the
front of DB_CACHE.  If DB_CACHE contains more than DB_CACHE_ENTRIES,
close enough dbs from the end of the DB_CACHE list such that DB_CACHE
only contains DB_CACHE_ENTRIES.  Don't directly close the dbs, instead
use the new closedb function.
[DEBUG_TOFU_CACHE]: Print out some statistics.
(record_binding): Use sqlite3_stepx instead of sqlite3_exec or
sqlite3_exec_printf.
(get_policy): Likewise.
(get_trust): Likewise.
(tofu_register): Likewise.

--
Signed-off-by: Neal H. Walfield <neal@g10code.com>
This commit is contained in:
Neal H. Walfield 2015-10-23 13:42:50 +02:00
parent cd879d4bd6
commit 297cf8660c

View File

@ -27,6 +27,7 @@
#include <stdio.h>
#include <sys/stat.h>
#include <assert.h>
#include <stdarg.h>
#include <sqlite3.h>
#include "gpg.h"
@ -41,6 +42,12 @@
#include "tofu.h"
#define DEBUG_TOFU_CACHE 0
#if DEBUG_TOFU_CACHE
static int prepares_saved;
static int queries;
#endif
/* The TOFU data can be saved in two different formats: either in a
single combined database (opt.tofu_db_format == TOFU_DB_FLAT) or in
a split file format (opt.tofu_db_format == TOFU_DB_SPLIT). In the
@ -71,17 +78,43 @@ enum db_type
struct db
{
struct db *next;
struct db **prevp;
enum db_type type;
sqlite3 *db;
struct
{
sqlite3_stmt *begin_transaction;
sqlite3_stmt *end_transaction;
sqlite3_stmt *rollback;
sqlite3_stmt *record_binding_get_old_policy;
sqlite3_stmt *record_binding_update;
sqlite3_stmt *record_binding_update2;
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 *register_already_seen;
sqlite3_stmt *register_insert;
} s;
#if DEBUG_TOFU_CACHE
int hits;
#endif
/* If TYPE is DB_COMBINED, this is "". Otherwise, it is either the
fingerprint (type == DB_KEY) or the normalized email address
(type == DB_EMAIL). */
char name[1];
};
#define STRINGIFY(s) STRINGIFY2(s)
#define STRINGIFY2(s) #s
/* The grouping parameters when collecting signature statistics. */
/* If a message is signed a couple of hours in the future, just assume
@ -187,6 +220,186 @@ sqlite3_exec_printf (sqlite3 *db,
return rc;
}
enum sqlite_arg_type
{
SQLITE_ARG_END = 0xdead001,
SQLITE_ARG_INT,
SQLITE_ARG_LONG_LONG,
SQLITE_ARG_STRING
};
static int
sqlite3_stepx (sqlite3 *db,
sqlite3_stmt **stmtp,
int (*callback) (void*,int,char**,char**),
void *cookie,
char **errmsg,
const char *sql, ...)
{
int rc;
int err = 0;
sqlite3_stmt *stmt = NULL;
va_list va;
int args;
enum sqlite_arg_type t;
int i;
int cols;
/* Names of the columns. We initialize this lazily to avoid the
overhead in case the query doesn't return any results. */
const char **azColName = 0;
int callback_initialized = 0;
const char **azVals = 0;
callback_initialized = 0;
if (stmtp && *stmtp)
{
stmt = *stmtp;
/* Make sure this statement is associated with the supplied db. */
assert (db == sqlite3_db_handle (stmt));
#if DEBUG_TOFU_CACHE
prepares_saved ++;
#endif
}
else
{
rc = sqlite3_prepare_v2 (db, sql, -1, &stmt, NULL);
if (rc)
log_fatal ("failed to prepare SQL: %s", sql);
if (stmtp)
*stmtp = stmt;
}
#if DEBUG_TOFU_CACHE
queries ++;
#endif
args = sqlite3_bind_parameter_count (stmt);
va_start (va, sql);
if (args)
{
for (i = 1; i <= args; i ++)
{
t = va_arg (va, enum sqlite_arg_type);
switch (t)
{
case SQLITE_ARG_INT:
{
int value = va_arg (va, int);
err = sqlite3_bind_int (stmt, i, value);
break;
}
case SQLITE_ARG_LONG_LONG:
{
long long value = va_arg (va, long long);
err = sqlite3_bind_int64 (stmt, i, value);
break;
}
case SQLITE_ARG_STRING:
{
char *text = va_arg (va, char *);
err = sqlite3_bind_text (stmt, i, text, -1, SQLITE_STATIC);
break;
}
default:
/* Internal error. Likely corruption. */
log_fatal ("Bad value for parameter type %d.\n", t);
}
if (err)
{
log_fatal ("Error binding parameter %d\n", i);
goto out;
}
}
}
t = va_arg (va, enum sqlite_arg_type);
assert (t == SQLITE_ARG_END);
va_end (va);
for (;;)
{
rc = sqlite3_step (stmt);
if (rc != SQLITE_ROW)
/* No more data (SQLITE_DONE) or an error occured. */
break;
if (! callback)
continue;
if (! callback_initialized)
{
cols = sqlite3_column_count (stmt);
azColName = xmalloc (2 * cols * sizeof (const char *) + 1);
for (i = 0; i < cols; i ++)
azColName[i] = sqlite3_column_name (stmt, i);
callback_initialized = 1;
}
azVals = &azColName[cols];
for (i = 0; i < cols; i ++)
{
azVals[i] = sqlite3_column_text (stmt, i);
if (! azVals[i] && sqlite3_column_type (stmt, i) != SQLITE_NULL)
/* Out of memory. */
{
err = SQLITE_NOMEM;
break;
}
}
if (callback (cookie, cols, (char **) azVals, (char **) azColName))
/* A non-zero result means to abort. */
{
err = SQLITE_ABORT;
break;
}
}
out:
xfree (azColName);
if (stmtp)
rc = sqlite3_reset (stmt);
else
rc = sqlite3_finalize (stmt);
if (rc == SQLITE_OK && err)
/* Local error. */
{
rc = err;
if (errmsg)
{
const char *e = sqlite3_errstr (err);
size_t l = strlen (e) + 1;
*errmsg = sqlite3_malloc (l);
if (! *errmsg)
log_fatal ("Out of memory.\n");
memcpy (*errmsg, e, l);
}
}
else if (rc != SQLITE_OK && errmsg)
/* Error reported by sqlite. */
{
const char * e = sqlite3_errmsg (db);
size_t l = strlen (e) + 1;
*errmsg = sqlite3_malloc (l);
if (! *errmsg)
log_fatal ("Out of memory.\n");
memcpy (*errmsg, e, l);
}
return rc;
}
/* Collect results of a select count (*) ...; style query. Aborts if
the argument is not a valid integer (or real of the form X.0). */
@ -451,8 +664,6 @@ initdb (sqlite3 *db, enum db_type type)
}
}
static sqlite3 *combined_db;
/* Open and initialize a low-level TOFU database. Returns NULL on
failure. This function should not normally be directly called to
get a database handle. Instead, use getdb(). */
@ -468,9 +679,6 @@ opendb (char *filename, enum db_type type)
assert (! filename);
assert (type == DB_COMBINED);
if (combined_db)
return combined_db;
filename = make_filename (opt.homedir, "tofu.db", NULL);
filename_free = 1;
}
@ -502,12 +710,36 @@ opendb (char *filename, enum db_type type)
db = NULL;
}
if (opt.tofu_db_format == TOFU_DB_FLAT)
combined_db = db;
return db;
}
struct dbs
{
struct db *db;
};
static void
unlink_db (struct db *db)
{
*db->prevp = db->next;
if (db->next)
db->next->prevp = db->prevp;
}
static void
link_db (struct db **head, struct db *db)
{
db->next = *head;
if (db->next)
db->next->prevp = &db->next;
db->prevp = head;
*head = db;
}
static struct db *db_cache;
static int db_cache_count;
#define DB_CACHE_ENTRIES 16
/* Return a database handle. <type, name> describes the required
database. If there is a cached handle in DBS, that handle is
returned. Otherwise, the database is opened and cached in DBS.
@ -517,38 +749,38 @@ opendb (char *filename, enum db_type type)
TYPE must be either DB_MAIL or DB_KEY. In the combined format, the
combined DB is always returned. */
static struct db *
getdb (struct db *dbs, const char *name, enum db_type type)
getdb (struct dbs *dbs, const char *name, enum db_type type)
{
struct db *t = NULL;
sqlite3 *sqlitedb = NULL;
char *name_sanitized = NULL;
int count;
char *filename = NULL;
int i;
int need_link = 1;
sqlite3 *sqlitedb = NULL;
assert (dbs);
assert (name);
assert (type == DB_EMAIL || type == DB_KEY);
assert (dbs);
/* The first entry is always for the combined DB. */
assert (dbs->type == DB_COMBINED);
assert (! dbs->name[0]);
if (opt.tofu_db_format == TOFU_DB_FLAT)
/* When using the flat format, we only have a single combined
DB. */
/* When using the flat format, we only have a single DB, the
combined DB. */
{
assert (dbs->db);
assert (! dbs->next);
return dbs;
if (dbs->db)
{
assert (dbs->db->type == DB_COMBINED);
assert (! dbs->db->next);
return dbs->db;
}
else
/* When using the split format the first entry on the DB list is a
dummy entry. */
assert (! dbs->db);
/* We have the split format. */
type = DB_COMBINED;
}
if (type != DB_COMBINED)
/* Only allow alpha-numeric characters in the name. */
{
int i;
/* Only allow alpha-numeric characters in the filename. */
name_sanitized = xstrdup (name);
for (i = 0; name[i]; i ++)
{
@ -558,12 +790,32 @@ getdb (struct db *dbs, const char *name, enum db_type type)
|| ('0' <= c && c <= '9')))
name_sanitized[i] = '_';
}
}
/* See if the DB is cached. */
for (t = dbs->next; t; t = t->next)
if (type == t->type && strcmp (t->name, name_sanitized) == 0)
for (t = dbs->db; t; t = t->next)
if (t->type == type
&& (type == DB_COMBINED || strcmp (t->name, name_sanitized) == 0))
{
need_link = 0;
goto out;
}
for (t = db_cache, count = 0; t; t = t->next, count ++)
if (type == t->type
&& (type == DB_COMBINED || strcmp (t->name, name_sanitized) == 0))
{
unlink_db (t);
db_cache_count --;
goto out;
}
assert (db_cache_count == count);
if (type == DB_COMBINED)
filename = NULL;
else
{
/* Open the DB. The filename has the form:
tofu.d/TYPE/PREFIX/NAME.db
@ -588,37 +840,74 @@ getdb (struct db *dbs, const char *name, enum db_type type)
(opt.homedir, "tofu.d", type_str, prefix, name_db, NULL);
xfree (name_db);
}
}
sqlitedb = opendb (filename, type);
if (! sqlitedb)
goto out;
t = xmalloc (sizeof (struct db) + strlen (name_sanitized));
t = xmalloc_clear (sizeof (struct db)
+ (name_sanitized ? strlen (name_sanitized) : 0));
t->type = type;
t->db = sqlitedb;
if (name_sanitized)
strcpy (t->name, name_sanitized);
/* Insert it immediately after the first element. */
t->next = dbs->next;
dbs->next = t;
out:
if (t && need_link)
link_db (&dbs->db, t);
#if DEBUG_TOFU_CACHE
if (t)
t->hits ++;
#endif
xfree (filename);
xfree (name_sanitized);
if (! t)
return NULL;
return t;
}
static void
closedb (struct db *db)
{
sqlite3_stmt **statements;
if (opt.tofu_db_format == TOFU_DB_FLAT)
/* If we are using the flat format, then there is only ever the
combined DB. */
assert (! db->next);
if (db->type == DB_COMBINED)
{
assert (opt.tofu_db_format == TOFU_DB_FLAT);
assert (! db->name[0]);
}
else
{
assert (opt.tofu_db_format == TOFU_DB_SPLIT);
assert (db->type != DB_COMBINED);
assert (db->name[0]);
}
for (statements = &db->s.begin_transaction;
(void *) statements < (void *) &(&db->s)[1];
statements ++)
sqlite3_finalize (*statements);
sqlite3_close (db->db);
#if DEBUG_TOFU_CACHE
log_debug ("Freeing db. Used %d times.\n", db->hits);
#endif
xfree (db);
}
/* Create a new DB meta-handle. Returns NULL on error. */
static struct db *
static struct dbs *
opendbs (void)
{
sqlite3 *db = NULL;
struct db *dbs;
if (opt.tofu_db_format == TOFU_DB_AUTO)
{
char *filename = make_filename (opt.homedir, "tofu.db", NULL);
@ -679,88 +968,63 @@ opendbs (void)
}
}
if (opt.tofu_db_format == TOFU_DB_FLAT)
{
db = opendb (NULL, DB_COMBINED);
if (! db)
return NULL;
}
else
{
/* Create a dummy entry so that we have a handle. */
}
dbs = xmalloc_clear (sizeof (*dbs));
dbs->db = db;
dbs->type = DB_COMBINED;
return dbs;
return xmalloc_clear (sizeof (struct dbs));
}
/* Release all of the resources associated with a DB meta-handle. */
static void
closedbs (struct db *dbs)
closedbs (struct dbs *dbs)
{
if (dbs->db)
{
struct db *old_head = db_cache;
struct db *db;
struct db *n;
int count;
/* The first entry is always the combined DB. */
assert (dbs->type == DB_COMBINED);
if (opt.tofu_db_format == TOFU_DB_FLAT)
{
/* If we are using the flat format, then there is only ever the
combined DB. */
assert (! dbs->next);
assert (dbs->db);
assert (dbs->db == combined_db);
}
else
/* In the split format, the combined record is just a place holder
so that we have a stable handle. */
assert (! dbs->db);
/* Find the last DB. */
for (db = dbs->db, count = 1; db->next; db = db->next, count ++)
;
for (db = dbs; db; db = n)
{
n = db->next;
/* Join the two lists. */
db->next = db_cache;
if (db_cache)
db_cache->prevp = &db->next;
if (combined_db && db->db == combined_db)
/* Update the (new) first element. */
db_cache = dbs->db;
dbs->db->prevp = &db_cache;
db_cache_count += count;
/* Make sure that we don't have too many DBs on DB_CACHE. If
so, free some. */
if (db_cache_count > DB_CACHE_ENTRIES)
{
assert (opt.tofu_db_format == TOFU_DB_FLAT);
assert (dbs == db);
assert (db->type == DB_COMBINED);
assert (! db->name[0]);
/* We need to find the (DB_CACHE_ENTRIES + 1)th entry. It
is easy to skip the first COUNT entries since we still
have a handle on the old head. */
int skip = DB_CACHE_ENTRIES - count;
while (-- skip > 0)
old_head = old_head->next;
*old_head->prevp = NULL;
while (old_head)
{
db = old_head->next;
closedb (old_head);
old_head = db;
db_cache_count --;
}
else if (db->db)
/* Not the dummy entry. */
{
if (dbs == db)
/* The first entry. */
{
assert (opt.tofu_db_format == TOFU_DB_FLAT);
assert (db->type == DB_COMBINED);
assert (! db->name[0]);
}
else
/* Not the first entry. */
{
assert (opt.tofu_db_format == TOFU_DB_SPLIT);
assert (db->type != DB_COMBINED);
assert (db->name[0]);
}
sqlite3_close (db->db);
}
else
/* The dummy entry. */
{
assert (opt.tofu_db_format == TOFU_DB_SPLIT);
assert (dbs == db);
assert (db->type == DB_COMBINED);
assert (! db->name[0]);
}
xfree (dbs);
xfree (db);
}
#if DEBUG_TOFU_CACHE
log_debug ("Queries: %d (prepares saved: %d)\n",
queries, prepares_saved);
#endif
}
@ -790,7 +1054,7 @@ get_single_long_cb (void *cookie, int argc, char **argv, char **azColName)
If SHOW_OLD is set, the binding's old policy is displayed. */
static gpg_error_t
record_binding (struct db *dbs, const char *fingerprint, const char *email,
record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
const char *user_id, enum tofu_policy policy, int show_old)
{
struct db *db_email = NULL, *db_key = NULL;
@ -820,7 +1084,9 @@ record_binding (struct db *dbs, const char *fingerprint, const char *email,
if (! db_key)
return gpg_error (GPG_ERR_GENERAL);
rc = sqlite3_exec (db_email->db, "begin transaction;", NULL, NULL, &err);
rc = sqlite3_stepx (db_email->db, &db_email->s.begin_transaction,
NULL, NULL, &err,
"begin transaction;", SQLITE_ARG_END);
if (rc)
{
log_error (_("error beginning transaction on TOFU %s database: %s\n"),
@ -829,7 +1095,9 @@ record_binding (struct db *dbs, const char *fingerprint, const char *email,
return gpg_error (GPG_ERR_GENERAL);
}
rc = sqlite3_exec (db_key->db, "begin transaction;", NULL, NULL, &err);
rc = sqlite3_stepx (db_key->db, &db_key->s.begin_transaction,
NULL, NULL, &err,
"begin transaction;", SQLITE_ARG_END);
if (rc)
{
log_error (_("error beginning transaction on TOFU %s database: %s\n"),
@ -844,10 +1112,12 @@ record_binding (struct db *dbs, const char *fingerprint, const char *email,
purposes, there is no need to start a transaction or to die if
there is a failure. */
{
rc = sqlite3_exec_printf
(db_email->db, get_single_long_cb, &policy_old, &err,
"select policy from bindings where fingerprint = %Q and email = %Q",
fingerprint, email);
rc = sqlite3_stepx
(db_email->db, &db_email->s.record_binding_get_old_policy,
get_single_long_cb, &policy_old, &err,
"select policy from bindings where fingerprint = ? and email = ?",
SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
SQLITE_ARG_END);
if (rc)
{
log_debug ("TOFU: Error reading from binding database"
@ -875,17 +1145,20 @@ record_binding (struct db *dbs, const char *fingerprint, const char *email,
/* Nothing to do. */
goto out;
rc = sqlite3_exec_printf
(db_email->db, NULL, NULL, &err,
rc = sqlite3_stepx
(db_email->db, &db_email->s.record_binding_update, NULL, NULL, &err,
"insert or replace into bindings\n"
" (oid, fingerprint, email, user_id, time, policy)\n"
" values (\n"
/* If we don't explicitly reuse the OID, then SQLite will
reallocate a new one. We just need to search for the OID
based on the fingerprint and email since they are unique. */
" (select oid from bindings where fingerprint = %Q and email = %Q),\n"
" %Q, %Q, %Q, strftime('%%s','now'), %d);",
fingerprint, email, fingerprint, email, user_id, policy);
" (select oid from bindings where fingerprint = ? and email = ?),\n"
" ?, ?, ?, strftime('%s','now'), ?);",
SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
SQLITE_ARG_STRING, user_id, SQLITE_ARG_INT, (int) policy,
SQLITE_ARG_END);
if (rc)
{
log_error (_("error updating TOFU binding database"
@ -901,17 +1174,19 @@ record_binding (struct db *dbs, const char *fingerprint, const char *email,
{
assert (opt.tofu_db_format == TOFU_DB_SPLIT);
rc = sqlite3_exec_printf
(db_key->db, NULL, NULL, &err,
rc = sqlite3_stepx
(db_key->db, &db_key->s.record_binding_update2, NULL, NULL, &err,
"insert or replace into bindings\n"
" (oid, fingerprint, email, user_id)\n"
" values (\n"
/* If we don't explicitly reuse the OID, then SQLite will
reallocate a new one. We just need to search for the OID
based on the fingerprint and email since they are unique. */
" (select oid from bindings where fingerprint = %Q and email = %Q),\n"
" %Q, %Q, %Q);",
fingerprint, email, fingerprint, email, user_id);
" (select oid from bindings where fingerprint = ? and email = ?),\n"
" ?, ?, ?);",
SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
SQLITE_ARG_STRING, user_id, SQLITE_ARG_END);
if (rc)
{
log_error (_("error updating TOFU binding database"
@ -930,8 +1205,14 @@ record_binding (struct db *dbs, const char *fingerprint, const char *email,
{
int rc2;
rc2 = sqlite3_exec_printf (db_key->db, NULL, NULL, &err,
rc ? "rollback;" : "end transaction;");
if (rc)
rc2 = sqlite3_stepx (db_key->db, &db_key->s.rollback,
NULL, NULL, &err,
"rollback;", SQLITE_ARG_END);
else
rc2 = sqlite3_stepx (db_key->db, &db_key->s.end_transaction,
NULL, NULL, &err,
"end transaction;", SQLITE_ARG_END);
if (rc2)
{
log_error (_("error ending transaction on TOFU database: %s\n"),
@ -940,8 +1221,14 @@ record_binding (struct db *dbs, const char *fingerprint, const char *email,
}
out_revert_one:
rc2 = sqlite3_exec_printf (db_email->db, NULL, NULL, &err,
rc ? "rollback;" : "end transaction;");
if (rc)
rc2 = sqlite3_stepx (db_email->db, &db_email->s.rollback,
NULL, NULL, &err,
"rollback;", SQLITE_ARG_END);
else
rc2 = sqlite3_stepx (db_email->db, &db_email->s.end_transaction,
NULL, NULL, &err,
"end transaction;", SQLITE_ARG_END);
if (rc2)
{
log_error (_("error ending transaction on TOFU database: %s\n"),
@ -1154,7 +1441,7 @@ time_ago_unit (signed long t)
if CONFLICT is not NULL. Returns _tofu_GET_POLICY_ERROR if an error
occurs. */
static enum tofu_policy
get_policy (struct db *dbs, const char *fingerprint, const char *email,
get_policy (struct dbs *dbs, const char *fingerprint, const char *email,
char **conflict)
{
struct db *db;
@ -1172,11 +1459,13 @@ get_policy (struct db *dbs, const char *fingerprint, const char *email,
(TOFU_POLICY_NONE cannot appear in the DB. Thus, if POLICY is
still TOFU_POLICY_NONE after executing the query, then the
result set was empty.) */
rc = sqlite3_exec_printf
(db->db, strings_collect_cb, &strlist, &err,
rc = sqlite3_stepx (db->db, &db->s.get_policy_select_policy_and_conflict,
strings_collect_cb, &strlist, &err,
"select policy, conflict from bindings\n"
" where fingerprint = %Q and email = %Q",
fingerprint, email);
" where fingerprint = ? and email = ?",
SQLITE_ARG_STRING, fingerprint,
SQLITE_ARG_STRING, email,
SQLITE_ARG_END);
if (rc)
{
log_error (_("error reading from TOFU database"
@ -1264,7 +1553,7 @@ get_policy (struct db *dbs, const char *fingerprint, const char *email,
conflicting binding's policy to TOFU_POLICY_ASK. In either case,
we return TRUST_UNDEFINED. */
static enum tofu_policy
get_trust (struct db *dbs, const char *fingerprint, const char *email,
get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
const char *user_id, int may_ask)
{
struct db *db;
@ -1408,16 +1697,17 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
(need to check for a conflict).
*/
/* Look for conflicts. This is need in all 3 cases.
/* 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. */
rc = sqlite3_exec_printf
(db->db, strings_collect_cb, &bindings_with_this_email, &err,
"select distinct fingerprint from bindings where email = %Q;",
email);
rc = sqlite3_stepx
(db->db, &db->s.get_trust_bindings_with_this_email,
strings_collect_cb, &bindings_with_this_email, &err,
"select distinct fingerprint from bindings where email = ?;",
SQLITE_ARG_STRING, email, SQLITE_ARG_END);
if (rc)
{
log_error (_("error reading from TOFU database"
@ -1556,11 +1846,13 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
if (db_key)
{
rc = sqlite3_exec_printf
(db_key->db, strings_collect_cb, &other_user_ids, &err,
"select user_id, %s from bindings where fingerprint = %Q;",
opt.tofu_db_format == TOFU_DB_SPLIT ? "email" : "policy",
fingerprint);
rc = sqlite3_stepx
(db_key->db, &db_key->s.get_trust_gather_other_user_ids,
strings_collect_cb, &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);
@ -1605,8 +1897,9 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
/* 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 = sqlite3_exec_printf
(db->db, signature_stats_collect_cb, &stats, &err,
rc = sqlite3_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"
@ -1615,27 +1908,30 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
small, medium or large units? (Note: whatever we do, we
keep the value in seconds. Then when we group, everything
that rounds to the same number of seconds is grouped.) */
" when delta < -%d then -1\n"
" when delta < %d then max(0, round(delta / %d) * %d)\n"
" when delta < %d then round(delta / %d) * %d\n"
" else round(delta / %d) * %d\n"
" 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"
" cast(strftime('%s','now') - sig_time as real) delta\n"
" from signatures) ss\n"
" on ss.binding = bindings.oid)\n"
" where email = %Q\n"
" where email = ?\n"
" group by fingerprint, time_ago\n"
/* Make sure the current key is first. */
" order by fingerprint = %Q asc, fingerprint desc, time_ago desc;\n",
TIME_AGO_FUTURE_IGNORE,
TIME_AGO_MEDIUM_THRESHOLD, TIME_AGO_UNIT_SMALL, TIME_AGO_UNIT_SMALL,
TIME_AGO_LARGE_THRESHOLD, TIME_AGO_UNIT_MEDIUM, TIME_AGO_UNIT_MEDIUM,
TIME_AGO_UNIT_LARGE, TIME_AGO_UNIT_LARGE,
email, fingerprint);
" 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;
@ -1825,7 +2121,7 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
}
static void
show_statistics (struct db *dbs, const char *fingerprint,
show_statistics (struct dbs *dbs, const char *fingerprint,
const char *email, const char *user_id,
const char *sig_exclude)
{
@ -2174,7 +2470,7 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
const byte *sig_digest_bin, int sig_digest_bin_len,
time_t sig_time, const char *origin, int may_ask)
{
struct db *dbs;
struct dbs *dbs;
struct db *db;
char *fingerprint = NULL;
char *email = NULL;
@ -2228,7 +2524,9 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
/* We do a query and then an insert. Make sure they are atomic
by wrapping them in a transaction. */
rc = sqlite3_exec (db->db, "begin transaction;", NULL, NULL, &err);
rc = sqlite3_stepx (db->db, &db->s.begin_transaction,
NULL, NULL, &err, "begin transaction;",
SQLITE_ARG_END);
if (rc)
{
log_error (_("error beginning transaction on TOFU database: %s\n"), err);
@ -2238,14 +2536,18 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
/* If we've already seen this signature before, then don't add
it again. */
rc = sqlite3_exec_printf
(db->db, get_single_unsigned_long_cb, &c, &err,
rc = sqlite3_stepx
(db->db, &db->s.register_already_seen,
get_single_unsigned_long_cb, &c, &err,
"select count (*)\n"
" from signatures left join bindings\n"
" on signatures.binding = bindings.oid\n"
" where fingerprint = %Q and email = %Q and sig_time = 0x%lx\n"
" and sig_digest = %Q",
fingerprint, email, (unsigned long) sig_time, sig_digest);
" where fingerprint = ? and email = ? and sig_time = ?\n"
" and sig_digest = ?",
SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
SQLITE_ARG_LONG_LONG, (long long) sig_time,
SQLITE_ARG_STRING, sig_digest,
SQLITE_ARG_END);
if (rc)
{
log_error (_("error reading from signatures database"
@ -2281,15 +2583,18 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
assert (c == 0);
rc = sqlite3_exec_printf
(db->db, NULL, NULL, &err,
rc = sqlite3_stepx
(db->db, &db->s.register_insert, NULL, NULL, &err,
"insert into signatures\n"
" (binding, sig_digest, origin, sig_time, time)\n"
" values\n"
" ((select oid from bindings\n"
" where fingerprint = %Q and email = %Q),\n"
" %Q, %Q, 0x%lx, strftime('%%s', 'now'));",
fingerprint, email, sig_digest, origin, (unsigned long) sig_time);
" where fingerprint = ? and email = ?),\n"
" ?, ?, ?, strftime('%s', 'now'));",
SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
SQLITE_ARG_STRING, sig_digest, SQLITE_ARG_STRING, origin,
SQLITE_ARG_LONG_LONG, (long long) sig_time,
SQLITE_ARG_END);
if (rc)
{
log_error (_("error updating TOFU DB"
@ -2302,9 +2607,11 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
/* It only matters whether we abort or commit the transaction
(so long as we do something) if we execute the insert. */
if (rc)
rc = sqlite3_exec (db->db, "rollback;", NULL, NULL, &err);
rc = sqlite3_stepx (db->db, &db->s.rollback, NULL, NULL, &err,
"rollback;", SQLITE_ARG_END);
else
rc = sqlite3_exec (db->db, "commit transaction;", NULL, NULL, &err);
rc = sqlite3_stepx (db->db, &db->s.end_transaction, NULL, NULL, &err,
"end transaction;", SQLITE_ARG_END);
if (rc)
{
log_error (_("error ending transaction on TOFU database: %s\n"), err);
@ -2392,7 +2699,7 @@ int
tofu_get_validity (const byte *fingerprint_bin, const char *user_id,
int may_ask)
{
struct db *dbs;
struct dbs *dbs;
char *fingerprint = NULL;
char *email = NULL;
int trust_level = TRUST_UNDEFINED;
@ -2441,7 +2748,7 @@ tofu_get_validity (const byte *fingerprint_bin, const char *user_id,
gpg_error_t
tofu_set_policy (kbnode_t kb, enum tofu_policy policy)
{
struct db *dbs;
struct dbs *dbs;
PKT_public_key *pk;
char fingerprint_bin[MAX_FINGERPRINT_LEN];
size_t fingerprint_bin_len = sizeof (fingerprint_bin);
@ -2524,7 +2831,7 @@ gpg_error_t
tofu_get_policy (PKT_public_key *pk, PKT_user_id *user_id,
enum tofu_policy *policy)
{
struct db *dbs;
struct dbs *dbs;
char fingerprint_bin[MAX_FINGERPRINT_LEN];
size_t fingerprint_bin_len = sizeof (fingerprint_bin);
char *fingerprint;