/* backend-sqlite.c - SQLite based backend for keyboxd * Copyright (C) 2019 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 . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #include "keyboxd.h" #include "../common/i18n.h" #include "../common/mbox-util.h" #include "backend.h" #include "keybox-search-desc.h" #include "keybox-defs.h" /* (for the openpgp parser) */ /* Add replacement error codes; GPGRT provides SQL error codes from * version 1.37 on. */ #if GPGRT_VERSION_NUMBER < 0x012500 /* 1.37 */ static GPGRT_INLINE gpg_error_t gpg_err_code_from_sqlite (int sqlres) { return sqlres? 1500 + (sqlres & 0xff) : 0; } #define GPG_ERR_SQL_OK 1500 #define GPG_ERR_SQL_ROW 1600 #define GPG_ERR_SQL_DONE 1601 #endif /*GPGRT_VERSION_NUMBER*/ /* Our definition of the backend handle. */ struct backend_handle_s { enum database_types db_type; /* Always DB_TYPE_SQLITE. */ unsigned int backend_id; /* Always the id of the backend. */ char filename[1]; }; /* Definition of local request data. */ struct be_sqlite_local_s { /* The statement object of the current select command. */ sqlite3_stmt *select_stmt; /* The search mode represented by the current select command. */ KeydbSearchMode select_mode; /* The select statement has been executed with success. */ int select_done; /* The last row has already been reached. */ int select_eof; }; /* The Mutex we use to protect all our SQLite calls. */ static npth_mutex_t database_mutex = NPTH_MUTEX_INITIALIZER; /* The one and only database handle. */ static sqlite3 *database_hd; /* A lockfile used make sure only we are accessing the database. */ static dotlock_t database_lock; static struct { const char *sql; int special; } table_definitions[] = { { "PRAGMA foreign_keys = ON" }, /* Table to store config values: * Standard name value pairs: * dbversion = 1 * created = */ { "CREATE TABLE IF NOT EXISTS config (" "name TEXT NOT NULL," "value TEXT NOT NULL " ")", 1 }, /* The actual data; either X.509 certificates or OpenPGP * keyblocks. */ { "CREATE TABLE IF NOT EXISTS pubkey (" /* The 20 octet truncted primary-fpr */ "ubid BLOB NOT NULL PRIMARY KEY," /* The type of the public key: 1 = openpgp, 2 = X.509. */ "type INTEGER NOT NULL," /* The OpenPGP keyblock or X.509 certificate. */ "keyblob BLOB NOT NULL" ")" }, /* Table with fingerprints and keyids of OpenPGP and X.509 keys. * It is also used for the primary key and the X.509 fingerprint * because we want to be able to use the keyid and keygrip. */ { "CREATE TABLE IF NOT EXISTS fingerprint (" "fpr BLOB NOT NULL PRIMARY KEY," /* The long keyid as 64 bit integer. */ "kid INTEGER NOT NULL," /* The keygrip for this key. */ "keygrip BLOB NOT NULL," /* 0 = primary, > 0 = subkey. */ "subkey INTEGER NOT NULL," /* The Unique Blob ID (possibly truncated fingerprint). */ "ubid BLOB NOT NULL REFERENCES pubkey" ")" }, /* Indices for the fingerprint table. */ { "CREATE INDEX IF NOT EXISTS fingerprintidx0 on fingerprint (ubid)" }, { "CREATE INDEX IF NOT EXISTS fingerprintidx1 on fingerprint (fpr)" }, { "CREATE INDEX IF NOT EXISTS fingerprintidx2 on fingerprint (keygrip)" }, /* Table to allow fast access via user ids or mail addresses. */ { "CREATE TABLE IF NOT EXISTS userid (" /* The full user id. */ "uid TEXT NOT NULL," /* The mail address if available or NULL. */ "addrspec TEXT," /* The type of the public key: 1 = openpgp, 2 = X.509. */ "type INTEGER NOT NULL," /* The Unique Blob ID (possibly truncated fingerprint). */ "ubid BLOB NOT NULL REFERENCES pubkey" ")" }, /* Indices for the userid table. */ { "CREATE INDEX IF NOT EXISTS userididx0 on userid (ubid)" }, { "CREATE INDEX IF NOT EXISTS userididx1 on userid (uid)" }, { "CREATE INDEX IF NOT EXISTS userididx3 on userid (addrspec)" } }; /* Take a lock for accessing SQLite. */ static void acquire_mutex (void) { int res = npth_mutex_lock (&database_mutex); if (res) log_fatal ("failed to acquire database lock: %s\n", gpg_strerror (gpg_error_from_errno (res))); } /* Release a lock. */ static void release_mutex (void) { int res = npth_mutex_unlock (&database_mutex); if (res) log_fatal ("failed to release database db lock: %s\n", gpg_strerror (gpg_error_from_errno (res))); } static void show_sqlstr (const char *sqlstr) { if (!opt.verbose) return; log_info ("(SQL: %s)\n", sqlstr); } static void show_sqlstmt (sqlite3_stmt *stmt) { char *p; if (!opt.verbose) return; p = sqlite3_expanded_sql (stmt); if (p) log_info ("(SQL: %s)\n", p); sqlite3_free (p); } static gpg_error_t diag_prepare_err (int res, const char *sqlstr) { gpg_error_t err; err = gpg_error (gpg_err_code_from_sqlite (res)); show_sqlstr (sqlstr); log_error ("error preparing SQL statement: %s\n", sqlite3_errstr (res)); return err; } static gpg_error_t diag_bind_err (int res, sqlite3_stmt *stmt) { gpg_error_t err; err = gpg_error (gpg_err_code_from_sqlite (res)); show_sqlstmt (stmt); log_error ("error binding a value to an SQL statement: %s\n", sqlite3_errstr (res)); return err; } static gpg_error_t diag_step_err (int res, sqlite3_stmt *stmt) { gpg_error_t err; err = gpg_error (gpg_err_code_from_sqlite (res)); show_sqlstmt (stmt); log_error ("error executing SQL statement: %s\n", sqlite3_errstr (res)); return err; } /* We store the keyid in the database as an integer - this function * converts it from a memory buffer. */ static GPGRT_INLINE sqlite3_int64 kid_from_mem (const unsigned char *keyid) { return ( ((uint64_t)keyid[0] << 56) | ((uint64_t)keyid[1] << 48) | ((uint64_t)keyid[2] << 40) | ((uint64_t)keyid[3] << 32) | ((uint64_t)keyid[4] << 24) | ((uint64_t)keyid[5] << 16) | ((uint64_t)keyid[6] << 8) | ((uint64_t)keyid[7]) ); } /* We store the keyid in the database as an integer - this function * converts it from the usual u32[2] array. */ static GPGRT_INLINE sqlite3_int64 kid_from_u32 (u32 *keyid) { return (((uint64_t)keyid[0] << 32) | ((uint64_t)keyid[1]) ); } /* Run an SQL reset on STMT. */ static gpg_error_t run_sql_reset (sqlite3_stmt *stmt) { gpg_error_t err; int res; res = sqlite3_reset (stmt); if (res) { err = gpg_error (gpg_err_code_from_sqlite (res)); show_sqlstmt (stmt); log_error ("error executing SQL reset: %s\n", sqlite3_errstr (res)); } else err = 0; return err; } /* Run an SQL prepare for SQLSTR and return a statement at R_STMT. */ static gpg_error_t run_sql_prepare (const char *sqlstr, sqlite3_stmt **r_stmt) { gpg_error_t err; int res; res = sqlite3_prepare_v2 (database_hd, sqlstr, -1, r_stmt, NULL); if (res) err = diag_prepare_err (res, sqlstr); else err = 0; return err; } /* Helper to bind a BLOB parameter to a statement. */ static gpg_error_t run_sql_bind_blob (sqlite3_stmt *stmt, int no, const void *blob, size_t bloblen) { gpg_error_t err; int res; res = sqlite3_bind_blob (stmt, no, blob, bloblen, SQLITE_TRANSIENT); if (res) err = diag_bind_err (res, stmt); else err = 0; return err; } /* Helper to bind an INTEGER parameter to a statement. */ static gpg_error_t run_sql_bind_int (sqlite3_stmt *stmt, int no, int value) { gpg_error_t err; int res; res = sqlite3_bind_int (stmt, no, value); if (res) err = diag_bind_err (res, stmt); else err = 0; return err; } /* Helper to bind an INTEGER64 parameter to a statement. */ static gpg_error_t run_sql_bind_int64 (sqlite3_stmt *stmt, int no, sqlite3_int64 value) { gpg_error_t err; int res; res = sqlite3_bind_int64 (stmt, no, value); if (res) err = diag_bind_err (res, stmt); else err = 0; return err; } /* Helper to bind a string parameter to a statement. VALUE is allowed * to be NULL to bind NULL. */ static gpg_error_t run_sql_bind_text (sqlite3_stmt *stmt, int no, const char *value) { gpg_error_t err; int res; res = sqlite3_bind_text (stmt, no, value, value? strlen (value):0, SQLITE_TRANSIENT); if (res) err = diag_bind_err (res, stmt); else err = 0; return err; } /* Helper to bind a string parameter to a statement. VALUE is allowed * to be NULL to bind NULL. A non-NULL VALUE is clamped with percent * signs. */ static gpg_error_t run_sql_bind_text_like (sqlite3_stmt *stmt, int no, const char *value) { gpg_error_t err; int res; char *buf; if (!value) { res = sqlite3_bind_null (stmt, no); buf = NULL; } else { buf = xtrymalloc (strlen (value) + 2 + 1); if (!buf) return gpg_error_from_syserror (); *buf = '%'; strcpy (buf+1, value); strcat (buf+1, "%"); res = sqlite3_bind_text (stmt, no, buf, strlen (buf), SQLITE_TRANSIENT); } if (res) err = diag_bind_err (res, stmt); else err = 0; xfree (buf); return err; } /* Wrapper around sqlite3_step for use with simple functions. */ static gpg_error_t run_sql_step (sqlite3_stmt *stmt) { gpg_error_t err; int res; res = sqlite3_step (stmt); if (res != SQLITE_DONE) err = diag_step_err (res, stmt); else err = 0; return err; } /* Wrapper around sqlite3_step for use with select. This version does * not print diags for SQLITE_DONE or SQLITE_ROW but returns them as * gpg error codes. */ static gpg_error_t run_sql_step_for_select (sqlite3_stmt *stmt) { gpg_error_t err; int res; res = sqlite3_step (stmt); if (res == SQLITE_DONE || res == SQLITE_ROW) err = gpg_error (gpg_err_code_from_sqlite (res)); else { /* SQL_OK is unexpected for a select so in this case we return * the OK error code by bypassing the special mapping. */ if (!res) err = gpg_error (GPG_ERR_SQL_OK); else err = gpg_error (gpg_err_code_from_sqlite (res)); show_sqlstmt (stmt); log_error ("error running SQL step: %s\n", sqlite3_errstr (res)); } return err; } /* Run the simple SQL statement in SQLSTR. If UBID is not NULL this * will be bound to :1 in SQLSTR. This command may not be used for * select or other command which return rows. */ static gpg_error_t run_sql_statement_bind_ubid (const char *sqlstr, const unsigned char *ubid) { gpg_error_t err; sqlite3_stmt *stmt; err = run_sql_prepare (sqlstr, &stmt); if (err) goto leave; if (ubid) { err = run_sql_bind_blob (stmt, 1, ubid, UBID_LEN); if (err) goto leave; } err = run_sql_step (stmt); sqlite3_finalize (stmt); if (err) goto leave; leave: return err; } /* Run the simple SQL statement in SQLSTR. This command may not be used * for select or other command which return rows. */ static gpg_error_t run_sql_statement (const char *sqlstr) { return run_sql_statement_bind_ubid (sqlstr, NULL); } /* Create and intitialize a new SQL database file if it does not * exists; else open it and check that all required objects are * available. */ static gpg_error_t create_or_open_database (const char *filename) { gpg_error_t err; int res; int idx; if (database_hd) return 0; /* Already initialized. */ acquire_mutex (); /* To avoid races with other temporary instances of keyboxd trying * to create or update the database, we run the database with a lock * file held. */ database_lock = dotlock_create (filename, 0); if (!database_lock) { err = gpg_error_from_syserror (); /* A reason for this to fail is that the directory is not * writable. However, this whole locking stuff does not make * sense if this is the case. An empty non-writable directory * with no database is not really useful at all. */ if (opt.verbose) log_info ("can't allocate lock for '%s': %s\n", filename, gpg_strerror (err)); goto leave; } if (dotlock_take (database_lock, -1)) { err = gpg_error_from_syserror (); /* This is something bad. Probably a stale lockfile. */ log_info ("can't lock '%s': %s\n", filename, gpg_strerror (err)); goto leave; } /* Database has not yet been opened. Open or create it, make sure * the tables exist, and prepare the required statements. We use * our own locking instead of the more complex serialization sqlite * would have to do and it avoid that we call * npth_unprotect/protect. */ res = sqlite3_open_v2 (filename, &database_hd, (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX), NULL); if (res) { err = gpg_error (gpg_err_code_from_sqlite (res)); log_error ("error opening '%s': %s\n", filename, sqlite3_errstr (res)); goto leave; } /* Enable extended error codes. */ sqlite3_extended_result_codes (database_hd, 1); /* Create the tables if needed. */ for (idx=0; idx < DIM(table_definitions); idx++) { err = run_sql_statement (table_definitions[idx].sql); if (err) goto leave; if (table_definitions[idx].special == 1) { /* Check and create dbversion etc entries. */ // FIXME } } if (!opt.quiet) log_info (_("database '%s' created\n"), filename); err = 0; leave: if (err) { log_error (_("error creating database '%s': %s\n"), filename, gpg_strerror (err)); dotlock_release (database_lock); dotlock_destroy (database_lock); database_lock = NULL; } release_mutex (); return err; } /* Install a new resource and return a handle for that backend. */ gpg_error_t be_sqlite_add_resource (ctrl_t ctrl, backend_handle_t *r_hd, const char *filename, int readonly) { gpg_error_t err; backend_handle_t hd; (void)ctrl; (void)readonly; /* FIXME: implement read-only mode. */ *r_hd = NULL; hd = xtrycalloc (1, sizeof *hd + strlen (filename)); if (!hd) return gpg_error_from_syserror (); hd->db_type = DB_TYPE_SQLITE; strcpy (hd->filename, filename); err = create_or_open_database (filename); if (err) goto leave; hd->backend_id = be_new_backend_id (); *r_hd = hd; hd = NULL; leave: xfree (hd); return err; } /* Release the backend handle HD and all its resources. HD is not * valid after a call to this function. */ void be_sqlite_release_resource (ctrl_t ctrl, backend_handle_t hd) { (void)ctrl; if (!hd) return; hd->db_type = DB_TYPE_NONE; xfree (hd); } /* Helper for be_find_request_part to initialize a sqlite request part. */ gpg_error_t be_sqlite_init_local (backend_handle_t backend_hd, db_request_part_t part) { (void)backend_hd; part->besqlite = xtrycalloc (1, sizeof *part->besqlite); if (!part->besqlite) return gpg_error_from_syserror (); return 0; } /* Release local data of a sqlite request part. */ void be_sqlite_release_local (be_sqlite_local_t ctx) { if (ctx->select_stmt) sqlite3_finalize (ctx->select_stmt); xfree (ctx); } /* Run a select for the search given by (DESC,NDESC). The data is not * returned but stored in the request item. */ static gpg_error_t run_select_statement (be_sqlite_local_t ctx, KEYDB_SEARCH_DESC *desc, unsigned int ndesc) { gpg_error_t err = 0; unsigned int descidx; descidx = 0; /* Fixme: take from context. */ if (descidx >= ndesc) { err = gpg_error (GPG_ERR_EOF); goto leave; } /* Check whether we can re-use the current select statement. */ if (!ctx->select_stmt) ; else if (ctx->select_mode != desc[descidx].mode) { sqlite3_finalize (ctx->select_stmt); ctx->select_stmt = NULL; } ctx->select_mode = desc[descidx].mode; /* Prepare the select and bind the parameters. */ if (ctx->select_stmt) { err = run_sql_reset (ctx->select_stmt); if (err) goto leave; } else err = 0; switch (desc[descidx].mode) { case KEYDB_SEARCH_MODE_NONE: never_reached (); err = gpg_error (GPG_ERR_INTERNAL); break; case KEYDB_SEARCH_MODE_EXACT: if (!ctx->select_stmt) err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob" " FROM pubkey as p, userid as u" " WHERE u.uid = ?1", &ctx->select_stmt); if (!err) err = run_sql_bind_text (ctx->select_stmt, 1, desc[descidx].u.name); break; case KEYDB_SEARCH_MODE_MAIL: if (!ctx->select_stmt) err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob" " FROM pubkey as p, userid as u" " WHERE u.addrspec = ?1", &ctx->select_stmt); if (!err) err = run_sql_bind_text (ctx->select_stmt, 1, desc[descidx].u.name); break; case KEYDB_SEARCH_MODE_MAILSUB: if (!ctx->select_stmt) err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob" " FROM pubkey as p, userid as u" " WHERE u.addrspec LIKE ?1", &ctx->select_stmt); if (!err) err = run_sql_bind_text_like (ctx->select_stmt, 1, desc[descidx].u.name); break; case KEYDB_SEARCH_MODE_SUBSTR: if (!ctx->select_stmt) err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob" " FROM pubkey as p, userid as u" " WHERE u.uid LIKE ?1", &ctx->select_stmt); if (!err) err = run_sql_bind_text_like (ctx->select_stmt, 1, desc[descidx].u.name); break; case KEYDB_SEARCH_MODE_MAILEND: case KEYDB_SEARCH_MODE_WORDS: err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); break; case KEYDB_SEARCH_MODE_ISSUER: err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ /* if (has_issuer (blob, desc[n].u.name)) */ /* goto found; */ break; case KEYDB_SEARCH_MODE_ISSUER_SN: err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ /* if (has_issuer_sn (blob, desc[n].u.name, */ /* sn_array? sn_array[n].sn : desc[n].sn, */ /* sn_array? sn_array[n].snlen : desc[n].snlen)) */ /* goto found; */ break; case KEYDB_SEARCH_MODE_SN: err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ /* if (has_sn (blob, sn_array? sn_array[n].sn : desc[n].sn, */ /* sn_array? sn_array[n].snlen : desc[n].snlen)) */ /* goto found; */ break; case KEYDB_SEARCH_MODE_SUBJECT: err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ /* if (has_subject (blob, desc[n].u.name)) */ /* goto found; */ break; case KEYDB_SEARCH_MODE_SHORT_KID: err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ /* pk_no = has_short_kid (blob, desc[n].u.kid[1]); */ /* if (pk_no) */ /* goto found; */ break; case KEYDB_SEARCH_MODE_LONG_KID: if (!ctx->select_stmt) err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob" " FROM pubkey as p, fingerprint as f" " WHERE p.ubid = f.ubid AND f.kid = ?1", &ctx->select_stmt); if (!err) err = run_sql_bind_int64 (ctx->select_stmt, 1, kid_from_u32 (desc[descidx].u.kid)); break; case KEYDB_SEARCH_MODE_FPR: if (!ctx->select_stmt) err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob" " FROM pubkey as p, fingerprint as f" " WHERE p.ubid = f.ubid AND f.fpr = ?1", &ctx->select_stmt); if (!err) err = run_sql_bind_blob (ctx->select_stmt, 1, desc[descidx].u.fpr, desc[descidx].fprlen); break; case KEYDB_SEARCH_MODE_KEYGRIP: if (!ctx->select_stmt) err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob" " FROM pubkey as p, fingerprint as f" " WHERE p.ubid = f.ubid AND f.keygrip = ?1", &ctx->select_stmt); if (!err) err = run_sql_bind_blob (ctx->select_stmt, 1, desc[descidx].u.grip, KEYGRIP_LEN); break; case KEYDB_SEARCH_MODE_UBID: if (!ctx->select_stmt) err = run_sql_prepare ("SELECT ubid, type, keyblob" " FROM pubkey" " WHERE ubid = ?1", &ctx->select_stmt); if (!err) err = run_sql_bind_blob (ctx->select_stmt, 1, desc[descidx].u.ubid, UBID_LEN); break; case KEYDB_SEARCH_MODE_FIRST: if (!ctx->select_stmt) err = run_sql_prepare ("SELECT ubid, type, keyblob" " FROM pubkey ORDER by ubid", &ctx->select_stmt); break; case KEYDB_SEARCH_MODE_NEXT: err = gpg_error (GPG_ERR_INTERNAL); break; default: err = gpg_error (GPG_ERR_INV_VALUE); break; } leave: return err; } /* Search for the keys described by (DESC,NDESC) and return them to * the caller. BACKEND_HD is the handle for this backend and REQUEST * is the current database request object. */ gpg_error_t be_sqlite_search (ctrl_t ctrl, backend_handle_t backend_hd, db_request_t request, KEYDB_SEARCH_DESC *desc, unsigned int ndesc) { gpg_error_t err; db_request_part_t part; be_sqlite_local_t ctx; log_assert (backend_hd && backend_hd->db_type == DB_TYPE_SQLITE); log_assert (request); acquire_mutex (); /* Find the specific request part or allocate it. */ err = be_find_request_part (backend_hd, request, &part); if (err) goto leave; ctx = part->besqlite; if (!desc) { /* Reset */ ctx->select_done = 0; ctx->select_eof = 0; err = 0; goto leave; } if (ctx->select_eof) { /* Still in EOF state. */ err = gpg_error (GPG_ERR_EOF); goto leave; } if (!ctx->select_done) { /* Initial search - run the select. */ err = run_select_statement (ctx, desc, ndesc); if (err) goto leave; ctx->select_done = 1; } show_sqlstmt (ctx->select_stmt); /* SQL select succeeded - get the first or next row. */ err = run_sql_step_for_select (ctx->select_stmt); if (gpg_err_code (err) == GPG_ERR_SQL_ROW) { int n; const void *ubid, *keyblob; size_t keybloblen; enum pubkey_types pubkey_type; ubid = sqlite3_column_blob (ctx->select_stmt, 0); n = sqlite3_column_bytes (ctx->select_stmt, 0); if (!ubid || n < 0) { if (!ubid && sqlite3_errcode (database_hd) == SQLITE_NOMEM) err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM)); else err = gpg_error (GPG_ERR_DB_CORRUPTED); show_sqlstmt (ctx->select_stmt); log_error ("error in returned SQL column UBID: No column (n=%d)\n",n); goto leave; } if (n != UBID_LEN) { show_sqlstmt (ctx->select_stmt); log_error ("error in returned SQL column UBID: Bad value (n=%d)\n",n); err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } n = sqlite3_column_int (ctx->select_stmt, 1); if (!n && sqlite3_errcode (database_hd) == SQLITE_NOMEM) { err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM)); show_sqlstmt (ctx->select_stmt); log_error ("error in returned SQL column TYPE: %s)\n", gpg_strerror (err)); goto leave; } pubkey_type = n; keyblob = sqlite3_column_blob (ctx->select_stmt, 2); n = sqlite3_column_bytes (ctx->select_stmt, 2); if (!keyblob || n < 0) { if (!keyblob && sqlite3_errcode (database_hd) == SQLITE_NOMEM) err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM)); else err = gpg_error (GPG_ERR_DB_CORRUPTED); show_sqlstmt (ctx->select_stmt); log_error ("error in returned SQL column KEYBLOB: %s\n", gpg_strerror (err)); goto leave; } keybloblen = n; err = be_return_pubkey (ctrl, keyblob, keybloblen, pubkey_type, ubid); if (!err) be_cache_pubkey (ctrl, ubid, keyblob, keybloblen, pubkey_type); } else if (gpg_err_code (err) == GPG_ERR_SQL_DONE) { /* FIXME: Move on to the next description index. */ err = gpg_error (GPG_ERR_EOF); ctx->select_eof = 1; } else { log_assert (err); } leave: release_mutex (); return err; } /* Helper for be_sqlite_store to update or insert a row in the pubkey * table. */ static gpg_error_t store_into_pubkey (enum kbxd_store_modes mode, enum pubkey_types pktype, const unsigned char *ubid, const void *blob, size_t bloblen) { gpg_error_t err; const char *sqlstr; sqlite3_stmt *stmt = NULL; if (mode == KBXD_STORE_UPDATE) sqlstr = ("UPDATE pubkey set keyblob = :3, type = :2 WHERE ubid = :1"); else if (mode == KBXD_STORE_INSERT) sqlstr = ("INSERT INTO pubkey(ubid,type,keyblob) VALUES(:1,:2,:3)"); else /* Auto */ sqlstr = ("INSERT OR REPLACE INTO pubkey(ubid,type,keyblob)" " VALUES(:1,:2,:3)"); err = run_sql_prepare (sqlstr, &stmt); if (err) goto leave; err = run_sql_bind_blob (stmt, 1, ubid, UBID_LEN); if (err) goto leave; err = run_sql_bind_int (stmt, 2, (int)pktype); if (err) goto leave; err = run_sql_bind_blob (stmt, 3, blob, bloblen); if (err) goto leave; err = run_sql_step (stmt); leave: if (stmt) sqlite3_finalize (stmt); return err; } /* Helper for be_sqlite_store to update or insert a row in the * fingerprint table. */ static gpg_error_t store_into_fingerprint (const unsigned char *ubid, int subkey, const unsigned char *keygrip, sqlite3_int64 kid, const unsigned char *fpr, int fprlen) { gpg_error_t err; const char *sqlstr; sqlite3_stmt *stmt = NULL; sqlstr = ("INSERT OR REPLACE INTO fingerprint(fpr,kid,keygrip,subkey,ubid)" " VALUES(:1,:2,:3,:4,:5)"); err = run_sql_prepare (sqlstr, &stmt); if (err) goto leave; err = run_sql_bind_blob (stmt, 1, fpr, fprlen); if (err) goto leave; err = run_sql_bind_int64 (stmt, 2, kid); if (err) goto leave; err = run_sql_bind_blob (stmt, 3, keygrip, KEYGRIP_LEN); if (err) goto leave; err = run_sql_bind_int (stmt, 4, subkey); if (err) goto leave; err = run_sql_bind_blob (stmt, 5, ubid, UBID_LEN); if (err) goto leave; err = run_sql_step (stmt); leave: if (stmt) sqlite3_finalize (stmt); return err; } /* Helper for be_sqlite_store to update or insert a row in the * userid table. */ static gpg_error_t store_into_userid (const unsigned char *ubid, enum pubkey_types pktype, const char *uid) { gpg_error_t err; const char *sqlstr; sqlite3_stmt *stmt = NULL; char *addrspec = NULL; sqlstr = ("INSERT OR REPLACE INTO userid(uid,addrspec,type,ubid)" " VALUES(:1,:2,:3,:4)"); err = run_sql_prepare (sqlstr, &stmt); if (err) goto leave; err = run_sql_bind_text (stmt, 1, uid); if (err) goto leave; addrspec = mailbox_from_userid (uid, 0); err = run_sql_bind_text (stmt, 2, addrspec); if (err) goto leave; err = run_sql_bind_int (stmt, 3, pktype); if (err) goto leave; err = run_sql_bind_blob (stmt, 4, ubid, UBID_LEN); if (err) goto leave; err = run_sql_step (stmt); leave: if (stmt) sqlite3_finalize (stmt); xfree (addrspec); return err; } /* Store (BLOB,BLOBLEN) into the database. UBID is the UBID macthing * that blob. BACKEND_HD is the handle for this backend and REQUEST * is the current database request object. MODE is the store * mode. */ gpg_error_t be_sqlite_store (ctrl_t ctrl, backend_handle_t backend_hd, db_request_t request, enum kbxd_store_modes mode, enum pubkey_types pktype, const unsigned char *ubid, const void *blob, size_t bloblen) { gpg_error_t err; db_request_part_t part; /* be_sqlite_local_t ctx; */ int got_mutex = 0; int in_transaction = 0; int info_valid = 0; struct _keybox_openpgp_info info; struct _keybox_openpgp_key_info *kinfo; (void)ctrl; log_assert (backend_hd && backend_hd->db_type == DB_TYPE_SQLITE); log_assert (request); /* Fixme: The code below is duplicated in be_ubid_from_blob - we * should have only one function and pass the passed info around * with the BLOB. */ if (be_is_x509_blob (blob, bloblen)) { /* The UBID is also our fingerprint. */ /* FIXME: Extract keygrip and KID. */ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); goto leave; } else { err = _keybox_parse_openpgp (blob, bloblen, NULL, &info); if (err) { log_info ("error parsing OpenPGP blob: %s\n", gpg_strerror (err)); err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE); goto leave; } info_valid = 1; log_assert (pktype == PUBKEY_TYPE_OPGP); log_assert (info.primary.fprlen >= 20); log_assert (!memcmp (ubid, info.primary.fpr, UBID_LEN)); } acquire_mutex (); got_mutex = 1; /* Find the specific request part or allocate it. */ err = be_find_request_part (backend_hd, request, &part); if (err) goto leave; /* ctx = part->besqlite; */ err = run_sql_statement ("begin transaction"); if (err) goto leave; in_transaction = 1; err = store_into_pubkey (mode, pktype, ubid, blob, bloblen); if (err) goto leave; /* Delete all related rows so that we can freshly add possibly added * or changed user ids and subkeys. */ err = run_sql_statement_bind_ubid ("DELETE FROM fingerprint WHERE ubid = :1", ubid); if (err) goto leave; err = run_sql_statement_bind_ubid ("DELETE FROM userid WHERE ubid = :1", ubid); if (err) goto leave; kinfo = &info.primary; err = store_into_fingerprint (ubid, 0, kinfo->grip, kid_from_mem (kinfo->keyid), kinfo->fpr, kinfo->fprlen); if (err) goto leave; if (info.nsubkeys) { int subkey = 1; for (kinfo = &info.subkeys; kinfo; kinfo = kinfo->next, subkey++) { err = store_into_fingerprint (ubid, subkey, kinfo->grip, kid_from_mem (kinfo->keyid), kinfo->fpr, kinfo->fprlen); if (err) goto leave; } } if (info.nuids) { struct _keybox_openpgp_uid_info *u; u = &info.uids; do { log_assert (u->off <= bloblen); log_assert (u->off + u->len <= bloblen); { char *uid = xtrymalloc (u->len + 1); if (!uid) { err = gpg_error_from_syserror (); goto leave; } memcpy (uid, (const unsigned char *)blob + u->off, u->len); uid[u->len] = 0; /* Note that we ignore embedded zeros in the user id; this * is what we do all over the place. */ err = store_into_userid (ubid, pktype, uid); xfree (uid); } if (err) goto leave; u = u->next; } while (u); } leave: if (in_transaction && !err) err = run_sql_statement ("commit"); else if (in_transaction) { if (run_sql_statement ("rollback")) log_error ("Warning: database rollback failed - should not happen!\n"); } if (got_mutex) release_mutex (); if (info_valid) _keybox_destroy_openpgp_info (&info); return err; } /* Delete the blob specified by UBID from the database. BACKEND_HD is * the handle for this backend and REQUEST is the current database * request object. */ gpg_error_t be_sqlite_delete (ctrl_t ctrl, backend_handle_t backend_hd, db_request_t request, const unsigned char *ubid) { gpg_error_t err; db_request_part_t part; /* be_sqlite_local_t ctx; */ sqlite3_stmt *stmt = NULL; int in_transaction = 0; (void)ctrl; log_assert (backend_hd && backend_hd->db_type == DB_TYPE_SQLITE); log_assert (request); acquire_mutex (); /* Find the specific request part or allocate it. */ err = be_find_request_part (backend_hd, request, &part); if (err) goto leave; /* ctx = part->besqlite; */ err = run_sql_statement ("begin transaction"); if (err) goto leave; in_transaction = 1; err = run_sql_statement_bind_ubid ("DELETE from userid WHERE ubid = :1", ubid); if (!err) err = run_sql_statement_bind_ubid ("DELETE from fingerprint WHERE ubid = :1", ubid); if (!err) err = run_sql_statement_bind_ubid ("DELETE from pubkey WHERE ubid = :1", ubid); leave: if (stmt) sqlite3_finalize (stmt); if (in_transaction && !err) err = run_sql_statement ("commit"); else if (in_transaction) { if (run_sql_statement ("rollback")) log_error ("Warning: database rollback failed - should not happen!\n"); } release_mutex (); return err; }