#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; }