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*/