diff --git a/kbx/backend-sqlite.c b/kbx/backend-sqlite.c index 2b5da9134..ab68163ab 100644 --- a/kbx/backend-sqlite.c +++ b/kbx/backend-sqlite.c @@ -1,1540 +1,1543 @@ /* backend-sqlite.c - SQLite based backend for keyboxd * Copyright (C) 2019, 2020 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 flags active when the select was first done. */ unsigned int filter_opgp : 1; unsigned int filter_x509 : 1; /* 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 truncated primary-fpr */ "ubid BLOB NOT NULL PRIMARY KEY," /* The type of the public key: 1 = openpgp, 2 = X.509. */ "type INTEGER NOT NULL," /* The Ephemeral flag as used by gpgsm. Values: 0 or 1. */ "ephemeral INTEGER NOT NULL DEFAULT 0," /* The Revoked flag as set by gpgsm. Values: 0 or 1. */ "revoked INTEGER NOT NULL DEFAULT 0," /* 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 (" /* The fingerprint, for OpenPGP either 20 octets or 32 octets; * for X.509 it is the same as the UBID. */ "fpr BLOB NOT NULL PRIMARY KEY," /* The long keyid as a 64 bit blob. */ "kid BLOB NOT NULL," /* The keygrip for this key. */ "keygrip BLOB NOT NULL," /* 0 = primary or X.509, > 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 - for X.509 the Subject or altSubject. */ "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)" }, /* Table to allow fast access via s/n + issuer DN (X.509 only). */ { "CREATE TABLE IF NOT EXISTS issuer (" /* The hex encoded S/N. */ "sn TEXT NOT NULL," /* The RFC2253 issuer DN. */ "dn TEXT NOT NULL," /* The Unique Blob ID (usually the truncated fingerprint). */ "ubid BLOB NOT NULL REFERENCES pubkey" ")" }, { "CREATE INDEX IF NOT EXISTS issueridx1 on issuer (dn)" } }; /* 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 8 byte blob. This * function converts it from the usual u32[2] array. BUFFER is a * caller provided buffer of at least 8 bytes; a pointer to that * buffer is the return value. */ static GPGRT_INLINE unsigned char * kid_from_u32 (u32 *keyid, unsigned char *buffer) { buffer[0] = keyid[0] >> 24; buffer[1] = keyid[0] >> 16; buffer[2] = keyid[0] >> 8; buffer[3] = keyid[0]; buffer[4] = keyid[1] >> 24; buffer[5] = keyid[1] >> 16; buffer[6] = keyid[1] >> 8; buffer[7] = keyid[1]; return buffer; } /* 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. If * EXTRA is not NULL that part is appended to the SQL statement. */ static gpg_error_t run_sql_prepare (const char *sqlstr, const char *extra, sqlite3_stmt **r_stmt) { gpg_error_t err; int res; char *buffer = NULL; if (extra) { buffer = strconcat (sqlstr, extra, NULL); if (!buffer) return gpg_error_from_syserror (); sqlstr = buffer; } res = sqlite3_prepare_v2 (database_hd, sqlstr, -1, r_stmt, NULL); if (res) err = diag_prepare_err (res, sqlstr); else err = 0; xfree (buffer); 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 a string parameter to a statement. VALUE is allowed * to be NULL to bind NULL. */ static gpg_error_t run_sql_bind_ntext (sqlite3_stmt *stmt, int no, const char *value, size_t valuelen) { gpg_error_t err; int res; res = sqlite3_bind_text (stmt, no, value, value? valuelen: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. */ static gpg_error_t run_sql_bind_text (sqlite3_stmt *stmt, int no, const char *value) { return run_sql_bind_ntext (stmt, no, value, value? strlen (value):0); } /* 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, NULL, &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 initialize 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 (ctrl_t ctrl, be_sqlite_local_t ctx, KEYDB_SEARCH_DESC *desc, unsigned int ndesc) { gpg_error_t err = 0; unsigned int descidx; const char *extra = NULL; unsigned char kidbuf[8]; 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; } else if (ctx->filter_opgp != ctrl->filter_opgp || ctx->filter_x509 != ctrl->filter_x509) { /* The filter flags changed, thus we can't reuse the statement. */ sqlite3_finalize (ctx->select_stmt); ctx->select_stmt = NULL; } ctx->select_mode = desc[descidx].mode; ctx->filter_opgp = ctrl->filter_opgp; ctx->filter_x509 = ctrl->filter_x509; /* Prepare the select and bind the parameters. */ if (ctx->select_stmt) { err = run_sql_reset (ctx->select_stmt); if (err) goto leave; } else { if (ctx->filter_opgp && ctx->filter_x509) extra = " AND ( p.type = 1 OR p.type = 2 )"; else if (ctx->filter_opgp && !ctx->filter_x509) extra = " AND p.type = 1"; else if (!ctx->filter_opgp && ctx->filter_x509) extra = " AND p.type = 2"; 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.ephemeral, p.revoked," " p.keyblob" " FROM pubkey as p, userid as u" " WHERE p.ubid = u.ubid AND u.uid = ?1", extra, &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.ephemeral, p.revoked," " p.keyblob" " FROM pubkey as p, userid as u" " WHERE p.ubid = u.ubid AND u.addrspec = ?1", extra, &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.ephemeral, p.revoked," " p.keyblob" " FROM pubkey as p, userid as u" " WHERE p.ubid = u.ubid AND u.addrspec LIKE ?1", extra, &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.ephemeral, p.revoked," " p.keyblob" " FROM pubkey as p, userid as u" " WHERE p.ubid = u.ubid AND u.uid LIKE ?1", extra, &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: if (!ctx->select_stmt) err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral, p.revoked," " p.keyblob" " FROM pubkey as p, issuer as i" " WHERE p.ubid = i.ubid" " AND i.dn = $1", extra, &ctx->select_stmt); if (!err) err = run_sql_bind_text (ctx->select_stmt, 1, desc[descidx].u.name); break; case KEYDB_SEARCH_MODE_ISSUER_SN: if (!desc[descidx].snhex) { /* We should never get a binary S/N here. */ log_debug ("%s: issuer_sn with binary s/n\n", __func__); err = gpg_error (GPG_ERR_INTERNAL); } else { if (!ctx->select_stmt) err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral," " p.revoked, p.keyblob" " FROM pubkey as p, issuer as i" " WHERE p.ubid = i.ubid" " AND i.sn = $1 AND i.dn = $2", extra, &ctx->select_stmt); if (!err) err = run_sql_bind_ntext (ctx->select_stmt, 1, desc[descidx].sn, desc[descidx].snlen); if (!err) err = run_sql_bind_text (ctx->select_stmt, 2, desc[descidx].u.name); } 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 = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral, p.revoked," " p.keyblob" " FROM pubkey as p, userid as u" " WHERE p.ubid = u.ubid" " AND u.uid = $1", extra, &ctx->select_stmt); if (!err) err = run_sql_bind_text (ctx->select_stmt, 1, desc[descidx].u.name); break; case KEYDB_SEARCH_MODE_SHORT_KID: if (!ctx->select_stmt) err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral," " p.revoked, p.keyblob" " FROM pubkey as p, fingerprint as f" " WHERE p.ubid = f.ubid AND" " substr(f.kid,5) = ?1", extra, &ctx->select_stmt); if (!err) err = run_sql_bind_blob (ctx->select_stmt, 1, kid_from_u32 (desc[descidx].u.kid, kidbuf)+4, 4); break; case KEYDB_SEARCH_MODE_LONG_KID: if (!ctx->select_stmt) err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral," " p.revoked, p.keyblob" " FROM pubkey as p, fingerprint as f" " WHERE p.ubid = f.ubid AND f.kid = ?1", extra, &ctx->select_stmt); if (!err) err = run_sql_bind_blob (ctx->select_stmt, 1, kid_from_u32 (desc[descidx].u.kid, kidbuf), 8); break; case KEYDB_SEARCH_MODE_FPR: if (!ctx->select_stmt) err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral," " p.revoked, p.keyblob" " FROM pubkey as p, fingerprint as f" " WHERE p.ubid = f.ubid AND f.fpr = ?1", extra, &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.ephemeral, p.revoked," " p.keyblob" " FROM pubkey as p, fingerprint as f" " WHERE p.ubid = f.ubid AND f.keygrip = ?1", extra, &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, ephemeral, revoked, keyblob" " FROM pubkey as p" " WHERE ubid = ?1", extra, &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) { if (ctx->filter_opgp && ctx->filter_x509) extra = " WHERE ( p.type = 1 OR p.type = 2 ) ORDER by ubid"; else if (ctx->filter_opgp && !ctx->filter_x509) extra = " WHERE p.type = 1 ORDER by ubid"; else if (!ctx->filter_opgp && ctx->filter_x509) extra = " WHERE p.type = 2 ORDER by ubid"; else extra = " ORDER by ubid"; err = run_sql_prepare ("SELECT ubid, type, ephemeral, revoked," " keyblob" " FROM pubkey as p", extra, &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 (ctrl, 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; int is_ephemeral, is_revoked; 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; n = sqlite3_column_int (ctx->select_stmt, 2); 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 EPHEMERAL: %s)\n", gpg_strerror (err)); goto leave; } is_ephemeral = !!n; n = sqlite3_column_int (ctx->select_stmt, 3); 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 REVOKED: %s)\n", gpg_strerror (err)); goto leave; } is_revoked = !!n; keyblob = sqlite3_column_blob (ctx->select_stmt, 4); n = sqlite3_column_bytes (ctx->select_stmt, 4); 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, is_ephemeral, is_revoked); 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, NULL, &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, const unsigned char *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, NULL, &stmt); if (err) goto leave; err = run_sql_bind_blob (stmt, 1, fpr, fprlen); if (err) goto leave; err = run_sql_bind_blob (stmt, 2, kid, 8); 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. If OVERRIDE_MBOX is set, that value is used instead of a * value extracted from UID. */ static gpg_error_t store_into_userid (const unsigned char *ubid, enum pubkey_types pktype, const char *uid, const char *override_mbox) { 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, NULL, &stmt); if (err) goto leave; err = run_sql_bind_text (stmt, 1, uid); if (err) goto leave; if (override_mbox) err = run_sql_bind_text (stmt, 2, override_mbox); else { 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; } /* Helper for be_sqlite_store to update or insert a row in the * issuer table. */ static gpg_error_t store_into_issuer (const unsigned char *ubid, const char *sn, const char *issuer) { gpg_error_t err; const char *sqlstr; sqlite3_stmt *stmt = NULL; char *addrspec = NULL; sqlstr = ("INSERT OR REPLACE INTO issuer(sn,dn,ubid)" " VALUES(:1,:2,:3)"); err = run_sql_prepare (sqlstr, NULL, &stmt); if (err) goto leave; err = run_sql_bind_text (stmt, 1, sn); if (err) goto leave; err = run_sql_bind_text (stmt, 2, issuer); if (err) goto leave; err = run_sql_bind_blob (stmt, 3, 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 matching * 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; ksba_cert_t cert = NULL; char *sn = NULL; char *dn = NULL; char *kludge_mbox = NULL; (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)) { log_assert (pktype == PUBKEY_TYPE_X509); err = ksba_cert_new (&cert); if (err) goto leave; err = ksba_cert_init_from_mem (cert, blob, bloblen); if (err) 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; if (cert) { err = run_sql_statement_bind_ubid ("DELETE FROM issuer WHERE ubid = :1", ubid); if (err) goto leave; } if (cert) /* X.509 */ { unsigned char grip[KEYGRIP_LEN]; int idx; err = be_get_x509_keygrip (cert, grip); if (err) goto leave; /* Note that for X.509 the UBID is also the fingerprint. */ err = store_into_fingerprint (ubid, 0, grip, ubid+12, ubid, UBID_LEN); if (err) goto leave; /* Now the issuer. */ sn = be_get_x509_serial (cert); if (!sn) { err = gpg_error_from_syserror (); goto leave; } dn = ksba_cert_get_issuer (cert, 0); if (!dn) { err = gpg_error_from_syserror (); goto leave; } err = store_into_issuer (ubid, sn, dn); if (err) goto leave; /* Loop over the subject and alternate subjects. */ for (idx=0; (xfree (dn), dn = ksba_cert_get_subject (cert, idx)); idx++) { /* In the case that the same email address is in the * subject DN as well as in an alternate subject name * we avoid printing it a second time. */ if (kludge_mbox && !strcmp (kludge_mbox, dn)) continue; err = store_into_userid (ubid, PUBKEY_TYPE_X509, dn, NULL); if (err) goto leave; if (!idx) { kludge_mbox = _keybox_x509_email_kludge (dn); if (kludge_mbox) { err = store_into_userid (ubid, PUBKEY_TYPE_X509, dn, kludge_mbox); if (err) goto leave; } } } /* end loop over the subjects. */ } else /* OpenPGP */ { struct _keybox_openpgp_key_info *kinfo; kinfo = &info.primary; err = store_into_fingerprint (ubid, 0, kinfo->grip, 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, 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, NULL); 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); if (cert) ksba_cert_release (cert); ksba_free (dn); xfree (sn); xfree (kludge_mbox); 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 issuer 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; } diff --git a/sm/delete.c b/sm/delete.c index 511ffb9b2..ccd389313 100644 --- a/sm/delete.c +++ b/sm/delete.c @@ -1,180 +1,180 @@ /* delete.c - Delete certificates from the keybox. * Copyright (C) 2002, 2009 Free Software Foundation, Inc. * * 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 . */ #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "../common/i18n.h" /* Delete a certificate or an secret key from a key database. */ static int delete_one (ctrl_t ctrl, const char *username) { int rc = 0; KEYDB_SEARCH_DESC desc; KEYDB_HANDLE kh = NULL; ksba_cert_t cert = NULL; int duplicates = 0; int is_ephem = 0; rc = classify_user_id (username, &desc, 0); if (rc) { log_error (_("certificate '%s' not found: %s\n"), username, gpg_strerror (rc)); gpgsm_status2 (ctrl, STATUS_DELETE_PROBLEM, "1", NULL); goto leave; } kh = keydb_new (ctrl); if (!kh) { log_error ("keydb_new failed\n"); goto leave; } /* If the key is specified in a unique way, include ephemeral keys in the search. */ if ( desc.mode == KEYDB_SEARCH_MODE_FPR || desc.mode == KEYDB_SEARCH_MODE_KEYGRIP ) { is_ephem = 1; keydb_set_ephemeral (kh, 1); } rc = keydb_search (ctrl, kh, &desc, 1); if (!rc) rc = keydb_get_cert (kh, &cert); if (!rc && !is_ephem) { unsigned char fpr[20]; gpgsm_get_fingerprint (cert, 0, fpr, NULL); next_ambigious: rc = keydb_search (ctrl, kh, &desc, 1); if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) rc = 0; else if (!rc) { ksba_cert_t cert2 = NULL; unsigned char fpr2[20]; /* We ignore all duplicated certificates which might have been inserted due to program bugs. */ if (!keydb_get_cert (kh, &cert2)) { gpgsm_get_fingerprint (cert2, 0, fpr2, NULL); ksba_cert_release (cert2); if (!memcmp (fpr, fpr2, 20)) { duplicates++; goto next_ambigious; } } rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME); } } if (rc) { if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) rc = gpg_error (GPG_ERR_NO_PUBKEY); log_error (_("certificate '%s' not found: %s\n"), username, gpg_strerror (rc)); gpgsm_status2 (ctrl, STATUS_DELETE_PROBLEM, "3", NULL); goto leave; } - /* We need to search again to get back to the right position. Neo + /* We need to search again to get back to the right position. Note * that the lock is kept until the KH is released. */ rc = keydb_lock (kh); if (rc) { log_error (_("error locking keybox: %s\n"), gpg_strerror (rc)); goto leave; } do { keydb_search_reset (kh); rc = keydb_search (ctrl, kh, &desc, 1); if (rc) { log_error ("problem re-searching certificate: %s\n", gpg_strerror (rc)); goto leave; } rc = keydb_delete (kh); if (rc) goto leave; if (opt.verbose) { if (duplicates) log_info (_("duplicated certificate '%s' deleted\n"), username); else log_info (_("certificate '%s' deleted\n"), username); } } while (duplicates--); leave: keydb_release (kh); ksba_cert_release (cert); return rc; } /* Delete the certificates specified by NAMES. */ int gpgsm_delete (ctrl_t ctrl, strlist_t names) { int rc; if (!names) { log_error ("nothing to delete\n"); return gpg_error (GPG_ERR_NO_DATA); } for (; names; names=names->next ) { rc = delete_one (ctrl, names->d); if (rc) { log_error (_("deleting certificate \"%s\" failed: %s\n"), names->d, gpg_strerror (rc) ); return rc; } } return 0; } diff --git a/sm/keydb.c b/sm/keydb.c index 47f8a0bce..f7d815e65 100644 --- a/sm/keydb.c +++ b/sm/keydb.c @@ -1,2126 +1,2150 @@ /* keydb.c - key database dispatcher * Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc. - * Copyright (C) 2014 g10 Code GmbH + * Copyright (C) 2014, 2020 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 . */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include "../kbx/keybox.h" #include "keydb.h" #include "../common/i18n.h" #include "../common/asshelp.h" #include "../kbx/kbx-client-util.h" typedef enum { KEYDB_RESOURCE_TYPE_NONE = 0, KEYDB_RESOURCE_TYPE_KEYBOX } KeydbResourceType; #define MAX_KEYDB_RESOURCES 20 struct resource_item { KeydbResourceType type; union { KEYBOX_HANDLE kr; } u; void *token; }; /* Data used to keep track of keybox daemon sessions. This allows us * to use several sessions with the keyboxd and also to re-use already * established sessions. Note that gpgdm.h defines the type * keydb_local_t for this structure. */ struct keydb_local_s { /* Link to other keyboxd contexts which are used simultaneously. */ struct keydb_local_s *next; /* The active Assuan context. */ assuan_context_t ctx; /* The client data helper context. */ kbx_client_data_t kcd; /* I/O buffer with the last search result or NULL. Used if * D-lines are used to convey the keyblocks. */ struct { char *buf; size_t len; } search_result; /* This flag set while an operation is running on this context. */ unsigned int is_active : 1; /* Flag indicating that a search reset is required. */ unsigned int need_search_reset : 1; }; static struct resource_item all_resources[MAX_KEYDB_RESOURCES]; static int used_resources; /* Whether we have successfully registered any resource. */ static int any_registered; /* Number of active handles. */ static int active_handles; struct keydb_handle { /* CTRL object passed to keydb_new. */ ctrl_t ctrl; /* If set the keyboxdd is used instead of the local files. */ int use_keyboxd; /* BEGIN USE_KEYBOXD */ /* (These fields are only valid if USE_KEYBOXD is set.) */ /* Connection info which also keeps the local state. (This points * into the CTRL->keybox_local list.) */ keydb_local_t kbl; /* Various flags. */ unsigned int last_ubid_valid:1; + unsigned int last_is_ephemeral; /* Last found key is ephemeral. */ /* The UBID of the last returned keyblock. */ unsigned char last_ubid[UBID_LEN]; /* END USE_KEYBOXD */ /* BEGIN !USE_KEYBOXD */ /* (The remaining fields are only valid if USE_KEYBOXD is cleared.) */ /* If this flag is set the resources is locked. */ int locked; /* If this flag is set a lock will only be released by * keydb_release. */ int keep_lock; int found; int saved_found; int current; int is_ephemeral; int used; /* items in active */ struct resource_item active[MAX_KEYDB_RESOURCES]; /* END !USE_KEYBOXD */ }; static int lock_all (KEYDB_HANDLE hd); static void unlock_all (KEYDB_HANDLE hd); /* Deinitialize all session resources pertaining to the keyboxd. */ void gpgsm_keydb_deinit_session_data (ctrl_t ctrl) { keydb_local_t kbl; while ((kbl = ctrl->keydb_local)) { ctrl->keydb_local = kbl->next; if (kbl->is_active) log_error ("oops: trying to cleanup an active keydb context\n"); else { kbx_client_data_release (kbl->kcd); kbl->kcd = NULL; assuan_release (kbl->ctx); kbl->ctx = NULL; } xfree (kbl); } } static void try_make_homedir (const char *fname) { const char *defhome = standard_homedir (); /* Create the directory only if the supplied directory name is the same as the default one. This way we avoid to create arbitrary directories when a non-default home directory is used. To cope with HOME, we do compare only the suffix if we see that the default homedir does start with a tilde. */ if ( opt.dry_run || opt.no_homedir_creation ) return; if ( #ifdef HAVE_W32_SYSTEM ( !compare_filenames (fname, defhome) ) #else ( *defhome == '~' && (strlen(fname) >= strlen (defhome+1) && !strcmp(fname+strlen(fname)-strlen(defhome+1), defhome+1 ) )) || (*defhome != '~' && !compare_filenames( fname, defhome ) ) #endif ) { if (gnupg_mkdir (fname, "-rwx")) log_info (_("can't create directory '%s': %s\n"), fname, strerror(errno) ); else if (!opt.quiet ) log_info (_("directory '%s' created\n"), fname); } } /* Handle the creation of a keybox if it does not yet exist. Take into account that other processes might have the keybox already locked. This lock check does not work if the directory itself is not yet available. If R_CREATED is not NULL it will be set to true if the function created a new keybox. */ static gpg_error_t maybe_create_keybox (char *filename, int force, int *r_created) { dotlock_t lockhd = NULL; FILE *fp; int rc; mode_t oldmask; char *last_slash_in_filename; int save_slash; if (r_created) *r_created = 0; /* A quick test whether the filename already exists. */ if (!access (filename, F_OK)) return !access (filename, R_OK)? 0 : gpg_error (GPG_ERR_EACCES); /* If we don't want to create a new file at all, there is no need to go any further - bail out right here. */ if (!force) return gpg_error (GPG_ERR_ENOENT); /* First of all we try to create the home directory. Note, that we don't do any locking here because any sane application of gpg would create the home directory by itself and not rely on gpg's tricky auto-creation which is anyway only done for some home directory name patterns. */ last_slash_in_filename = strrchr (filename, DIRSEP_C); #if HAVE_W32_SYSTEM { /* Windows may either have a slash or a backslash. Take care of it. */ char *p = strrchr (filename, '/'); if (!last_slash_in_filename || p > last_slash_in_filename) last_slash_in_filename = p; } #endif /*HAVE_W32_SYSTEM*/ if (!last_slash_in_filename) return gpg_error (GPG_ERR_ENOENT); /* No slash at all - should not happen though. */ save_slash = *last_slash_in_filename; *last_slash_in_filename = 0; if (access(filename, F_OK)) { static int tried; if (!tried) { tried = 1; try_make_homedir (filename); } if (access (filename, F_OK)) { rc = gpg_error_from_syserror (); *last_slash_in_filename = save_slash; goto leave; } } *last_slash_in_filename = save_slash; /* To avoid races with other instances of gpg trying to create or update the keybox (it is removed during an update for a short time), we do the next stuff in a locked state. */ lockhd = dotlock_create (filename, 0); if (!lockhd) { /* 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 keyring is not really useful at all. */ if (opt.verbose) log_info ("can't allocate lock for '%s'\n", filename ); if (!force) return gpg_error (GPG_ERR_ENOENT); else return gpg_error (GPG_ERR_GENERAL); } if ( dotlock_take (lockhd, -1) ) { /* This is something bad. Probably a stale lockfile. */ log_info ("can't lock '%s'\n", filename); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } /* Now the real test while we are locked. */ if (!access(filename, F_OK)) { rc = 0; /* Okay, we may access the file now. */ goto leave; } /* The file does not yet exist, create it now. */ oldmask = umask (077); fp = fopen (filename, "wb"); if (!fp) { rc = gpg_error_from_syserror (); umask (oldmask); log_error (_("error creating keybox '%s': %s\n"), filename, gpg_strerror (rc)); goto leave; } umask (oldmask); /* Make sure that at least one record is in a new keybox file, so that the detection magic for OpenPGP keyboxes works the next time it is used. */ rc = _keybox_write_header_blob (fp, NULL, 0); if (rc) { fclose (fp); log_error (_("error creating keybox '%s': %s\n"), filename, gpg_strerror (rc)); goto leave; } if (!opt.quiet) log_info (_("keybox '%s' created\n"), filename); if (r_created) *r_created = 1; fclose (fp); rc = 0; leave: if (lockhd) { dotlock_release (lockhd); dotlock_destroy (lockhd); } return rc; } /* * Register a resource (which currently may only be a keybox file). * The first keybox which is added by this function is created if it * does not exist. If AUTO_CREATED is not NULL it will be set to true * if the function has created a new keybox. */ gpg_error_t keydb_add_resource (ctrl_t ctrl, const char *url, int force, int *auto_created) { const char *resname = url; char *filename = NULL; gpg_error_t err = 0; KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE; if (auto_created) *auto_created = 0; /* Do we have an URL? gnupg-kbx:filename := this is a plain keybox filename := See what it is, but create as plain keybox. */ if (strlen (resname) > 10) { if (!strncmp (resname, "gnupg-kbx:", 10) ) { rt = KEYDB_RESOURCE_TYPE_KEYBOX; resname += 10; } #if !defined(HAVE_DRIVE_LETTERS) && !defined(__riscos__) else if (strchr (resname, ':')) { log_error ("invalid key resource URL '%s'\n", url ); err = gpg_error (GPG_ERR_GENERAL); goto leave; } #endif /* !HAVE_DRIVE_LETTERS && !__riscos__ */ } if (*resname != DIRSEP_C ) { /* do tilde expansion etc */ if (strchr(resname, DIRSEP_C) ) filename = make_filename (resname, NULL); else filename = make_filename (gnupg_homedir (), resname, NULL); } else filename = xstrdup (resname); if (!force) force = !any_registered; /* see whether we can determine the filetype */ if (rt == KEYDB_RESOURCE_TYPE_NONE) { FILE *fp = fopen( filename, "rb" ); if (fp) { u32 magic; /* FIXME: check for the keybox magic */ if (fread (&magic, 4, 1, fp) == 1 ) { if (magic == 0x13579ace || magic == 0xce9a5713) ; /* GDBM magic - no more support */ else rt = KEYDB_RESOURCE_TYPE_KEYBOX; } else /* maybe empty: assume keybox */ rt = KEYDB_RESOURCE_TYPE_KEYBOX; fclose (fp); } else /* no file yet: create keybox */ rt = KEYDB_RESOURCE_TYPE_KEYBOX; } switch (rt) { case KEYDB_RESOURCE_TYPE_NONE: log_error ("unknown type of key resource '%s'\n", url ); err = gpg_error (GPG_ERR_GENERAL); goto leave; case KEYDB_RESOURCE_TYPE_KEYBOX: err = maybe_create_keybox (filename, force, auto_created); if (err) goto leave; /* Now register the file */ { void *token; err = keybox_register_file (filename, 0, &token); if (gpg_err_code (err) == GPG_ERR_EEXIST) ; /* Already registered - ignore. */ else if (err) ; /* Other error. */ else if (used_resources >= MAX_KEYDB_RESOURCES) err = gpg_error (GPG_ERR_RESOURCE_LIMIT); else { KEYBOX_HANDLE kbxhd; all_resources[used_resources].type = rt; all_resources[used_resources].u.kr = NULL; /* Not used here */ all_resources[used_resources].token = token; /* Do a compress run if needed and the keybox is not locked. */ kbxhd = keybox_new_x509 (token, 0); if (kbxhd) { if (!keybox_lock (kbxhd, 1, 0)) { keybox_compress (kbxhd); keybox_lock (kbxhd, 0, 0); } keybox_release (kbxhd); } used_resources++; } } break; default: log_error ("resource type of '%s' not supported\n", url); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } /* fixme: check directory permissions and print a warning */ leave: if (err) { log_error ("keyblock resource '%s': %s\n", filename, gpg_strerror (err)); gpgsm_status_with_error (ctrl, STATUS_ERROR, "add_keyblock_resource", err); } else any_registered = 1; xfree (filename); return err; } /* Print a warning if the server's version number is less than our version number. Returns an error code on a connection problem. */ static gpg_error_t warn_version_mismatch (assuan_context_t ctx, const char *servername) { return warn_server_version_mismatch (ctx, servername, 0, gpgsm_status2, NULL, !opt.quiet); } /* Connect to the keybox daemon and launch it if necessary. Handle * the server's initial greeting and set global options. Returns a * new assuan context or an error. */ static gpg_error_t create_new_context (ctrl_t ctrl, assuan_context_t *r_ctx) { gpg_error_t err; assuan_context_t ctx; *r_ctx = NULL; err = start_new_keyboxd (&ctx, GPG_ERR_SOURCE_DEFAULT, opt.keyboxd_program, opt.autostart, opt.verbose, DBG_IPC, NULL, ctrl); if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_KEYBOXD) { static int shown; if (!shown) { shown = 1; log_info (_("no keyboxd running in this session\n")); } } else if (!err && !(err = warn_version_mismatch (ctx, KEYBOXD_NAME))) { /* Place to emit global options. */ } if (err) assuan_release (ctx); else *r_ctx = ctx; return err; } /* Get a context for accessing keyboxd. If no context is available a * new one is created and if necessary keyboxd is started. R_KBL * receives a pointer to the local context object. */ static gpg_error_t open_context (ctrl_t ctrl, keydb_local_t *r_kbl) { gpg_error_t err; keydb_local_t kbl; *r_kbl = NULL; for (;;) { for (kbl = ctrl->keydb_local; kbl && kbl->is_active; kbl = kbl->next) ; if (kbl) { /* Found an inactive keyboxd session - return that. */ log_assert (!kbl->is_active); kbl->is_active = 1; kbl->need_search_reset = 1; *r_kbl = kbl; return 0; } /* None found. Create a new session and retry. */ kbl = xtrycalloc (1, sizeof *kbl); if (!kbl) return gpg_error_from_syserror (); err = create_new_context (ctrl, &kbl->ctx); if (err) { xfree (kbl); return err; } err = kbx_client_data_new (&kbl->kcd, kbl->ctx, 1); if (err) { assuan_release (kbl->ctx); xfree (kbl); return err; } /* For thread-saftey we add it to the list and retry; this is * easier than to employ a lock. */ kbl->next = ctrl->keydb_local; ctrl->keydb_local = kbl; } /*NOTREACHED*/ } KEYDB_HANDLE keydb_new (ctrl_t ctrl) { gpg_error_t err; KEYDB_HANDLE hd; int rc, i, j; if (DBG_CLOCK) log_clock ("%s: enter\n", __func__); hd = xcalloc (1, sizeof *hd); hd->found = -1; hd->saved_found = -1; hd->use_keyboxd = opt.use_keyboxd; hd->ctrl = ctrl; if (hd->use_keyboxd) { err = open_context (ctrl, &hd->kbl); if (err) { log_error (_("error opening key DB: %s\n"), gpg_strerror (err)); xfree (hd); hd = NULL; if (!(rc = gpg_err_code_to_errno (err))) rc = gpg_err_code_to_errno (GPG_ERR_EIO); gpg_err_set_errno (rc); goto leave; } } else /* Use the local keybox. */ { log_assert (used_resources <= MAX_KEYDB_RESOURCES); for (i=j=0; i < used_resources; i++) { switch (all_resources[i].type) { case KEYDB_RESOURCE_TYPE_NONE: /* ignore */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: hd->active[j].type = all_resources[i].type; hd->active[j].token = all_resources[i].token; hd->active[j].u.kr = keybox_new_x509 (all_resources[i].token, 0); if (!hd->active[j].u.kr) { xfree (hd); return NULL; /* fixme: free all previously allocated handles*/ } j++; break; } } hd->used = j; } active_handles++; leave: if (DBG_CLOCK) log_clock ("%s: leave (hd=%p)\n", __func__, hd); return hd; } void keydb_release (KEYDB_HANDLE hd) { keydb_local_t kbl; int i; if (!hd) return; if (DBG_CLOCK) log_clock ("%s: enter (hd=%p)\n", __func__, hd); log_assert (active_handles > 0); active_handles--; if (hd->use_keyboxd) { kbl = hd->kbl; if (DBG_CLOCK) log_clock ("close_context (found)"); if (!kbl->is_active) log_fatal ("closing inactive keyboxd context %p\n", kbl); kbl->is_active = 0; hd->kbl = NULL; } else { hd->keep_lock = 0; unlock_all (hd); for (i=0; i < hd->used; i++) { switch (hd->active[i].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYBOX: keybox_release (hd->active[i].u.kr); break; } } } xfree (hd); if (DBG_CLOCK) log_clock ("%s: leave\n", __func__); } /* Return the name of the current resource. This is function first looks for the last found found, then for the current search position, and last returns the first available resource. The returned string is only valid as long as the handle exists. This function does only return NULL if no handle is specified, in all other error cases an empty string is returned. */ const char * keydb_get_resource_name (KEYDB_HANDLE hd) { int idx; const char *s = NULL; if (!hd) return NULL; if (hd->use_keyboxd) return "[keyboxd]"; if ( hd->found >= 0 && hd->found < hd->used) idx = hd->found; else if ( hd->current >= 0 && hd->current < hd->used) idx = hd->current; else idx = 0; switch (hd->active[idx].type) { case KEYDB_RESOURCE_TYPE_NONE: s = NULL; break; case KEYDB_RESOURCE_TYPE_KEYBOX: s = keybox_get_resource_name (hd->active[idx].u.kr); break; } return s? s: ""; } /* Switch the handle into ephemeral mode and return the original value. */ int keydb_set_ephemeral (KEYDB_HANDLE hd, int yes) { int i; if (!hd) return 0; if (hd->use_keyboxd) return 0; /* FIXME: No support yet. */ yes = !!yes; if (hd->is_ephemeral != yes) { for (i=0; i < hd->used; i++) { switch (hd->active[i].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYBOX: keybox_set_ephemeral (hd->active[i].u.kr, yes); break; } } } i = hd->is_ephemeral; hd->is_ephemeral = yes; return i; } /* If the keyring has not yet been locked, lock it now. This * operation is required before any update operation; it is optional * for an insert operation. The lock is kept until a keydb_release so * that internal unlock_all calls have no effect. */ gpg_error_t keydb_lock (KEYDB_HANDLE hd) { gpg_error_t err; if (!hd) return gpg_error (GPG_ERR_INV_HANDLE); if (hd->use_keyboxd) return 0; if (DBG_CLOCK) log_clock ("%s: enter (hd=%p)\n", __func__, hd); err = lock_all (hd); if (!err) hd->keep_lock = 1; if (DBG_CLOCK) log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err)); return err; } static int lock_all (KEYDB_HANDLE hd) { int i, rc = 0; if (hd->use_keyboxd) return 0; /* Fixme: This locking scheme may lead to deadlock if the resources are not added in the same order by all processes. We are currently only allowing one resource so it is not a problem. */ for (i=0; i < hd->used; i++) { switch (hd->active[i].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYBOX: rc = keybox_lock (hd->active[i].u.kr, 1, -1); break; } if (rc) break; } if (rc) { /* Revert the already set locks. */ for (i--; i >= 0; i--) { switch (hd->active[i].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYBOX: keybox_lock (hd->active[i].u.kr, 0, 0); break; } } } else hd->locked = 1; return rc; } static void unlock_all (KEYDB_HANDLE hd) { int i; if (hd->use_keyboxd) return; if (!hd->locked || hd->keep_lock) return; for (i=hd->used-1; i >= 0; i--) { switch (hd->active[i].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYBOX: keybox_lock (hd->active[i].u.kr, 0, 0); break; } } hd->locked = 0; } /* Push the last found state if any. */ void keydb_push_found_state (KEYDB_HANDLE hd) { if (!hd) return; if (hd->use_keyboxd) return; /* FIXME: Do we need this? */ if (hd->found < 0 || hd->found >= hd->used) { hd->saved_found = -1; return; } switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYBOX: keybox_push_found_state (hd->active[hd->found].u.kr); break; } hd->saved_found = hd->found; hd->found = -1; if (DBG_CLOCK) log_clock ("%s: done (hd=%p)\n", __func__, hd); } /* Pop the last found state. */ void keydb_pop_found_state (KEYDB_HANDLE hd) { if (!hd) return; if (hd->use_keyboxd) return; /* FIXME: Do we need this? */ hd->found = hd->saved_found; hd->saved_found = -1; if (hd->found < 0 || hd->found >= hd->used) return; switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYBOX: keybox_pop_found_state (hd->active[hd->found].u.kr); break; } if (DBG_CLOCK) log_clock ("%s: done (hd=%p)\n", __func__, hd); } /* Return the last found certificate. Caller must free it. */ int keydb_get_cert (KEYDB_HANDLE hd, ksba_cert_t *r_cert) { int err = 0; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if (DBG_CLOCK) log_clock ("%s: enter (hd=%p)\n", __func__, hd); if (hd->use_keyboxd) { ksba_cert_t cert; /* Fixme: We should clear that also in non-keyboxd mode but we * did not in the past and thus all code should be checked * whether this is okay. If we run into error in keyboxd mode, * this is a not as severe because keyboxd is currently * experimental. */ *r_cert = NULL; if (!hd->kbl->search_result.buf || !hd->kbl->search_result.len) { err = gpg_error (GPG_ERR_VALUE_NOT_FOUND); goto leave; } err = ksba_cert_new (&cert); if (err) goto leave; err = ksba_cert_init_from_mem (cert, hd->kbl->search_result.buf, hd->kbl->search_result.len); if (err) { ksba_cert_release (cert); goto leave; } xfree (hd->kbl->search_result.buf); hd->kbl->search_result.buf = NULL; hd->kbl->search_result.len = 0; *r_cert = cert; goto leave; } if ( hd->found < 0 || hd->found >= hd->used) { /* Fixme: It would be better to use GPG_ERR_VALUE_NOT_FOUND here * but for now we use NOT_FOUND because that is our standard * replacement for the formerly used (-1). */ err = gpg_error (GPG_ERR_NOT_FOUND); /* nothing found */ goto leave; } err = GPG_ERR_BUG; switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: err = gpg_error (GPG_ERR_GENERAL); /* oops */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: err = keybox_get_cert (hd->active[hd->found].u.kr, r_cert); break; } leave: if (DBG_CLOCK) log_clock ("%s: leave (rc=%d)\n", __func__, err); return err; } /* Return a flag of the last found object. WHICH is the flag requested; it should be one of the KEYBOX_FLAG_ values. If the operation is successful, the flag value will be stored at the address given by VALUE. Return 0 on success or an error code. */ gpg_error_t keydb_get_flags (KEYDB_HANDLE hd, int which, int idx, unsigned int *value) { gpg_error_t err; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if (DBG_CLOCK) log_clock ("%s: enter (hd=%p)\n", __func__, hd); if (hd->use_keyboxd) { /* FIXME */ *value = 0; err = 0; goto leave; } if ( hd->found < 0 || hd->found >= hd->used) { err = gpg_error (GPG_ERR_NOTHING_FOUND); goto leave; } err = gpg_error (GPG_ERR_BUG); switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: err = gpg_error (GPG_ERR_GENERAL); /* oops */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: err = keybox_get_flags (hd->active[hd->found].u.kr, which, idx, value); break; } leave: if (DBG_CLOCK) log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err)); return err; } /* Set a flag of the last found object. WHICH is the flag to be set; it should be one of the KEYBOX_FLAG_ values. If the operation is successful, the flag value will be stored in the keybox. Note, that some flag values can't be updated and thus may return an error, some other flag values may be masked out before an update. Returns 0 on success or an error code. */ gpg_error_t keydb_set_flags (KEYDB_HANDLE hd, int which, int idx, unsigned int value) { gpg_error_t err = 0; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if (DBG_CLOCK) log_clock ("%s: enter (hd=%p)\n", __func__, hd); if (hd->use_keyboxd) { /* FIXME */ goto leave; } if ( hd->found < 0 || hd->found >= hd->used) { err = gpg_error (GPG_ERR_NOTHING_FOUND); goto leave; } if (!hd->locked) { err = gpg_error (GPG_ERR_NOT_LOCKED); goto leave; } switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: err = gpg_error (GPG_ERR_GENERAL); /* oops */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: err = keybox_set_flags (hd->active[hd->found].u.kr, which, idx, value); break; } leave: if (DBG_CLOCK) log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err)); return err; } /* Communication object for Keyboxd STORE commands. */ struct store_parm_s { assuan_context_t ctx; const void *data; /* The certificate in X.509 binary format. */ size_t datalen; /* The length of DATA. */ }; /* Handle the inquiries from the STORE command. */ static gpg_error_t store_inq_cb (void *opaque, const char *line) { struct store_parm_s *parm = opaque; gpg_error_t err = 0; if (has_leading_keyword (line, "BLOB")) { if (parm->data) err = assuan_send_data (parm->ctx, parm->data, parm->datalen); } else return gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); return err; } /* * Insert a new Certificate into one of the resources. */ gpg_error_t keydb_insert_cert (KEYDB_HANDLE hd, ksba_cert_t cert) { gpg_error_t err; int idx; unsigned char digest[20]; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if (opt.dry_run) return 0; if (DBG_CLOCK) log_clock ("%s: enter (hd=%p)\n", __func__, hd); if (hd->use_keyboxd) { struct store_parm_s parm; parm.ctx = hd->kbl->ctx; parm.data = ksba_cert_get_image (cert, &parm.datalen); if (!parm.data) { log_debug ("broken ksba cert object\n"); err = gpg_error (GPG_ERR_GENERAL); goto leave; } err = assuan_transact (hd->kbl->ctx, "STORE --insert", NULL, NULL, store_inq_cb, &parm, NULL, NULL); goto leave; } if ( hd->found >= 0 && hd->found < hd->used) idx = hd->found; else if ( hd->current >= 0 && hd->current < hd->used) idx = hd->current; else { err = gpg_error (GPG_ERR_GENERAL); goto leave; } if (!hd->locked) { err = gpg_error (GPG_ERR_NOT_LOCKED); goto leave; } gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/ err = gpg_error (GPG_ERR_BUG); switch (hd->active[idx].type) { case KEYDB_RESOURCE_TYPE_NONE: err = gpg_error (GPG_ERR_GENERAL); break; case KEYDB_RESOURCE_TYPE_KEYBOX: err = keybox_insert_cert (hd->active[idx].u.kr, cert, digest); break; } unlock_all (hd); leave: if (DBG_CLOCK) log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err)); return err; } /* Update the current keyblock with KB. */ /* Note: This function is currently not called. */ gpg_error_t keydb_update_cert (KEYDB_HANDLE hd, ksba_cert_t cert) { + (void)hd; + (void)cert; + return GPG_ERR_BUG; +#if 0 gpg_error_t err; unsigned char digest[20]; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if ( hd->found < 0 || hd->found >= hd->used) return gpg_error (GPG_ERR_NOT_FOUND); if (opt.dry_run) return 0; if (DBG_CLOCK) log_clock ("%s: enter (hd=%p)\n", __func__, hd); if (hd->use_keyboxd) { /* FIXME */ goto leave; } err = lock_all (hd); if (err) goto leave; gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/ err = gpg_error (GPG_ERR_BUG); switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: err = gpg_error (GPG_ERR_GENERAL); /* oops */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: err = keybox_update_cert (hd->active[hd->found].u.kr, cert, digest); break; } unlock_all (hd); leave: if (DBG_CLOCK) log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err)); return err; +#endif /*0*/ } /* * The current keyblock or cert will be deleted. */ gpg_error_t keydb_delete (KEYDB_HANDLE hd) { gpg_error_t err; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); - if ( hd->found < 0 || hd->found >= hd->used) + if (!hd->use_keyboxd && (hd->found < 0 || hd->found >= hd->used)) return gpg_error (GPG_ERR_NOT_FOUND); if (opt.dry_run) return 0; if (DBG_CLOCK) log_clock ("%s: enter (hd=%p)\n", __func__, hd); if (hd->use_keyboxd) { - /* FIXME */ + unsigned char hexubid[UBID_LEN * 2 + 1]; + char line[ASSUAN_LINELENGTH]; + + if (!hd->last_ubid_valid) + { + err = gpg_error (GPG_ERR_VALUE_NOT_FOUND); + goto leave; + } + + bin2hex (hd->last_ubid, UBID_LEN, hexubid); + snprintf (line, sizeof line, "DELETE %s", hexubid); + err = assuan_transact (hd->kbl->ctx, line, + NULL, NULL, + NULL, NULL, + NULL, NULL); goto leave; } if (!hd->locked) { err = gpg_error (GPG_ERR_NOT_LOCKED); goto leave; } err = gpg_error (GPG_ERR_BUG); switch (hd->active[hd->found].type) { case KEYDB_RESOURCE_TYPE_NONE: err = gpg_error (GPG_ERR_GENERAL); break; case KEYDB_RESOURCE_TYPE_KEYBOX: err = keybox_delete (hd->active[hd->found].u.kr); break; } unlock_all (hd); leave: if (DBG_CLOCK) log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err)); return err; } /* * Locate the default writable key resource, so that the next * operation (which is only relevant for inserts) will be done on this * resource. */ static gpg_error_t keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved) { int rc; (void)reserved; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if (hd->use_keyboxd) return 0; /* Not required. */ rc = keydb_search_reset (hd); /* this does reset hd->current */ if (rc) return rc; for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++) { switch (hd->active[hd->current].type) { case KEYDB_RESOURCE_TYPE_NONE: BUG(); break; case KEYDB_RESOURCE_TYPE_KEYBOX: if (keybox_is_writable (hd->active[hd->current].token)) return 0; /* found (hd->current is set to it) */ break; } } return gpg_error (GPG_ERR_NOT_FOUND); } /* * Rebuild the caches of all key resources. */ void keydb_rebuild_caches (void) { int i; /* This function does nothing and thus we don't need to handle keyboxd in a * special way. */ for (i=0; i < used_resources; i++) { switch (all_resources[i].type) { case KEYDB_RESOURCE_TYPE_NONE: /* ignore */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: /* rc = keybox_rebuild_cache (all_resources[i].token); */ /* if (rc) */ /* log_error (_("failed to rebuild keybox cache: %s\n"), */ /* g10_errstr (rc)); */ break; } } } /* * Start the next search on this handle right at the beginning */ gpg_error_t keydb_search_reset (KEYDB_HANDLE hd) { gpg_error_t err = 0; int i; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if (DBG_CLOCK) log_clock ("%s: enter (hd=%p)\n", __func__, hd); hd->current = 0; hd->found = -1; if (hd->use_keyboxd) { /* All we need is to tell search that a reset is pending. Note that * keydb_new sets this flag as well. To comply with the * specification of keydb_delete_keyblock we also need to clear the * ubid flag so that after a reset a delete can't be performed. */ hd->kbl->need_search_reset = 1; hd->last_ubid_valid = 0; } else { /* Reset all resources */ for (i=0; !err && i < hd->used; i++) { switch (hd->active[i].type) { case KEYDB_RESOURCE_TYPE_NONE: break; case KEYDB_RESOURCE_TYPE_KEYBOX: err = keybox_search_reset (hd->active[i].u.kr); break; } } } if (DBG_CLOCK) log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err)); return err; } char * keydb_search_desc_dump (struct keydb_search_desc *desc) { char *fpr; char *result; switch (desc->mode) { case KEYDB_SEARCH_MODE_EXACT: return xasprintf ("EXACT: '%s'", desc->u.name); case KEYDB_SEARCH_MODE_SUBSTR: return xasprintf ("SUBSTR: '%s'", desc->u.name); case KEYDB_SEARCH_MODE_MAIL: return xasprintf ("MAIL: '%s'", desc->u.name); case KEYDB_SEARCH_MODE_MAILSUB: return xasprintf ("MAILSUB: '%s'", desc->u.name); case KEYDB_SEARCH_MODE_MAILEND: return xasprintf ("MAILEND: '%s'", desc->u.name); case KEYDB_SEARCH_MODE_WORDS: return xasprintf ("WORDS: '%s'", desc->u.name); case KEYDB_SEARCH_MODE_SHORT_KID: return xasprintf ("SHORT_KID: '%08lX'", (ulong)desc->u.kid[1]); case KEYDB_SEARCH_MODE_LONG_KID: return xasprintf ("LONG_KID: '%08lX%08lX'", (ulong)desc->u.kid[0], (ulong)desc->u.kid[1]); case KEYDB_SEARCH_MODE_FPR: fpr = bin2hexcolon (desc->u.fpr, desc->fprlen, NULL); result = xasprintf ("FPR%02d: '%s'", desc->fprlen, fpr); xfree (fpr); return result; case KEYDB_SEARCH_MODE_ISSUER: return xasprintf ("ISSUER: '%s'", desc->u.name); case KEYDB_SEARCH_MODE_ISSUER_SN: return xasprintf ("ISSUER_SN: '#%.*s/%s'", (int)desc->snlen,desc->sn, desc->u.name); case KEYDB_SEARCH_MODE_SN: return xasprintf ("SN: '%.*s'", (int)desc->snlen, desc->sn); case KEYDB_SEARCH_MODE_SUBJECT: return xasprintf ("SUBJECT: '%s'", desc->u.name); case KEYDB_SEARCH_MODE_KEYGRIP: return xasprintf ("KEYGRIP: %s", desc->u.grip); case KEYDB_SEARCH_MODE_FIRST: return xasprintf ("FIRST"); case KEYDB_SEARCH_MODE_NEXT: return xasprintf ("NEXT"); default: return xasprintf ("Bad search mode (%d)", desc->mode); } } /* Status callback for SEARCH and NEXT operaions. */ static gpg_error_t search_status_cb (void *opaque, const char *line) { KEYDB_HANDLE hd = opaque; gpg_error_t err = 0; const char *s; if ((s = has_leading_keyword (line, "PUBKEY_INFO"))) { if (atoi (s) != PUBKEY_TYPE_X509) err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE); else { hd->last_ubid_valid = 0; while (*s && !spacep (s)) s++; if (hex2fixedbuf (s, hd->last_ubid, sizeof hd->last_ubid)) hd->last_ubid_valid = 1; else err = gpg_error (GPG_ERR_INV_VALUE); + while (spacep (s)) + s++; + hd->last_is_ephemeral = (*s == 'e'); } } return err; } /* Search through all keydb resources, starting at the current * position, for a keyblock which contains one of the keys described * in the DESC array. In keyboxd mode the search is instead delegated * to the keyboxd. * * DESC is an array of search terms with NDESC entries. The search * terms are or'd together. That is, the next entry in the DB that * matches any of the descriptions will be returned. * * Note: this function resumes searching where the last search left * off (i.e., at the current file position). If you want to search * from the start of the database, then you need to first call * keydb_search_reset(). * * If no key matches the search description, the error code * GPG_ERR_NOT_FOUND is retruned. If there was a match, 0 is * returned. If an error occurred, that error code is returned. * * The returned key is considered to be selected and the certificate * can be detched via keydb_get_cert. */ gpg_error_t keydb_search (ctrl_t ctrl, KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc) { gpg_error_t err = gpg_error (GPG_ERR_EOF); unsigned long skipped; int i; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); if (!any_registered && !hd->use_keyboxd) { gpgsm_status_with_error (ctrl, STATUS_ERROR, "keydb_search", gpg_error (GPG_ERR_KEYRING_OPEN)); return gpg_error (GPG_ERR_NOT_FOUND); } if (DBG_CLOCK) log_clock ("%s: enter (hd=%p)\n", __func__, hd); if (DBG_LOOKUP) { log_debug ("%s: %zd search description(s):\n", __func__, ndesc); for (i = 0; i < ndesc; i ++) { char *t = keydb_search_desc_dump (&desc[i]); log_debug ("%s: %d: %s\n", __func__, i, t); xfree (t); } } if (hd->use_keyboxd) { char line[ASSUAN_LINELENGTH]; /* Clear the result objects. */ if (hd->kbl->search_result.buf) { xfree (hd->kbl->search_result.buf); hd->kbl->search_result.buf = NULL; hd->kbl->search_result.len = 0; } /* Check whether this is a NEXT search. */ if (!hd->kbl->need_search_reset) { /* A reset was not requested thus continue the search. The * keyboxd keeps the context of the search and thus the NEXT * operates on the last search pattern. This is the way how * we always used the keydb functions. In theory we were * able to modify the search pattern between searches but * that is not anymore supported by keyboxd and a cursory * check does not show that we actually made use of that * misfeature. */ snprintf (line, sizeof line, "NEXT --x509"); goto do_search; } hd->kbl->need_search_reset = 0; if (!ndesc) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } /* FIXME: Implement --multi */ switch (desc->mode) { case KEYDB_SEARCH_MODE_EXACT: snprintf (line, sizeof line, "SEARCH --x509 =%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_SUBSTR: snprintf (line, sizeof line, "SEARCH --x509 *%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_MAIL: snprintf (line, sizeof line, "SEARCH --x509 <%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_MAILSUB: snprintf (line, sizeof line, "SEARCH --x509 @%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_MAILEND: snprintf (line, sizeof line, "SEARCH --x509 .%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_WORDS: snprintf (line, sizeof line, "SEARCH --x509 +%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_SHORT_KID: snprintf (line, sizeof line, "SEARCH --x509 0x%08lX", (ulong)desc->u.kid[1]); break; case KEYDB_SEARCH_MODE_LONG_KID: snprintf (line, sizeof line, "SEARCH --x509 0x%08lX%08lX", (ulong)desc->u.kid[0], (ulong)desc->u.kid[1]); break; case KEYDB_SEARCH_MODE_FPR: { unsigned char hexfpr[MAX_FINGERPRINT_LEN * 2 + 1]; log_assert (desc[0].fprlen <= MAX_FINGERPRINT_LEN); bin2hex (desc[0].u.fpr, desc[0].fprlen, hexfpr); snprintf (line, sizeof line, "SEARCH --x509 0x%s", hexfpr); } break; case KEYDB_SEARCH_MODE_ISSUER: snprintf (line, sizeof line, "SEARCH --x509 #/%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_ISSUER_SN: if (desc[0].snhex) snprintf (line, sizeof line, "SEARCH --x509 #%.*s/%s", (int)desc[0].snlen, desc[0].sn, desc[0].u.name); else { char *hexsn = bin2hex (desc[0].sn, desc[0].snlen, NULL); if (!hexsn) { err = gpg_error_from_syserror (); goto leave; } snprintf (line, sizeof line, "SEARCH --x509 #%s/%s", hexsn, desc[0].u.name); xfree (hexsn); } break; case KEYDB_SEARCH_MODE_SN: snprintf (line, sizeof line, "SEARCH --x509 #%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_SUBJECT: snprintf (line, sizeof line, "SEARCH --x509 /%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_KEYGRIP: { unsigned char hexgrip[KEYGRIP_LEN * 2 + 1]; bin2hex (desc[0].u.grip, KEYGRIP_LEN, hexgrip); snprintf (line, sizeof line, "SEARCH --x509 &%s", hexgrip); } break; case KEYDB_SEARCH_MODE_UBID: { unsigned char hexubid[UBID_LEN * 2 + 1]; bin2hex (desc[0].u.ubid, UBID_LEN, hexubid); snprintf (line, sizeof line, "SEARCH --x509 ^%s", hexubid); } break; case KEYDB_SEARCH_MODE_FIRST: snprintf (line, sizeof line, "SEARCH --x509"); break; case KEYDB_SEARCH_MODE_NEXT: log_debug ("%s: mode next - we should not get to here!\n", __func__); snprintf (line, sizeof line, "NEXT --x509"); break; default: err = gpg_error (GPG_ERR_INV_ARG); goto leave; } do_search: hd->last_ubid_valid = 0; /* To avoid silent truncation we error out on a too long line. */ if (strlen (line) + 5 >= sizeof line) err = gpg_error (GPG_ERR_ASS_LINE_TOO_LONG); else err = kbx_client_data_cmd (hd->kbl->kcd, line, search_status_cb, hd); if (!err && !(err = kbx_client_data_wait (hd->kbl->kcd, &hd->kbl->search_result.buf, &hd->kbl->search_result.len))) { /* if (hd->last_ubid_valid) */ - /* log_printhex (hd->last_ubid, 20, "found UBID:"); */ + /* log_printhex (hd->last_ubid, 20, "found UBID%s:", */ + /* hd->last_is_ephemeral? "(ephemeral)":""); */ } } else /* Local keyring search. */ { while (gpg_err_code (err) == GPG_ERR_EOF && hd->current >= 0 && hd->current < hd->used) { switch (hd->active[hd->current].type) { case KEYDB_RESOURCE_TYPE_NONE: BUG(); /* we should never see it here */ break; case KEYDB_RESOURCE_TYPE_KEYBOX: err = keybox_search (hd->active[hd->current].u.kr, desc, ndesc, KEYBOX_BLOBTYPE_X509, NULL, &skipped); if (err == -1) /* Map legacy code. */ err = gpg_error (GPG_ERR_EOF); break; } if (DBG_LOOKUP) log_debug ("%s: searched %s (resource %d of %d) => %s\n", __func__, hd->active[hd->current].type==KEYDB_RESOURCE_TYPE_KEYBOX ? "keybox" : "unknown type", hd->current, hd->used, gpg_strerror (err)); if (gpg_err_code (err) == GPG_ERR_EOF) { /* EOF -> switch to next resource */ hd->current++; } else if (!err) hd->found = hd->current; } } leave: /* The NOTHING_FOUND error is triggered by a NEXT command. */ if (gpg_err_code (err) == GPG_ERR_EOF || gpg_err_code (err) == GPG_ERR_NOTHING_FOUND) err = gpg_error (GPG_ERR_NOT_FOUND); if (DBG_CLOCK) log_clock ("%s: leave (%s)\n", __func__, gpg_strerror (err)); return err; } int keydb_search_first (ctrl_t ctrl, KEYDB_HANDLE hd) { KEYDB_SEARCH_DESC desc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_FIRST; return keydb_search (ctrl, hd, &desc, 1); } int keydb_search_next (ctrl_t ctrl, KEYDB_HANDLE hd) { KEYDB_SEARCH_DESC desc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_NEXT; return keydb_search (ctrl, hd, &desc, 1); } int keydb_search_kid (ctrl_t ctrl, KEYDB_HANDLE hd, u32 *kid) { KEYDB_SEARCH_DESC desc; (void)kid; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_LONG_KID; desc.u.kid[0] = kid[0]; desc.u.kid[1] = kid[1]; return keydb_search (ctrl, hd, &desc, 1); } int keydb_search_fpr (ctrl_t ctrl, KEYDB_HANDLE hd, const byte *fpr) { KEYDB_SEARCH_DESC desc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_FPR; memcpy (desc.u.fpr, fpr, 20); desc.fprlen = 20; return keydb_search (ctrl, hd, &desc, 1); } int keydb_search_issuer (ctrl_t ctrl, KEYDB_HANDLE hd, const char *issuer) { KEYDB_SEARCH_DESC desc; int rc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_ISSUER; desc.u.name = issuer; rc = keydb_search (ctrl, hd, &desc, 1); return rc; } int keydb_search_issuer_sn (ctrl_t ctrl, KEYDB_HANDLE hd, const char *issuer, ksba_const_sexp_t serial) { KEYDB_SEARCH_DESC desc; int rc; const unsigned char *s; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_ISSUER_SN; s = serial; if (*s !='(') return gpg_error (GPG_ERR_INV_VALUE); s++; for (desc.snlen = 0; digitp (s); s++) desc.snlen = 10*desc.snlen + atoi_1 (s); if (*s !=':') return gpg_error (GPG_ERR_INV_VALUE); desc.sn = s+1; desc.u.name = issuer; rc = keydb_search (ctrl, hd, &desc, 1); return rc; } int keydb_search_subject (ctrl_t ctrl, KEYDB_HANDLE hd, const char *name) { KEYDB_SEARCH_DESC desc; int rc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_SUBJECT; desc.u.name = name; rc = keydb_search (ctrl, hd, &desc, 1); return rc; } /* Store the certificate in the key DB but make sure that it does not already exists. We do this simply by comparing the fingerprint. If EXISTED is not NULL it will be set to true if the certificate was already in the DB. */ int keydb_store_cert (ctrl_t ctrl, ksba_cert_t cert, int ephemeral, int *existed) { KEYDB_HANDLE kh; int rc; unsigned char fpr[20]; if (existed) *existed = 0; if (!gpgsm_get_fingerprint (cert, 0, fpr, NULL)) { log_error (_("failed to get the fingerprint\n")); return gpg_error (GPG_ERR_GENERAL); } kh = keydb_new (ctrl); if (!kh) { log_error (_("failed to allocate keyDB handle\n")); return gpg_error (GPG_ERR_ENOMEM);; } /* Set the ephemeral flag so that the search looks at all records. */ keydb_set_ephemeral (kh, 1); if (!kh->use_keyboxd) { rc = lock_all (kh); if (rc) return rc; } rc = keydb_search_fpr (ctrl, kh, fpr); if (gpg_err_code (rc) != GPG_ERR_NOT_FOUND) { keydb_release (kh); if (!rc) { if (existed) *existed = 1; if (!ephemeral) { /* Remove ephemeral flags from existing certificate to "store" it permanently. */ rc = keydb_set_cert_flags (ctrl, cert, 1, KEYBOX_FLAG_BLOB, 0, KEYBOX_FLAG_BLOB_EPHEMERAL, 0); if (rc) { log_error ("clearing ephemeral flag failed: %s\n", gpg_strerror (rc)); return rc; } } return 0; /* okay */ } log_error (_("problem looking for existing certificate: %s\n"), gpg_strerror (rc)); return rc; } /* Reset the ephemeral flag if not requested. */ if (!ephemeral) keydb_set_ephemeral (kh, 0); rc = keydb_locate_writable (kh, 0); if (rc) { log_error (_("error finding writable keyDB: %s\n"), gpg_strerror (rc)); keydb_release (kh); return rc; } rc = keydb_insert_cert (kh, cert); if (rc) { log_error (_("error storing certificate: %s\n"), gpg_strerror (rc)); keydb_release (kh); return rc; } keydb_release (kh); return 0; } /* This is basically keydb_set_flags but it implements a complete transaction by locating the certificate in the DB and updating the flags. */ gpg_error_t keydb_set_cert_flags (ctrl_t ctrl, ksba_cert_t cert, int ephemeral, int which, int idx, unsigned int mask, unsigned int value) { KEYDB_HANDLE kh; gpg_error_t err; unsigned char fpr[20]; unsigned int old_value; if (!gpgsm_get_fingerprint (cert, 0, fpr, NULL)) { log_error (_("failed to get the fingerprint\n")); return gpg_error (GPG_ERR_GENERAL); } kh = keydb_new (ctrl); if (!kh) { log_error (_("failed to allocate keyDB handle\n")); return gpg_error (GPG_ERR_ENOMEM);; } if (ephemeral) keydb_set_ephemeral (kh, 1); if (!kh->use_keyboxd) { err = keydb_lock (kh); if (err) { log_error (_("error locking keybox: %s\n"), gpg_strerror (err)); keydb_release (kh); return err; } } err = keydb_search_fpr (ctrl, kh, fpr); if (err) { if (gpg_err_code (err) != gpg_error (GPG_ERR_NOT_FOUND)) log_error (_("problem re-searching certificate: %s\n"), gpg_strerror (err)); keydb_release (kh); return err; } err = keydb_get_flags (kh, which, idx, &old_value); if (err) { log_error (_("error getting stored flags: %s\n"), gpg_strerror (err)); keydb_release (kh); return err; } value = ((old_value & ~mask) | (value & mask)); if (value != old_value) { err = keydb_set_flags (kh, which, idx, value); if (err) { log_error (_("error storing flags: %s\n"), gpg_strerror (err)); keydb_release (kh); return err; } } keydb_release (kh); return 0; } /* Reset all the certificate flags we have stored with the certificates for performance reasons. */ void keydb_clear_some_cert_flags (ctrl_t ctrl, strlist_t names) { gpg_error_t err; KEYDB_HANDLE hd = NULL; KEYDB_SEARCH_DESC *desc = NULL; int ndesc; strlist_t sl; int rc=0; unsigned int old_value, value; (void)ctrl; hd = keydb_new (ctrl); if (!hd) { log_error ("keydb_new failed\n"); goto leave; } if (!names) ndesc = 1; else { for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++) ; } desc = xtrycalloc (ndesc, sizeof *desc); if (!ndesc) { log_error ("allocating memory failed: %s\n", gpg_strerror (out_of_core ())); goto leave; } if (!names) desc[0].mode = KEYDB_SEARCH_MODE_FIRST; else { for (ndesc=0, sl=names; sl; sl = sl->next) { rc = classify_user_id (sl->d, desc+ndesc, 0); if (rc) log_error ("key '%s' not found: %s\n", sl->d, gpg_strerror (rc)); else ndesc++; } } if (!hd->use_keyboxd) { err = keydb_lock (hd); if (err) { log_error (_("error locking keybox: %s\n"), gpg_strerror (err)); goto leave; } } while (!(rc = keydb_search (ctrl, hd, desc, ndesc))) { if (!names) desc[0].mode = KEYDB_SEARCH_MODE_NEXT; err = keydb_get_flags (hd, KEYBOX_FLAG_VALIDITY, 0, &old_value); if (err) { log_error (_("error getting stored flags: %s\n"), gpg_strerror (err)); goto leave; } value = (old_value & ~VALIDITY_REVOKED); if (value != old_value) { err = keydb_set_flags (hd, KEYBOX_FLAG_VALIDITY, 0, value); if (err) { log_error (_("error storing flags: %s\n"), gpg_strerror (err)); goto leave; } } } if (rc && gpg_err_code (rc) != GPG_ERR_NOT_FOUND) log_error ("keydb_search failed: %s\n", gpg_strerror (rc)); leave: xfree (desc); keydb_release (hd); }