/* tofu.c - TOFU trust model. * Copyright (C) 2015 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ /* TODO: - Format the fingerprints nicely when printing (similar to gpg --list-keys) */ #include #include #include #include #include #include #include #include "gpg.h" #include "types.h" #include "logging.h" #include "stringhelp.h" #include "options.h" #include "mbox-util.h" #include "i18n.h" #include "trustdb.h" #include "mkdir_p.h" #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 split format, there is one database per normalized email address (DB_EMAIL) and one per key (DB_KEY). */ enum db_type { DB_COMBINED, DB_EMAIL, DB_KEY }; /* A list of open DBs. In the flat format, this consists of a single element with the type DB_COMBINED and whose name is the empty string. In the split format, the first element is a dummy element (DB is NULL) whose type is DB_COMBINED and whose name is the empty string. Any following elements describe either DB_EMAIL or DB_KEY DBs. In theis case, NAME is either the normalized email address or the fingerprint. To initialize this data structure, call opendbs(). When you are done, clean it up using closedbs(). To get a handle to a database, use the getdb() function. This will either return an existing handle or open a new DB connection, as appropriate. */ struct db { struct db *next; struct db **prevp; enum db_type type; sqlite3 *db; struct { sqlite3_stmt *savepoint_batch; sqlite3_stmt *savepoint_batch_commit; sqlite3_stmt *savepoint_inner; sqlite3_stmt *savepoint_inner_commit; 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 int batch_update; /* 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]; }; static struct db *db_cache; static int db_cache_count; #define DB_CACHE_ENTRIES 16 static void tofu_cache_dump (struct db *db) GPGRT_ATTR_USED; static void tofu_cache_dump (struct db *db) { log_info ("Connection %p:\n", db); for (; db; db = db->next) log_info (" %s: %sbatch mode\n", db->name, db->batch_update ? "" : "NOT "); log_info ("Cache:\n"); for (db = db_cache; db; db = db->next) log_info (" %s: %sbatch mode\n", db->name, db->batch_update ? "" : "NOT "); } #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 some clock skew. */ #define TIME_AGO_FUTURE_IGNORE (2 * 60 * 60) #if 0 # define TIME_AGO_UNIT_SMALL 60 # define TIME_AGO_UNIT_SMALL_NAME _("minute") # define TIME_AGO_UNIT_SMALL_NAME_PLURAL _("minutes") # define TIME_AGO_MEDIUM_THRESHOLD (60 * TIME_AGO_UNIT_SMALL) # define TIME_AGO_UNIT_MEDIUM (60 * 60) # define TIME_AGO_UNIT_MEDIUM_NAME _("hour") # define TIME_AGO_UNIT_MEDIUM_NAME_PLURAL _("hours") # define TIME_AGO_LARGE_THRESHOLD (24 * 60 * TIME_AGO_UNIT_SMALL) # define TIME_AGO_UNIT_LARGE (24 * 60 * 60) # define TIME_AGO_UNIT_LARGE_NAME _("day") # define TIME_AGO_UNIT_LARGE_NAME_PLURAL _("days") #else # define TIME_AGO_UNIT_SMALL (24 * 60 * 60) # define TIME_AGO_UNIT_SMALL_NAME _("day") # define TIME_AGO_UNIT_SMALL_NAME_PLURAL _("days") # define TIME_AGO_MEDIUM_THRESHOLD (4 * TIME_AGO_UNIT_SMALL) # define TIME_AGO_UNIT_MEDIUM (7 * 24 * 60 * 60) # define TIME_AGO_UNIT_MEDIUM_NAME _("week") # define TIME_AGO_UNIT_MEDIUM_NAME_PLURAL _("weeks") # define TIME_AGO_LARGE_THRESHOLD (28 * TIME_AGO_UNIT_SMALL) # define TIME_AGO_UNIT_LARGE (30 * 24 * 60 * 60) # define TIME_AGO_UNIT_LARGE_NAME _("month") # define TIME_AGO_UNIT_LARGE_NAME_PLURAL _("months") #endif const char * tofu_policy_str (enum tofu_policy policy) { switch (policy) { case TOFU_POLICY_NONE: return "none"; case TOFU_POLICY_AUTO: return "auto"; case TOFU_POLICY_GOOD: return "good"; case TOFU_POLICY_UNKNOWN: return "unknown"; case TOFU_POLICY_BAD: return "bad"; case TOFU_POLICY_ASK: return "ask"; default: return "???"; } } /* Convert a binding policy (e.g., TOFU_POLICY_BAD) to a trust level (e.g., TRUST_BAD) in light of the current configuration. */ int tofu_policy_to_trust_level (enum tofu_policy policy) { if (policy == TOFU_POLICY_AUTO) /* If POLICY is AUTO, fallback to OPT.TOFU_DEFAULT_POLICY. */ policy = opt.tofu_default_policy; switch (policy) { case TOFU_POLICY_AUTO: /* If POLICY and OPT.TOFU_DEFAULT_POLICY are both AUTO, default to marginal trust. */ return TRUST_MARGINAL; case TOFU_POLICY_GOOD: return TRUST_FULLY; case TOFU_POLICY_UNKNOWN: return TRUST_UNKNOWN; case TOFU_POLICY_BAD: return TRUST_NEVER; case TOFU_POLICY_ASK: return TRUST_UNKNOWN; default: log_bug ("Bad value for trust policy: %d\n", opt.tofu_default_policy); return 0; } } /* This is a convenience function that combines sqlite3_mprintf and sqlite3_exec. */ static int sqlite3_exec_printf (sqlite3 *db, int (*callback)(void*,int,char**,char**), void *cookie, char **errmsg, const char *sql, ...) { va_list ap; int rc; char *sql2; va_start (ap, sql); sql2 = sqlite3_vmprintf (sql, ap); va_end (ap); #if 0 log_debug ("tofo db: executing: '%s'\n", sql2); #endif rc = sqlite3_exec (db, sql2, callback, cookie, errmsg); sqlite3_free (sql2); return rc; } 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; } static int batch_update; static time_t batch_update_started; static gpg_error_t end_transaction (struct db *db, int only_batch); /* Start a transaction on DB. */ static gpg_error_t begin_transaction (struct db *db, int only_batch) { int rc; char *err = NULL; if (batch_update && batch_update_started != gnupg_get_time ()) /* We've been in batch update mode for a while (on average, more than 500 ms). To prevent starving other gpg processes, we drop and retake the batch lock. Note: if we wanted higher resolution, we could use npth_clock_gettime. */ { struct db *t; for (t = db_cache; t; t = t->next) if (t->batch_update) end_transaction (t, 1); for (t = db; t; t = t->next) if (t->batch_update) end_transaction (t, 1); batch_update_started = gnupg_get_time (); /* Yield to allow another process a chance to run. */ sched_yield (); } /* XXX: In split mode, this can end in deadlock. Consider: we have two gpg processes running simultaneously and they each want to lock DB A and B, but in different orders. This will be automatically resolved by causing one of them to return EBUSY and aborting. A more intelligent approach would be to commit and retake the batch transaction. This requires a list of all DBs that are currently in batch mode. */ if (batch_update && ! db->batch_update) { rc = sqlite3_stepx (db->db, &db->s.savepoint_batch, NULL, NULL, &err, "savepoint batch;", SQLITE_ARG_END); if (rc) { log_error (_("error beginning %s transaction on TOFU database '%s': %s\n"), "batch", *db->name ? db->name : "combined", err); sqlite3_free (err); return gpg_error (GPG_ERR_GENERAL); } db->batch_update = 1; } if (only_batch) return 0; rc = sqlite3_stepx (db->db, &db->s.savepoint_inner, NULL, NULL, &err, "savepoint inner;", SQLITE_ARG_END); if (rc) { log_error (_("error beginning %s transaction on TOFU database '%s': %s\n"), "inner", *db->name ? db->name : "combined", err); sqlite3_free (err); return gpg_error (GPG_ERR_GENERAL); } return 0; } /* Commit a transaction. If ONLY_BATCH is 1, then this only ends the batch transaction if we have left batch mode. If ONLY_BATCH is 2, this ends any open batch transaction even if we are still in batch mode. */ static gpg_error_t end_transaction (struct db *db, int only_batch) { int rc; char *err = NULL; if ((! batch_update || only_batch == 2) && db->batch_update) /* The batch transaction is still in open, but we left batch mode. */ { db->batch_update = 0; rc = sqlite3_stepx (db->db, &db->s.savepoint_batch_commit, NULL, NULL, &err, "release batch;", SQLITE_ARG_END); if (rc) { log_error (_("error committing %s transaction on TOFU database '%s': %s\n"), "batch", *db->name ? db->name : "combined", err); sqlite3_free (err); return gpg_error (GPG_ERR_GENERAL); } /* Releasing an outer transaction releases an open inner transactions. We're done. */ return 0; } if (only_batch) return 0; rc = sqlite3_stepx (db->db, &db->s.savepoint_inner_commit, NULL, NULL, &err, "release inner;", SQLITE_ARG_END); if (rc) { log_error (_("error committing %s transaction on TOFU database '%s': %s\n"), "inner", *db->name ? db->name : "combined", err); sqlite3_free (err); return gpg_error (GPG_ERR_GENERAL); } return 0; } static gpg_error_t rollback_transaction (struct db *db) { int rc; char *err = NULL; if (db->batch_update) /* Just undo the most recent update; don't revert any progress made by the batch transaction. */ rc = sqlite3_exec (db->db, "rollback to inner;", NULL, NULL, &err); else /* Rollback the whole she-bang. */ rc = sqlite3_exec (db->db, "rollback;", NULL, NULL, &err); if (rc) { log_error (_("error rolling back inner transaction on TOFU database '%s': %s\n"), *db->name ? db->name : "combined", err); sqlite3_free (err); return gpg_error (GPG_ERR_GENERAL); } return 0; } void tofu_begin_batch_update (void) { if (! batch_update) batch_update_started = gnupg_get_time (); batch_update ++; } void tofu_end_batch_update (void) { assert (batch_update > 0); batch_update --; if (batch_update == 0) { struct db *db; for (db = db_cache; db; db = db->next) end_transaction (db, 1); } } /* Collect results of a select count (*) ...; style query. Aborts if the argument is not a valid integer (or real of the form X.0). */ static int get_single_unsigned_long_cb (void *cookie, int argc, char **argv, char **azColName) { unsigned long int *count = cookie; char *tail = NULL; (void) azColName; assert (argc == 1); errno = 0; *count = strtoul (argv[0], &tail, 0); if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0')) /* Abort. */ return 1; return 0; } /* We expect a single integer column whose name is "version". COOKIE must point to an int. This function always aborts. On error or a if the version is bad, sets *VERSION to -1. */ static int version_check_cb (void *cookie, int argc, char **argv, char **azColName) { int *version = cookie; if (argc != 1 || strcmp (azColName[0], "version") != 0) { *version = -1; return 1; } if (strcmp (argv[0], "1") == 0) *version = 1; else { log_error (_("unsupported TOFU DB version: %s\n"), argv[0]); *version = -1; } /* Don't run again. */ return 1; } /* If the DB is new, initialize it. Otherwise, check the DB's version. Return 0 if the database is okay and 1 otherwise. */ static int initdb (sqlite3 *db, enum db_type type) { char *err = NULL; int rc; unsigned long int count; int version = -1; rc = sqlite3_exec (db, "begin transaction;", NULL, NULL, &err); if (rc) { log_error (_("error beginning transaction on TOFU database: %s\n"), err); sqlite3_free (err); return 1; } /* If the DB has no tables, then assume this is a new DB that needs to be initialized. */ rc = sqlite3_exec (db, "select count(*) from sqlite_master where type='table';", get_single_unsigned_long_cb, &count, &err); if (rc) { log_error (_("error querying TOFU DB's available tables: %s\n"), err); sqlite3_free (err); goto out; } else if (count != 0) /* Assume that the DB is already initialized. Make sure the version is okay. */ { rc = sqlite3_exec (db, "select version from version;", version_check_cb, &version, &err); if (rc == SQLITE_ABORT && version == 1) /* Happy, happy, joy, joy. */ { sqlite3_free (err); rc = 0; goto out; } else if (rc == SQLITE_ABORT && version == -1) /* Unsupported version. */ { /* An error message was already displayed. */ sqlite3_free (err); goto out; } else if (rc) /* Some error. */ { log_error (_("error determining TOFU DB's version: %s\n"), err); sqlite3_free (err); goto out; } else /* Unexpected success. This can only happen if there are no rows. */ { log_error (_("error determining TOFU DB's version: %s\n"), "select returned 0, but expected ABORT"); rc = 1; goto out; } } /* Create the version table. */ rc = sqlite3_exec (db, "create table version (version INTEGER);", NULL, NULL, &err); if (rc) { log_error (_("error initializing TOFU database (%s): %s\n"), "version", err); sqlite3_free (err); goto out; } /* Initialize the version table, which contains a single integer value. */ rc = sqlite3_exec (db, "insert into version values (1);", NULL, NULL, &err); if (rc) { log_error (_("error initializing TOFU database (%s): %s\n"), "version, init", err); sqlite3_free (err); goto out; } /* The list of bindings and auxiliary data. OID is a unique ID identifying this binding (and used by the signatures table, see below). Note: OIDs will never be reused. FINGERPRINT: The key's fingerprint. EMAIL: The normalized email address. USER_ID: The unmodified user id from which EMAIL was extracted. TIME: The time this binding was first observed. POLICY: The trust policy (-1, 0, 1, or 2; see the documentation for TOFU_POLICY_BAD, etc. above). CONFLICT is either NULL or a fingerprint. Assume that we have a binding <0xdeadbeef, foo@example.com> and then we observe <0xbaddecaf, foo@example.com>. There two bindings conflict (they have the same email address). When we observe the latter binding, we warn the user about the conflict and ask for a policy decision about the new binding. We also change the old binding's policy to ask if it was auto. So that we know why this occured, we also set conflict to 0xbaddecaf. */ if (type == DB_EMAIL || type == DB_COMBINED) rc = sqlite3_exec_printf (db, NULL, NULL, &err, "create table bindings\n" " (oid INTEGER PRIMARY KEY AUTOINCREMENT,\n" " fingerprint TEXT, email TEXT, user_id TEXT, time INTEGER,\n" " policy BOOLEAN CHECK (policy in (%d, %d, %d, %d, %d)),\n" " conflict STRING,\n" " unique (fingerprint, email));\n" "create index bindings_fingerprint_email\n" " on bindings (fingerprint, email);\n" "create index bindings_email on bindings (email);\n", TOFU_POLICY_AUTO, TOFU_POLICY_GOOD, TOFU_POLICY_UNKNOWN, TOFU_POLICY_BAD, TOFU_POLICY_ASK); else /* In the split DB case, the fingerprint DB only contains a subset of the fields. This reduces the amount of duplicated data. Note: since the data is split on the email address, there is no need to index the email column. */ rc = sqlite3_exec_printf (db, NULL, NULL, &err, "create table bindings\n" " (oid INTEGER PRIMARY KEY AUTOINCREMENT,\n" " fingerprint TEXT, email TEXT, user_id,\n" " unique (fingerprint, email));\n" "create index bindings_fingerprint\n" " on bindings (fingerprint);\n"); if (rc) { log_error (_("error initializing TOFU database (%s): %s\n"), "bindings", err); sqlite3_free (err); goto out; } if (type != DB_KEY) { /* The signatures that we have observed. BINDING refers to a record in the bindings table, which describes the binding (i.e., this is a foreign key that references bindings.oid). SIG_DIGEST is the digest stored in the signature. SIG_TIME is the timestamp stored in the signature. ORIGIN is a free-form string that describes who fed this signature to GnuPG (e.g., email:claws). TIME is the time this signature was registered. */ rc = sqlite3_exec (db, "create table signatures " " (binding INTEGER NOT NULL, sig_digest TEXT," " origin TEXT, sig_time INTEGER, time INTEGER," " primary key (binding, sig_digest, origin));", NULL, NULL, &err); if (rc) { log_error (_("error initializing TOFU database (%s): %s\n"), "signatures", err); sqlite3_free (err); goto out; } } out: if (rc) { rc = sqlite3_exec (db, "rollback;", NULL, NULL, &err); if (rc) { log_error (_("error aborting transaction on TOFU DB: %s\n"), err); sqlite3_free (err); } return 1; } else { rc = sqlite3_exec (db, "end transaction;", NULL, NULL, &err); if (rc) { log_error (_("error committing transaction on TOFU DB: %s\n"), err); sqlite3_free (err); return 1; } return 0; } } /* 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(). */ static sqlite3 * opendb (char *filename, enum db_type type) { sqlite3 *db; int filename_free = 0; int rc; if (opt.tofu_db_format == TOFU_DB_FLAT) { assert (! filename); assert (type == DB_COMBINED); filename = make_filename (opt.homedir, "tofu.db", NULL); filename_free = 1; } else assert (type == DB_EMAIL || type == DB_KEY); assert (filename); rc = sqlite3_open (filename, &db); if (rc) { log_error (_("can't open TOFU DB ('%s'): %s\n"), filename, sqlite3_errmsg (db)); /* Even if an error occurs, DB is guaranteed to be valid. */ sqlite3_close (db); db = NULL; } /* If a DB is locked wait up to 5 seconds for the lock to be cleared before failing. */ sqlite3_busy_timeout (db, 5 * 1000); if (filename_free) xfree (filename); if (db && initdb (db, type)) { sqlite3_close (db); db = NULL; } 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; } /* Return a database handle. 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. NAME is the name of the DB and may not be NULL. TYPE must be either DB_MAIL or DB_KEY. In the combined format, the combined DB is always returned. */ static struct db * getdb (struct dbs *dbs, const char *name, enum db_type type) { struct db *t = NULL; char *name_sanitized = NULL; int count; char *filename = NULL; int need_link = 1; sqlite3 *sqlitedb = NULL; assert (dbs); assert (name); assert (type == DB_EMAIL || type == DB_KEY); if (opt.tofu_db_format == TOFU_DB_FLAT) /* When using the flat format, we only have a single DB, the combined DB. */ { if (dbs->db) { assert (dbs->db->type == DB_COMBINED); assert (! dbs->db->next); return dbs->db; } type = DB_COMBINED; } if (type != DB_COMBINED) /* Only allow alpha-numeric characters in the name. */ { int i; name_sanitized = xstrdup (name); for (i = 0; name[i]; i ++) { char c = name_sanitized[i]; if (! (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9'))) name_sanitized[i] = '_'; } } /* See if the DB is cached. */ 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 We use a short prefix to try to avoid having many files in a single directory. */ { char *type_str = type == DB_EMAIL ? "email" : "key"; char prefix[3] = { name_sanitized[0], name_sanitized[1], 0 }; char *name_db; /* Make the directory. */ if (gnupg_mkdir_p (opt.homedir, "tofu.d", type_str, prefix, NULL) != 0) { log_error (_("unable to create directory %s/%s/%s/%s"), opt.homedir, "tofu.d", type_str, prefix); goto out; } name_db = xstrconcat (name_sanitized, ".db", NULL); filename = make_filename (opt.homedir, "tofu.d", type_str, prefix, name_db, NULL); xfree (name_db); } } sqlitedb = opendb (filename, type); if (! sqlitedb) goto out; 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); out: if (t && need_link) link_db (&dbs->db, t); #if DEBUG_TOFU_CACHE if (t) t->hits ++; #endif xfree (filename); xfree (name_sanitized); 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]); } if (db->batch_update) end_transaction (db, 2); for (statements = (void *) &db->s; (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 dbs * opendbs (void) { if (opt.tofu_db_format == TOFU_DB_AUTO) { char *filename = make_filename (opt.homedir, "tofu.db", NULL); struct stat s; int have_tofu_db = 0; int have_tofu_d = 0; if (stat (filename, &s) == 0) { have_tofu_db = 1; if (DBG_TRUST) log_debug ("%s exists.\n", filename); } else { if (DBG_TRUST) log_debug ("%s does not exist.\n", filename); } /* We now have tofu.d. */ filename[strlen (filename) - 1] = '\0'; if (stat (filename, &s) == 0) { have_tofu_d = 1; if (DBG_TRUST) log_debug ("%s exists.\n", filename); } else { if (DBG_TRUST) log_debug ("%s does not exist.\n", filename); } xfree (filename); if (have_tofu_db && have_tofu_d) { log_info (_("Warning: Home directory contains both tofu.db and tofu.d. Using split format for TOFU DB.\n")); opt.tofu_db_format = TOFU_DB_SPLIT; } else if (have_tofu_db) { opt.tofu_db_format = TOFU_DB_FLAT; if (DBG_TRUST) log_debug ("Using flat format for TOFU DB.\n"); } else if (have_tofu_d) { opt.tofu_db_format = TOFU_DB_SPLIT; if (DBG_TRUST) log_debug ("Using split format for TOFU DB.\n"); } else { opt.tofu_db_format = TOFU_DB_SPLIT; if (DBG_TRUST) log_debug ("Using split format for TOFU DB.\n"); } } return xmalloc_clear (sizeof (struct dbs)); } /* Release all of the resources associated with a DB meta-handle. */ static void closedbs (struct dbs *dbs) { if (dbs->db) { struct db *old_head = db_cache; struct db *db; int count; /* Find the last DB. */ for (db = dbs->db, count = 1; db->next; db = db->next, count ++) { /* When we leave batch mode we leave batch mode on any cached connections. */ if (! batch_update) assert (! db->batch_update); } if (! batch_update) assert (! db->batch_update); /* Join the two lists. */ db->next = db_cache; if (db_cache) db_cache->prevp = &db->next; /* 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) { /* 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 --; } } } xfree (dbs); #if DEBUG_TOFU_CACHE log_debug ("Queries: %d (prepares saved: %d)\n", queries, prepares_saved); #endif } /* Collect results of a select min (foo) ...; style query. Aborts if the argument is not a valid integer (or real of the form X.0). */ static int get_single_long_cb (void *cookie, int argc, char **argv, char **azColName) { long *count = cookie; char *tail = NULL; (void) azColName; assert (argc == 1); errno = 0; *count = strtol (argv[0], &tail, 0); if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0')) /* Abort. */ return 1; return 0; } /* Record (or update) a trust policy about a (possibly new) binding. If SHOW_OLD is set, the binding's old policy is displayed. */ static gpg_error_t 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; int rc; char *err = NULL; enum tofu_policy policy_old = TOFU_POLICY_NONE; if (! (policy == TOFU_POLICY_AUTO || policy == TOFU_POLICY_GOOD || policy == TOFU_POLICY_UNKNOWN || policy == TOFU_POLICY_BAD || policy == TOFU_POLICY_ASK)) log_bug ("%s: Bad value for policy (%d)!\n", __func__, policy); db_email = getdb (dbs, email, DB_EMAIL); if (! db_email) return gpg_error (GPG_ERR_GENERAL); if (opt.tofu_db_format == TOFU_DB_SPLIT) /* In the split format, we need to update two DBs. To keep them consistent, we start a transaction on each. Note: this is the only place where we start two transaction and we always start transaction on the DB_KEY DB first, thus deadlock is not possible. */ { db_key = getdb (dbs, fingerprint, DB_KEY); if (! db_key) return gpg_error (GPG_ERR_GENERAL); rc = begin_transaction (db_email, 0); if (rc) return gpg_error (GPG_ERR_GENERAL); rc = begin_transaction (db_key, 0); if (rc) goto out_revert_one; } else { rc = begin_transaction (db_email, 1); if (rc) return gpg_error (GPG_ERR_GENERAL); } if (show_old) /* Get the old policy. Since this is just for informational purposes, there is no need to start a transaction or to die if there is a failure. */ { 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" " (reading policy for <%s, %s>): %s\n", fingerprint, email, err); sqlite3_free (err); } } if (DBG_TRUST) { if (policy_old != TOFU_POLICY_NONE) log_debug ("Changing TOFU trust policy for binding <%s, %s>" " from %s to %s.\n", fingerprint, email, tofu_policy_str (policy_old), tofu_policy_str (policy)); else log_debug ("Set TOFU trust policy for binding <%s, %s> to %s.\n", fingerprint, email, tofu_policy_str (policy)); } if (policy_old == policy) /* Nothing to do. */ goto out; 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 = ? 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" " (inserting <%s, %s> = %s): %s\n"), fingerprint, email, tofu_policy_str (policy), err); sqlite3_free (err); goto out; } if (db_key) /* We also need to update the key DB. */ { assert (opt.tofu_db_format == TOFU_DB_SPLIT); 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 = ? 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" " (inserting <%s, %s>): %s\n"), fingerprint, email, err); sqlite3_free (err); goto out; } } else assert (opt.tofu_db_format == TOFU_DB_FLAT); out: if (opt.tofu_db_format == TOFU_DB_SPLIT) /* We only need a transaction for the split format. */ { int rc2; if (rc) rc2 = rollback_transaction (db_key); else rc2 = end_transaction (db_key, 0); if (rc2) { log_error (_("error ending transaction on TOFU database: %s\n"), err); sqlite3_free (err); } out_revert_one: if (rc) rc2 = rollback_transaction (db_email); else rc2 = end_transaction (db_email, 0); if (rc2) { log_error (_("error ending transaction on TOFU database: %s\n"), err); sqlite3_free (err); } } if (rc) return gpg_error (GPG_ERR_GENERAL); return 0; } /* Collect the strings returned by a query in a simply string list. Any NULL values are converted to the empty string. If a result has 3 rows and each row contains two columns, then the results are added to the list as follows (the value is parentheses is the 1-based index in the final list): row 1, col 2 (6) row 1, col 1 (5) row 2, col 2 (4) row 2, col 1 (3) row 3, col 2 (2) row 3, col 1 (1) This is because add_to_strlist pushes the results onto the front of the list. The end result is that the rows are backwards, but the columns are in the expected order. */ static int strings_collect_cb (void *cookie, int argc, char **argv, char **azColName) { int i; strlist_t *strlist = cookie; (void) azColName; for (i = argc - 1; i >= 0; i --) add_to_strlist (strlist, argv[i] ? argv[i] : ""); return 0; } /* Auxiliary data structure to collect statistics about signatures. */ struct signature_stats { struct signature_stats *next; /* The user-assigned policy for this binding. */ enum tofu_policy policy; /* How long ago the signature was created (rounded to a multiple of TIME_AGO_UNIT_SMALL, etc.). */ long time_ago; /* Number of signatures during this time. */ unsigned long count; /* The key that generated this signature. */ char fingerprint[1]; }; static void signature_stats_free (struct signature_stats *stats) { while (stats) { struct signature_stats *next = stats->next; xfree (stats); stats = next; } } static void signature_stats_prepend (struct signature_stats **statsp, const char *fingerprint, enum tofu_policy policy, long time_ago, unsigned long count) { struct signature_stats *stats = xmalloc (sizeof (*stats) + strlen (fingerprint)); stats->next = *statsp; *statsp = stats; strcpy (stats->fingerprint, fingerprint); stats->policy = policy; stats->time_ago = time_ago; stats->count = count; } /* Process rows that contain the four columns: . */ static int signature_stats_collect_cb (void *cookie, int argc, char **argv, char **azColName) { struct signature_stats **statsp = cookie; char *tail; int i = 0; enum tofu_policy policy; long time_ago; unsigned long count; (void) azColName; i ++; tail = NULL; errno = 0; policy = strtol (argv[i], &tail, 0); if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0')) { /* Abort. */ log_error ("%s: Error converting %s to an integer (tail = '%s')\n", __func__, argv[i], tail); return 1; } i ++; if (! argv[i]) time_ago = 0; else { tail = NULL; errno = 0; time_ago = strtol (argv[i], &tail, 0); if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0')) { /* Abort. */ log_error ("%s: Error converting %s to an integer (tail = '%s')\n", __func__, argv[i], tail); return 1; } } i ++; /* If time_ago is NULL, then we had no messages, but we still have a single row, which count(*) turns into 1. */ if (! argv[i - 1]) count = 0; else { tail = NULL; errno = 0; count = strtoul (argv[i], &tail, 0); if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0')) { /* Abort. */ log_error ("%s: Error converting %s to an integer (tail = '%s')\n", __func__, argv[i], tail); return 1; } } i ++; assert (argc == i); signature_stats_prepend (statsp, argv[0], policy, time_ago, count); return 0; } /* Convert from seconds to time units. Note: T should already be a multiple of TIME_AGO_UNIT_SMALL or TIME_AGO_UNIT_MEDIUM or TIME_AGO_UNIT_LARGE. */ signed long time_ago_scale (signed long t) { if (t < TIME_AGO_UNIT_MEDIUM) return t / TIME_AGO_UNIT_SMALL; if (t < TIME_AGO_UNIT_LARGE) return t / TIME_AGO_UNIT_MEDIUM; return t / TIME_AGO_UNIT_LARGE; } /* Return the appropriate unit (respecting whether it is plural or singular). */ const char * time_ago_unit (signed long t) { signed long t_scaled = time_ago_scale (t); if (t < TIME_AGO_UNIT_MEDIUM) { if (t_scaled == 1) return TIME_AGO_UNIT_SMALL_NAME; return TIME_AGO_UNIT_SMALL_NAME_PLURAL; } if (t < TIME_AGO_UNIT_LARGE) { if (t_scaled == 1) return TIME_AGO_UNIT_MEDIUM_NAME; return TIME_AGO_UNIT_MEDIUM_NAME_PLURAL; } if (t_scaled == 1) return TIME_AGO_UNIT_LARGE_NAME; return TIME_AGO_UNIT_LARGE_NAME_PLURAL; } /* Return the policy for the binding (email has already been normalized) and any conflict information in *CONFLICT if CONFLICT is not NULL. Returns _tofu_GET_POLICY_ERROR if an error occurs. */ static enum tofu_policy get_policy (struct dbs *dbs, const char *fingerprint, const char *email, char **conflict) { struct db *db; int rc; char *err = NULL; strlist_t strlist = NULL; char *tail = NULL; enum tofu_policy policy = _tofu_GET_POLICY_ERROR; db = getdb (dbs, email, DB_EMAIL); if (! db) return _tofu_GET_POLICY_ERROR; /* Check if the binding is known (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_stepx (db->db, &db->s.get_policy_select_policy_and_conflict, strings_collect_cb, &strlist, &err, "select policy, conflict from bindings\n" " where fingerprint = ? and email = ?", SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email, SQLITE_ARG_END); if (rc) { log_error (_("error reading from TOFU database" " (checking for existing bad bindings): %s\n"), err); sqlite3_free (err); goto out; } if (strlist_length (strlist) == 0) /* No results. */ { policy = TOFU_POLICY_NONE; goto out; } else if (strlist_length (strlist) != 2) /* The result has the wrong form. */ { log_error (_("error reading from TOFU database" " (checking for existing bad bindings):" " expected 2 results, got %d\n"), strlist_length (strlist)); goto out; } /* The result has the right form. */ errno = 0; policy = strtol (strlist->d, &tail, 0); if (errno || *tail != '\0') { log_error (_("error reading from TOFU database: bad value for policy: %s\n"), strlist->d); goto out; } if (! (policy == TOFU_POLICY_AUTO || policy == TOFU_POLICY_GOOD || policy == TOFU_POLICY_UNKNOWN || policy == TOFU_POLICY_BAD || policy == TOFU_POLICY_ASK)) { log_error (_("TOFU DB is corrupted. Invalid value for policy (%d).\n"), policy); policy = _tofu_GET_POLICY_ERROR; goto out; } /* If CONFLICT is set, then policy should be TOFU_POLICY_ASK. But, just in case, we do the check again here and ignore the conflict is POLICY is not TOFU_POLICY_ASK. */ if (conflict) { if (policy == TOFU_POLICY_ASK && *strlist->next->d) *conflict = xstrdup (strlist->next->d); else *conflict = NULL; } out: assert (policy == _tofu_GET_POLICY_ERROR || policy == TOFU_POLICY_NONE || policy == TOFU_POLICY_AUTO || policy == TOFU_POLICY_GOOD || policy == TOFU_POLICY_UNKNOWN || policy == TOFU_POLICY_BAD || policy == TOFU_POLICY_ASK); free_strlist (strlist); return policy; } /* Return the trust level (TRUST_NEVER, etc.) for the binding (email is already normalized). If no policy is registered, returns TOFU_POLICY_NONE. If an error occurs, returns _tofu_GET_TRUST_ERROR. USER_ID is the unadultered user id. If MAY_ASK is set, then we may interact with the user. This is necessary if there is a conflict or the binding's policy is TOFU_POLICY_ASK. In the case of a conflict, we set the new conflicting binding's policy to TOFU_POLICY_ASK. In either case, we return TRUST_UNDEFINED. */ static enum tofu_policy get_trust (struct dbs *dbs, const char *fingerprint, const char *email, const char *user_id, int may_ask) { struct db *db; enum tofu_policy policy; char *conflict = NULL; int rc; char *err = NULL; strlist_t bindings_with_this_email = NULL; int bindings_with_this_email_count; int change_conflicting_to_ask = 0; int trust_level = TRUST_UNKNOWN; if (opt.batch) may_ask = 0; /* Make sure _tofu_GET_TRUST_ERROR isn't equal to any of the trust levels. */ assert (_tofu_GET_TRUST_ERROR != TRUST_UNKNOWN && _tofu_GET_TRUST_ERROR != TRUST_EXPIRED && _tofu_GET_TRUST_ERROR != TRUST_UNDEFINED && _tofu_GET_TRUST_ERROR != TRUST_NEVER && _tofu_GET_TRUST_ERROR != TRUST_MARGINAL && _tofu_GET_TRUST_ERROR != TRUST_FULLY && _tofu_GET_TRUST_ERROR != TRUST_ULTIMATE); db = getdb (dbs, email, DB_EMAIL); if (! db) return _tofu_GET_TRUST_ERROR; 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. */ { int i, j; char keyid[17]; KEYDB_SEARCH_DESC desc; /* We need to convert the fingerprint as a string to a long keyid. FINGERPRINT has the form: 362D 3527 F53A AD19 71AA FDE6 5885 9975 EE37 CF96 ------------------- The last 16 characters are the long keyid. */ assert (strlen (fingerprint) > 4 * 4 + 3); for (i = strlen (fingerprint) - (4 * 4 + 3), j = 0; j < 16; i ++, j ++) { if (fingerprint[i] == ' ') i ++; keyid[j] = fingerprint[i]; } keyid[j] = 0; rc = classify_user_id (keyid, &desc, 1); if (rc || desc.mode != KEYDB_SEARCH_MODE_LONG_KID) { log_error (_("'%s' is not a valid long keyID\n"), keyid); return _tofu_GET_TRUST_ERROR; } if (tdb_keyid_is_utk (desc.u.kid)) { if (policy == TOFU_POLICY_NONE) { if (record_binding (dbs, fingerprint, email, user_id, TOFU_POLICY_AUTO, 0) != 0) { log_error (_("error setting TOFU binding's trust level to %s\n"), "auto"); trust_level = _tofu_GET_TRUST_ERROR; goto out; } } trust_level = TRUST_ULTIMATE; goto out; } } if (policy == TOFU_POLICY_AUTO) { policy = opt.tofu_default_policy; if (DBG_TRUST) log_debug ("TOFU: binding <%s, %s>'s policy is auto (default: %s).\n", fingerprint, email, tofu_policy_str (opt.tofu_default_policy)); } switch (policy) { case TOFU_POLICY_AUTO: case TOFU_POLICY_GOOD: 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. */ if (DBG_TRUST) log_debug ("TOFU: Known binding <%s, %s>'s policy: %s\n", fingerprint, email, tofu_policy_str (policy)); trust_level = tofu_policy_to_trust_level (policy); goto out; case TOFU_POLICY_ASK: /* We need to ask the user what to do. Case #1 or #2 below. */ if (! may_ask) { trust_level = TRUST_UNDEFINED; goto out; } break; case TOFU_POLICY_NONE: /* The binding is new, we need to check for conflicts. Case #3 below. */ break; case _tofu_GET_POLICY_ERROR: trust_level = _tofu_GET_TRUST_ERROR; goto out; default: log_bug ("%s: Impossible value for policy (%d)\n", __func__, policy); } /* We get here if: 1. The saved policy is auto and the default 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. */ 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" " (listing fingerprints): %s\n"), err); sqlite3_free (err); 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. */ { /* If we've seen this binding, then we've seen this email and policy couldn't possibly be TOFU_POLICY_NONE. */ assert (policy == TOFU_POLICY_NONE); if (DBG_TRUST) log_debug ("TOFU: New binding <%s, %s>, no conflict.\n", email, fingerprint); if (record_binding (dbs, fingerprint, email, user_id, TOFU_POLICY_AUTO, 0) != 0) { log_error (_("error setting TOFU binding's trust level to %s\n"), "auto"); trust_level = _tofu_GET_TRUST_ERROR; goto out; } trust_level = tofu_policy_to_trust_level (TOFU_POLICY_AUTO); goto out; } 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; 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). */ { assert (policy == TOFU_POLICY_NONE); if (record_binding (dbs, fingerprint, email, user_id, TOFU_POLICY_ASK, 0) != 0) log_error (_("error setting TOFU binding's trust level to %s\n"), "ask"); trust_level = TRUST_UNDEFINED; 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; char *binding; int binding_shown; strlist_t other_user_ids = NULL; struct signature_stats *stats = NULL; struct signature_stats *stats_iter = NULL; char *prompt; char *choices; fp = es_fopenmem (0, "rw,samethread"); if (! fp) log_fatal ("Error creating memory stream\n"); binding = xasprintf ("<%s, %s>", fingerprint, email); binding_shown = 0; if (policy == TOFU_POLICY_NONE) { es_fprintf (fp, _("The binding %s is NOT known. "), binding); 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 (fp, _("The key %s raised a conflict with this binding (%s)." " Since this binding's policy was 'auto', it was" "changed to 'ask'. "), conflict, binding); binding_shown = 1; } 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).\n\n"), binding_shown ? "" : binding, binding_shown ? "" : " "); xfree (binding); /* 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 = 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); 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; 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 (policy: %s)\n"), other_user_id, tofu_policy_str (other_policy)); } 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 = 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" /* 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, _("The email address (%s) is associated with %d keys:\n"), 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 '%s':\n"), email); for (stats_iter = stats; stats_iter; stats_iter = stats_iter->next) { if (! key || strcmp (key, stats_iter->fingerprint) != 0) { int this_key; key = stats_iter->fingerprint; this_key = strcmp (key, fingerprint) == 0; if (this_key) es_fprintf (fp, _(" %s (this key):"), key); else es_fprintf (fp, _(" %s (policy: %s):"), key, tofu_policy_str (stats_iter->policy)); es_fprintf (fp, "\n"); } if (stats_iter->time_ago == -1) es_fprintf (fp, _(" %ld %s signed in the future.\n"), stats_iter->count, stats_iter->count == 1 ? _("message") : _("messages")); else if (stats_iter->count == 0) es_fprintf (fp, _(" 0 signed messages.\n")); else es_fprintf (fp, _(" %ld %s signed over the past %ld %s.\n"), stats_iter->count, stats_iter->count == 1 ? _("message") : _("messages"), time_ago_scale (stats_iter->time_ago), time_ago_unit (stats_iter->time_ago)); } } if (is_conflict) { /* TRANSLATORS: translate the below text. We don't directly internationalize that text so that we can tweak it without breaking translations. */ char *text = _("TOFU detected a binding conflict"); 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."; es_fprintf (fp, "\n%s\n", text); } es_fputc ('\n', fp); /* 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"); es_fprintf (fp, _("(G)ood/(A)ccept once/(U)nknown/(R)eject once/(B)ad? ")); /* Add a NUL terminator. */ es_fputc (0, fp); if (es_fclose_snatch (fp, (void **) &prompt, NULL)) log_fatal ("error snatching memory stream\n"); while (1) { char *response; if (strlen (choices) != 10) log_bug ("Bad TOFU conflict translation! Please report."); response = cpr_get ("tofu conflict", prompt); trim_spaces (response); cpr_kill_prompt (); if (strlen (response) == 1) { char *choice = strchr (choices, *response); if (choice) { int c = ((size_t) choice - (size_t) choices) / 2; assert (0 <= c && c <= 4); 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: if (change_conflicting_to_ask) { if (! may_ask) /* If we weren't allowed to ask, also update this key as conflicting with itself. */ rc = sqlite3_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); else rc = sqlite3_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); if (rc) { log_error (_("error changing TOFU policy: %s\n"), err); sqlite3_free (err); goto out; } } xfree (conflict); free_strlist (bindings_with_this_email); return trust_level; } static void show_statistics (struct dbs *dbs, const char *fingerprint, const char *email, const char *user_id, const char *sig_exclude) { struct db *db; int rc; strlist_t strlist = NULL; char *err = NULL; db = getdb (dbs, email, DB_EMAIL); if (! db) return; rc = sqlite3_exec_printf (db->db, strings_collect_cb, &strlist, &err, "select count (*), strftime('%%s','now') - min (signatures.time)\n" " from signatures\n" " left join bindings on signatures.binding = bindings.oid\n" " where fingerprint = %Q and email = %Q and sig_digest %s%s%s;", fingerprint, email, /* We want either: sig_digest != 'SIG_EXCLUDE' or sig_digest is not NULL. */ sig_exclude ? "!= '" : "is not NULL", sig_exclude ? sig_exclude : "", sig_exclude ? "'" : ""); if (rc) { log_error (_("error reading from TOFU database" " (getting statistics): %s\n"), err); sqlite3_free (err); goto out; } if (! strlist) log_info (_("Have never verified a message signed by key %s!\n"), fingerprint); else { char *tail = NULL; signed long messages; signed long first_seen_ago; assert (strlist_length (strlist) == 2); errno = 0; messages = strtol (strlist->d, &tail, 0); if (errno || *tail != '\0') /* Abort. */ { log_debug ("%s:%d: Couldn't convert %s (messages) to an int: %s.\n", __func__, __LINE__, strlist->d, strerror (errno)); messages = -1; } if (messages == 0 && *strlist->next->d == '\0') /* min(NULL) => NULL => "". */ first_seen_ago = -1; else { errno = 0; first_seen_ago = strtol (strlist->next->d, &tail, 0); if (errno || *tail != '\0') /* Abort. */ { log_debug ("%s:%d: Cound't convert %s (first_seen) to an int: %s.\n", __func__, __LINE__, strlist->next->d, strerror (errno)); first_seen_ago = 0; } } if (messages == -1 || first_seen_ago == 0) log_info (_("Failed to collect signature statistics for \"%s\" (key %s)\n"), user_id, fingerprint); else { enum tofu_policy policy = get_policy (dbs, fingerprint, email, NULL); estream_t fp; char *msg; fp = es_fopenmem (0, "rw,samethread"); if (! fp) log_fatal ("error creating memory stream\n"); if (messages == 0) es_fprintf (fp, _("Verified 0 messages signed by \"%s\"" " (key: %s, policy %s)."), user_id, fingerprint, tofu_policy_str (policy)); else { int years = 0; int months = 0; int days = 0; int hours = 0; int minutes = 0; int seconds = 0; /* The number of units that we've printed so far. */ int count = 0; /* The first unit that we printed (year = 0, month = 1, etc.). */ int first = -1; /* The current unit. */ int i = 0; es_fprintf (fp, _("Verified %ld messages signed by \"%s\"" " (key: %s, policy: %s) in the past "), messages, user_id, fingerprint, tofu_policy_str (policy)); /* It would be nice to use a macro to do this, but gettext works on the unpreprocessed code. */ #define MIN_SECS (60) #define HOUR_SECS (60 * MIN_SECS) #define DAY_SECS (24 * HOUR_SECS) #define MONTH_SECS (30 * DAY_SECS) #define YEAR_SECS (365 * DAY_SECS) if (first_seen_ago > YEAR_SECS) { years = first_seen_ago / YEAR_SECS; first_seen_ago -= years * YEAR_SECS; } if (first_seen_ago > MONTH_SECS) { months = first_seen_ago / MONTH_SECS; first_seen_ago -= months * MONTH_SECS; } if (first_seen_ago > DAY_SECS) { days = first_seen_ago / DAY_SECS; first_seen_ago -= days * DAY_SECS; } if (first_seen_ago > HOUR_SECS) { hours = first_seen_ago / HOUR_SECS; first_seen_ago -= hours * HOUR_SECS; } if (first_seen_ago > MIN_SECS) { minutes = first_seen_ago / MIN_SECS; first_seen_ago -= minutes * MIN_SECS; } seconds = first_seen_ago; #undef MIN_SECS #undef HOUR_SECS #undef DAY_SECS #undef MONTH_SECS #undef YEAR_SECS if (years) { if (years > 1) es_fprintf (fp, _("%d years"), years); else es_fprintf (fp, _("%d year"), years); count ++; first = i; } i ++; if ((first == -1 || i - first <= 3) && months) { if (count) es_fprintf (fp, _(", ")); if (months > 1) es_fprintf (fp, _("%d months"), months); else es_fprintf (fp, _("%d month"), months); count ++; first = i; } i ++; if ((first == -1 || i - first <= 3) && count < 2 && days) { if (count) es_fprintf (fp, _(", ")); if (days > 1) es_fprintf (fp, _("%d days"), days); else es_fprintf (fp, _("%d day"), days); count ++; first = i; } i ++; if ((first == -1 || i - first <= 3) && count < 2 && hours) { if (count) es_fprintf (fp, _(", ")); if (hours > 1) es_fprintf (fp, _("%d hours"), hours); else es_fprintf (fp, _("%d hour"), hours); count ++; first = i; } i ++; if ((first == -1 || i - first <= 3) && count < 2 && minutes) { if (count) es_fprintf (fp, _(", ")); if (minutes > 1) es_fprintf (fp, _("%d minutes"), minutes); else es_fprintf (fp, _("%d minute"), minutes); count ++; first = i; } i ++; if ((first == -1 || i - first <= 3) && count < 2) { if (count) es_fprintf (fp, _(", ")); if (seconds > 1) es_fprintf (fp, _("%d seconds"), seconds); else es_fprintf (fp, _("%d second"), seconds); } es_fprintf (fp, _(".")); } es_fputc (0, fp); if (es_fclose_snatch (fp, (void **) &msg, NULL)) log_fatal ("error snatching memory stream\n"); log_info ("%s\n", msg); if (policy == TOFU_POLICY_AUTO && messages < 10) { char *set_policy_command; const char *text; if (messages == 0) log_info (_("Warning: we've have yet to see a message signed by this key!\n")); else if (messages == 1) log_info (_("Warning: we've only seen a single message signed by this key!\n")); set_policy_command = xasprintf ("gpg --tofu-policy bad \"%s\"", fingerprint); /* TRANSLATORS: translate the below text. We don't directly internationalize that text so that we can tweak it without breaking translations. */ text = _("TOFU: few signatures %d %s %s"); if (strcmp (text, "TOFU: few signatures %d %s %s") == 0) text = "Warning: if you think you've seen more than %d %s " "signed by this key, then this key might be a forgery! " "Carefully examine the email address for small variations " "(e.g., additional white space). If the key is suspect, " "then use '%s' to mark it as being bad.\n"; log_info (text, messages, messages == 1 ? _("message") : _("message"), set_policy_command); free (set_policy_command); } } } out: free_strlist (strlist); return; } /* Extract the email address from a user id and normalize it. If the user id doesn't contain an email address, then we use the whole user_id and normalize that. The returned string must be freed. */ static char * email_from_user_id (const char *user_id) { char *email = mailbox_from_userid (user_id); if (! email) /* Hmm, no email address was provided. Just take the lower-case version of the whole user id. It could be a hostname, for instance. */ email = ascii_strlwr (xstrdup (user_id)); return email; } /* Pretty print a MAX_FINGERPRINT_LEN-byte binary fingerprint into a malloc'd string. */ static char * fingerprint_pp (const byte *fingerprint_bin) { char fingerprint[MAX_FINGERPRINT_LEN * 2 + 1]; char *fingerprint_pretty; int space = (/* The characters and the NUL. */ sizeof (fingerprint) /* After every fourth character, we add a space (except the last). */ + (sizeof (fingerprint) - 1) / 4 - 1 /* Half way through we add a second space. */ + 1); int i; int j; bin2hex (fingerprint_bin, MAX_FINGERPRINT_LEN, fingerprint); fingerprint_pretty = xmalloc (space); for (i = 0, j = 0; i < MAX_FINGERPRINT_LEN * 2; i ++) { if (i && i % 4 == 0) fingerprint_pretty[j ++] = ' '; if (i == MAX_FINGERPRINT_LEN * 2 / 2) fingerprint_pretty[j ++] = ' '; fingerprint_pretty[j ++] = fingerprint[i]; } fingerprint_pretty[j ++] = 0; assert (j == space); return fingerprint_pretty; } /* Register the signature with the binding . FINGERPRINT must be MAX_FINGERPRINT_LEN bytes long. SIG_DIGEST_BIN is the binary representation of the message's digest. SIG_DIGEST_BIN_LEN is its length. SIG_TIME is the time that the signature was generated. ORIGIN is a free-formed string describing the origin of the signature. If this was from an email and the Claws MUA was used, then this should be something like: "email:claws". If this is NULL, the default is simply "unknown". If MAY_ASK is 1, then this function may interact with the user. This is necessary if there is a conflict or the binding's policy is TOFU_POLICY_ASK. This function returns the binding's trust level on return. If an error occurs, this function returns TRUST_UNKNOWN. */ int tofu_register (const byte *fingerprint_bin, const char *user_id, const byte *sig_digest_bin, int sig_digest_bin_len, time_t sig_time, const char *origin, int may_ask) { struct dbs *dbs; struct db *db; char *fingerprint = NULL; char *email = NULL; char *err = NULL; int rc; int trust_level = TRUST_UNKNOWN; char *sig_digest; unsigned long c; int already_verified = 0; sig_digest = make_radix64_string (sig_digest_bin, sig_digest_bin_len); dbs = opendbs (); if (! dbs) { log_error (_("error opening TOFU DB.\n")); goto die; } fingerprint = fingerprint_pp (fingerprint_bin); if (! *user_id) { log_debug ("TOFU: user id is empty. Can't continue.\n"); goto die; } email = email_from_user_id (user_id); if (! origin) /* The default origin is simply "unknown". */ origin = "unknown"; /* It's necessary to get the trust so that we are certain that the binding has been registered. */ trust_level = get_trust (dbs, fingerprint, email, user_id, may_ask); if (trust_level == _tofu_GET_TRUST_ERROR) /* An error. */ { trust_level = TRUST_UNKNOWN; goto die; } /* Save the observed signature in the DB. */ db = getdb (dbs, email, DB_EMAIL); if (! db) { log_error (_("error opening TOFU DB.\n")); goto die; } /* We do a query and then an insert. Make sure they are atomic by wrapping them in a transaction. */ rc = begin_transaction (db, 0); if (rc) goto die; /* If we've already seen this signature before, then don't add it again. */ 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 = ? 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" " (checking existence): %s\n"), err); sqlite3_free (err); } else if (c > 1) /* Duplicates! This should not happen. In particular, because is the primary key! */ log_debug ("SIGNATURES DB contains duplicate records" " ." " Please report.\n", fingerprint, email, (unsigned long) sig_time, sig_digest, origin); else if (c == 1) { already_verified = 1; if (DBG_TRUST) log_debug ("Already observed the signature" " \n", fingerprint, email, (unsigned long) sig_time, sig_digest, origin); } else /* This is the first time that we've seen this signature. Record it. */ { if (DBG_TRUST) log_debug ("TOFU: Saving signature <%s, %s, %s>\n", fingerprint, email, sig_digest); assert (c == 0); 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 = ? 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" " (inserting into signatures table): %s\n"), err); sqlite3_free (err); } } /* It only matters whether we abort or commit the transaction (so long as we do something) if we execute the insert. */ if (rc) rc = rollback_transaction (db); else rc = end_transaction (db, 0); if (rc) { log_error (_("error ending transaction on TOFU database: %s\n"), err); sqlite3_free (err); goto die; } die: if (may_ask && trust_level != TRUST_ULTIMATE) /* It's only appropriate to show the statistics in an interactive context. */ show_statistics (dbs, fingerprint, email, user_id, already_verified ? NULL : sig_digest); xfree (email); xfree (fingerprint); if (dbs) closedbs (dbs); xfree (sig_digest); return trust_level; } /* 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). This function ors together the upper bits (the values not covered by TRUST_MASK, i.e., TRUST_FLAG_REVOKED, etc.). */ int tofu_wot_trust_combine (int tofu_base, int wot_base) { int tofu = tofu_base & TRUST_MASK; int wot = wot_base & TRUST_MASK; int upper = (tofu_base & ~TRUST_MASK) | (wot_base & ~TRUST_MASK); assert (tofu == TRUST_UNKNOWN || tofu == TRUST_EXPIRED || tofu == TRUST_UNDEFINED || tofu == TRUST_NEVER || tofu == TRUST_MARGINAL || tofu == TRUST_FULLY || tofu == TRUST_ULTIMATE); assert (wot == TRUST_UNKNOWN || wot == TRUST_EXPIRED || wot == TRUST_UNDEFINED || wot == TRUST_NEVER || wot == TRUST_MARGINAL || wot == TRUST_FULLY || wot == TRUST_ULTIMATE); /* We first consider negative trust policys. These trump positive trust policies. */ if (tofu == TRUST_NEVER || wot == TRUST_NEVER) /* TRUST_NEVER trumps everything else. */ return upper | TRUST_NEVER; if (tofu == TRUST_EXPIRED || wot == TRUST_EXPIRED) /* TRUST_EXPIRED trumps everything but TRUST_NEVER. */ return upper | TRUST_EXPIRED; /* Now we only have positive or neutral trust policies. We take the max. */ if (tofu == TRUST_ULTIMATE || wot == TRUST_ULTIMATE) return upper | TRUST_ULTIMATE; if (tofu == TRUST_FULLY || wot == TRUST_FULLY) return upper | TRUST_FULLY; if (tofu == TRUST_MARGINAL || wot == TRUST_MARGINAL) return upper | TRUST_MARGINAL; if (tofu == TRUST_UNDEFINED || wot == TRUST_UNDEFINED) return upper | TRUST_UNDEFINED; return upper | TRUST_UNKNOWN; } /* Return the validity (TRUST_NEVER, etc.) of the binding . FINGERPRINT must be a MAX_FINGERPRINT_LEN-byte fingerprint. If MAY_ASK is 1 and the policy is TOFU_POLICY_ASK, then the user will be prompted to choose a different policy. If MAY_ASK is 0 and the policy is TOFU_POLICY_ASK, then TRUST_UNKNOWN is returned. Returns TRUST_UNDEFINED if an error occurs. */ int tofu_get_validity (const byte *fingerprint_bin, const char *user_id, int may_ask) { struct dbs *dbs; char *fingerprint = NULL; char *email = NULL; int trust_level = TRUST_UNDEFINED; dbs = opendbs (); if (! dbs) { log_error (_("error opening TOFU DB.\n")); goto die; } fingerprint = fingerprint_pp (fingerprint_bin); if (! *user_id) { log_debug ("user id is empty. Can't get TOFU validity for this binding.\n"); goto die; } email = email_from_user_id (user_id); trust_level = get_trust (dbs, fingerprint, email, user_id, may_ask); if (trust_level == _tofu_GET_TRUST_ERROR) /* An error. */ trust_level = TRUST_UNDEFINED; if (may_ask && trust_level != TRUST_ULTIMATE) show_statistics (dbs, fingerprint, email, user_id, NULL); die: xfree (email); xfree (fingerprint); if (dbs) closedbs (dbs); return trust_level; } /* Set the policy for all non-revoked user ids in the keyblock KB to POLICY. If no key is available with the specified key id, then this function returns GPG_ERR_NO_PUBKEY. Returns 0 on success and an error code otherwise. */ gpg_error_t tofu_set_policy (kbnode_t kb, enum tofu_policy policy) { struct dbs *dbs; PKT_public_key *pk; char fingerprint_bin[MAX_FINGERPRINT_LEN]; size_t fingerprint_bin_len = sizeof (fingerprint_bin); char *fingerprint = NULL; assert (kb->pkt->pkttype == PKT_PUBLIC_KEY); pk = kb->pkt->pkt.public_key; dbs = opendbs (); if (! dbs) { log_error (_("error opening TOFU DB.\n")); return gpg_error (GPG_ERR_GENERAL); } if (DBG_TRUST) log_debug ("Setting TOFU policy for %s to %s\n", keystr (pk->keyid), tofu_policy_str (policy)); if (! (pk->main_keyid[0] == pk->keyid[0] && pk->main_keyid[1] == pk->keyid[1])) log_bug ("%s: Passed a subkey, but expecting a primary key.\n", __func__); fingerprint_from_pk (pk, fingerprint_bin, &fingerprint_bin_len); assert (fingerprint_bin_len == sizeof (fingerprint_bin)); fingerprint = fingerprint_pp (fingerprint_bin); for (; kb; kb = kb->next) { PKT_user_id *user_id; char *email; if (kb->pkt->pkttype != PKT_USER_ID) continue; user_id = kb->pkt->pkt.user_id; if (user_id->is_revoked) /* Skip revoked user ids. (Don't skip expired user ids, the expiry can be changed.) */ continue; email = email_from_user_id (user_id->name); record_binding (dbs, fingerprint, email, user_id->name, policy, 1); xfree (email); } xfree (fingerprint); closedbs (dbs); return 0; } /* Set the TOFU policy for all non-revoked user ids in the KEY with the key id KEYID to POLICY. If no key is available with the specified key id, then this function returns GPG_ERR_NO_PUBKEY. Returns 0 on success and an error code otherwise. */ gpg_error_t tofu_set_policy_by_keyid (u32 *keyid, enum tofu_policy policy) { kbnode_t keyblock = get_pubkeyblock (keyid); if (! keyblock) return gpg_error (GPG_ERR_NO_PUBKEY); return tofu_set_policy (keyblock, policy); } /* Return the TOFU policy for the specified binding in *POLICY. If no policy has been set for the binding, sets *POLICY to TOFU_POLICY_NONE. PK is a primary public key and USER_ID is a user id. Returns 0 on success and an error code otherwise. */ gpg_error_t tofu_get_policy (PKT_public_key *pk, PKT_user_id *user_id, enum tofu_policy *policy) { struct dbs *dbs; char fingerprint_bin[MAX_FINGERPRINT_LEN]; size_t fingerprint_bin_len = sizeof (fingerprint_bin); char *fingerprint; char *email; /* Make sure PK is a primary key. */ assert (pk->main_keyid[0] == pk->keyid[0] && pk->main_keyid[1] == pk->keyid[1]); dbs = opendbs (); if (! dbs) { log_error (_("error opening TOFU DB.\n")); return gpg_error (GPG_ERR_GENERAL); } fingerprint_from_pk (pk, fingerprint_bin, &fingerprint_bin_len); assert (fingerprint_bin_len == sizeof (fingerprint_bin)); fingerprint = fingerprint_pp (fingerprint_bin); email = email_from_user_id (user_id->name); *policy = get_policy (dbs, fingerprint, email, NULL); xfree (email); xfree (fingerprint); closedbs (dbs); if (*policy == _tofu_GET_POLICY_ERROR) return gpg_error (GPG_ERR_GENERAL); return 0; }