diff --git a/g10/Makefile.am b/g10/Makefile.am index 10714f6af..16e9ce9f1 100644 --- a/g10/Makefile.am +++ b/g10/Makefile.am @@ -30,7 +30,7 @@ include $(top_srcdir)/am/cmacros.am AM_CFLAGS = $(SQLITE3_CFLAGS) $(LIBGCRYPT_CFLAGS) \ $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) -needed_libs = ../kbx/libkeybox.a $(libcommon) +needed_libs = ../kbx/libkeybox.a $(libcommon) $(SQLITE3_LIBS) bin_PROGRAMS = gpg2 if !HAVE_W32CE_SYSTEM @@ -58,7 +58,7 @@ trust_source = trustdb.c trustdb.h tdbdump.c tdbio.c tdbio.h endif if USE_TOFU -tofu_source = tofu.h tofu.c sqlite.c sqlite.h +tofu_source = tofu.h tofu.c else tofu_source = endif @@ -102,7 +102,9 @@ common_source = \ sig-check.c \ keylist.c \ pkglue.c pkglue.h \ - ecdh.c + ecdh.c \ + kdb.c kdb.h \ + sqlite.c sqlite.h gpg2_SOURCES = gpg.c \ server.c \ @@ -148,7 +150,7 @@ gpgv2_SOURCES = gpgv.c \ LDADD = $(needed_libs) ../common/libgpgrl.a \ $(ZLIBS) $(LIBINTL) $(CAPLIBS) $(NETLIBS) -gpg2_LDADD = $(LDADD) $(SQLITE3_LIBS) $(LIBGCRYPT_LIBS) $(LIBREADLINE) \ +gpg2_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(LIBREADLINE) \ $(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \ $(LIBICONV) $(resource_objs) $(extra_sys_libs) gpg2_LDFLAGS = $(extra_bin_ldflags) diff --git a/g10/import.c b/g10/import.c index e1577b8a1..7555cc683 100644 --- a/g10/import.c +++ b/g10/import.c @@ -1154,6 +1154,9 @@ import_one (ctrl_t ctrl, goto leave; } + assert (strcmp (keystr_from_pk(keyblock_orig->pkt->pkt.public_key), + keystr_from_pk(pk)) == 0); + /* Make sure the original direct key sigs are all sane. */ n_sigs_cleaned = fix_bad_direct_key_sigs (keyblock_orig, keyid); if (n_sigs_cleaned) diff --git a/g10/kdb.c b/g10/kdb.c new file mode 100644 index 000000000..b1c554430 --- /dev/null +++ b/g10/kdb.c @@ -0,0 +1,1349 @@ +#include +#include +#include + +#include "gpg.h" +#include "util.h" +#include "logging.h" +#include "i18n.h" +#include "mbox-util.h" +#include "sqlite.h" + +#include "kdb.h" + +#if 0 +# define DEBUG(fmt, ...) \ + do { \ + log_debug("%s:%d: "fmt, __func__, __LINE__, ##__VA_ARGS__); \ + } while (0) +#else +# define DEBUG(fmt, ...) do {} while (0) +#endif + + +struct kdb_resource +{ + struct kdb_resource *next; + int read_only; + sqlite3 *db; + + long long int key; + char *kb; + int kb_len; + u32 *sigstatus; + + char fname[1]; +}; +typedef struct kdb_resource *KDB_RESOURCE; +typedef struct kdb_resource const * CONST_KDB_RESOURCE; + +/* All registered resources. */ +static KDB_RESOURCE kdb_resources; + +static void +hdr_cache_clear (KDB_RESOURCE resource) +{ + xfree (resource->kb); + resource->kb = NULL; + + xfree (resource->sigstatus); + resource->sigstatus = NULL; + + resource->key = -1; +} + +struct key +{ + unsigned long int key; + char *kb; + size_t kb_len; + u32 *sigstatus; + struct key *next; +}; + +struct keydb_handle +{ + KDB_RESOURCE resource; + /* Current key. */ + long long int key; + int eof; + + struct key *full_scan; + + struct { + long long int key; + int pk_no; + int uid_no; + } found, saved_found; +}; + +/* Perform an in-place reversal. Returns the new head. */ +static struct key * +key_list_reverse (struct key *list) +{ + struct key *list_rev = NULL; + + while (list) + { + struct key *list_next_orig = list->next; + list->next = list_rev; + list_rev = list; + list = list_next_orig; + } + return list_rev; +} + +static struct key * +key_free (struct key *key) +{ + struct key *key_next; + + if (! key) + return NULL; + + key_next = key->next; + xfree (key->kb); + xfree (key->sigstatus); + xfree (key); + + return key_next; +} + +static void +hd_cache_clear (KDB_HANDLE hd) +{ + struct key *key_next; + + if (! hd->full_scan) + return; + + key_next = hd->full_scan; + while (key_next) + key_next = key_free (key_next); + + hd->full_scan = NULL; +} + +/* RESOURCE is a value returned by a previous call to + kdb_register_file in *RESOURCEP. */ +KDB_HANDLE +kdb_new (void *resource) +{ + KDB_RESOURCE r; + KDB_HANDLE hd; + + /* Assert that the resource was indeed previously registered. */ + for (r = kdb_resources; r; r = r->next) + if (r == resource) + break; + assert (r); + + hd = xmalloc_clear (sizeof (*hd)); + hd->resource = resource; + hd->key = -1; + return hd; +} + + +/* Collect a series of integers from a query. Aborts if the argument + is not a valid integer (or real of the form X.0). COOKIE points to + an array of unsigned long ints, which has enough space for ARGC + values. */ +static int +get_unsigned_longs_cb (void *cookie, int argc, char **argv, char **azColName) +{ + unsigned long int *values = cookie; + int i; + char *tail = NULL; + + (void) azColName; + + for (i = 0; i < argc; i ++) + { + if (! argv[i]) + values[i] = 0; + else + { + errno = 0; + values[i] = strtoul (argv[i], &tail, 0); + if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0')) + /* Abort. */ + return 1; + } + } + + return 0; +} + +static int +get_unsigned_longs_cb2 (void *cookie, int argc, char **argv, char **azColName, + sqlite3_stmt *stmt) +{ + (void) stmt; + return get_unsigned_longs_cb (cookie, argc, argv, azColName); +} + +/* 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 kdb version: %s\n"), argv[0]); + *version = -1; + } + + /* Don't run again. */ + return 1; +} + +/* Register a new file. If the file has already been registered then + returns NULL otherwise returns */ +gpg_error_t +kdb_register_file (const char *fname, int read_only, void **resourcep) +{ + KDB_RESOURCE resource; + int rc; + sqlite3 *db = NULL; + char *err; + unsigned long int count; + int need_init = 1; + + for (resource = kdb_resources; resource; resource = resource->next) + if (same_file_p (resource->fname, fname)) + { + if (resourcep) + *resourcep = resource; + if (read_only) + resource->read_only = 1; + return 0; + } + + rc = sqlite3_open_v2 (fname, &db, + read_only + ? SQLITE_OPEN_READONLY + : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE), + NULL); + if (rc) + { + log_error ("Failed to open the key db '%s': %s\n", + fname, sqlite3_errstr (rc)); + return rc; + } + + /* 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_unsigned_longs_cb, &count, &err); + if (rc) + { + log_error (_("error querying kdb'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. */ + { + int version = -1; + 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; + need_init = 0; + } + 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 kdb'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 kdb's version: %s\n"), + "select returned 0, but expected ABORT"); + rc = 1; + goto out; + } + } + + if (need_init) + { + /* Create the version table. */ + rc = sqlite3_exec (db, + "create table version (version INTEGER);", + NULL, NULL, &err); + if (rc) + { + log_error (_("error initializing kdb 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 kdb database (%s): %s\n"), + "version, init", err); + sqlite3_free (err); + goto out; + } + + /* We have 3 tables: + + primaries - the list of all primary keys and the key block. + + keys - the list of all keys and subkeys. + + user ids - the list of all user ids. */ + + rc = sqlite3_exec + (db, + /* Enable foreign key constraints. */ + "pragma foreign_keys = on;\n" + "create table primaries\n" + " (oid INTEGER PRIMARY KEY AUTOINCREMENT,\n" + " fingerprint_rev TEXT COLLATE NOCASE, keyblock BLOB,\n" + " sigstatus TEXT);\n" + "create index primaries_fingerprint on primaries\n" + " (fingerprint_rev COLLATE NOCASE);\n" + "\n" + "create table keys\n" + " (primary_key INTEGER, fingerprint_rev TEXT COLLATE NOCASE,\n" + " pk_no INTEGER,\n" + " unique (primary_key, pk_no),\n" + " foreign key (primary_key) references primaries(oid));\n" + "create index keys_fingerprint_primary_key_pk_no on keys\n" + " (fingerprint_rev COLLATE NOCASE, primary_key, pk_no);\n" + "create index keys_primary_key_pk_no on keys (primary_key, pk_no);\n" + "\n" + /* XXX: Is COLLATE NOCASE reasonable? */ + "create table uids\n" + " (primary_key INTEGER, uid TEXT COLLATE NOCASE,\n" + " email TEXT COLLATE NOCASE, uid_no INTEGER,\n" + " unique (primary_key, uid_no),\n" + " foreign key (primary_key) references primaries(oid));\n" + "create index uids_ordered on uids (primary_key, uid_no);\n" + /* In most cases, we search for a substring (like + '%foo@bar.com%'. This can't exploit an index so the + following indices mostly represent overhead. */ +#if 0 + "create index uids_uid_ordered on uids\n" + " (uid COLLATE NOCASE, primary_key, uid_no);\n" + "create index uids_email_ordered on uids\n" + " (email COLLATE NOCASE, primary_key, uid_no);\n" +#endif + , + NULL, NULL, &err); + if (rc) + { + log_error (_("error initializing kdb database: %s\n"), err); + sqlite3_free (err); + goto out; + } + } + + resource = xmalloc_clear (sizeof *resource + strlen (fname)); + strcpy (resource->fname, fname); + resource->read_only = read_only; + resource->db = db; + resource->next = kdb_resources; + kdb_resources = resource; + + if (resourcep) + *resourcep = resource; + + out: + if (rc) + { + if (resourcep) + *resourcep = NULL; + + sqlite3_close (db); + return gpg_error (GPG_ERR_GENERAL); + } + + return 0; +} + +int +kdb_is_writable (void *token) +{ + KDB_RESOURCE resource = token; + if (resource->read_only) + return 0; + return 1; +} + +/* Release the handle. */ +void +kdb_release (KDB_HANDLE hd) +{ + KDB_RESOURCE r; + + if (! hd) + return; + + /* Check for double frees. */ + assert (hd->resource); + for (r = kdb_resources; r; r = r->next) + if (r == hd->resource) + break; + assert (r); + + hd_cache_clear (hd); + + hd->resource = NULL; + + xfree (hd); +} + +void +kdb_push_found_state (KDB_HANDLE hd) +{ + hd->saved_found = hd->found; + hd->found.key = -1; +} + +void +kdb_pop_found_state (KDB_HANDLE hd) +{ + hd->found = hd->saved_found; + hd->saved_found.key = -1; +} + +const char * +kdb_get_resource_name (KDB_HANDLE hd) +{ + if (!hd || !hd->resource) + return NULL; + return hd->resource->fname; +} + +/* If YES is 1, lock the DB. Otherwise, unlock it. Returns an error + code if locking failed. */ +int +kdb_lock (KDB_HANDLE hd, int yes) +{ + int rc; + char *err; + + if (yes) + /* Lock. */ + { + rc = sqlite3_exec (hd->resource->db, "savepoint lock;", + NULL, NULL, &err); + if (rc) + { + log_error (_("error beginning transaction on KDB database: %s\n"), + err); + sqlite3_free (err); + return 1; + } + + return 0; + } + else + /* Unlock. */ + { + rc = sqlite3_exec (hd->resource->db, "release lock;", NULL, NULL, &err); + if (rc) + { + log_error (_("error ending transaction on KDB database: %s\n"), + err); + sqlite3_free (err); + return 1; + } + + return 0; + } +} + +static u32 * +sigstatus_parse (const char *sigstatus_str) +{ + int entries; + int i; + u32 *sigstatus; + char *tail; + + /* Count the number of values (= # of semicolons plus 1). */ + entries = 1; + for (i = 0; i < strlen (sigstatus_str); i ++) + if (sigstatus_str[i] == ';') + entries ++; + + /* The first entry is the number of entries. */ + sigstatus = xmalloc (sizeof (sigstatus[0]) * (1 + entries)); + sigstatus[0] = entries; + + for (i = 0; i < entries; i ++) + { + errno = 0; + sigstatus[i + 1] = strtoul (sigstatus_str, &tail, 0); + if (errno || ! ((i < entries - 1 && *tail == ';') + || (i == entries - 1 && *tail == '\0'))) + /* Abort. */ + { + log_info ("%s: Failed to parse %s\n", __func__, sigstatus_str); + return NULL; + } + + sigstatus_str = tail; + if (i < entries - 1) + { + assert (*tail == ';'); + sigstatus_str ++; + } + else + assert (*tail == '\0'); + } + + return sigstatus; +} + +static int keyblock_cached; +static int keyblock_cache_hit; +static int keyblock_cache_miss; + +/* The caller needs to make sure that hd->resource->key is updated! */ +static int +keyblock_cb (void *cookie, int cols, char **values, char **names, + sqlite3_stmt *stmt) +{ + KDB_HANDLE hd = cookie; + + (void) cols; + (void) values; + (void) names; + + assert (cols == 2); + assert (strcmp (names[0], "keyblock") == 0); + assert (strcmp (names[1], "sigstatus") == 0); + + xfree (hd->resource->kb); + hd->resource->kb_len = sqlite3_column_bytes (stmt, 0); + hd->resource->kb = xmalloc (hd->resource->kb_len); + memcpy (hd->resource->kb, sqlite3_column_blob (stmt, 0), + hd->resource->kb_len); + hd->resource->sigstatus = sigstatus_parse (values[1]); + + keyblock_cached ++; + + /* Abort to indicate success. */ + return 1; +} + +int +kdb_get_keyblock (KDB_HANDLE hd, iobuf_t *iobuf, + int *pk_no, int *uid_no, u32 **sigstatus) +{ + int rc; + char *err; + sqlite3_stmt *stmt = NULL; + + if (pk_no) + *pk_no = 0; + if (uid_no) + *uid_no = 0; + if (sigstatus) + *sigstatus = NULL; + + if (hd->found.key == -1) + /* Got nothing. */ + return gpg_error (GPG_ERR_EOF); + + DEBUG ("getting keyblock for key #%lld\n", hd->found.key); + + if (keyblock_cache_hit || keyblock_cache_miss) + DEBUG ("keyblock cache: %d fills, %d hits (%d%%), %d misses\n", + keyblock_cached, keyblock_cache_hit, + (keyblock_cache_hit * 100) + / (keyblock_cache_hit + keyblock_cache_miss), + keyblock_cache_miss); + + if (hd->resource->kb && hd->resource->key == hd->found.key) + { + DEBUG("read keyblock from resource cache.\n"); + keyblock_cache_hit ++; + *iobuf = iobuf_temp_with_content (hd->resource->kb, hd->resource->kb_len); + if (hd->resource->sigstatus) + { + size_t s = (sizeof (hd->resource->sigstatus[0]) + * (1 + hd->resource->sigstatus[0])); + *sigstatus = xmalloc (s); + memcpy (*sigstatus, hd->resource->sigstatus, s); + } + return 0; + } + else if (hd->full_scan && hd->full_scan->key == hd->found.key) + { + DEBUG("read keyblock from full scan cache.\n"); + *iobuf = iobuf_temp_with_content (hd->full_scan->kb, + hd->full_scan->kb_len); + if (hd->full_scan->sigstatus) + { + size_t s = (sizeof (hd->full_scan->sigstatus[0]) + * (1 + hd->full_scan->sigstatus[0])); + *sigstatus = xmalloc (s); + memcpy (*sigstatus, hd->full_scan->sigstatus, s); + } + return 0; + } + else + keyblock_cache_miss ++; + + rc = sqlite3_stepx + (hd->resource->db, + &stmt, keyblock_cb, hd, &err, + "select keyblock, sigstatus from primaries where oid = ?", + SQLITE_ARG_LONG_LONG, hd->found.key, SQLITE_ARG_END); + if (rc == SQLITE_ABORT) + /* Success. */ + { + assert (hd->resource->kb); + hd->resource->key = hd->found.key; + *iobuf = iobuf_temp_with_content (hd->resource->kb, hd->resource->kb_len); + + rc = 0; + if (uid_no) + *uid_no = hd->found.uid_no; + if (pk_no) + *pk_no = hd->found.pk_no; + if (sigstatus && hd->resource->sigstatus) + { + size_t s = (sizeof (hd->resource->sigstatus[0]) + * (1 + hd->resource->sigstatus[0])); + *sigstatus = xmalloc (s); + memcpy (*sigstatus, hd->resource->sigstatus, s); + } + } + else if (! rc) + /* If we don't get an abort, then we didn't find the record. */ + rc = gpg_error (GPG_ERR_NOT_FOUND); + else + { + log_error (_("reading keyblock from keydb DB: %s\n"), err); + sqlite3_free (err); + rc = gpg_error (GPG_ERR_GENERAL); + } + + sqlite3_finalize (stmt); + return rc; +} + +int +kdb_update_keyblock (KDB_HANDLE hd, kbnode_t kb, + const void *image, size_t imagelen) +{ + (void) hd; + (void) kb; + (void) image; + (void) imagelen; + + log_fatal ("Implement %s.", __func__); +} + +static char * +strrev (char *str) +{ + int i; + int l = strlen (str); + + for (i = 0; i < l / 2; i ++) + { + char t = str[i]; + str[i] = str[l - 1 - i]; + str[l - 1 - i] = t; + } + + return str; +} + +static char * +fingerprint_ascii_rev (char *fingerprint_bin, int len) +{ + char *fingerprint = xmalloc (2 * len + 1); + bin2hex (fingerprint_bin, len, fingerprint); + return strrev (fingerprint); +} + +gpg_error_t +kdb_insert_keyblock (KDB_HANDLE hd, kbnode_t root, + const void *image, size_t imagelen, u32 *sigstatus) +{ + PKT_public_key *mainpk = root->pkt->pkt.public_key; + char fingerprint_bin[MAX_FINGERPRINT_LEN]; + size_t fingerprint_bin_len = sizeof (fingerprint_bin); + char *fingerprint_rev = NULL; + + char *sigstatus_str = NULL; + + int rc; + char *err; + + sqlite3_stmt *uid_stmt = NULL; + sqlite3_stmt *key_stmt = NULL; + + long long oid; + int uid_no; + int pk_no; + kbnode_t k; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + hdr_cache_clear (hd->resource); + hd_cache_clear (hd); + + /* XXX: If we have a search result (hd->found), are we supposed to + replace it even if it isn't for the same key? */ + /* See if we are replacing or adding this record to the + database. */ + fingerprint_from_pk (mainpk, fingerprint_bin, &fingerprint_bin_len); + assert (fingerprint_bin_len == sizeof (fingerprint_bin)); + fingerprint_rev = + fingerprint_ascii_rev (fingerprint_bin, fingerprint_bin_len); + + if (sigstatus) + { + int i; + char *p; + p = sigstatus_str = xmalloc ((10 + 1) * sigstatus[0]); + for (i = 0; i < sigstatus[0]; i ++) + { + p += sprintf (p, "%d", sigstatus[i + 1]); + if (i != sigstatus[0] - 1) + *p ++ = ';'; + } + } + + oid = -1; + rc = sqlite3_stepx + (hd->resource->db, NULL, get_unsigned_longs_cb2, &oid, &err, + "select oid from primaries where fingerprint_rev = ?;", + SQLITE_ARG_STRING, fingerprint_rev, SQLITE_ARG_END); + if (rc) + { + log_error (_("looking up key in keydb DB: %s\n"), err); + sqlite3_free (err); + return gpg_error (GPG_ERR_GENERAL); + } + + if (oid != -1) + /* This key is already in the DB. Replace it. */ + { + DEBUG ("%s already in DB (oid = %lld), updating.\n", + fingerprint_rev, oid); + + hdr_cache_clear (hd->resource); + + rc = sqlite3_exec_printf + (hd->resource->db, NULL, NULL, &err, + "delete from primaries where oid = %lld;" + "delete from keys where primary_key = %lld;" + "delete from uids where primary_key = %lld;", + oid, oid, oid); + if (rc) + { + log_error (_("updating key in keydb DB: %s\n"), err); + sqlite3_free (err); + return gpg_error (GPG_ERR_GENERAL); + } + + /* Reuse the oid. So that any extant search won't return the + new record. */ + rc = sqlite3_stepx + (hd->resource->db, NULL, NULL, NULL, &err, + "insert into primaries (oid, fingerprint_rev, keyblock, sigstatus)\n" + " values (?, ?, ?, ?);", + SQLITE_ARG_LONG_LONG, oid, + SQLITE_ARG_STRING, fingerprint_rev, + SQLITE_ARG_BLOB, image, (long long) imagelen, + SQLITE_ARG_STRING, sigstatus_str, + SQLITE_ARG_END); + } + else + { + DEBUG ("New keyblock for %s.\n", fingerprint_rev); + rc = sqlite3_stepx + (hd->resource->db, NULL, NULL, NULL, &err, + "insert into primaries (fingerprint_rev, keyblock, sigstatus)\n" + " values (?, ?, ?);", + SQLITE_ARG_STRING, fingerprint_rev, + SQLITE_ARG_BLOB, image, (long long) imagelen, + SQLITE_ARG_STRING, sigstatus_str, + SQLITE_ARG_END); + } + + xfree (sigstatus_str); + xfree (fingerprint_rev); + fingerprint_rev = NULL; + + if (rc) + { + log_error (_("inserting %s record into keydb DB: %s\n"), + "primary key", err); + sqlite3_free (err); + return gpg_error (GPG_ERR_GENERAL); + } + + oid = sqlite3_last_insert_rowid (hd->resource->db); + + uid_no = 0; + pk_no = 0; + for (k = root; k; k = k->next) + { + if (k->pkt->pkttype == PKT_USER_ID) + { + PKT_user_id *uid = k->pkt->pkt.user_id; + const char *user_id = uid->name; + char *email = mailbox_from_userid (user_id); + + uid_no ++; + + rc = sqlite3_stepx + (hd->resource->db, &uid_stmt, NULL, NULL, &err, + "insert into uids (primary_key, uid, email, uid_no)" + " values (?, ?, ?, ?);", + SQLITE_ARG_LONG_LONG, oid, + SQLITE_ARG_STRING, user_id, SQLITE_ARG_STRING, email, + SQLITE_ARG_INT, uid_no, + SQLITE_ARG_END); + xfree (email); + if (rc) + { + log_error (_("inserting %s record into keydb DB: %s\n"), + "uid", err); + sqlite3_free (err); + return gpg_error (GPG_ERR_GENERAL); + } + } + else if (k->pkt->pkttype == PKT_PUBLIC_KEY + || k->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + PKT_public_key *pk = k->pkt->pkt.public_key; + + pk_no ++; + + fingerprint_from_pk (pk, fingerprint_bin, &fingerprint_bin_len); + assert (fingerprint_bin_len == sizeof (fingerprint_bin)); + fingerprint_rev = fingerprint_ascii_rev (fingerprint_bin, + fingerprint_bin_len); + + rc = sqlite3_stepx + (hd->resource->db, &key_stmt, NULL, NULL, &err, + "insert into keys (primary_key, fingerprint_rev, pk_no)" + " values (?, ?, ?);", + SQLITE_ARG_LONG_LONG, oid, + SQLITE_ARG_STRING, fingerprint_rev, + SQLITE_ARG_INT, pk_no, + SQLITE_ARG_END); + + xfree (fingerprint_rev); + fingerprint_rev = NULL; + + if (rc) + { + log_error (_("inserting %s record into keydb DB: %s\n"), + "key", err); + sqlite3_free (err); + return gpg_error (GPG_ERR_GENERAL); + } + } + } + + sqlite3_finalize (uid_stmt); + sqlite3_finalize (key_stmt); + + return 0; +} + +int +kdb_delete (KDB_HANDLE hd) +{ + int rc; + char *err; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + if (hd->found.key == -1) + /* No search result. */ + return gpg_error (GPG_ERR_NOTHING_FOUND); + + hdr_cache_clear (hd->resource); + hd_cache_clear (hd); + + rc = sqlite3_exec_printf + (hd->resource->db, NULL, NULL, &err, + "delete from keys where primary_key = %d;\n" + "delete from uids where primary_key = %d;\n" + "delete from primaries where oid = %d;\n", + hd->found.key, hd->found.key, hd->found.key); + if (rc) + { + log_error (_("error deleting key from kdb database: %s\n"), err); + sqlite3_free (err); + rc = gpg_error (GPG_ERR_GENERAL); + } + + return rc; +} + +int +kdb_search_reset (KDB_HANDLE hd) +{ + hd->key = -1; + hd->eof = 0; + + hd->found.key = -1; + + hd_cache_clear (hd); + + return 0; +} + +struct key_array +{ + long int count; + /* The maximum amount of memory to use before aborting. If 0, + unlimited. */ + size_t memory; + struct key *keys; +}; + +static int +get_keyblock_array_cb (void *cookie, int argc, char **argv, + char **azColName, sqlite3_stmt *stmt) +{ + struct key_array *a = cookie; + struct key *entry; + size_t len = sqlite3_column_bytes (stmt, 0); + char *tail; + + assert (argc == 3); + + (void) azColName; + + entry = xmalloc_clear (sizeof (*entry)); + entry->kb_len = len; + entry->kb = xmalloc (len); + memcpy (entry->kb, sqlite3_column_blob (stmt, 0), len); + + entry->sigstatus = sigstatus_parse (argv[1]); + + errno = 0; + entry->key = strtoul (argv[2], &tail, 0); + if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0')) + /* Abort. */ + { + xfree (entry); + return 1; + } + + /* Attach it. */ + entry->next = a->keys; + a->keys = entry; + + a->count ++; + + if (len >= a->memory) + /* Don't read another record. We're out of memory. */ + { + log_debug ("%s: Stopped prefilling read-ahead cache (%zd bytes larger than %zd bytes of available memory.\n", + __func__, len, a->memory); + return 1; + } + else + log_debug ("%s: Used %zd bytes of remaining %zd.\n", + __func__, len, a->memory); + + a->memory -= len; + + return 0; +} + +static gpg_error_t +key_list_load (struct keydb_handle *hd) +{ + int rc; + char *err = NULL; + struct key_array a; + a.count = 0; + /* Don't read much more than 20 MB of keyblocks into memory at a + time. */ + a.memory = 20 * 1024 * 1024; + a.keys = NULL; + + hd_cache_clear (hd); + + if (hd->key == -1) + /* From the beginning. */ + rc = sqlite3_stepx + (hd->resource->db, NULL, + get_keyblock_array_cb, &a, &err, + "select keyblock, sigstatus, oid from primaries" + " order by oid", + SQLITE_ARG_END); + else + rc = sqlite3_stepx + (hd->resource->db, NULL, + get_keyblock_array_cb, &a, &err, + "select keyblock, sigstatus, oid from primaries" + " where oid > ?" + " order by oid", + SQLITE_ARG_LONG_LONG, hd->key, + SQLITE_ARG_END); + + if (! rc) + /* We got the whole thing. */ + { + /* Add an EOF record. */ + struct key *k = xmalloc_clear (sizeof (*k)); + k->key = -1; + k->next = a.keys; + hd->full_scan = k; + } + else if (rc == SQLITE_ABORT) + /* Partial list. */ + { + rc = 0; + hd->full_scan = a.keys; + } + else + /* It is possible that we got a partial listing. */ + { + log_fatal ("error listing primary table: %s\n", err); + sqlite3_free (err); + } + + log_debug ("%s: Got %ld records.\n", __func__, a.count); + + hd->full_scan = key_list_reverse (hd->full_scan); + + if (rc) + return gpg_error (GPG_ERR_GENERAL); + return 0; +} + +static int +kdb_search_cb (void *cookie, int argc, char **argv, char **azColName, + sqlite3_stmt *stmt) +{ + KDB_HANDLE hd = cookie; + int i = 0; + unsigned long int values[argc]; + int got_keyblock = 0; + + /* Get the keyblock. */ + if (argc >= 2 + && strcmp (azColName[0], "keyblock") == 0 + && strcmp (azColName[1], "sigstatus") == 0) + { + /* When we do: select keyblock, min(oid) and we don't have any + results, then keyblock will be NULL. */ + if (argv[0]) + { + keyblock_cb (hd, 2, argv, azColName, stmt); + got_keyblock = 1; + } + i = 2; + } + + get_unsigned_longs_cb (&values[i], argc - i, &argv[i], &azColName[i]); + hd->found.uid_no = hd->found.pk_no = 0; + for (; i < argc; i ++) + if (strcmp (azColName[i], "oid") == 0) + { + hd->key = hd->found.key = values[i]; + if (got_keyblock) + hd->resource->key = hd->key; + } + else if (strcmp (azColName[i], "uid_no") == 0) + hd->found.uid_no = values[i]; + else if (strcmp (azColName[i], "pk_no") == 0) + hd->found.pk_no = values[i]; + else + log_bug ("%s: Bad column name: %s\n", __func__, azColName[i]); + + /* Abort. */ + return 1; +} + +int +kdb_search (KDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, + size_t ndesc, size_t *descindex) +{ + int n; + int have_next = 0; + char *where_uid = NULL; + char *where_key = NULL; + char *text; + int anyskip = 0; + int rc = 0; + char *err = NULL; + char *sql = NULL; + + hd->found.key = -1; + + for (n = 0; n < ndesc; n ++) + { + if (desc[n].mode == KEYDB_SEARCH_MODE_FIRST) + { + kdb_search_reset (hd); + desc[n].mode = KEYDB_SEARCH_MODE_NEXT; + have_next = 1; + } + if (desc[n].mode == KEYDB_SEARCH_MODE_NEXT) + have_next = 1; + } + + if (have_next) + { + if (hd->full_scan && hd->full_scan->key == hd->key) + /* The head of the full_scan list is the current key. + Remove the current key from the full_scan list and + return the next one. */ + hd->full_scan = key_free (hd->full_scan); + else + hd_cache_clear (hd); + + if (! hd->full_scan) + { + rc = key_list_load (hd); + if (rc) + goto out; + } + + /* We always add an EOF record. So, this can't be NULL. */ + assert (hd->full_scan); + + if (hd->full_scan->key == -1) + /* EOF! */ + hd->eof = 1; + else + hd->key = hd->found.key = hd->full_scan->key; + + goto out; + } + + hd_cache_clear (hd); + + if (hd->eof) + /* We're at the end of the file. There is nothing else to get. */ + return gpg_error (GPG_ERR_EOF); + +#define ADD_TERM(thing, op, fmt, ...) do { \ + char *t = sqlite3_mprintf \ + ("%s%s("fmt")", \ + thing ? thing : "", thing ? "\n "op" " : "", \ + ##__VA_ARGS__); \ + sqlite3_free (thing); \ + thing = t; \ + } while (0) +#define O(thing, fmt, ...) ADD_TERM(thing, "OR", fmt, ##__VA_ARGS__) +#define A(thing, fmt, ...) ADD_TERM(thing, "AND", fmt, ##__VA_ARGS__) + + if (descindex) + log_fatal ("Implement descindex\n"); + + for (n = 0; n < ndesc; n ++) + { + KEYDB_SEARCH_DESC *d = &desc[n]; + + switch (d->mode) + { + case KEYDB_SEARCH_MODE_EXACT: + O(where_uid, "uids.uid = %Q", desc[n].u.name); + break; + + case KEYDB_SEARCH_MODE_SUBSTR: + case KEYDB_SEARCH_MODE_MAIL: + case KEYDB_SEARCH_MODE_MAILSUB: + case KEYDB_SEARCH_MODE_MAILEND: + { + char *escaped = xmalloc (1 + 2 * strlen (d->u.name) + 1 + 1); + int i, j = 0; + + if (d->mode == KEYDB_SEARCH_MODE_SUBSTR + || d->mode == KEYDB_SEARCH_MODE_MAILSUB + || d->mode == KEYDB_SEARCH_MODE_MAILEND) + escaped[j ++] = '%'; + + for (i = 0; i < strlen (d->u.name); i ++) + { + if (d->u.name[i] == '%' || d->u.name[i] == '_' + || d->u.name[i] == '\'' || d->u.name[i] == '\\') + escaped[j ++] = '\\'; + escaped[j ++] = d->u.name[i]; + } + + if (d->mode == KEYDB_SEARCH_MODE_SUBSTR + || d->mode == KEYDB_SEARCH_MODE_MAILSUB) + escaped[j ++] = '%'; + + escaped[j] = 0; + + O(where_uid, "uids.%s like %Q", + d->mode == KEYDB_SEARCH_MODE_SUBSTR ? "uid" : "email", + escaped); + } + break; + + + case KEYDB_SEARCH_MODE_WORDS: + log_fatal ("Implement me!\n"); + break; + + + case KEYDB_SEARCH_MODE_SHORT_KID: + text = xmalloc (8 + 1); + snprintf (text, 9, "%08lX", (ulong) d->u.kid[1]); + O(where_key, "keys.fingerprint_rev like '%s%%'", strrev (text)); + xfree (text); + break; + + case KEYDB_SEARCH_MODE_LONG_KID: + text = xmalloc (8 * 2 + 1); + snprintf (text, 8 * 2 + 1, "%08lX%08lX", + (ulong) d->u.kid[0], (ulong) d->u.kid[1]); + O(where_key, "keys.fingerprint_rev like '%s%%'", strrev (text)); + xfree (text); + break; + + case KEYDB_SEARCH_MODE_FPR16: + if (d->mode == KEYDB_SEARCH_MODE_FPR16) + text = bin2hex (d->u.fpr, 16, NULL); + /* Fall through. */ + + case KEYDB_SEARCH_MODE_FPR20: + case KEYDB_SEARCH_MODE_FPR: + if (d->mode == KEYDB_SEARCH_MODE_FPR20 + || d->mode == KEYDB_SEARCH_MODE_FPR) + text = bin2hex (d->u.fpr, 20, NULL); + + strrev (text); + O(where_key, "keys.fingerprint_rev = '%s'", text); + xfree (text); + break; + + case KEYDB_SEARCH_MODE_FIRST: + case KEYDB_SEARCH_MODE_NEXT: + /* Already handled above. */ + break; + + default: + break; + } + + if (d->skipfnc) + anyskip = 1; + } + + if (anyskip) + log_fatal ("Implement anyskip."); + + DEBUG ("uid: %s\n", where_uid); + DEBUG ("key: %s\n", where_key); + + assert (where_uid || where_key); + if (where_uid && where_key) + sql = sqlite3_mprintf + ("select keyblock, sigstatus,\n" + " keys.primary_key oid, keys.pk_no, uids.uid_no\n" + " from primaries\n" + " left join keys on primaries.oid = keys.primary_key\n" + " left join uids on primaries.oid = uids.primary_key\n" + " where %s%lld and (%s and %s)\n" + " order by keys.primary_key, keys.pk_no, uids.uid_no\n" + " limit 1\n", + hd->key == -1 ? "" : "keys.primary_key > ", hd->key, + where_uid, where_key); + else if (where_key) + sql = sqlite3_mprintf + ("select primary_key oid, pk_no\n" + " from keys\n" + " where %s%lld and (%s)\n" + " order by primary_key, pk_no\n" + " limit 1;\n", + hd->key == -1 ? "" : "primary_key > ", hd->key, where_key); + else + sql = sqlite3_mprintf + ("select primary_key oid, uid_no\n" + " from uids\n" + " where %s%lld and (%s)\n" + " order by primary_key, uid_no\n" + " limit 1;\n", + hd->key == -1 ? "" : "primary_key > ", hd->key, where_uid); + DEBUG ("Running '%s'\n", sql); + rc = sqlite3_stepx (hd->resource->db, NULL, kdb_search_cb, hd, &err, + sql, SQLITE_ARG_END); + if (rc == SQLITE_ABORT) + /* Success. */ + rc = 0; + else if (rc) + { + log_fatal ("error search DB: %s\n", err); + sqlite3_free (err); + goto out; + } + else + /* EOF. */ + { + assert (hd->found.key == -1); + hd->eof = 1; + } + + out: + sqlite3_free (sql); + sqlite3_free (where_uid); + sqlite3_free (where_key); + + if (rc) + { + DEBUG ("Search result: Error.\n"); + return gpg_error (GPG_ERR_GENERAL); + } + if (hd->eof) + { + DEBUG ("Search result: ENOENT.\n"); + return gpg_error (GPG_ERR_EOF); + } + + DEBUG ("Search result: key #%lld.\n", hd->key); + + return 0; +} diff --git a/g10/kdb.h b/g10/kdb.h new file mode 100644 index 000000000..55e6962e0 --- /dev/null +++ b/g10/kdb.h @@ -0,0 +1,29 @@ +#ifndef GNUPG_KDB_H +#define GNUPG_KDB_H + +#include "keydb.h" + +typedef struct keydb_handle *KDB_HANDLE; + +gpg_error_t kdb_register_file (const char *fname, int read_only, void **ptr); +int kdb_is_writable (void *token); + +KDB_HANDLE kdb_new (void *token); +void kdb_release (KDB_HANDLE hd); +void kdb_push_found_state (KDB_HANDLE hd); +void kdb_pop_found_state (KDB_HANDLE hd); +const char *kdb_get_resource_name (KDB_HANDLE hd); +int kdb_lock (KDB_HANDLE hd, int yes); +int kdb_get_keyblock (KDB_HANDLE hd, iobuf_t *iobuf, + int *pk_no, int *uid_no, u32 **sigstatus); +int kdb_update_keyblock (KDB_HANDLE hd, kbnode_t kb, + const void *image, size_t imagelen); +gpg_error_t kdb_insert_keyblock (KDB_HANDLE hd, kbnode_t kb, + const void *image, size_t imagelen, + u32 *sigstatus); +int kdb_delete (KDB_HANDLE hd); +int kdb_search_reset (KDB_HANDLE hd); +int kdb_search (KDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, + size_t ndesc, size_t *descindex); + +#endif diff --git a/g10/keydb.c b/g10/keydb.c index 8a689809a..2540af86c 100644 --- a/g10/keydb.c +++ b/g10/keydb.c @@ -37,6 +37,7 @@ #include "../kbx/keybox.h" #include "keydb.h" #include "i18n.h" +#include "kdb.h" static int active_handles; @@ -44,7 +45,8 @@ typedef enum { KEYDB_RESOURCE_TYPE_NONE = 0, KEYDB_RESOURCE_TYPE_KEYRING, - KEYDB_RESOURCE_TYPE_KEYBOX + KEYDB_RESOURCE_TYPE_KEYBOX, + KEYDB_RESOURCE_TYPE_KEYDB } KeydbResourceType; #define MAX_KEYDB_RESOURCES 40 @@ -54,6 +56,7 @@ struct resource_item union { KEYRING_HANDLE kr; KEYBOX_HANDLE kb; + KDB_HANDLE kdb; } u; void *token; }; @@ -251,13 +254,14 @@ keyblock_cache_clear (struct keydb_handle *hd) /* Handle the creation of a keyring or a keybox if it does not yet exist. Take into account that other processes might have the keyring/keybox already locked. This lock check does not work if - the directory itself is not yet available. If IS_BOX is true the - filename is expected to refer to a keybox. If FORCE_CREATE is true - the keyring or keybox will be created. + the directory itself is not yet available. RT is the type of + resource being created. If FORCE_CREATE is true the keyring or + keybox will be created. Return 0 if it is okay to access the specified file. */ static int -maybe_create_keyring_or_box (char *filename, int is_box, int force_create) +maybe_create_resource (char *filename, KeydbResourceType rt, + int force_create) { dotlock_t lockhd = NULL; IOBUF iobuf; @@ -361,12 +365,25 @@ maybe_create_keyring_or_box (char *filename, int is_box, int force_create) if (!iobuf) { rc = gpg_error_from_syserror (); - if (is_box) - log_error (_("error creating keybox '%s': %s\n"), - filename, gpg_strerror (rc)); - else - log_error (_("error creating keyring '%s': %s\n"), - filename, gpg_strerror (rc)); + switch (rt) + { + case KEYDB_RESOURCE_TYPE_NONE: + log_fatal ("Bad value for resource type: %d\n", rt); + break; + + case KEYDB_RESOURCE_TYPE_KEYRING: + log_error (_("error creating keyring '%s': %s\n"), + filename, gpg_strerror (rc)); + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + log_error (_("error creating keybox '%s': %s\n"), + filename, gpg_strerror (rc)); + break; + case KEYDB_RESOURCE_TYPE_KEYDB: + log_error (_("error creating keydb '%s': %s\n"), + filename, gpg_strerror (rc)); + break; + } goto leave; } @@ -376,7 +393,7 @@ maybe_create_keyring_or_box (char *filename, int is_box, int force_create) /* Make sure that at least one record is in a new keybox file, so that the detection magic will work the next time it is used. */ - if (is_box) + if (rt == KEYDB_RESOURCE_TYPE_KEYBOX) { FILE *fp = fopen (filename, "w"); if (!fp) @@ -388,22 +405,29 @@ maybe_create_keyring_or_box (char *filename, int is_box, int force_create) } if (rc) { - if (is_box) - log_error (_("error creating keybox '%s': %s\n"), - filename, gpg_strerror (rc)); - else - log_error (_("error creating keyring '%s': %s\n"), - filename, gpg_strerror (rc)); + log_error (_("error creating keybox '%s': %s\n"), + filename, gpg_strerror (rc)); goto leave; } } if (!opt.quiet) { - if (is_box) - log_info (_("keybox '%s' created\n"), filename); - else - log_info (_("keyring '%s' created\n"), filename); + switch (rt) + { + case KEYDB_RESOURCE_TYPE_NONE: + log_fatal ("Bad value for resource type: %d\n", rt); + break; + case KEYDB_RESOURCE_TYPE_KEYRING: + log_info (_("keyring '%s' created\n"), filename); + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + log_info (_("keybox '%s' created\n"), filename); + break; + case KEYDB_RESOURCE_TYPE_KEYDB: + log_info (_("keydb '%s' created\n"), filename); + break; + } } rc = 0; @@ -445,6 +469,8 @@ rt_from_file (const char *filename, int *r_found, int *r_openpgp) { if (magic == 0x13579ace || magic == 0xce9a5713) ; /* GDBM magic - not anymore supported. */ + else if (memcmp (&magic, "SQLi", 4) == 0) + rt = KEYDB_RESOURCE_TYPE_KEYDB; else if (fread (&verbuf, 4, 1, fp) == 1 && verbuf[0] == 1 && fread (&magic, 4, 1, fp) == 1 @@ -559,6 +585,11 @@ keydb_add_resource (const char *url, unsigned int flags) rt = KEYDB_RESOURCE_TYPE_KEYBOX; resname += 10; } + else if (strlen (resname) > 10 && !strncmp (resname, "gnupg-kdb:", 10) ) + { + rt = KEYDB_RESOURCE_TYPE_KEYDB; + resname += 10; + } #if !defined(HAVE_DRIVE_LETTERS) && !defined(__riscos__) else if (strchr (resname, ':')) { @@ -662,7 +693,8 @@ keydb_add_resource (const char *url, unsigned int flags) goto leave; case KEYDB_RESOURCE_TYPE_KEYRING: - rc = maybe_create_keyring_or_box (filename, 0, create); + rc = maybe_create_resource (filename, KEYDB_RESOURCE_TYPE_KEYRING, + create); if (rc) goto leave; @@ -692,7 +724,8 @@ keydb_add_resource (const char *url, unsigned int flags) case KEYDB_RESOURCE_TYPE_KEYBOX: { - rc = maybe_create_keyring_or_box (filename, 1, create); + rc = maybe_create_resource (filename, KEYDB_RESOURCE_TYPE_KEYBOX, + create); if (rc) goto leave; @@ -727,6 +760,36 @@ keydb_add_resource (const char *url, unsigned int flags) } break; + case KEYDB_RESOURCE_TYPE_KEYDB: + { + rc = maybe_create_resource (filename, KEYDB_RESOURCE_TYPE_KEYDB, create); + if (rc) + goto leave; + + rc = kdb_register_file (filename, read_only, &token); + if (rc == 0) + { + if (used_resources >= MAX_KEYDB_RESOURCES) + rc = gpg_error (GPG_ERR_RESOURCE_LIMIT); + else + { + /* if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY)) */ + /* primary_keyring = token; */ + all_resources[used_resources].type = rt; + all_resources[used_resources].u.kb = NULL; /* Not used here */ + all_resources[used_resources].token = token; + + used_resources++; + } + } + + /* XXX: How to mark this as a primary if it was already + registered. */ + /* if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY)) */ + /* primary_keyring = token; */ + } + break; + default: log_error ("resource type of '%s' not supported\n", url); rc = gpg_error (GPG_ERR_GENERAL); @@ -792,6 +855,14 @@ keydb_new (void) die = 1; j++; break; + case KEYDB_RESOURCE_TYPE_KEYDB: + hd->active[j].type = all_resources[i].type; + hd->active[j].token = all_resources[i].token; + hd->active[j].u.kdb = kdb_new (all_resources[i].token); + if (!hd->active[j].u.kdb) + die = 1; + j++; + break; } } hd->used = j; @@ -831,6 +902,9 @@ keydb_release (KEYDB_HANDLE hd) case KEYDB_RESOURCE_TYPE_KEYBOX: keybox_release (hd->active[i].u.kb); break; + case KEYDB_RESOURCE_TYPE_KEYDB: + kdb_release (hd->active[i].u.kdb); + break; } } @@ -873,6 +947,9 @@ keydb_get_resource_name (KEYDB_HANDLE hd) case KEYDB_RESOURCE_TYPE_KEYBOX: s = keybox_get_resource_name (hd->active[idx].u.kb); break; + case KEYDB_RESOURCE_TYPE_KEYDB: + s = kdb_get_resource_name (hd->active[idx].u.kdb); + break; } return s? s: ""; @@ -904,6 +981,9 @@ lock_all (KEYDB_HANDLE hd) case KEYDB_RESOURCE_TYPE_KEYBOX: rc = keybox_lock (hd->active[i].u.kb, 1); break; + case KEYDB_RESOURCE_TYPE_KEYDB: + rc = kdb_lock (hd->active[i].u.kdb, 1); + break; } } @@ -922,6 +1002,9 @@ lock_all (KEYDB_HANDLE hd) case KEYDB_RESOURCE_TYPE_KEYBOX: rc = keybox_lock (hd->active[i].u.kb, 0); break; + case KEYDB_RESOURCE_TYPE_KEYDB: + rc = kdb_lock (hd->active[i].u.kdb, 0); + break; } } } @@ -952,6 +1035,9 @@ unlock_all (KEYDB_HANDLE hd) case KEYDB_RESOURCE_TYPE_KEYBOX: keybox_lock (hd->active[i].u.kb, 0); break; + case KEYDB_RESOURCE_TYPE_KEYDB: + kdb_lock (hd->active[i].u.kdb, 0); + break; } } hd->locked = 0; @@ -981,6 +1067,9 @@ keydb_push_found_state (KEYDB_HANDLE hd) case KEYDB_RESOURCE_TYPE_KEYBOX: keybox_push_found_state (hd->active[hd->found].u.kb); break; + case KEYDB_RESOURCE_TYPE_KEYDB: + kdb_push_found_state (hd->active[hd->found].u.kdb); + break; } hd->saved_found = hd->found; @@ -1009,6 +1098,9 @@ keydb_pop_found_state (KEYDB_HANDLE hd) case KEYDB_RESOURCE_TYPE_KEYBOX: keybox_pop_found_state (hd->active[hd->found].u.kb); break; + case KEYDB_RESOURCE_TYPE_KEYDB: + kdb_pop_found_state (hd->active[hd->found].u.kdb); + break; } } @@ -1254,6 +1346,23 @@ keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb) } } break; + case KEYDB_RESOURCE_TYPE_KEYDB: + { + iobuf_t iobuf; + u32 *sigstatus; + int pk_no, uid_no; + + err = kdb_get_keyblock (hd->active[hd->found].u.kdb, &iobuf, + &pk_no, &uid_no, &sigstatus); + if (!err) + { + err = parse_keyblock_image (iobuf, pk_no, uid_no, sigstatus, + ret_kb); + xfree (sigstatus); + iobuf_close (iobuf); + } + break; + } } if (hd->keyblock_cache.state != KEYBLOCK_CACHE_FILLED) @@ -1399,6 +1508,20 @@ keydb_update_keyblock (KEYDB_HANDLE hd, kbnode_t kb) } } break; + case KEYDB_RESOURCE_TYPE_KEYDB: + { + iobuf_t iobuf; + + err = build_keyblock_image (kb, &iobuf, NULL); + if (!err) + { + err = kdb_update_keyblock (hd->active[hd->found].u.kdb, kb, + iobuf_get_temp_buffer (iobuf), + iobuf_get_temp_length (iobuf)); + iobuf_close (iobuf); + } + } + break; } unlock_all (hd); @@ -1460,6 +1583,23 @@ keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb) } } break; + case KEYDB_RESOURCE_TYPE_KEYDB: + { + iobuf_t iobuf; + u32 *sigstatus; + + err = build_keyblock_image (kb, &iobuf, &sigstatus); + if (!err) + { + err = kdb_insert_keyblock (hd->active[idx].u.kdb, kb, + iobuf_get_temp_buffer (iobuf), + iobuf_get_temp_length (iobuf), + sigstatus); + xfree (sigstatus); + iobuf_close (iobuf); + } + } + break; } unlock_all (hd); @@ -1499,6 +1639,9 @@ keydb_delete_keyblock (KEYDB_HANDLE hd) case KEYDB_RESOURCE_TYPE_KEYBOX: rc = keybox_delete (hd->active[hd->found].u.kb); break; + case KEYDB_RESOURCE_TYPE_KEYDB: + rc = kdb_delete (hd->active[hd->found].u.kdb); + break; } unlock_all (hd); @@ -1553,6 +1696,10 @@ keydb_locate_writable (KEYDB_HANDLE hd) if (keybox_is_writable (hd->active[hd->current].token)) return 0; /* found (hd->current is set to it) */ break; + case KEYDB_RESOURCE_TYPE_KEYDB: + if (kdb_is_writable (hd->active[hd->current].token)) + return 0; /* found (hd->current is set to it) */ + break; } } @@ -1581,6 +1728,9 @@ keydb_rebuild_caches (int noisy) case KEYDB_RESOURCE_TYPE_KEYBOX: /* N/A. */ break; + case KEYDB_RESOURCE_TYPE_KEYDB: + /* N/A. */ + break; } } } @@ -1626,6 +1776,9 @@ keydb_search_reset (KEYDB_HANDLE hd) case KEYDB_RESOURCE_TYPE_KEYBOX: rc = keybox_search_reset (hd->active[i].u.kb); break; + case KEYDB_RESOURCE_TYPE_KEYDB: + rc = kdb_search_reset (hd->active[i].u.kdb); + break; } } hd->is_reset = 1; @@ -1717,6 +1870,10 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, descindex, &hd->skipped_long_blobs); while (rc == GPG_ERR_LEGACY_KEY); break; + case KEYDB_RESOURCE_TYPE_KEYDB: + rc = kdb_search (hd->active[hd->current].u.kdb, desc, + ndesc, descindex); + break; } if (DBG_LOOKUP) diff --git a/g10/trustdb.c b/g10/trustdb.c index 386796c68..f59fa1a4e 100644 --- a/g10/trustdb.c +++ b/g10/trustdb.c @@ -1716,7 +1716,8 @@ validate_key_list (KEYDB_HANDLE hd, KeyHashTable full_trust, desc.skipfnc = search_skipfnc; desc.skipfncvalue = full_trust; rc = keydb_search (hd, &desc, 1, NULL); - if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) + if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND + || gpg_err_code (rc) == GPG_ERR_EOF) { keys[nkeys].keyblock = NULL; return keys;