diff --git a/g10/call-keyboxd.c b/g10/call-keyboxd.c index 31bccefec..e0ee26e6b 100644 --- a/g10/call-keyboxd.c +++ b/g10/call-keyboxd.c @@ -1,790 +1,790 @@ /* call-keyboxd.c - Access to the keyboxd storage server * 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 #ifdef HAVE_LOCALE_H # include #endif #include #include "gpg.h" #include #include "../common/util.h" #include "../common/membuf.h" #include "options.h" #include "../common/i18n.h" #include "../common/asshelp.h" #include "../common/host2net.h" #include "../common/exechelp.h" #include "../common/status.h" #include "../kbx/kbx-client-util.h" #include "keydb.h" #include "keydb-private.h" /* For struct keydb_handle_s */ /* 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 gpg.h defines the type * keyboxd_local_t for this structure. */ struct keyboxd_local_s { /* Link to other keyboxd contexts which are used simultaneously. */ struct keyboxd_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. */ iobuf_t 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; }; /* Deinitialize all session resources pertaining to the keyboxd. */ void gpg_keyboxd_deinit_session_data (ctrl_t ctrl) { keyboxd_local_t kbl; while ((kbl = ctrl->keyboxd_local)) { ctrl->keyboxd_local = kbl->next; if (kbl->is_active) log_error ("oops: trying to cleanup an active keyboxd context\n"); else { kbx_client_data_release (kbl->kcd); kbl->kcd = NULL; assuan_release (kbl->ctx); kbl->ctx = NULL; } xfree (kbl); } } /* 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, write_status_strings2, 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, keyboxd_local_t *r_kbl) { gpg_error_t err; keyboxd_local_t kbl; *r_kbl = NULL; for (;;) { for (kbl = ctrl->keyboxd_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); 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->keyboxd_local; ctrl->keyboxd_local = kbl; } /*NOTREACHED*/ } /* Create a new database handle. A database handle is similar to a * file handle: it contains a local file position. This is used when * searching: subsequent searches resume where the previous search * left off. To rewind the position, use keydb_search_reset(). This * function returns NULL on error, sets ERRNO, and prints an error * diagnostic. Depending on --use-keyboxd either the old internal * keydb code is used (keydb.c) or, if set, the processing is diverted * to the keyboxd. */ /* FIXME: We should change the interface to return a gpg_error_t. */ KEYDB_HANDLE keydb_new (ctrl_t ctrl) { gpg_error_t err; KEYDB_HANDLE hd; if (DBG_CLOCK) log_clock ("keydb_new"); hd = xtrycalloc (1, sizeof *hd); if (!hd) { err = gpg_error_from_syserror (); goto leave; } if (!opt.use_keyboxd) { err = internal_keydb_init (hd); goto leave; } hd->use_keyboxd = 1; hd->ctrl = ctrl; err = open_context (ctrl, &hd->kbl); leave: if (err) { int rc; 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); } return hd; } /* Release a keydb handle. */ void keydb_release (KEYDB_HANDLE hd) { keyboxd_local_t kbl; if (!hd) return; if (DBG_CLOCK) log_clock ("keydb_release"); if (!hd->use_keyboxd) internal_keydb_deinit (hd); else { 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; hd->ctrl = NULL; } xfree (hd); } /* Take a lock if we are not using the keyboxd. */ gpg_error_t keydb_lock (KEYDB_HANDLE hd) { if (!hd) return gpg_error (GPG_ERR_INV_ARG); if (!hd->use_keyboxd) return internal_keydb_lock (hd); return 0; } /* Return the keyblock last found by keydb_search() in *RET_KB. * * On success, the function returns 0 and the caller must free *RET_KB * using release_kbnode(). Otherwise, the function returns an error * code. * * The returned keyblock has the kbnode flag bit 0 set for the node * with the public key used to locate the keyblock or flag bit 1 set * for the user ID node. */ gpg_error_t keydb_get_keyblock (KEYDB_HANDLE hd, kbnode_t *ret_kb) { gpg_error_t err; int pk_no, uid_no; *ret_kb = NULL; if (!hd) return gpg_error (GPG_ERR_INV_ARG); if (DBG_CLOCK) log_clock ("%s enter", __func__); if (!hd->use_keyboxd) { err = internal_keydb_get_keyblock (hd, ret_kb); goto leave; } if (hd->kbl->search_result) { pk_no = uid_no = 0; /*FIXME: Get this from the keyboxd. */ err = keydb_parse_keyblock (hd->kbl->search_result, pk_no, uid_no, ret_kb); /* In contrast to the old code we close the iobuf here and thus * this function may be called only once to get a keyblock. */ iobuf_close (hd->kbl->search_result); hd->kbl->search_result = NULL; } else { err = gpg_error (GPG_ERR_VALUE_NOT_FOUND); goto leave; } leave: if (DBG_CLOCK) log_clock ("%s leave%s", __func__, err? " (failed)":""); return err; } /* Communication object for STORE commands. */ struct store_parm_s { assuan_context_t ctx; const void *data; /* The key in OpenPGP 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; } /* Update the keyblock KB (i.e., extract the fingerprint and find the * corresponding keyblock in the keyring). * * This doesn't do anything if --dry-run was specified. * * Returns 0 on success. Otherwise, it returns an error code. Note: * if there isn't a keyblock in the keyring corresponding to KB, then * this function returns GPG_ERR_VALUE_NOT_FOUND. * * This function selects the matching record and modifies the current * file position to point to the record just after the selected entry. * Thus, if you do a subsequent search using HD, you should first do a * keydb_search_reset. Further, if the selected record is important, * you should use keydb_push_found_state and keydb_pop_found_state to * save and restore it. */ gpg_error_t keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb) { gpg_error_t err; iobuf_t iobuf = NULL; struct store_parm_s parm = {NULL}; log_assert (kb); log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY); if (!hd) return gpg_error (GPG_ERR_INV_ARG); if (!hd->use_keyboxd) { err = internal_keydb_update_keyblock (ctrl, hd, kb); goto leave; } if (opt.dry_run) { err = 0; goto leave; } err = build_keyblock_image (kb, &iobuf); if (err) goto leave; parm.ctx = hd->kbl->ctx; parm.data = iobuf_get_temp_buffer (iobuf); parm.datalen = iobuf_get_temp_length (iobuf); err = assuan_transact (hd->kbl->ctx, "STORE --update", NULL, NULL, store_inq_cb, &parm, NULL, NULL); leave: iobuf_close (iobuf); return err; } /* Insert a keyblock into one of the underlying keyrings or keyboxes. * * By default, the keyring / keybox from which the last search result * came is used. If there was no previous search result (or * keydb_search_reset was called), then the keyring / keybox where the * next search would start is used (i.e., the current file position). * In keyboxd mode the keyboxd decides where to store it. * * Note: this doesn't do anything if --dry-run was specified. * * Returns 0 on success. Otherwise, it returns an error code. */ gpg_error_t keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb) { gpg_error_t err; iobuf_t iobuf = NULL; struct store_parm_s parm = {NULL}; if (!hd) return gpg_error (GPG_ERR_INV_ARG); if (!hd->use_keyboxd) { err = internal_keydb_insert_keyblock (hd, kb); goto leave; } if (opt.dry_run) { err = 0; goto leave; } err = build_keyblock_image (kb, &iobuf); if (err) goto leave; parm.ctx = hd->kbl->ctx; parm.data = iobuf_get_temp_buffer (iobuf); parm.datalen = iobuf_get_temp_length (iobuf); err = assuan_transact (hd->kbl->ctx, "STORE --insert", NULL, NULL, store_inq_cb, &parm, NULL, NULL); leave: iobuf_close (iobuf); return err; } /* Delete the currently selected keyblock. If you haven't done a * search yet on this database handle (or called keydb_search_reset), * then this function returns an error. * * Returns 0 on success or an error code, if an error occurred. */ gpg_error_t keydb_delete_keyblock (KEYDB_HANDLE hd) { gpg_error_t err; unsigned char hexubid[UBID_LEN * 2 + 1]; char line[ASSUAN_LINELENGTH]; if (!hd) return gpg_error (GPG_ERR_INV_ARG); if (!hd->use_keyboxd) { err = internal_keydb_delete_keyblock (hd); goto leave; } if (opt.dry_run) { err = 0; goto leave; } 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); leave: return err; } /* Clears the current search result and resets the handle's position * so that the next search starts at the beginning of the database. * * Returns 0 on success and an error code if an error occurred. */ gpg_error_t keydb_search_reset (KEYDB_HANDLE hd) { gpg_error_t err; if (!hd) return gpg_error (GPG_ERR_INV_ARG); if (DBG_CLOCK) log_clock ("%s", __func__); if (DBG_CACHE) log_debug ("%s (hd=%p)", __func__, hd); if (!hd->use_keyboxd) { err = internal_keydb_search_reset (hd); goto leave; } /* 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; err = 0; leave: return err; } /* 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_OPGP) 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); } } return err; } /* Search the database for keys matching the search description. If * the DB contains any legacy keys, these are silently ignored. * * 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, returns * GPG_ERR_NOT_FOUND. If there was a match, returns 0. If an error * occurred, returns an error code. * * The returned key is considered to be selected and the raw data can, * for instance, be returned by calling keydb_get_keyblock(). */ gpg_error_t keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc, size_t *descindex) { gpg_error_t err; int i; char line[ASSUAN_LINELENGTH]; char *buffer; size_t len; if (!hd) return gpg_error (GPG_ERR_INV_ARG); if (descindex) *descindex = 0; /* Make sure it is always set on return. */ if (DBG_CLOCK) log_clock ("%s enter", __func__); if (DBG_LOOKUP) { log_debug ("%s: %zu search descriptions:\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) { err = internal_keydb_search (hd, desc, ndesc, descindex); goto leave; } /* Clear the result objects. */ if (hd->kbl->search_result) { iobuf_close (hd->kbl->search_result); hd->kbl->search_result = NULL; } /* Check whether this is a NEXT search. */ if (!hd->kbl->need_search_reset) { /* No reset 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 how we always used the * keydb.c 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 used of that misfeature. */ snprintf (line, sizeof line, "NEXT"); 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 =%s", desc[0].u.name); + snprintf (line, sizeof line, "SEARCH --openpgp -- =%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_SUBSTR: - snprintf (line, sizeof line, "SEARCH *%s", desc[0].u.name); + snprintf (line, sizeof line, "SEARCH --openpgp -- *%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_MAIL: - snprintf (line, sizeof line, "SEARCH <%s", desc[0].u.name); + snprintf (line, sizeof line, "SEARCH --openpgp -- <%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_MAILSUB: - snprintf (line, sizeof line, "SEARCH @%s", desc[0].u.name); + snprintf (line, sizeof line, "SEARCH --openpgp -- @%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_MAILEND: - snprintf (line, sizeof line, "SEARCH .%s", desc[0].u.name); + snprintf (line, sizeof line, "SEARCH --openpgp -- .%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_WORDS: - snprintf (line, sizeof line, "SEARCH +%s", desc[0].u.name); + snprintf (line, sizeof line, "SEARCH --openpgp -- +%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_SHORT_KID: - snprintf (line, sizeof line, "SEARCH 0x%08lX", + snprintf (line, sizeof line, "SEARCH --openpgp -- 0x%08lX", (ulong)desc->u.kid[1]); break; case KEYDB_SEARCH_MODE_LONG_KID: - snprintf (line, sizeof line, "SEARCH 0x%08lX%08lX", + snprintf (line, sizeof line, "SEARCH --openpgp -- 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 0x%s", hexfpr); + snprintf (line, sizeof line, "SEARCH --openpgp -- 0x%s", hexfpr); } break; case KEYDB_SEARCH_MODE_ISSUER: - snprintf (line, sizeof line, "SEARCH #/%s", desc[0].u.name); + snprintf (line, sizeof line, "SEARCH --openpgp -- #/%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_ISSUER_SN: case KEYDB_SEARCH_MODE_SN: - snprintf (line, sizeof line, "SEARCH #%s", desc[0].u.name); + snprintf (line, sizeof line, "SEARCH --openpgp -- #%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_SUBJECT: - snprintf (line, sizeof line, "SEARCH /%s", desc[0].u.name); + snprintf (line, sizeof line, "SEARCH --openpgp -- /%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 &%s", hexgrip); + snprintf (line, sizeof line, "SEARCH --openpgp -- &%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 ^%s", hexubid); + snprintf (line, sizeof line, "SEARCH --openpgp -- ^%s", hexubid); } break; case KEYDB_SEARCH_MODE_FIRST: - snprintf (line, sizeof line, "SEARCH"); + snprintf (line, sizeof line, "SEARCH --openpgp"); break; case KEYDB_SEARCH_MODE_NEXT: log_debug ("%s: mode next - we should not get to here!\n", __func__); snprintf (line, sizeof line, "NEXT"); break; default: err = gpg_error (GPG_ERR_INV_ARG); goto leave; } do_search: hd->last_ubid_valid = 0; err = kbx_client_data_cmd (hd->kbl->kcd, line, search_status_cb, hd); if (!err && !(err = kbx_client_data_wait (hd->kbl->kcd, &buffer, &len))) { hd->kbl->search_result = iobuf_temp_with_content (buffer, len); xfree (buffer); /* if (hd->last_ubid_valid) */ /* log_printhex (hd->last_ubid, 20, "found UBID:"); */ } leave: if (DBG_CLOCK) log_clock ("%s leave (%sfound)", __func__, err? "not ":""); return err; } diff --git a/kbx/backend-sqlite.c b/kbx/backend-sqlite.c index 67d40f0b6..c4e54298e 100644 --- a/kbx/backend-sqlite.c +++ b/kbx/backend-sqlite.c @@ -1,1288 +1,1345 @@ /* 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 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 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. */ +/* 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, sqlite3_stmt **r_stmt) +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 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) +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? strlen (value):0, + 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, &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 (be_sqlite_local_t ctx, +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; + 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 - err = 0; + { + 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.keyblob" " FROM pubkey as p, userid as u" " WHERE p.ubid = u.ubid AND u.uid = ?1", - &ctx->select_stmt); + 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.keyblob" " FROM pubkey as p, userid as u" " WHERE p.ubid = u.ubid AND u.addrspec = ?1", - &ctx->select_stmt); + 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.keyblob" " FROM pubkey as p, userid as u" " WHERE p.ubid = u.ubid AND u.addrspec LIKE ?1", - &ctx->select_stmt); + 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.keyblob" " FROM pubkey as p, userid as u" " WHERE p.ubid = u.ubid AND u.uid LIKE ?1", - &ctx->select_stmt); + 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: 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); + extra, &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); + 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.keyblob" " FROM pubkey as p, fingerprint as f" " WHERE p.ubid = f.ubid AND f.keygrip = ?1", - &ctx->select_stmt); + 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, keyblob" - " FROM pubkey" + " FROM pubkey as p" " WHERE ubid = ?1", - &ctx->select_stmt); + 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) - err = run_sql_prepare ("SELECT ubid, type, keyblob" - " FROM pubkey ORDER by ubid", - &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, 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 (ctx, desc, ndesc); + 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; 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); + 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, 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); + 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_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); + err = run_sql_prepare (sqlstr, NULL, &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 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; 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; } diff --git a/kbx/frontend.c b/kbx/frontend.c index ea7d2e23f..48b6fffa2 100644 --- a/kbx/frontend.c +++ b/kbx/frontend.c @@ -1,487 +1,485 @@ /* frontend.c - Database fronend code 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+ */ #include #include #include #include #include #include "keyboxd.h" #include #include "../common/i18n.h" #include "../common/userids.h" #include "backend.h" #include "frontend.h" /* An object to keep infos about the database. */ struct { enum database_types db_type; backend_handle_t backend_handle; } the_database; /* Take a lock for reading the databases. */ static void take_read_lock (ctrl_t ctrl) { /* FIXME */ (void)ctrl; } /* Take a lock for reading and writing the databases. */ static void take_read_write_lock (ctrl_t ctrl) { /* FIXME */ (void)ctrl; } /* Release a lock. It is valid to call this even if no lock has been * taken in which case this is a nop. */ static void release_lock (ctrl_t ctrl) { /* FIXME */ (void)ctrl; } /* Set the database to use. Depending on the FILENAME suffix we * decide which one to use. This function must be called at daemon * startup because it employs no locking. If FILENAME has no * directory separator, the file is expected or created below * "$GNUPGHOME/public-keys.d/". In READONLY mode the file must exists; * otherwise it is created. */ gpg_error_t kbxd_set_database (ctrl_t ctrl, const char *filename_arg, int readonly) { gpg_error_t err; char *filename; enum database_types db_type = 0; backend_handle_t handle = NULL; unsigned int n; /* Do tilde expansion etc. */ if (strchr (filename_arg, DIRSEP_C) #ifdef HAVE_W32_SYSTEM || strchr (filename_arg, '/') /* Windows also accepts a slash. */ #endif ) filename = make_filename (filename_arg, NULL); else filename = make_filename (gnupg_homedir (), GNUPG_PUBLIC_KEYS_DIR, filename_arg, NULL); /* If this is the first call to the function and the request is not * for the cache backend, add the cache backend so that it will * always be the first to be queried. */ if (the_database.db_type) { log_error ("error: only one database allowed\n"); err = gpg_error (GPG_ERR_CONFLICT); goto leave; } /* Init the cache. */ err = be_cache_initialize (); if (err) goto leave; n = strlen (filename); if (db_type) ; /* We already know it. */ else if (n > 4 && !strcmp (filename + n - 4, ".kbx")) db_type = DB_TYPE_KBX; else if (n > 3 && !strcmp (filename + n - 3, ".db")) db_type = DB_TYPE_SQLITE; else { log_error (_("can't use file '%s': %s\n"), filename, _("unknown suffix")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } err = gpg_error (GPG_ERR_BUG); switch (db_type) { case DB_TYPE_NONE: /* NOTREACHED */ break; case DB_TYPE_CACHE: err = be_cache_add_resource (ctrl, &handle); break; case DB_TYPE_KBX: err = be_kbx_add_resource (ctrl, &handle, filename, readonly); break; case DB_TYPE_SQLITE: err = be_sqlite_add_resource (ctrl, &handle, filename, readonly); break; } if (err) goto leave; the_database.db_type = db_type; the_database.backend_handle = handle; handle = NULL; leave: if (err) { log_error ("error setting database '%s': %s\n", filename, gpg_strerror (err)); be_generic_release_backend (ctrl, handle); } xfree (filename); return err; } /* Release all per session objects. */ void kbxd_release_session_info (ctrl_t ctrl) { if (!ctrl) return; - be_release_request (ctrl->opgp_req); - ctrl->opgp_req = NULL; - be_release_request (ctrl->x509_req); - ctrl->x509_req = NULL; + be_release_request (ctrl->db_req); + ctrl->db_req = NULL; } /* Search for the keys described by (DESC,NDESC) and return them to * the caller. If RESET is set, the search state is first reset. * Only a reset guarantees that changed search description in DESC are * considered. */ gpg_error_t kbxd_search (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, unsigned int ndesc, int reset) { gpg_error_t err; int i; db_request_t request; if (DBG_CLOCK) log_clock ("%s: enter", __func__); if (DBG_LOOKUP) { log_debug ("%s: %u search descriptions:\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); */ } } take_read_lock (ctrl); /* Allocate a handle object if none exists for this context. */ - if (!ctrl->opgp_req) + if (!ctrl->db_req) { - ctrl->opgp_req = xtrycalloc (1, sizeof *ctrl->opgp_req); - if (!ctrl->opgp_req) + ctrl->db_req = xtrycalloc (1, sizeof *ctrl->db_req); + if (!ctrl->db_req) { err = gpg_error_from_syserror (); goto leave; } } - request = ctrl->opgp_req; + request = ctrl->db_req; if (!the_database.db_type) { log_error ("%s: error: no database configured\n", __func__); err = gpg_error (GPG_ERR_NOT_INITIALIZED); goto leave; } /* If requested do a reset. Using the reset flag is faster than * letting the caller do a separate call for an initial reset. */ if (!desc || reset) { switch (the_database.db_type) { case DB_TYPE_CACHE: err = 0; /* Nothing to do. */ break; case DB_TYPE_KBX: err = be_kbx_search (ctrl, the_database.backend_handle, request, NULL, 0); break; case DB_TYPE_SQLITE: err = be_sqlite_search (ctrl, the_database.backend_handle, request, NULL, 0); break; default: err = gpg_error (GPG_ERR_INTERNAL); break; } if (err) { log_error ("error during the %ssearch reset: %s\n", reset? "initial ":"", gpg_strerror (err)); goto leave; } request->any_search = 0; request->any_found = 0; request->next_dbidx = 0; if (!desc) /* Reset only mode */ { err = 0; goto leave; } } /* Divert to the backend for the actual search. */ switch (the_database.db_type) { case DB_TYPE_CACHE: err = be_cache_search (ctrl, the_database.backend_handle, request, desc, ndesc); /* Expected error codes from the cache lookup are: * 0 - found and returned via the cache * GPG_ERR_NOT_FOUND - marked in the cache as not available * GPG_ERR_EOF - cache miss. */ break; case DB_TYPE_KBX: err = be_kbx_search (ctrl, the_database.backend_handle, request, desc, ndesc); break; case DB_TYPE_SQLITE: err = be_sqlite_search (ctrl, the_database.backend_handle, request, desc, ndesc); break; default: log_error ("%s: unsupported database type %d\n", __func__, the_database.db_type); err = gpg_error (GPG_ERR_INTERNAL); break; } if (DBG_LOOKUP) log_debug ("%s: searched %s => %s\n", __func__, strdbtype (the_database.db_type), gpg_strerror (err)); request->any_search = 1; if (!err) { request->any_found = 1; } else if (gpg_err_code (err) == GPG_ERR_EOF) { if (the_database.db_type == DB_TYPE_CACHE && request->last_cached_valid) { if (request->last_cached_final) goto leave; } request->next_dbidx++; /* FIXME: We need to see which pubkey type we need to insert. */ be_cache_not_found (ctrl, PUBKEY_TYPE_UNKNOWN, desc, ndesc); err = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } leave: release_lock (ctrl); if (DBG_CLOCK) log_clock ("%s: leave (%s)", __func__, err? "not found" : "found"); return err; } /* Store; that is insert or update the key (BLOB,BLOBLEN). MODE * controls whether only updates or only inserts are allowed. */ gpg_error_t kbxd_store (ctrl_t ctrl, const void *blob, size_t bloblen, enum kbxd_store_modes mode) { gpg_error_t err; db_request_t request; char ubid[UBID_LEN]; enum pubkey_types pktype; int insert = 0; if (DBG_CLOCK) log_clock ("%s: enter", __func__); take_read_write_lock (ctrl); /* Allocate a handle object if none exists for this context. */ - if (!ctrl->opgp_req) + if (!ctrl->db_req) { - ctrl->opgp_req = xtrycalloc (1, sizeof *ctrl->opgp_req); - if (!ctrl->opgp_req) + ctrl->db_req = xtrycalloc (1, sizeof *ctrl->db_req); + if (!ctrl->db_req) { err = gpg_error_from_syserror (); goto leave; } } - request = ctrl->opgp_req; + request = ctrl->db_req; if (!the_database.db_type) { log_error ("%s: error: no database configured\n", __func__); err = gpg_error (GPG_ERR_NOT_INITIALIZED); goto leave; } /* Check whether to insert or update. */ err = be_ubid_from_blob (blob, bloblen, &pktype, ubid); if (err) goto leave; if (the_database.db_type == DB_TYPE_KBX) { err = be_kbx_seek (ctrl, the_database.backend_handle, request, ubid); if (!err) ; /* Found - need to update. */ else if (gpg_err_code (err) == GPG_ERR_EOF) insert = 1; /* Not found - need to insert. */ else { log_debug ("%s: searching fingerprint failed: %s\n", __func__, gpg_strerror (err)); goto leave; } if (insert) { if (mode == KBXD_STORE_UPDATE) err = gpg_error (GPG_ERR_CONFLICT); else err = be_kbx_insert (ctrl, the_database.backend_handle, request, pktype, blob, bloblen); } else /* Update. */ { if (mode == KBXD_STORE_INSERT) err = gpg_error (GPG_ERR_CONFLICT); else err = be_kbx_update (ctrl, the_database.backend_handle, request, pktype, blob, bloblen); } } else if (the_database.db_type == DB_TYPE_SQLITE) { err = be_sqlite_store (ctrl, the_database.backend_handle, request, mode, pktype, ubid, blob, bloblen); } else { log_error ("%s: unsupported database type %d\n", __func__, the_database.db_type); err = gpg_error (GPG_ERR_INTERNAL); } leave: release_lock (ctrl); if (DBG_CLOCK) log_clock ("%s: leave", __func__); return err; } /* Delete; remove the blob identified by UBID. */ gpg_error_t kbxd_delete (ctrl_t ctrl, const unsigned char *ubid) { gpg_error_t err; db_request_t request; if (DBG_CLOCK) log_clock ("%s: enter", __func__); take_read_write_lock (ctrl); /* Allocate a handle object if none exists for this context. */ - if (!ctrl->opgp_req) + if (!ctrl->db_req) { - ctrl->opgp_req = xtrycalloc (1, sizeof *ctrl->opgp_req); - if (!ctrl->opgp_req) + ctrl->db_req = xtrycalloc (1, sizeof *ctrl->db_req); + if (!ctrl->db_req) { err = gpg_error_from_syserror (); goto leave; } } - request = ctrl->opgp_req; + request = ctrl->db_req; if (!the_database.db_type) { log_error ("%s: error: no database configured\n", __func__); err = gpg_error (GPG_ERR_NOT_INITIALIZED); goto leave; } if (the_database.db_type == DB_TYPE_KBX) { err = be_kbx_seek (ctrl, the_database.backend_handle, request, ubid); if (!err) ; /* Found - we can delete. */ else if (gpg_err_code (err) == GPG_ERR_EOF) { err = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } else { log_debug ("%s: searching primary fingerprint failed: %s\n", __func__, gpg_strerror (err)); goto leave; } err = be_kbx_delete (ctrl, the_database.backend_handle, request); } else if (the_database.db_type == DB_TYPE_SQLITE) { err = be_sqlite_delete (ctrl, the_database.backend_handle, request, ubid); } else { log_error ("%s: unsupported database type %d\n", __func__, the_database.db_type); err = gpg_error (GPG_ERR_INTERNAL); } leave: release_lock (ctrl); if (DBG_CLOCK) log_clock ("%s: leave", __func__); return err; } diff --git a/kbx/kbxserver.c b/kbx/kbxserver.c index 4a5e1f48a..264c3be4e 100644 --- a/kbx/kbxserver.c +++ b/kbx/kbxserver.c @@ -1,875 +1,880 @@ /* kbxserver.c - Handle Assuan commands send to the 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+ */ #include #include #include #include #include #include #include #include #include #include #include "keyboxd.h" #include #include "../common/i18n.h" #include "../common/server-help.h" #include "../common/userids.h" #include "../common/asshelp.h" #include "../common/host2net.h" #include "frontend.h" #define PARM_ERROR(t) assuan_set_error (ctx, \ gpg_error (GPG_ERR_ASS_PARAMETER), (t)) #define set_error(e,t) (ctx ? assuan_set_error (ctx, gpg_error (e), (t)) \ /**/: gpg_error (e)) /* Control structure per connection. */ struct server_local_s { /* Data used to associate an Assuan context with local server data */ assuan_context_t assuan_ctx; /* The session id (a counter). */ unsigned int session_id; /* If this flag is set to true this process will be terminated after * the end of this session. */ int stopme; /* If the first both flags are set the assuan logging of data lines * is suppressed. The count variable is used to show the number of * non-logged bytes. */ size_t inhibit_data_logging_count; unsigned int inhibit_data_logging : 1; unsigned int inhibit_data_logging_now : 1; /* This flag is set if the last search command was called with --more. */ unsigned int search_expecting_more : 1; /* This flag is set if the last search command was successful. */ unsigned int search_any_found : 1; /* The first is the current search description as parsed by the * cmd_search. If more than one pattern is required, cmd_search * also allocates and sets multi_search_desc and * multi_search_desc_len. If a search description has ever been * allocated the allocated size is stored at * multi_search_desc_size. */ KEYBOX_SEARCH_DESC search_desc; KEYBOX_SEARCH_DESC *multi_search_desc; unsigned int multi_search_desc_size; unsigned int multi_search_desc_len; /* If not NULL write output to this stream instead of using D lines. */ estream_t outstream; }; /* Return the assuan contxt from the local server info in CTRL. */ static assuan_context_t get_assuan_ctx_from_ctrl (ctrl_t ctrl) { if (!ctrl || !ctrl->server_local) return NULL; return ctrl->server_local->assuan_ctx; } /* If OUTPUT has been used prepare the output FD for use. This needs * to be called by all functions which will in any way use * kbxd_write_data_line later. Whether the output goes to the output * stream is decided by this function. */ static gpg_error_t prepare_outstream (ctrl_t ctrl) { int fd; log_assert (ctrl && ctrl->server_local); if (ctrl->server_local->outstream) return 0; /* Already enabled. */ fd = translate_sys2libc_fd (assuan_get_output_fd (get_assuan_ctx_from_ctrl (ctrl)), 1); if (fd == -1) return 0; /* No Output command active. */ ctrl->server_local->outstream = es_fdopen_nc (fd, "w"); if (!ctrl->server_local->outstream) return gpg_err_code_from_syserror (); return 0; } /* The usual writen function; here with diagnostic output. */ static gpg_error_t kbxd_writen (estream_t fp, const void *buffer, size_t length) { gpg_error_t err; size_t nwritten; if (es_write (fp, buffer, length, &nwritten)) { err = gpg_error_from_syserror (); log_error ("error writing OUTPUT: %s\n", gpg_strerror (err)); } else if (length != nwritten) { err = gpg_error (GPG_ERR_EIO); log_error ("error writing OUTPUT: %s\n", "short write"); } else err = 0; return err; } /* A wrapper around assuan_send_data which makes debugging the output * in verbose mode easier. It also takes CTRL as argument. */ gpg_error_t kbxd_write_data_line (ctrl_t ctrl, const void *buffer_arg, size_t size) { const char *buffer = buffer_arg; assuan_context_t ctx = get_assuan_ctx_from_ctrl (ctrl); gpg_error_t err; if (!ctx) /* Oops - no assuan context. */ return gpg_error (GPG_ERR_NOT_PROCESSED); /* Write toa file descriptor if enabled. */ if (ctrl && ctrl->server_local && ctrl->server_local->outstream) { unsigned char lenbuf[4]; ulongtobuf (lenbuf, size); err = kbxd_writen (ctrl->server_local->outstream, lenbuf, 4); if (!err) err = kbxd_writen (ctrl->server_local->outstream, buffer, size); if (!err && es_fflush (ctrl->server_local->outstream)) { err = gpg_error_from_syserror (); log_error ("error writing OUTPUT: %s\n", gpg_strerror (err)); } goto leave; } /* If we do not want logging, enable it here. */ if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging) ctrl->server_local->inhibit_data_logging_now = 1; if (0 && opt.verbose && buffer && size) { /* Ease reading of output by limiting the line length. */ size_t n, nbytes; nbytes = size; do { n = nbytes > 64? 64 : nbytes; err = assuan_send_data (ctx, buffer, n); if (err) { gpg_err_set_errno (EIO); goto leave; } buffer += n; nbytes -= n; if (nbytes && (err=assuan_send_data (ctx, NULL, 0))) /* Flush line. */ { gpg_err_set_errno (EIO); goto leave; } } while (nbytes); } else { err = assuan_send_data (ctx, buffer, size); if (err) { gpg_err_set_errno (EIO); /* For use by data_line_cookie_write. */ goto leave; } } leave: if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging) { ctrl->server_local->inhibit_data_logging_count += size; ctrl->server_local->inhibit_data_logging_now = 0; } return err; } /* Helper to print a message while leaving a command. */ static gpg_error_t leave_cmd (assuan_context_t ctx, gpg_error_t err) { if (err && opt.verbose) { const char *name = assuan_get_command_name (ctx); if (!name) name = "?"; if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT) log_error ("command '%s' failed: %s\n", name, gpg_strerror (err)); else log_error ("command '%s' failed: %s <%s>\n", name, gpg_strerror (err), gpg_strsource (err)); } return err; } /* Handle OPTION commands. */ static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; if (!strcmp (key, "lc-messages")) { if (ctrl->lc_messages) xfree (ctrl->lc_messages); ctrl->lc_messages = xtrystrdup (value); if (!ctrl->lc_messages) return out_of_core (); } else err = gpg_error (GPG_ERR_UNKNOWN_OPTION); return err; } static const char hlp_search[] = - "SEARCH [--no-data] [[--more] PATTERN]\n" + "SEARCH [--no-data] [--openpgp|--x509] [[--more] PATTERN]\n" "\n" "Search for the keys identified by PATTERN. With --more more\n" "patterns to be used for the search are expected with the next\n" "command. With --no-data only the search status is returned but\n" - "not the actual data. See also \"NEXT\"."; + "not the actual data. With --openpgp or --x509 only the respective\n" + "keys are returned. See also \"NEXT\"."; static gpg_error_t cmd_search (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); - int opt_more, opt_no_data; + int opt_more, opt_no_data, opt_openpgp, opt_x509; gpg_error_t err; unsigned int n, k; opt_no_data = has_option (line, "--no-data"); opt_more = has_option (line, "--more"); + opt_openpgp = has_option (line, "--openpgp"); + opt_x509 = has_option (line, "--x509"); line = skip_options (line); ctrl->server_local->search_any_found = 0; if (!*line) { if (opt_more) { err = set_error (GPG_ERR_INV_ARG, "--more but no pattern"); goto leave; } else if (!*line && ctrl->server_local->search_expecting_more) { /* It would be too surprising to first set a pattern but * finally add no pattern to search the entire DB. */ err = set_error (GPG_ERR_INV_ARG, "--more pending but no pattern"); goto leave; } else /* No pattern - return the first item. */ { memset (&ctrl->server_local->search_desc, 0, sizeof ctrl->server_local->search_desc); ctrl->server_local->search_desc.mode = KEYDB_SEARCH_MODE_FIRST; } } else { err = classify_user_id (line, &ctrl->server_local->search_desc, 0); if (err) goto leave; } if (opt_more || ctrl->server_local->search_expecting_more) { /* More pattern are expected - store the current one and return * success. */ if (!ctrl->server_local->multi_search_desc_size) { n = 10; ctrl->server_local->multi_search_desc = xtrycalloc (n, sizeof *ctrl->server_local->multi_search_desc); if (!ctrl->server_local->multi_search_desc) { err = gpg_error_from_syserror (); goto leave; } ctrl->server_local->multi_search_desc_size = n; } if (ctrl->server_local->multi_search_desc_len == ctrl->server_local->multi_search_desc_size) { KEYBOX_SEARCH_DESC *desc; n = ctrl->server_local->multi_search_desc_size + 10; desc = xtrycalloc (n, sizeof *desc); if (!desc) { err = gpg_error_from_syserror (); goto leave; } for (k=0; k < ctrl->server_local->multi_search_desc_size; k++) desc[k] = ctrl->server_local->multi_search_desc[k]; xfree (ctrl->server_local->multi_search_desc); ctrl->server_local->multi_search_desc = desc; ctrl->server_local->multi_search_desc_size = n; } /* Actually store. */ ctrl->server_local->multi_search_desc [ctrl->server_local->multi_search_desc_len++] = ctrl->server_local->search_desc; if (opt_more) { /* We need to be called aagain with more pattern. */ ctrl->server_local->search_expecting_more = 1; goto leave; } ctrl->server_local->search_expecting_more = 0; /* Continue with the actual search. */ } else ctrl->server_local->multi_search_desc_len = 0; ctrl->server_local->inhibit_data_logging = 1; ctrl->server_local->inhibit_data_logging_now = 0; ctrl->server_local->inhibit_data_logging_count = 0; ctrl->no_data_return = opt_no_data; + ctrl->filter_opgp = opt_openpgp; + ctrl->filter_x509 = opt_x509; err = prepare_outstream (ctrl); if (err) ; else if (ctrl->server_local->multi_search_desc_len) err = kbxd_search (ctrl, ctrl->server_local->multi_search_desc, ctrl->server_local->multi_search_desc_len, 1); else err = kbxd_search (ctrl, &ctrl->server_local->search_desc, 1, 1); if (err) goto leave; /* Set a flag for use by NEXT. */ ctrl->server_local->search_any_found = 1; leave: if (err) ctrl->server_local->multi_search_desc_len = 0; ctrl->no_data_return = 0; ctrl->server_local->inhibit_data_logging = 0; return leave_cmd (ctx, err); } static const char hlp_next[] = "NEXT [--no-data]\n" "\n" "Get the next search result from a previous search."; static gpg_error_t cmd_next (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int opt_no_data; gpg_error_t err; opt_no_data = has_option (line, "--no-data"); line = skip_options (line); if (*line) { err = set_error (GPG_ERR_INV_ARG, "no args expected"); goto leave; } if (!ctrl->server_local->search_any_found) { err = set_error (GPG_ERR_NOTHING_FOUND, "no previous SEARCH"); goto leave; } ctrl->server_local->inhibit_data_logging = 1; ctrl->server_local->inhibit_data_logging_now = 0; ctrl->server_local->inhibit_data_logging_count = 0; ctrl->no_data_return = opt_no_data; err = prepare_outstream (ctrl); if (err) ; else if (ctrl->server_local->multi_search_desc_len) { /* The next condition should never be tru but we better handle * the first/next transition anyway. */ if (ctrl->server_local->multi_search_desc[0].mode == KEYDB_SEARCH_MODE_FIRST) ctrl->server_local->multi_search_desc[0].mode = KEYDB_SEARCH_MODE_NEXT; err = kbxd_search (ctrl, ctrl->server_local->multi_search_desc, ctrl->server_local->multi_search_desc_len, 0); } else { /* We need to do the transition from first to next here. */ if (ctrl->server_local->search_desc.mode == KEYDB_SEARCH_MODE_FIRST) ctrl->server_local->search_desc.mode = KEYDB_SEARCH_MODE_NEXT; err = kbxd_search (ctrl, &ctrl->server_local->search_desc, 1, 0); } if (err) goto leave; leave: ctrl->no_data_return = 0; ctrl->server_local->inhibit_data_logging = 0; return leave_cmd (ctx, err); } static const char hlp_store[] = "STORE [--update|--insert]\n" "\n" "Insert a key into the database. Whether to insert or update\n" "the key is decided by looking at the primary key's fingerprint.\n" "With option --update the key must already exist.\n" "With option --insert the key must not already exist.\n" "The actual key material is requested by this function using\n" " INQUIRE BLOB"; static gpg_error_t cmd_store (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int opt_update, opt_insert; enum kbxd_store_modes mode; gpg_error_t err; unsigned char *value = NULL; size_t valuelen; opt_update = has_option (line, "--update"); opt_insert = has_option (line, "--insert"); line = skip_options (line); if (*line) { err = set_error (GPG_ERR_INV_ARG, "no args expected"); goto leave; } if (opt_update && !opt_insert) mode = KBXD_STORE_UPDATE; else if (!opt_update && opt_insert) mode = KBXD_STORE_INSERT; else mode = KBXD_STORE_AUTO; /* Ask for the key material. */ err = assuan_inquire (ctx, "BLOB", &value, &valuelen, 0); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data received. */ { err = gpg_error (GPG_ERR_MISSING_VALUE); goto leave; } err = kbxd_store (ctrl, value, valuelen, mode); leave: xfree (value); return leave_cmd (ctx, err); } static const char hlp_delete[] = "DELETE \n" "\n" "Delete a key into the database. The UBID identifies the key.\n"; static gpg_error_t cmd_delete (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; int n; unsigned char ubid[UBID_LEN]; line = skip_options (line); if (!*line) { err = set_error (GPG_ERR_INV_ARG, "UBID missing"); goto leave; } /* Skip an optional UBID identifier character. */ if (*line == '^' && line[1]) line++; if ((n=hex2bin (line, ubid, UBID_LEN)) < 0) { err = set_error (GPG_ERR_INV_USER_ID, "invalid UBID"); goto leave; } if (line[n]) { err = set_error (GPG_ERR_INV_ARG, "garbage after UBID"); goto leave; } err = kbxd_delete (ctrl, ubid); leave: return leave_cmd (ctx, err); } static const char hlp_getinfo[] = "GETINFO \n" "\n" "Multi purpose command to return certain information. \n" "Supported values of WHAT are:\n" "\n" "version - Return the version of the program.\n" "pid - Return the process id of the server.\n" "socket_name - Return the name of the socket.\n" "session_id - Return the current session_id.\n" "getenv NAME - Return value of envvar NAME\n"; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; char numbuf[50]; if (!strcmp (line, "version")) { const char *s = VERSION; err = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "pid")) { snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); err = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "socket_name")) { const char *s = get_kbxd_socket_name (); if (!s) s = "[none]"; err = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "session_id")) { snprintf (numbuf, sizeof numbuf, "%u", ctrl->server_local->session_id); err = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strncmp (line, "getenv", 6) && (line[6] == ' ' || line[6] == '\t' || !line[6])) { line += 6; while (*line == ' ' || *line == '\t') line++; if (!*line) err = gpg_error (GPG_ERR_MISSING_VALUE); else { const char *s = getenv (line); if (!s) err = set_error (GPG_ERR_NOT_FOUND, "No such envvar"); else err = assuan_send_data (ctx, s, strlen (s)); } } else err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); return leave_cmd (ctx, err); } static const char hlp_killkeyboxd[] = "KILLKEYBOXD\n" "\n" "This command allows a user - given sufficient permissions -\n" "to kill this keyboxd process.\n"; static gpg_error_t cmd_killkeyboxd (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; ctrl->server_local->stopme = 1; assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); - return gpg_error (GPG_ERR_EOF); + return 0; } static const char hlp_reloadkeyboxd[] = "RELOADKEYBOXD\n" "\n" "This command is an alternative to SIGHUP\n" "to reload the configuration."; static gpg_error_t cmd_reloadkeyboxd (assuan_context_t ctx, char *line) { (void)ctx; (void)line; kbxd_sighup_action (); return 0; } static const char hlp_output[] = "OUTPUT FD[=]\n" "\n" "Set the file descriptor to write the output data to N. If N is not\n" "given and the operating system supports file descriptor passing, the\n" "file descriptor currently in flight will be used."; /* Tell the assuan library about our commands. */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; assuan_handler_t handler; const char * const help; } table[] = { { "SEARCH", cmd_search, hlp_search }, { "NEXT", cmd_next, hlp_next }, { "STORE", cmd_store, hlp_store }, { "DELETE", cmd_delete, hlp_delete }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "OUTPUT", NULL, hlp_output }, { "KILLKEYBOXD",cmd_killkeyboxd,hlp_killkeyboxd }, { "RELOADKEYBOXD",cmd_reloadkeyboxd,hlp_reloadkeyboxd }, { NULL, NULL } }; int i, j, rc; for (i=j=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, table[i].help); if (rc) return rc; } return 0; } /* Note that we do not reset the list of configured keyservers. */ static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; (void)ctrl; return 0; } /* This function is called by our assuan log handler to test whether a * log message shall really be printed. The function must return * false to inhibit the logging of MSG. CAT gives the requested log * category. MSG might be NULL. */ int kbxd_assuan_log_monitor (assuan_context_t ctx, unsigned int cat, const char *msg) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)cat; (void)msg; if (!ctrl || !ctrl->server_local) return 1; /* Can't decide - allow logging. */ if (!ctrl->server_local->inhibit_data_logging) return 1; /* Not requested - allow logging. */ /* Disallow logging if *_now is true. */ return !ctrl->server_local->inhibit_data_logging_now; } /* Startup the server and run the main command loop. With FD = -1, * use stdin/stdout. SESSION_ID is either 0 or a unique number * identifying a session. */ void kbxd_start_command_handler (ctrl_t ctrl, gnupg_fd_t fd, unsigned int session_id) { static const char hello[] = "Keyboxd " VERSION " at your service"; static char *hello_line; int rc; assuan_context_t ctx; ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local); if (!ctrl->server_local) { log_error (_("can't allocate control structure: %s\n"), gpg_strerror (gpg_error_from_syserror ())); xfree (ctrl); return; } rc = assuan_new (&ctx); if (rc) { log_error (_("failed to allocate assuan context: %s\n"), gpg_strerror (rc)); kbxd_exit (2); } if (fd == GNUPG_INVALID_FD) { assuan_fd_t filedes[2]; filedes[0] = assuan_fdopen (0); filedes[1] = assuan_fdopen (1); rc = assuan_init_pipe_server (ctx, filedes); } else { rc = assuan_init_socket_server (ctx, fd, (ASSUAN_SOCKET_SERVER_ACCEPTED |ASSUAN_SOCKET_SERVER_FDPASSING)); } if (rc) { assuan_release (ctx); log_error (_("failed to initialize the server: %s\n"), gpg_strerror (rc)); kbxd_exit (2); } rc = register_commands (ctx); if (rc) { log_error (_("failed to the register commands with Assuan: %s\n"), gpg_strerror(rc)); kbxd_exit (2); } if (!hello_line) { hello_line = xtryasprintf ("Home: %s\n" "Config: %s\n" "%s", gnupg_homedir (), /*opt.config_filename? opt.config_filename :*/ "[none]", hello); } ctrl->server_local->assuan_ctx = ctx; assuan_set_pointer (ctx, ctrl); assuan_set_hello_line (ctx, hello_line); assuan_register_option_handler (ctx, option_handler); assuan_register_reset_notify (ctx, reset_notify); ctrl->server_local->session_id = session_id; /* The next call enable the use of status_printf. */ set_assuan_context_func (get_assuan_ctx_from_ctrl); for (;;) { rc = assuan_accept (ctx); if (rc == -1) break; if (rc) { log_info (_("Assuan accept problem: %s\n"), gpg_strerror (rc)); break; } #ifndef HAVE_W32_SYSTEM if (opt.verbose) { assuan_peercred_t peercred; if (!assuan_get_peercred (ctx, &peercred)) log_info ("connection from process %ld (%ld:%ld)\n", (long)peercred->pid, (long)peercred->uid, (long)peercred->gid); } #endif rc = assuan_process (ctx); if (rc) { log_info (_("Assuan processing failed: %s\n"), gpg_strerror (rc)); continue; } } assuan_close_output_fd (ctx); set_assuan_context_func (NULL); ctrl->server_local->assuan_ctx = NULL; assuan_release (ctx); if (ctrl->server_local->stopme) kbxd_exit (0); if (ctrl->refcount) log_error ("oops: connection control structure still referenced (%d)\n", ctrl->refcount); else { xfree (ctrl->server_local->multi_search_desc); xfree (ctrl->server_local); ctrl->server_local = NULL; } } diff --git a/kbx/keyboxd.h b/kbx/keyboxd.h index f0b705aad..22988bf1b 100644 --- a/kbx/keyboxd.h +++ b/kbx/keyboxd.h @@ -1,165 +1,170 @@ /* keyboxd.h - Global definitions for keyboxd * Copyright (C) 2018 Werner Koch * * 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 . */ #ifndef KEYBOXD_H #define KEYBOXD_H #ifdef GPG_ERR_SOURCE_DEFAULT #error GPG_ERR_SOURCE_DEFAULT already defined #endif #define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_KEYBOX #include #include #include "../common/util.h" #include "../common/membuf.h" #include "../common/sysutils.h" /* (gnupg_fd_t) */ /* A large struct name "opt" to keep global flags */ EXTERN_UNLESS_MAIN_MODULE struct { unsigned int debug; /* Debug flags (DBG_foo_VALUE) */ int verbose; /* Verbosity level */ int quiet; /* Be as quiet as possible */ int dry_run; /* Don't change any persistent data */ int batch; /* Batch mode */ /* True if we are running detached from the tty. */ int running_detached; } opt; /* Bit values for the --debug option. */ #define DBG_MPI_VALUE 2 /* debug mpi details */ #define DBG_CRYPTO_VALUE 4 /* debug low level crypto */ #define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */ #define DBG_CACHE_VALUE 64 /* debug the caching */ #define DBG_MEMSTAT_VALUE 128 /* show memory statistics */ #define DBG_HASHING_VALUE 512 /* debug hashing operations */ #define DBG_IPC_VALUE 1024 /* Enable Assuan debugging. */ #define DBG_CLOCK_VALUE 4096 /* debug timings (required build option). */ #define DBG_LOOKUP_VALUE 8192 /* debug the key lookup */ /* Test macros for the debug option. */ #define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) #define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE) #define DBG_CACHE (opt.debug & DBG_CACHE_VALUE) #define DBG_HASHING (opt.debug & DBG_HASHING_VALUE) #define DBG_IPC (opt.debug & DBG_IPC_VALUE) #define DBG_CLOCK (opt.debug & DBG_CLOCK_VALUE) #define DBG_LOOKUP (opt.debug & DBG_LOOKUP_VALUE) /* Declaration of a database request object. This is used for all * database operation (search, insert, update, delete). */ struct db_request_s; typedef struct db_request_s *db_request_t; /* Forward reference for local definitions in command.c. */ struct server_local_s; #if SIZEOF_UNSIGNED_LONG == 8 # define SERVER_CONTROL_MAGIC 0x6b6579626f786420 #else # define SERVER_CONTROL_MAGIC 0x6b627864 #endif /* Collection of data per session (aka connection). */ struct server_control_s { unsigned long magic;/* Always has SERVER_CONTROL_MAGIC. */ int refcount; /* Count additional references to this object. */ /* Private data used to fire up the connection thread. We use this * structure do avoid an extra allocation for only a few bytes while * spawning a new connection thread. */ struct { gnupg_fd_t fd; } thread_startup; /* Private data of the server (kbxserver.c). */ struct server_local_s *server_local; /* Environment settings for the connection. */ char *lc_messages; /* Miscellaneous info on the connection. */ unsigned long client_pid; int client_uid; - /* Two database request objects used with a connection. They are + /* The database request object used with a connection. It is * auto-created as needed. */ - db_request_t opgp_req; - db_request_t x509_req; + db_request_t db_req; /* Flags for the current request. */ - unsigned int no_data_return : 1; /* Used by SEARCH and NEXT. */ + + /* If the any of the filter flags are set a search returns only + * results with a blob type matching one of these filter flags. */ + unsigned int filter_opgp : 1; + unsigned int filter_x509 : 1; + /* Used by SEARCH and NEXT. */ + unsigned int no_data_return : 1; }; /* This is a special version of the usual _() gettext macro. It * assumes a server connection control variable with the name "ctrl" * and uses that to translate a string according to the locale set for * the connection. The macro LunderscoreIMPL is used by i18n to * actually define the inline function when needed. */ #if defined (ENABLE_NLS) || defined (USE_SIMPLE_GETTEXT) #define L_(a) keyboxd_Lunderscore (ctrl, (a)) #define LunderscorePROTO \ static inline const char *keyboxd_Lunderscore (ctrl_t ctrl, \ const char *string) \ GNUPG_GCC_ATTR_FORMAT_ARG(2); #define LunderscoreIMPL \ static inline const char * \ keyboxd_Lunderscore (ctrl_t ctrl, const char *string) \ { \ return ctrl? i18n_localegettext (ctrl->lc_messages, string) \ /* */: gettext (string); \ } #else #define L_(a) (a) #endif enum kbxd_store_modes { KBXD_STORE_AUTO = 0, /* Update or insert. */ KBXD_STORE_INSERT, /* Allow only inserts. */ KBXD_STORE_UPDATE /* Allow only updates. */ }; /*-- keyboxd.c --*/ void kbxd_exit (int rc) GPGRT_ATTR_NORETURN; void kbxd_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what, int printchar, int current, int total), ctrl_t ctrl); const char *get_kbxd_socket_name (void); int get_kbxd_active_connection_count (void); void kbxd_sighup_action (void); /*-- kbxserver.c --*/ gpg_error_t kbxd_write_data_line (ctrl_t ctrl, const void *buffer_arg, size_t size); void kbxd_start_command_handler (ctrl_t, gnupg_fd_t, unsigned int); #endif /*KEYBOXD_H*/