diff --git a/g10/call-keyboxd.c b/g10/call-keyboxd.c
index b54dbffc4..bb9901ab9 100644
--- a/g10/call-keyboxd.c
+++ b/g10/call-keyboxd.c
@@ -1,811 +1,831 @@
/* 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, 1);
if (err)
{
assuan_release (kbl->ctx);
xfree (kbl);
return err;
}
/* For thread-saftey we add it to the list and retry; this is
* easier than to employ a lock. */
kbl->next = ctrl->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;
*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)
{
err = keydb_parse_keyblock (hd->kbl->search_result,
hd->last_ubid_valid? hd->last_pk_no : 0,
hd->last_ubid_valid? hd->last_uid_no : 0,
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;
unsigned int n;
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 (!(n=hex2fixedbuf (s, hd->last_ubid, sizeof hd->last_ubid)))
err = gpg_error (GPG_ERR_INV_VALUE);
else
{
hd->last_ubid_valid = 1;
hd->last_uid_no = 0;
hd->last_pk_no = 0;
s += n;
while (*s && !spacep (s))
s++;
while (spacep (s))
s++;
if (*s)
{
hd->last_uid_no = atoi (s);
while (*s && !spacep (s))
s++;
while (spacep (s))
s++;
if (*s)
hd->last_pk_no = atoi (s);
}
}
}
}
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 --openpgp -- =%s", desc[0].u.name);
- break;
-
- case KEYDB_SEARCH_MODE_SUBSTR:
- snprintf (line, sizeof line, "SEARCH --openpgp -- *%s", desc[0].u.name);
- break;
-
- case KEYDB_SEARCH_MODE_MAIL:
- snprintf (line, sizeof line, "SEARCH --openpgp -- <%s", desc[0].u.name);
- break;
-
- case KEYDB_SEARCH_MODE_MAILSUB:
- snprintf (line, sizeof line, "SEARCH --openpgp -- @%s", desc[0].u.name);
- break;
-
- case KEYDB_SEARCH_MODE_MAILEND:
- snprintf (line, sizeof line, "SEARCH --openpgp -- .%s", desc[0].u.name);
- break;
-
- case KEYDB_SEARCH_MODE_WORDS:
- snprintf (line, sizeof line, "SEARCH --openpgp -- +%s", desc[0].u.name);
- break;
-
- case KEYDB_SEARCH_MODE_SHORT_KID:
- 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 --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 --openpgp -- 0x%s", hexfpr);
- }
- break;
-
- case KEYDB_SEARCH_MODE_ISSUER:
- 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 --openpgp -- #%s", desc[0].u.name);
- break;
-
- case KEYDB_SEARCH_MODE_SUBJECT:
- snprintf (line, sizeof line, "SEARCH --openpgp -- /%s", desc[0].u.name);
- break;
-
- case KEYDB_SEARCH_MODE_KEYGRIP:
+ for (i = 0; i < ndesc; i++)
+ if (desc->mode == KEYDB_SEARCH_MODE_FIRST)
{
- unsigned char hexgrip[KEYGRIP_LEN * 2 + 1];
- bin2hex (desc[0].u.grip, KEYGRIP_LEN, hexgrip);
- snprintf (line, sizeof line, "SEARCH --openpgp -- &%s", hexgrip);
+ /* If any description has mode FIRST, this item trumps all
+ * other descriptions. */
+ snprintf (line, sizeof line, "SEARCH --openpgp");
+ goto do_search;
}
- 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 --openpgp -- ^%s", hexubid);
- }
- break;
-
- case KEYDB_SEARCH_MODE_FIRST:
- snprintf (line, sizeof line, "SEARCH --openpgp");
- break;
+ for ( ; ndesc; desc++, ndesc--)
+ {
+ const char *more = ndesc > 1 ? "--openpgp --more" : "--openpgp";
- case KEYDB_SEARCH_MODE_NEXT:
- log_debug ("%s: mode next - we should not get to here!\n", __func__);
- snprintf (line, sizeof line, "NEXT");
- break;
+ switch (desc->mode)
+ {
+ case KEYDB_SEARCH_MODE_EXACT:
+ snprintf (line, sizeof line, "SEARCH %s -- =%s", more, desc->u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_SUBSTR:
+ snprintf (line, sizeof line, "SEARCH %s -- *%s", more, desc->u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_MAIL:
+ snprintf (line, sizeof line, "SEARCH %s -- <%s", more, desc->u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_MAILSUB:
+ snprintf (line, sizeof line, "SEARCH %s -- @%s", more, desc->u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_MAILEND:
+ snprintf (line, sizeof line, "SEARCH %s -- .%s", more, desc->u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_WORDS:
+ snprintf (line, sizeof line, "SEARCH %s -- +%s", more, desc->u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_SHORT_KID:
+ snprintf (line, sizeof line, "SEARCH %s -- 0x%08lX", more,
+ (ulong)desc->u.kid[1]);
+ break;
+
+ case KEYDB_SEARCH_MODE_LONG_KID:
+ snprintf (line, sizeof line, "SEARCH %s -- 0x%08lX%08lX", more,
+ (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->fprlen <= MAX_FINGERPRINT_LEN);
+ bin2hex (desc->u.fpr, desc->fprlen, hexfpr);
+ snprintf (line, sizeof line, "SEARCH %s -- 0x%s", more, hexfpr);
+ }
+ break;
+
+ case KEYDB_SEARCH_MODE_ISSUER:
+ snprintf (line, sizeof line, "SEARCH %s -- #/%s", more, desc->u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_ISSUER_SN:
+ case KEYDB_SEARCH_MODE_SN:
+ snprintf (line, sizeof line, "SEARCH %s -- #%s", more, desc->u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_SUBJECT:
+ snprintf (line, sizeof line, "SEARCH %s -- /%s", more, desc->u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_KEYGRIP:
+ {
+ unsigned char hexgrip[KEYGRIP_LEN * 2 + 1];
+ bin2hex (desc->u.grip, KEYGRIP_LEN, hexgrip);
+ snprintf (line, sizeof line, "SEARCH %s -- &%s", more, hexgrip);
+ }
+ break;
+
+ case KEYDB_SEARCH_MODE_UBID:
+ {
+ unsigned char hexubid[UBID_LEN * 2 + 1];
+ bin2hex (desc->u.ubid, UBID_LEN, hexubid);
+ snprintf (line, sizeof line, "SEARCH %s -- ^%s", more, hexubid);
+ }
+ 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;
+
+ case KEYDB_SEARCH_MODE_FIRST:
+ log_debug ("%s: mode first - we should not get to here!\n", __func__);
+ /*fallthru*/
+ default:
+ err = gpg_error (GPG_ERR_INV_ARG);
+ goto leave;
+ }
- default:
- err = gpg_error (GPG_ERR_INV_ARG);
- goto leave;
+ if (ndesc > 1)
+ {
+ err = kbx_client_data_simple (hd->kbl->kcd, line);
+ if (err)
+ goto leave;
+ }
}
+ while (ndesc);
+
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 (DBG_LOOKUP && hd->last_ubid_valid)
log_printhex (hd->last_ubid, 20, "found UBID (%d,%d):",
hd->last_uid_no, hd->last_pk_no);
}
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 6ba1886d6..0b5155a8f 100644
--- a/kbx/backend-sqlite.c
+++ b/kbx/backend-sqlite.c
@@ -1,1610 +1,1619 @@
/* backend-sqlite.c - SQLite based backend for keyboxd
* Copyright (C) 2019, 2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include
#include
#include
#include
#include
#include
#include
#include "keyboxd.h"
#include "../common/i18n.h"
#include "../common/mbox-util.h"
#include "backend.h"
#include "keybox-search-desc.h"
#include "keybox-defs.h" /* (for the openpgp parser) */
/* Add replacement error codes; GPGRT provides SQL error codes from
* version 1.37 on. */
#if GPGRT_VERSION_NUMBER < 0x012500 /* 1.37 */
static GPGRT_INLINE gpg_error_t
gpg_err_code_from_sqlite (int sqlres)
{
return sqlres? 1500 + (sqlres & 0xff) : 0;
}
#define GPG_ERR_SQL_OK 1500
#define GPG_ERR_SQL_ROW 1600
#define GPG_ERR_SQL_DONE 1601
#endif /*GPGRT_VERSION_NUMBER*/
/* Our definition of the backend handle. */
struct backend_handle_s
{
enum database_types db_type; /* Always DB_TYPE_SQLITE. */
unsigned int backend_id; /* Always the id of the backend. */
char filename[1];
};
/* Definition of local request data. */
struct be_sqlite_local_s
{
/* The statement object of the current select command. */
sqlite3_stmt *select_stmt;
/* The column numbers for UIDNO and SUBKEY or 0. */
int select_col_uidno;
int select_col_subkey;
/* 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 current description index. */
+ unsigned int descidx;
+
/* The select statement has been executed with success. */
int select_done;
/* The last row has already been reached. */
int select_eof;
};
/* The Mutex we use to protect all our SQLite calls. */
static npth_mutex_t database_mutex = NPTH_MUTEX_INITIALIZER;
/* The one and only database handle. */
static sqlite3 *database_hd;
/* A lockfile used make sure only we are accessing the database. */
static dotlock_t database_lock;
static struct
{
const char *sql;
int special;
} table_definitions[] =
{
{ "PRAGMA foreign_keys = ON" },
/* Table to store config values:
* Standard name value pairs:
* dbversion = 1
* created =
*/
{ "CREATE TABLE IF NOT EXISTS config ("
"name TEXT NOT NULL,"
"value TEXT NOT NULL "
")", 1 },
/* The actual data; either X.509 certificates or OpenPGP
* keyblocks. */
{ "CREATE TABLE IF NOT EXISTS pubkey ("
/* The 20 octet truncated primary-fpr */
"ubid BLOB NOT NULL PRIMARY KEY,"
/* The type of the public key: 1 = openpgp, 2 = X.509. */
"type INTEGER NOT NULL,"
/* The Ephemeral flag as used by gpgsm. Values: 0 or 1. */
"ephemeral INTEGER NOT NULL DEFAULT 0,"
/* The Revoked flag as set by gpgsm. Values: 0 or 1. */
"revoked INTEGER NOT NULL DEFAULT 0,"
/* The OpenPGP keyblock or X.509 certificate. */
"keyblob BLOB NOT NULL"
")" },
/* Table with fingerprints and keyids of OpenPGP and X.509 keys.
* It is also used for the primary key and the X.509 fingerprint
* because we want to be able to use the keyid and keygrip. */
{ "CREATE TABLE IF NOT EXISTS fingerprint ("
/* The fingerprint, for OpenPGP either 20 octets or 32 octets;
* for X.509 it is the same as the UBID. */
"fpr BLOB NOT NULL PRIMARY KEY,"
/* The long keyid as a 64 bit blob. */
"kid BLOB NOT NULL,"
/* The keygrip for this key. */
"keygrip BLOB NOT NULL,"
/* 0 = primary or X.509, > 0 = subkey. Also used as
* order number for the keys similar to uidno. */
"subkey INTEGER NOT NULL,"
/* The Unique Blob ID (possibly truncated fingerprint). */
"ubid BLOB NOT NULL REFERENCES pubkey"
")" },
/* Indices for the fingerprint table. */
{ "CREATE INDEX IF NOT EXISTS fingerprintidx0 on fingerprint (ubid)" },
{ "CREATE INDEX IF NOT EXISTS fingerprintidx1 on fingerprint (fpr)" },
{ "CREATE INDEX IF NOT EXISTS fingerprintidx2 on fingerprint (keygrip)" },
/* Table to allow fast access via user ids or mail addresses. */
{ "CREATE TABLE IF NOT EXISTS userid ("
/* The full user id - for X.509 the Subject or altSubject. */
"uid TEXT NOT NULL,"
/* The mail address if available or NULL. */
"addrspec TEXT,"
/* The type of the public key: 1 = openpgp, 2 = X.509. */
"type INTEGER NOT NULL,"
/* The order number of the user id within the keyblock or
* certificates. For X.509 0 is reserved for the issuer, 1 the
* subject, 2 and up the altSubjects. For OpenPGP this starts
* with 1 for the first user id in the keyblock. */
"uidno INTEGER NOT NULL,"
/* The Unique Blob ID (possibly truncated fingerprint). */
"ubid BLOB NOT NULL REFERENCES pubkey"
")" },
/* Indices for the userid table. */
{ "CREATE INDEX IF NOT EXISTS userididx0 on userid (ubid)" },
{ "CREATE INDEX IF NOT EXISTS userididx1 on userid (uid)" },
{ "CREATE INDEX IF NOT EXISTS userididx3 on userid (addrspec)" },
/* Table to allow fast access via s/n + issuer DN (X.509 only). */
{ "CREATE TABLE IF NOT EXISTS issuer ("
/* The hex encoded S/N. */
"sn TEXT NOT NULL,"
/* The RFC2253 issuer DN. */
"dn TEXT NOT NULL,"
/* The Unique Blob ID (usually the truncated fingerprint). */
"ubid BLOB NOT NULL REFERENCES pubkey"
")" },
{ "CREATE INDEX IF NOT EXISTS issueridx1 on issuer (dn)" }
};
/* Take a lock for accessing SQLite. */
static void
acquire_mutex (void)
{
int res = npth_mutex_lock (&database_mutex);
if (res)
log_fatal ("failed to acquire database lock: %s\n",
gpg_strerror (gpg_error_from_errno (res)));
}
/* Release a lock. */
static void
release_mutex (void)
{
int res = npth_mutex_unlock (&database_mutex);
if (res)
log_fatal ("failed to release database db lock: %s\n",
gpg_strerror (gpg_error_from_errno (res)));
}
static void
show_sqlstr (const char *sqlstr)
{
if (!opt.verbose)
return;
log_info ("(SQL: %s)\n", sqlstr);
}
static void
show_sqlstmt (sqlite3_stmt *stmt)
{
char *p;
if (!opt.verbose)
return;
p = sqlite3_expanded_sql (stmt);
if (p)
log_info ("(SQL: %s)\n", p);
sqlite3_free (p);
}
static gpg_error_t
diag_prepare_err (int res, const char *sqlstr)
{
gpg_error_t err;
err = gpg_error (gpg_err_code_from_sqlite (res));
show_sqlstr (sqlstr);
log_error ("error preparing SQL statement: %s\n", sqlite3_errstr (res));
return err;
}
static gpg_error_t
diag_bind_err (int res, sqlite3_stmt *stmt)
{
gpg_error_t err;
err = gpg_error (gpg_err_code_from_sqlite (res));
show_sqlstmt (stmt);
log_error ("error binding a value to an SQL statement: %s\n",
sqlite3_errstr (res));
return err;
}
static gpg_error_t
diag_step_err (int res, sqlite3_stmt *stmt)
{
gpg_error_t err;
err = gpg_error (gpg_err_code_from_sqlite (res));
show_sqlstmt (stmt);
log_error ("error executing SQL statement: %s\n", sqlite3_errstr (res));
return err;
}
/* We store the keyid in the database as an 8 byte blob. This
* function converts it from the usual u32[2] array. BUFFER is a
* caller provided buffer of at least 8 bytes; a pointer to that
* buffer is the return value. */
static GPGRT_INLINE unsigned char *
kid_from_u32 (u32 *keyid, unsigned char *buffer)
{
buffer[0] = keyid[0] >> 24;
buffer[1] = keyid[0] >> 16;
buffer[2] = keyid[0] >> 8;
buffer[3] = keyid[0];
buffer[4] = keyid[1] >> 24;
buffer[5] = keyid[1] >> 16;
buffer[6] = keyid[1] >> 8;
buffer[7] = keyid[1];
return buffer;
}
/* Run an SQL reset on STMT. */
static gpg_error_t
run_sql_reset (sqlite3_stmt *stmt)
{
gpg_error_t err;
int res;
res = sqlite3_reset (stmt);
if (res)
{
err = gpg_error (gpg_err_code_from_sqlite (res));
show_sqlstmt (stmt);
log_error ("error executing SQL reset: %s\n", sqlite3_errstr (res));
}
else
err = 0;
return err;
}
/* Run an SQL prepare for SQLSTR and return a statement at R_STMT. If
* EXTRA is not NULL that part is appended to the SQL statement. */
static gpg_error_t
run_sql_prepare (const char *sqlstr, const char *extra, sqlite3_stmt **r_stmt)
{
gpg_error_t err;
int res;
char *buffer = NULL;
if (extra)
{
buffer = strconcat (sqlstr, extra, NULL);
if (!buffer)
return gpg_error_from_syserror ();
sqlstr = buffer;
}
res = sqlite3_prepare_v2 (database_hd, sqlstr, -1, r_stmt, NULL);
if (res)
err = diag_prepare_err (res, sqlstr);
else
err = 0;
xfree (buffer);
return err;
}
/* Helper to bind a BLOB parameter to a statement. */
static gpg_error_t
run_sql_bind_blob (sqlite3_stmt *stmt, int no,
const void *blob, size_t bloblen)
{
gpg_error_t err;
int res;
res = sqlite3_bind_blob (stmt, no, blob, bloblen, SQLITE_TRANSIENT);
if (res)
err = diag_bind_err (res, stmt);
else
err = 0;
return err;
}
/* Helper to bind an INTEGER parameter to a statement. */
static gpg_error_t
run_sql_bind_int (sqlite3_stmt *stmt, int no, int value)
{
gpg_error_t err;
int res;
res = sqlite3_bind_int (stmt, no, value);
if (res)
err = diag_bind_err (res, stmt);
else
err = 0;
return err;
}
/* Helper to bind a string parameter to a statement. VALUE is allowed
* to be NULL to bind NULL. */
static gpg_error_t
run_sql_bind_ntext (sqlite3_stmt *stmt, int no,
const char *value, size_t valuelen)
{
gpg_error_t err;
int res;
res = sqlite3_bind_text (stmt, no, value, value? valuelen:0,
SQLITE_TRANSIENT);
if (res)
err = diag_bind_err (res, stmt);
else
err = 0;
return err;
}
/* Helper to bind a string parameter to a statement. VALUE is allowed
* to be NULL to bind NULL. */
static gpg_error_t
run_sql_bind_text (sqlite3_stmt *stmt, int no, const char *value)
{
return run_sql_bind_ntext (stmt, no, value, value? strlen (value):0);
}
/* Helper to bind a string parameter to a statement. VALUE is allowed
* to be NULL to bind NULL. A non-NULL VALUE is clamped with percent
* signs. */
static gpg_error_t
run_sql_bind_text_like (sqlite3_stmt *stmt, int no, const char *value)
{
gpg_error_t err;
int res;
char *buf;
if (!value)
{
res = sqlite3_bind_null (stmt, no);
buf = NULL;
}
else
{
buf = xtrymalloc (strlen (value) + 2 + 1);
if (!buf)
return gpg_error_from_syserror ();
*buf = '%';
strcpy (buf+1, value);
strcat (buf+1, "%");
res = sqlite3_bind_text (stmt, no, buf, strlen (buf), SQLITE_TRANSIENT);
}
if (res)
err = diag_bind_err (res, stmt);
else
err = 0;
xfree (buf);
return err;
}
/* Wrapper around sqlite3_step for use with simple functions. */
static gpg_error_t
run_sql_step (sqlite3_stmt *stmt)
{
gpg_error_t err;
int res;
show_sqlstmt (stmt);
res = sqlite3_step (stmt);
if (res != SQLITE_DONE)
err = diag_step_err (res, stmt);
else
err = 0;
return err;
}
/* Wrapper around sqlite3_step for use with select. This version does
* not print diags for SQLITE_DONE or SQLITE_ROW but returns them as
* gpg error codes. */
static gpg_error_t
run_sql_step_for_select (sqlite3_stmt *stmt)
{
gpg_error_t err;
int res;
res = sqlite3_step (stmt);
if (res == SQLITE_DONE || res == SQLITE_ROW)
err = gpg_error (gpg_err_code_from_sqlite (res));
else
{
/* SQL_OK is unexpected for a select so in this case we return
* the OK error code by bypassing the special mapping. */
if (!res)
err = gpg_error (GPG_ERR_SQL_OK);
else
err = gpg_error (gpg_err_code_from_sqlite (res));
show_sqlstmt (stmt);
log_error ("error running SQL step: %s\n", sqlite3_errstr (res));
}
return err;
}
/* Run the simple SQL statement in SQLSTR. If UBID is not NULL this
* will be bound to ?1 in SQLSTR. This command may not be used for
* select or other command which return rows. */
static gpg_error_t
run_sql_statement_bind_ubid (const char *sqlstr, const unsigned char *ubid)
{
gpg_error_t err;
sqlite3_stmt *stmt;
err = run_sql_prepare (sqlstr, NULL, &stmt);
if (err)
goto leave;
if (ubid)
{
err = run_sql_bind_blob (stmt, 1, ubid, UBID_LEN);
if (err)
goto leave;
}
err = run_sql_step (stmt);
sqlite3_finalize (stmt);
if (err)
goto leave;
leave:
return err;
}
/* Run the simple SQL statement in SQLSTR. This command may not be used
* for select or other command which return rows. */
static gpg_error_t
run_sql_statement (const char *sqlstr)
{
return run_sql_statement_bind_ubid (sqlstr, NULL);
}
/* Create and initialize a new SQL database file if it does not
* exists; else open it and check that all required objects are
* available. */
static gpg_error_t
create_or_open_database (const char *filename)
{
gpg_error_t err;
int res;
int idx;
if (database_hd)
return 0; /* Already initialized. */
acquire_mutex ();
/* To avoid races with other temporary instances of keyboxd trying
* to create or update the database, we run the database with a lock
* file held. */
database_lock = dotlock_create (filename, 0);
if (!database_lock)
{
err = gpg_error_from_syserror ();
/* A reason for this to fail is that the directory is not
* writable. However, this whole locking stuff does not make
* sense if this is the case. An empty non-writable directory
* with no database is not really useful at all. */
if (opt.verbose)
log_info ("can't allocate lock for '%s': %s\n",
filename, gpg_strerror (err));
goto leave;
}
if (dotlock_take (database_lock, -1))
{
err = gpg_error_from_syserror ();
/* This is something bad. Probably a stale lockfile. */
log_info ("can't lock '%s': %s\n", filename, gpg_strerror (err));
goto leave;
}
/* Database has not yet been opened. Open or create it, make sure
* the tables exist, and prepare the required statements. We use
* our own locking instead of the more complex serialization sqlite
* would have to do and it avoid that we call
* npth_unprotect/protect. */
res = sqlite3_open_v2 (filename,
&database_hd,
(SQLITE_OPEN_READWRITE
| SQLITE_OPEN_CREATE
| SQLITE_OPEN_NOMUTEX),
NULL);
if (res)
{
err = gpg_error (gpg_err_code_from_sqlite (res));
log_error ("error opening '%s': %s\n", filename, sqlite3_errstr (res));
goto leave;
}
/* Enable extended error codes. */
sqlite3_extended_result_codes (database_hd, 1);
/* Create the tables if needed. */
for (idx=0; idx < DIM(table_definitions); idx++)
{
err = run_sql_statement (table_definitions[idx].sql);
if (err)
goto leave;
if (table_definitions[idx].special == 1)
{
/* Check and create dbversion etc entries. */
// FIXME
}
}
if (!opt.quiet)
log_info (_("database '%s' created\n"), filename);
err = 0;
leave:
if (err)
{
log_error (_("error creating database '%s': %s\n"),
filename, gpg_strerror (err));
dotlock_release (database_lock);
dotlock_destroy (database_lock);
database_lock = NULL;
}
release_mutex ();
return err;
}
/* Install a new resource and return a handle for that backend. */
gpg_error_t
be_sqlite_add_resource (ctrl_t ctrl, backend_handle_t *r_hd,
const char *filename, int readonly)
{
gpg_error_t err;
backend_handle_t hd;
(void)ctrl;
(void)readonly; /* FIXME: implement read-only mode. */
*r_hd = NULL;
hd = xtrycalloc (1, sizeof *hd + strlen (filename));
if (!hd)
return gpg_error_from_syserror ();
hd->db_type = DB_TYPE_SQLITE;
strcpy (hd->filename, filename);
err = create_or_open_database (filename);
if (err)
goto leave;
hd->backend_id = be_new_backend_id ();
*r_hd = hd;
hd = NULL;
leave:
xfree (hd);
return err;
}
/* Release the backend handle HD and all its resources. HD is not
* valid after a call to this function. */
void
be_sqlite_release_resource (ctrl_t ctrl, backend_handle_t hd)
{
(void)ctrl;
if (!hd)
return;
hd->db_type = DB_TYPE_NONE;
xfree (hd);
}
/* Helper for be_find_request_part to initialize a sqlite request part. */
gpg_error_t
be_sqlite_init_local (backend_handle_t backend_hd, db_request_part_t part)
{
(void)backend_hd;
part->besqlite = xtrycalloc (1, sizeof *part->besqlite);
if (!part->besqlite)
return gpg_error_from_syserror ();
return 0;
}
/* Release local data of a sqlite request part. */
void
be_sqlite_release_local (be_sqlite_local_t ctx)
{
if (ctx->select_stmt)
sqlite3_finalize (ctx->select_stmt);
xfree (ctx);
}
/* Run a select for the search given by (DESC,NDESC). The data is not
* returned but stored in the request item. */
static gpg_error_t
run_select_statement (ctrl_t ctrl, be_sqlite_local_t ctx,
KEYDB_SEARCH_DESC *desc, unsigned int ndesc)
{
gpg_error_t err = 0;
unsigned int descidx;
const char *extra = NULL;
unsigned char kidbuf[8];
- descidx = 0; /* Fixme: take from context. */
+ descidx = ctx->descidx;
if (descidx >= ndesc)
{
err = gpg_error (GPG_ERR_EOF);
goto leave;
}
/* Check whether we can re-use the current select statement. */
if (!ctx->select_stmt)
;
else if (ctx->select_mode != desc[descidx].mode)
{
sqlite3_finalize (ctx->select_stmt);
ctx->select_stmt = NULL;
}
else if (ctx->filter_opgp != ctrl->filter_opgp
|| ctx->filter_x509 != ctrl->filter_x509)
{
/* The filter flags changed, thus we can't reuse the statement. */
sqlite3_finalize (ctx->select_stmt);
ctx->select_stmt = NULL;
}
ctx->select_mode = desc[descidx].mode;
ctx->filter_opgp = ctrl->filter_opgp;
ctx->filter_x509 = ctrl->filter_x509;
/* Prepare the select and bind the parameters. */
if (ctx->select_stmt)
{
err = run_sql_reset (ctx->select_stmt);
if (err)
goto leave;
}
else
{
if (ctx->filter_opgp && ctx->filter_x509)
extra = " AND ( p.type = 1 OR p.type = 2 )";
else if (ctx->filter_opgp && !ctx->filter_x509)
extra = " AND p.type = 1";
else if (!ctx->filter_opgp && ctx->filter_x509)
extra = " AND p.type = 2";
err = 0;
}
ctx->select_col_uidno = ctx->select_col_subkey = 0;
switch (desc[descidx].mode)
{
case KEYDB_SEARCH_MODE_NONE:
never_reached ();
err = gpg_error (GPG_ERR_INTERNAL);
break;
case KEYDB_SEARCH_MODE_EXACT:
ctx->select_col_uidno = 5;
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral, p.revoked,"
" p.keyblob, u.uidno"
" FROM pubkey as p, userid as u"
" WHERE p.ubid = u.ubid AND u.uid = ?1",
extra, &ctx->select_stmt);
if (!err)
err = run_sql_bind_text (ctx->select_stmt, 1, desc[descidx].u.name);
break;
case KEYDB_SEARCH_MODE_MAIL:
ctx->select_col_uidno = 5;
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral, p.revoked,"
" p.keyblob, u.uidno"
" FROM pubkey as p, userid as u"
" WHERE p.ubid = u.ubid AND u.addrspec = ?1",
extra, &ctx->select_stmt);
if (!err)
err = run_sql_bind_text (ctx->select_stmt, 1, desc[descidx].u.name);
break;
case KEYDB_SEARCH_MODE_MAILSUB:
ctx->select_col_uidno = 5;
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral, p.revoked,"
" p.keyblob, u.uidno"
" FROM pubkey as p, userid as u"
" WHERE p.ubid = u.ubid AND u.addrspec LIKE ?1",
extra, &ctx->select_stmt);
if (!err)
err = run_sql_bind_text_like (ctx->select_stmt, 1,
desc[descidx].u.name);
break;
case KEYDB_SEARCH_MODE_SUBSTR:
ctx->select_col_uidno = 5;
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral, p.revoked,"
" p.keyblob, u.uidno"
" FROM pubkey as p, userid as u"
" WHERE p.ubid = u.ubid AND u.uid LIKE ?1",
extra, &ctx->select_stmt);
if (!err)
err = run_sql_bind_text_like (ctx->select_stmt, 1,
desc[descidx].u.name);
break;
case KEYDB_SEARCH_MODE_MAILEND:
case KEYDB_SEARCH_MODE_WORDS:
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
break;
case KEYDB_SEARCH_MODE_ISSUER:
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral, p.revoked,"
" p.keyblob"
" FROM pubkey as p, issuer as i"
" WHERE p.ubid = i.ubid"
" AND i.dn = $1",
extra, &ctx->select_stmt);
if (!err)
err = run_sql_bind_text (ctx->select_stmt, 1,
desc[descidx].u.name);
break;
case KEYDB_SEARCH_MODE_ISSUER_SN:
if (!desc[descidx].snhex)
{
/* We should never get a binary S/N here. */
log_debug ("%s: issuer_sn with binary s/n\n", __func__);
err = gpg_error (GPG_ERR_INTERNAL);
}
else
{
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral,"
" p.revoked, p.keyblob"
" FROM pubkey as p, issuer as i"
" WHERE p.ubid = i.ubid"
" AND i.sn = $1 AND i.dn = $2",
extra, &ctx->select_stmt);
if (!err)
err = run_sql_bind_ntext (ctx->select_stmt, 1,
desc[descidx].sn, desc[descidx].snlen);
if (!err)
err = run_sql_bind_text (ctx->select_stmt, 2,
desc[descidx].u.name);
}
break;
case KEYDB_SEARCH_MODE_SN:
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
/* if (has_sn (blob, sn_array? sn_array[n].sn : desc[n].sn, */
/* sn_array? sn_array[n].snlen : desc[n].snlen)) */
/* goto found; */
break;
case KEYDB_SEARCH_MODE_SUBJECT:
ctx->select_col_uidno = 5;
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral, p.revoked,"
" p.keyblob, u.uidno"
" FROM pubkey as p, userid as u"
" WHERE p.ubid = u.ubid"
" AND u.uid = $1",
extra, &ctx->select_stmt);
if (!err)
err = run_sql_bind_text (ctx->select_stmt, 1,
desc[descidx].u.name);
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
ctx->select_col_subkey = 5;
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral,"
" p.revoked, p.keyblob, f.subkey"
" FROM pubkey as p, fingerprint as f"
" WHERE p.ubid = f.ubid AND"
" substr(f.kid,5) = ?1",
extra, &ctx->select_stmt);
if (!err)
err = run_sql_bind_blob (ctx->select_stmt, 1,
kid_from_u32 (desc[descidx].u.kid, kidbuf)+4,
4);
break;
case KEYDB_SEARCH_MODE_LONG_KID:
ctx->select_col_subkey = 5;
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral,"
" p.revoked, p.keyblob, f.subkey"
" FROM pubkey as p, fingerprint as f"
" WHERE p.ubid = f.ubid AND f.kid = ?1",
extra, &ctx->select_stmt);
if (!err)
err = run_sql_bind_blob (ctx->select_stmt, 1,
kid_from_u32 (desc[descidx].u.kid, kidbuf),
8);
break;
case KEYDB_SEARCH_MODE_FPR:
ctx->select_col_subkey = 5;
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral,"
" p.revoked, p.keyblob, f.subkey"
" FROM pubkey as p, fingerprint as f"
" WHERE p.ubid = f.ubid AND f.fpr = ?1",
extra, &ctx->select_stmt);
if (!err)
err = run_sql_bind_blob (ctx->select_stmt, 1,
desc[descidx].u.fpr, desc[descidx].fprlen);
break;
case KEYDB_SEARCH_MODE_KEYGRIP:
ctx->select_col_subkey = 5;
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral, p.revoked,"
" p.keyblob, f.subkey"
" FROM pubkey as p, fingerprint as f"
" WHERE p.ubid = f.ubid AND f.keygrip = ?1",
extra, &ctx->select_stmt);
if (!err)
err = run_sql_bind_blob (ctx->select_stmt, 1,
desc[descidx].u.grip, KEYGRIP_LEN);
break;
case KEYDB_SEARCH_MODE_UBID:
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT ubid, type, ephemeral, revoked, keyblob"
" FROM pubkey as p"
" WHERE ubid = ?1",
extra, &ctx->select_stmt);
if (!err)
err = run_sql_bind_blob (ctx->select_stmt, 1,
desc[descidx].u.ubid, UBID_LEN);
break;
case KEYDB_SEARCH_MODE_FIRST:
if (!ctx->select_stmt)
{
if (ctx->filter_opgp && ctx->filter_x509)
extra = " WHERE ( p.type = 1 OR p.type = 2 ) ORDER by ubid";
else if (ctx->filter_opgp && !ctx->filter_x509)
extra = " WHERE p.type = 1 ORDER by ubid";
else if (!ctx->filter_opgp && ctx->filter_x509)
extra = " WHERE p.type = 2 ORDER by ubid";
else
extra = " ORDER by ubid";
err = run_sql_prepare ("SELECT ubid, type, ephemeral, revoked,"
" keyblob"
" FROM pubkey as p",
extra, &ctx->select_stmt);
}
break;
case KEYDB_SEARCH_MODE_NEXT:
err = gpg_error (GPG_ERR_INTERNAL);
break;
default:
err = gpg_error (GPG_ERR_INV_VALUE);
break;
}
leave:
return err;
}
/* Search for the keys described by (DESC,NDESC) and return them to
* the caller. BACKEND_HD is the handle for this backend and REQUEST
* is the current database request object. */
gpg_error_t
be_sqlite_search (ctrl_t ctrl,
backend_handle_t backend_hd, db_request_t request,
KEYDB_SEARCH_DESC *desc, unsigned int ndesc)
{
gpg_error_t err;
db_request_part_t part;
be_sqlite_local_t ctx;
log_assert (backend_hd && backend_hd->db_type == DB_TYPE_SQLITE);
log_assert (request);
acquire_mutex ();
/* Find the specific request part or allocate it. */
err = be_find_request_part (backend_hd, request, &part);
if (err)
goto leave;
ctx = part->besqlite;
if (!desc)
{
/* Reset */
ctx->select_done = 0;
ctx->select_eof = 0;
+ ctx->descidx = 0;
err = 0;
goto leave;
}
if (ctx->select_eof)
{
/* Still in EOF state. */
err = gpg_error (GPG_ERR_EOF);
goto leave;
}
+ again:
if (!ctx->select_done)
{
/* Initial search - run the select. */
err = run_select_statement (ctrl, ctx, desc, ndesc);
if (err)
goto leave;
ctx->select_done = 1;
}
show_sqlstmt (ctx->select_stmt);
/* SQL select succeeded - get the first or next row. */
err = run_sql_step_for_select (ctx->select_stmt);
if (gpg_err_code (err) == GPG_ERR_SQL_ROW)
{
int n;
const void *ubid, *keyblob;
size_t keybloblen;
enum pubkey_types pubkey_type;
int is_ephemeral, is_revoked;
int pk_no, uid_no;
ubid = sqlite3_column_blob (ctx->select_stmt, 0);
n = sqlite3_column_bytes (ctx->select_stmt, 0);
if (!ubid || n < 0)
{
if (!ubid && sqlite3_errcode (database_hd) == SQLITE_NOMEM)
err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM));
else
err = gpg_error (GPG_ERR_DB_CORRUPTED);
show_sqlstmt (ctx->select_stmt);
log_error ("error in returned SQL column UBID: No column (n=%d)\n",n);
goto leave;
}
if (n != UBID_LEN)
{
show_sqlstmt (ctx->select_stmt);
log_error ("error in returned SQL column UBID: Bad value (n=%d)\n",n);
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
n = sqlite3_column_int (ctx->select_stmt, 1);
if (!n && sqlite3_errcode (database_hd) == SQLITE_NOMEM)
{
err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM));
show_sqlstmt (ctx->select_stmt);
log_error ("error in returned SQL column TYPE: %s)\n",
gpg_strerror (err));
goto leave;
}
pubkey_type = n;
n = sqlite3_column_int (ctx->select_stmt, 2);
if (!n && sqlite3_errcode (database_hd) == SQLITE_NOMEM)
{
err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM));
show_sqlstmt (ctx->select_stmt);
log_error ("error in returned SQL column EPHEMERAL: %s)\n",
gpg_strerror (err));
goto leave;
}
is_ephemeral = !!n;
n = sqlite3_column_int (ctx->select_stmt, 3);
if (!n && sqlite3_errcode (database_hd) == SQLITE_NOMEM)
{
err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM));
show_sqlstmt (ctx->select_stmt);
log_error ("error in returned SQL column REVOKED: %s)\n",
gpg_strerror (err));
goto leave;
}
is_revoked = !!n;
keyblob = sqlite3_column_blob (ctx->select_stmt, 4);
n = sqlite3_column_bytes (ctx->select_stmt, 4);
if (!keyblob || n < 0)
{
if (!keyblob && sqlite3_errcode (database_hd) == SQLITE_NOMEM)
err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM));
else
err = gpg_error (GPG_ERR_DB_CORRUPTED);
show_sqlstmt (ctx->select_stmt);
log_error ("error in returned SQL column KEYBLOB: %s\n",
gpg_strerror (err));
goto leave;
}
keybloblen = n;
if (ctx->select_col_uidno)
{
n = sqlite3_column_int (ctx->select_stmt, ctx->select_col_uidno);
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 UIDNO: %s)\n",
gpg_strerror (err));
uid_no = 0;
}
else if (n < 0)
uid_no = 0;
else
uid_no = n + 1;
}
else
uid_no = 0;
if (ctx->select_col_subkey)
{
n = sqlite3_column_int (ctx->select_stmt, ctx->select_col_subkey);
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 SUBKEY: %s)\n",
gpg_strerror (err));
goto leave;
}
else if (n < 0)
pk_no = 0;
else
pk_no = n + 1;
}
else
pk_no = 0;
err = be_return_pubkey (ctrl, keyblob, keybloblen, pubkey_type,
ubid, is_ephemeral, is_revoked, uid_no, pk_no);
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. */
+ if (++ctx->descidx < ndesc)
+ {
+ ctx->select_done = 0;
+ goto again;
+ }
err = gpg_error (GPG_ERR_EOF);
ctx->select_eof = 1;
}
else
{
log_assert (err);
}
leave:
release_mutex ();
return err;
}
/* Helper for be_sqlite_store to update or insert a row in the pubkey
* table. */
static gpg_error_t
store_into_pubkey (enum kbxd_store_modes mode,
enum pubkey_types pktype, const unsigned char *ubid,
const void *blob, size_t bloblen)
{
gpg_error_t err;
const char *sqlstr;
sqlite3_stmt *stmt = NULL;
if (mode == KBXD_STORE_UPDATE)
sqlstr = ("UPDATE pubkey set keyblob = ?3, type = ?2 WHERE ubid = ?1");
else if (mode == KBXD_STORE_INSERT)
sqlstr = ("INSERT INTO pubkey(ubid,type,keyblob) VALUES(?1,?2,?3)");
else /* Auto */
sqlstr = ("INSERT OR REPLACE INTO pubkey(ubid,type,keyblob)"
" VALUES(?1,?2,?3)");
err = run_sql_prepare (sqlstr, NULL, &stmt);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 1, ubid, UBID_LEN);
if (err)
goto leave;
err = run_sql_bind_int (stmt, 2, (int)pktype);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 3, blob, bloblen);
if (err)
goto leave;
err = run_sql_step (stmt);
leave:
if (stmt)
sqlite3_finalize (stmt);
return err;
}
/* Helper for be_sqlite_store to update or insert a row in the
* fingerprint table. */
static gpg_error_t
store_into_fingerprint (const unsigned char *ubid, int subkey,
const unsigned char *keygrip,
const unsigned char *kid,
const unsigned char *fpr, int fprlen)
{
gpg_error_t err;
const char *sqlstr;
sqlite3_stmt *stmt = NULL;
sqlstr = ("INSERT OR REPLACE INTO fingerprint(fpr,kid,keygrip,subkey,ubid)"
" VALUES(?1,?2,?3,?4,?5)");
err = run_sql_prepare (sqlstr, NULL, &stmt);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 1, fpr, fprlen);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 2, kid, 8);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 3, keygrip, KEYGRIP_LEN);
if (err)
goto leave;
err = run_sql_bind_int (stmt, 4, subkey);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 5, ubid, UBID_LEN);
if (err)
goto leave;
err = run_sql_step (stmt);
leave:
if (stmt)
sqlite3_finalize (stmt);
return err;
}
/* Helper for be_sqlite_store to update or insert a row in the userid
* table. If OVERRIDE_MBOX is set, that value is used instead of a
* value extracted from UID. */
static gpg_error_t
store_into_userid (const unsigned char *ubid, enum pubkey_types pktype,
const char *uid, int uidno, const char *override_mbox)
{
gpg_error_t err;
const char *sqlstr;
sqlite3_stmt *stmt = NULL;
char *addrspec = NULL;
sqlstr = ("INSERT OR REPLACE INTO userid(uid,addrspec,type,ubid,uidno)"
" VALUES(?1,?2,?3,?4,?5)");
err = run_sql_prepare (sqlstr, NULL, &stmt);
if (err)
goto leave;
err = run_sql_bind_text (stmt, 1, uid);
if (err)
goto leave;
if (override_mbox)
err = run_sql_bind_text (stmt, 2, override_mbox);
else
{
addrspec = mailbox_from_userid (uid, 0);
err = run_sql_bind_text (stmt, 2, addrspec);
}
if (err)
goto leave;
err = run_sql_bind_int (stmt, 3, pktype);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 4, ubid, UBID_LEN);
if (err)
goto leave;
err = run_sql_bind_int (stmt, 5, uidno);
if (err)
goto leave;
err = run_sql_step (stmt);
leave:
if (stmt)
sqlite3_finalize (stmt);
xfree (addrspec);
return err;
}
/* Helper for be_sqlite_store to update or insert a row in the
* issuer table. */
static gpg_error_t
store_into_issuer (const unsigned char *ubid,
const char *sn, const char *issuer)
{
gpg_error_t err;
const char *sqlstr;
sqlite3_stmt *stmt = NULL;
char *addrspec = NULL;
sqlstr = ("INSERT OR REPLACE INTO issuer(sn,dn,ubid)"
" VALUES(?1,?2,?3)");
err = run_sql_prepare (sqlstr, NULL, &stmt);
if (err)
goto leave;
err = run_sql_bind_text (stmt, 1, sn);
if (err)
goto leave;
err = run_sql_bind_text (stmt, 2, issuer);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 3, ubid, UBID_LEN);
if (err)
goto leave;
err = run_sql_step (stmt);
leave:
if (stmt)
sqlite3_finalize (stmt);
xfree (addrspec);
return err;
}
/* Store (BLOB,BLOBLEN) into the database. UBID is the UBID matching
* that blob. BACKEND_HD is the handle for this backend and REQUEST
* is the current database request object. MODE is the store
* mode. */
gpg_error_t
be_sqlite_store (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request, enum kbxd_store_modes mode,
enum pubkey_types pktype, const unsigned char *ubid,
const void *blob, size_t bloblen)
{
gpg_error_t err;
db_request_part_t part;
/* be_sqlite_local_t ctx; */
int got_mutex = 0;
int in_transaction = 0;
int info_valid = 0;
struct _keybox_openpgp_info info;
ksba_cert_t cert = NULL;
char *sn = NULL;
char *dn = NULL;
char *kludge_mbox = NULL;
int uidno;
(void)ctrl;
log_assert (backend_hd && backend_hd->db_type == DB_TYPE_SQLITE);
log_assert (request);
/* Fixme: The code below is duplicated in be_ubid_from_blob - we
* should have only one function and pass the passed info around
* with the BLOB. */
if (be_is_x509_blob (blob, bloblen))
{
log_assert (pktype == PUBKEY_TYPE_X509);
err = ksba_cert_new (&cert);
if (err)
goto leave;
err = ksba_cert_init_from_mem (cert, blob, bloblen);
if (err)
goto leave;
}
else
{
err = _keybox_parse_openpgp (blob, bloblen, NULL, &info);
if (err)
{
log_info ("error parsing OpenPGP blob: %s\n", gpg_strerror (err));
err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
goto leave;
}
info_valid = 1;
log_assert (pktype == PUBKEY_TYPE_OPGP);
log_assert (info.primary.fprlen >= 20);
log_assert (!memcmp (ubid, info.primary.fpr, UBID_LEN));
}
acquire_mutex ();
got_mutex = 1;
/* Find the specific request part or allocate it. */
err = be_find_request_part (backend_hd, request, &part);
if (err)
goto leave;
/* ctx = part->besqlite; */
err = run_sql_statement ("begin transaction");
if (err)
goto leave;
in_transaction = 1;
err = store_into_pubkey (mode, pktype, ubid, blob, bloblen);
if (err)
goto leave;
/* Delete all related rows so that we can freshly add possibly added
* or changed user ids and subkeys. */
err = run_sql_statement_bind_ubid
("DELETE FROM fingerprint WHERE ubid = ?1", ubid);
if (err)
goto leave;
err = run_sql_statement_bind_ubid
("DELETE FROM userid WHERE ubid = ?1", ubid);
if (err)
goto leave;
if (cert)
{
err = run_sql_statement_bind_ubid
("DELETE FROM issuer WHERE ubid = ?1", ubid);
if (err)
goto leave;
}
if (cert) /* X.509 */
{
unsigned char grip[KEYGRIP_LEN];
int idx;
err = be_get_x509_keygrip (cert, grip);
if (err)
goto leave;
/* Note that for X.509 the UBID is also the fingerprint. */
err = store_into_fingerprint (ubid, 0, grip,
ubid+12,
ubid, UBID_LEN);
if (err)
goto leave;
/* Now the issuer. */
sn = be_get_x509_serial (cert);
if (!sn)
{
err = gpg_error_from_syserror ();
goto leave;
}
dn = ksba_cert_get_issuer (cert, 0);
if (!dn)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = store_into_issuer (ubid, sn, dn);
if (err)
goto leave;
/* Loop over the subject and alternate subjects. */
uidno = 0;
for (idx=0; (xfree (dn), dn = ksba_cert_get_subject (cert, idx)); idx++)
{
/* In the case that the same email address is in the
* subject DN as well as in an alternate subject name
* we avoid printing it a second time. */
if (kludge_mbox && !strcmp (kludge_mbox, dn))
continue;
err = store_into_userid (ubid, PUBKEY_TYPE_X509, dn, ++uidno, NULL);
if (err)
goto leave;
if (!idx)
{
kludge_mbox = _keybox_x509_email_kludge (dn);
if (kludge_mbox)
{
err = store_into_userid (ubid, PUBKEY_TYPE_X509,
dn, ++uidno, kludge_mbox);
if (err)
goto leave;
}
}
} /* end loop over the subjects. */
}
else /* OpenPGP */
{
struct _keybox_openpgp_key_info *kinfo;
kinfo = &info.primary;
err = store_into_fingerprint (ubid, 0, kinfo->grip,
kinfo->keyid,
kinfo->fpr, kinfo->fprlen);
if (err)
goto leave;
if (info.nsubkeys)
{
int subkey = 1;
for (kinfo = &info.subkeys; kinfo; kinfo = kinfo->next, subkey++)
{
err = store_into_fingerprint (ubid, subkey, kinfo->grip,
kinfo->keyid,
kinfo->fpr, kinfo->fprlen);
if (err)
goto leave;
}
}
if (info.nuids)
{
struct _keybox_openpgp_uid_info *u;
uidno = 0;
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, ++uidno, NULL);
xfree (uid);
}
if (err)
goto leave;
u = u->next;
}
while (u);
}
}
leave:
if (in_transaction && !err)
err = run_sql_statement ("commit");
else if (in_transaction)
{
if (run_sql_statement ("rollback"))
log_error ("Warning: database rollback failed - should not happen!\n");
}
if (got_mutex)
release_mutex ();
if (info_valid)
_keybox_destroy_openpgp_info (&info);
if (cert)
ksba_cert_release (cert);
ksba_free (dn);
xfree (sn);
xfree (kludge_mbox);
return err;
}
/* Delete the blob specified by UBID from the database. BACKEND_HD is
* the handle for this backend and REQUEST is the current database
* request object. */
gpg_error_t
be_sqlite_delete (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request, const unsigned char *ubid)
{
gpg_error_t err;
db_request_part_t part;
/* be_sqlite_local_t ctx; */
sqlite3_stmt *stmt = NULL;
int in_transaction = 0;
(void)ctrl;
log_assert (backend_hd && backend_hd->db_type == DB_TYPE_SQLITE);
log_assert (request);
acquire_mutex ();
/* Find the specific request part or allocate it. */
err = be_find_request_part (backend_hd, request, &part);
if (err)
goto leave;
/* ctx = part->besqlite; */
err = run_sql_statement ("begin transaction");
if (err)
goto leave;
in_transaction = 1;
err = run_sql_statement_bind_ubid
("DELETE from userid WHERE ubid = ?1", ubid);
if (!err)
err = run_sql_statement_bind_ubid
("DELETE from fingerprint WHERE ubid = ?1", ubid);
if (!err)
err = run_sql_statement_bind_ubid
("DELETE from issuer WHERE ubid = ?1", ubid);
if (!err)
err = run_sql_statement_bind_ubid
("DELETE from pubkey WHERE ubid = ?1", ubid);
leave:
if (stmt)
sqlite3_finalize (stmt);
if (in_transaction && !err)
err = run_sql_statement ("commit");
else if (in_transaction)
{
if (run_sql_statement ("rollback"))
log_error ("Warning: database rollback failed - should not happen!\n");
}
release_mutex ();
return err;
}
diff --git a/kbx/kbx-client-util.c b/kbx/kbx-client-util.c
index e301ae025..f7477661f 100644
--- a/kbx/kbx-client-util.c
+++ b/kbx/kbx-client-util.c
@@ -1,454 +1,464 @@
/* kbx-client-util.c - Utility functions to implement a keyboxd client
* Copyright (C) 2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
* SPDX-License-Identifier: GPL-3.0+
*/
#include
#include
#include
#include
#include
#include
#include
#include "../common/util.h"
#include "../common/membuf.h"
#include "../common/i18n.h"
#include "../common/asshelp.h"
#include "../common/exechelp.h"
#include "../common/sysutils.h"
#include "../common/host2net.h"
#include "kbx-client-util.h"
#define MAX_DATABLOB_SIZE (16*1024*1024)
/* This object is used to implement a client to the keyboxd. */
struct kbx_client_data_s
{
/* The used assuan context. */
assuan_context_t ctx;
/* A stream used to receive data. If this is NULL D-lines are used
* to receive the data. */
estream_t fp;
/* Condition variable to sync the datastream with the command. */
npth_mutex_t mutex;
npth_cond_t cond;
/* The data received from the keyboxd and an error code if there was
* a problem (in which case DATA is also set to NULL. This is only
* used if FP is not NULL. */
char *data;
size_t datalen;
gpg_error_t dataerr;
/* Helper variables in case D-lines are used (FP is NULL) */
char *dlinedata;
size_t dlinedatalen;
gpg_error_t dlineerr;
};
static void *datastream_thread (void *arg);
static void
lock_datastream (kbx_client_data_t kcd)
{
int rc = npth_mutex_lock (&kcd->mutex);
if (rc)
log_fatal ("%s: failed to acquire mutex: %s\n", __func__,
gpg_strerror (gpg_error_from_errno (rc)));
}
static void
unlock_datastream (kbx_client_data_t kcd)
{
int rc = npth_mutex_unlock (&kcd->mutex);
if (rc)
log_fatal ("%s: failed to release mutex: %s\n", __func__,
gpg_strerror (gpg_error_from_errno (rc)));
}
/* Setup the pipe used for receiving data from the keyboxd. Store the
* info on KCD. */
static gpg_error_t
prepare_data_pipe (kbx_client_data_t kcd)
{
gpg_error_t err;
int rc;
int inpipe[2];
estream_t infp;
npth_t thread;
npth_attr_t tattr;
kcd->fp = NULL;
kcd->data = NULL;
kcd->datalen = 0;
kcd->dataerr = 0;
err = gnupg_create_inbound_pipe (inpipe, &infp, 0);
if (err)
{
log_error ("error creating inbound pipe: %s\n", gpg_strerror (err));
return err; /* That should not happen. */
}
err = assuan_sendfd (kcd->ctx, INT2FD (inpipe[1]));
if (err)
{
log_error ("sending sending fd %d to keyboxd: %s <%s>\n",
inpipe[1], gpg_strerror (err), gpg_strsource (err));
es_fclose (infp);
gnupg_close_pipe (inpipe[1]);
return 0; /* Server may not support fd-passing. */
}
err = assuan_transact (kcd->ctx, "OUTPUT FD",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
{
log_info ("keyboxd does not accept our fd: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
es_fclose (infp);
return 0;
}
kcd->fp = infp;
rc = npth_attr_init (&tattr);
if (rc)
{
err = gpg_error_from_errno (rc);
log_error ("error preparing thread for keyboxd: %s\n",gpg_strerror (err));
es_fclose (infp);
kcd->fp = NULL;
return err;
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
rc = npth_create (&thread, &tattr, datastream_thread, kcd);
if (rc)
{
err = gpg_error_from_errno (rc);
log_error ("error spawning thread for keyboxd: %s\n", gpg_strerror (err));
npth_attr_destroy (&tattr);
es_fclose (infp);
kcd->fp = NULL;
return err;
}
return 0;
}
/* The thread used to read from the data stream. This is running as
* long as the connection and its datastream exists. */
static void *
datastream_thread (void *arg)
{
kbx_client_data_t kcd = arg;
gpg_error_t err;
int rc;
unsigned char lenbuf[4];
size_t nread, datalen;
char *data, *tmpdata;
/* log_debug ("%s: started\n", __func__); */
while (kcd->fp)
{
/* log_debug ("%s: waiting ...\n", __func__); */
if (es_read (kcd->fp, lenbuf, 4, &nread))
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_EAGAIN)
continue;
log_error ("error reading data length from keyboxd: %s\n",
gpg_strerror (err));
gnupg_sleep (1);
continue;
}
if (nread != 4)
{
err = gpg_error (GPG_ERR_EIO);
log_error ("error reading data length from keyboxd: %s\n",
"short read");
continue;
}
datalen = buf32_to_size_t (lenbuf);
/* log_debug ("keyboxd announced %zu bytes\n", datalen); */
if (!datalen)
{
log_info ("ignoring empty blob received from keyboxd\n");
continue;
}
if (datalen > MAX_DATABLOB_SIZE)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
/* Drop connection or what shall we do? */
}
else if (!(data = xtrymalloc (datalen+1)))
{
err = gpg_error_from_syserror ();
}
else if (es_read (kcd->fp, data, datalen, &nread))
{
err = gpg_error_from_syserror ();
}
else if (datalen != nread)
{
err = gpg_error (GPG_ERR_TOO_SHORT);
}
else
err = 0;
if (err)
{
log_error ("error reading data from keyboxd: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
xfree (data);
data = NULL;
datalen = 0;
}
else
{
/* log_debug ("parsing datastream succeeded\n"); */
}
/* Thread-safe assignment to the result var: */
tmpdata = kcd->data;
kcd->data = data;
kcd->datalen = datalen;
kcd->dataerr = err;
xfree (tmpdata);
data = NULL;
/* Tell the main thread. */
lock_datastream (kcd);
rc = npth_cond_signal (&kcd->cond);
if (rc)
{
err = gpg_error_from_errno (rc);
log_error ("%s: signaling condition failed: %s\n",
__func__, gpg_strerror (err));
}
unlock_datastream (kcd);
}
/* log_debug ("%s: finished\n", __func__); */
return NULL;
}
/* Create a new keyboxd client data object and return it at R_KCD.
* CTX is the assuan context to be used for connecting the keyboxd.
* If dlines is set, communication is done without fd passing via
* D-lines. */
gpg_error_t
kbx_client_data_new (kbx_client_data_t *r_kcd, assuan_context_t ctx,
int dlines)
{
kbx_client_data_t kcd;
int rc;
gpg_error_t err;
kcd = xtrycalloc (1, sizeof *kcd);
if (!kcd)
return gpg_error_from_syserror ();
kcd->ctx = ctx;
if (dlines)
goto leave;
rc = npth_mutex_init (&kcd->mutex, NULL);
if (rc)
{
err = gpg_error_from_errno (rc);
log_error ("error initializing mutex: %s\n", gpg_strerror (err));
xfree (kcd);
return err;
}
rc = npth_cond_init (&kcd->cond, NULL);
if (rc)
{
err = gpg_error_from_errno (rc);
log_error ("error initializing condition: %s\n", gpg_strerror (err));
npth_mutex_destroy (&kcd->mutex);
xfree (kcd);
return err;
}
err = prepare_data_pipe (kcd);
if (err)
{
npth_cond_destroy (&kcd->cond);
npth_mutex_destroy (&kcd->mutex);
xfree (kcd);
return err;
}
leave:
*r_kcd = kcd;
return 0;
}
void
kbx_client_data_release (kbx_client_data_t kcd)
{
estream_t fp;
if (!kcd)
return;
fp = kcd->fp;
kcd->fp = NULL;
es_fclose (fp); /* That close should let the thread run into an error. */
/* FIXME: Make thread killing explicit. Otherwise we run in a
* log_fatal due to the destroyed mutex. */
npth_cond_destroy (&kcd->cond);
npth_mutex_destroy (&kcd->mutex);
xfree (kcd);
}
+/* Send a simple Assuan command to the server. */
+gpg_error_t
+kbx_client_data_simple (kbx_client_data_t kcd, const char *command)
+{
+ /* log_debug ("%s: sending command '%s'\n", __func__, command); */
+ return assuan_transact (kcd->ctx, command,
+ NULL, NULL, NULL, NULL, NULL, NULL);
+}
+
+
/* Send the COMMAND down to the keyboxd associated with KCD.
* STATUS_CB and STATUS_CB_VALUE are the usual status callback as used
* by assuan_transact. After this function has returned success
* kbx_client_data_wait needs to be called to actually return the
* data. */
gpg_error_t
kbx_client_data_cmd (kbx_client_data_t kcd, const char *command,
gpg_error_t (*status_cb)(void *opaque, const char *line),
void *status_cb_value)
{
gpg_error_t err;
xfree (kcd->dlinedata);
kcd->dlinedata = NULL;
kcd->dlinedatalen = 0;
kcd->dlineerr = 0;
if (kcd->fp)
{
/* log_debug ("%s: sending command '%s'\n", __func__, command); */
err = assuan_transact (kcd->ctx, command,
NULL, NULL,
NULL, NULL,
status_cb, status_cb_value);
if (err)
{
if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
log_debug ("%s: finished command with error: %s\n",
__func__, gpg_strerror (err));
/* Fixme: On unexpected errors we need a way to cancel the
* data stream. Probably it will be best to close and
* reopen it. */
}
}
else /* Slower D-line version if fd-passing is not available. */
{
membuf_t mb;
size_t len;
/* log_debug ("%s: sending command '%s' (no fd-passing)\n", */
/* __func__, command); */
init_membuf (&mb, 8192);
err = assuan_transact (kcd->ctx, command,
put_membuf_cb, &mb,
NULL, NULL,
status_cb, status_cb_value);
if (err)
{
if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
log_debug ("%s: finished command with error: %s\n",
__func__, gpg_strerror (err));
xfree (get_membuf (&mb, &len));
kcd->dlineerr = err;
goto leave;
}
kcd->dlinedata = get_membuf (&mb, &kcd->dlinedatalen);
if (!kcd->dlinedata)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
leave:
return err;
}
/* Wait for the data from the server and on success return it at
* (R_DATA, R_DATALEN). */
gpg_error_t
kbx_client_data_wait (kbx_client_data_t kcd, char **r_data, size_t *r_datalen)
{
gpg_error_t err = 0;
int rc;
*r_data = NULL;
*r_datalen = 0;
if (kcd->fp)
{
lock_datastream (kcd);
if (!kcd->data && !kcd->dataerr)
{
/* log_debug ("%s: waiting on datastream_cond ...\n", __func__); */
rc = npth_cond_wait (&kcd->cond, &kcd->mutex);
if (rc)
{
err = gpg_error_from_errno (rc);
log_error ("%s: waiting on condition failed: %s\n",
__func__, gpg_strerror (err));
}
/* else */
/* log_debug ("%s: waiting on datastream.cond done\n", __func__); */
}
*r_data = kcd->data;
kcd->data = NULL;
*r_datalen = kcd->datalen;
err = err? err : kcd->dataerr;
unlock_datastream (kcd);
}
else
{
*r_data = kcd->dlinedata;
kcd->dlinedata = NULL;
*r_datalen = kcd->dlinedatalen;
err = kcd->dlineerr;
}
return err;
}
diff --git a/kbx/kbx-client-util.h b/kbx/kbx-client-util.h
index 1cc68b5a0..07cf78ec7 100644
--- a/kbx/kbx-client-util.h
+++ b/kbx/kbx-client-util.h
@@ -1,41 +1,42 @@
/* kbx-client-util.c - Defs for utility functions for a keyboxd client
* Copyright (C) 2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef GNUPG_KBX_CLIENT_UTIL_H
#define GNUPG_KBX_CLIENT_UTIL_H 1
struct kbx_client_data_s;
typedef struct kbx_client_data_s *kbx_client_data_t;
gpg_error_t kbx_client_data_new (kbx_client_data_t *r_kcd,
assuan_context_t ctx, int dlines);
void kbx_client_data_release (kbx_client_data_t kcd);
+gpg_error_t kbx_client_data_simple (kbx_client_data_t kcd, const char *command);
gpg_error_t kbx_client_data_cmd (kbx_client_data_t kcd, const char *command,
gpg_error_t (*status_cb)(void *opaque,
const char *line),
void *status_cb_value);
gpg_error_t kbx_client_data_wait (kbx_client_data_t kcd,
char **r_data, size_t *r_datalen);
#endif /*GNUPG_KBX_CLIENT_UTIL_H*/