Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F27683864
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
162 KB
Subscribers
None
View Options
diff --git a/g10/call-keyboxd.c b/g10/call-keyboxd.c
index 9042c833c..e2fede235 100644
--- a/g10/call-keyboxd.c
+++ b/g10/call-keyboxd.c
@@ -1,864 +1,865 @@
/* 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 <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
#include <npth.h>
#include "gpg.h"
#include <assuan.h>
#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;
};
/* Flag indicating that for example bulk import is enabled. */
static unsigned int in_transaction;
/* Deinitialize all session resources pertaining to the keyboxd. */
void
gpg_keyboxd_deinit_session_data (ctrl_t ctrl)
{
keyboxd_local_t kbl;
gpg_error_t err;
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;
if (kbl->ctx && in_transaction)
{
/* This is our hack to commit the changes done during a
* bulk import. If we won't do that the loss of the
* connection would trigger a rollback in keyboxd. Note
* that transactions are not associated with a
* connection. */
err = assuan_transact (kbl->ctx, "TRANSACTION commit",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
log_error ("error commiting last transaction: %s\n",
gpg_strerror (err));
in_transaction = 0;
}
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 ((opt.import_options & IMPORT_BULK) && !in_transaction)
{
err = assuan_transact (ctx, "TRANSACTION begin",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
{
log_error ("error enabling bulk import option: %s\n",
gpg_strerror (err));
}
else
in_transaction = 1;
}
}
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;
}
for (i = 0; i < ndesc; i++)
if (desc->mode == KEYDB_SEARCH_MODE_FIRST)
{
/* If any description has mode FIRST, this item trumps all
* other descriptions. */
snprintf (line, sizeof line, "SEARCH --openpgp");
goto do_search;
}
for ( ; ndesc; desc++, ndesc--)
{
const char *more = ndesc > 1 ? "--openpgp --more" : "--openpgp";
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);
+ snprintf (line, sizeof line, "SEARCH %s -- <%s",
+ more, desc->u.name+(desc->u.name[0] == '<') );
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;
}
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 ae2530d1a..202897e91 100644
--- a/kbx/backend-sqlite.c
+++ b/kbx/backend-sqlite.c
@@ -1,1820 +1,1835 @@
/* 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 <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <sqlite3.h>
#include <npth.h>
#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) */
/* 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;
/* Flag indicating that LASTUBID has a value. */
unsigned int lastubid_valid : 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 last UBID found by a select; only valid if LASTUBID_VALID is
* set. This is required to return only one blob in case a search
* is done over the user id and the same user id occurs several
* times in a blob. */
unsigned char lastubid[UBID_LEN];
};
/* 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;
/* The version of our current database schema. */
#define DATABASE_VERSION 1
/* Table definitions for the database. */
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 = <ISO time string>
*/
{ "CREATE TABLE IF NOT EXISTS config ("
"name TEXT NOT NULL UNIQUE,"
"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)" }
};
/*-- prototypes --*/
static gpg_error_t get_config_value (const char *name, char **r_value);
static gpg_error_t set_config_value (const char *name, const char *value);
/* 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 or EXTRA2 are not NULL these parts are appended to the SQL
* statement. */
static gpg_error_t
run_sql_prepare (const char *sqlstr, const char *extra, const char *extra2,
sqlite3_stmt **r_stmt)
{
gpg_error_t err;
int res;
char *buffer = NULL;
if (extra || extra2)
{
buffer = strconcat (sqlstr, extra?extra:"", extra2, 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, 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;
char *value;
int dbversion;
int setdbversion = 0;
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. */
err = get_config_value ("dbversion", &value);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{
dbversion = 0;
setdbversion = 1;
}
else if (err)
{
log_error ("error reading database version: %s\n",
gpg_strerror (err));
err = 0;
dbversion = 0;
}
else if ((dbversion = atoi (value)) < 1)
{
log_error ("database version %d is not valid\n", dbversion);
dbversion = 0;
}
log_info ("database version: %d\n", dbversion);
xfree (value);
err = get_config_value ("created", &value);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
log_info ("database created: %.50s\n", "[unknown]");
else if (err)
log_error ("error getting database creation date: %s\n",
gpg_strerror (err));
else
log_info ("database created: %.50s\n", value);
xfree (value);
value = NULL;
}
}
if (!opt.quiet)
log_info (_("database '%s' created\n"), filename);
if (setdbversion)
{
err = set_config_value ("dbversion", STR2(DATABASE_VERSION));
if (!err)
err = set_config_value ("created", isotimestamp (gnupg_get_time ()));
}
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);
}
gpg_error_t
be_sqlite_rollback (void)
{
opt.in_transaction = 0;
if (!opt.active_transaction)
return 0; /* Nothing to do. */
if (!database_hd)
{
log_error ("Warning: No database handle for global rollback\n");
return gpg_error (GPG_ERR_INTERNAL);
}
opt.active_transaction = 0;
return run_sql_statement ("rollback");
}
gpg_error_t
be_sqlite_commit (void)
{
opt.in_transaction = 0;
if (!opt.active_transaction)
return 0; /* Nothing to do. */
if (!database_hd)
{
log_error ("Warning: No database handle for global commit\n");
return gpg_error (GPG_ERR_INTERNAL);
}
opt.active_transaction = 0;
return run_sql_statement ("commit");
}
/* Return a value from the config table. NAME most not have quotes
* etc. If no error is returned the caller must xfree the value
* stored at R_VALUE. On error NULL is stored there. */
static gpg_error_t
get_config_value (const char *name, char **r_value)
{
gpg_error_t err;
sqlite3_stmt *stmt;
char *sqlstr;
const char *s;
*r_value = NULL;
sqlstr = strconcat ("SELECT value FROM config WHERE name='", name, "'", NULL);
if (!sqlstr)
return gpg_error_from_syserror ();
err = run_sql_prepare (sqlstr, NULL, NULL, &stmt);
xfree (sqlstr);
if (err)
return err;
err = run_sql_step_for_select (stmt);
if (gpg_err_code (err) == GPG_ERR_SQL_ROW)
{
s = sqlite3_column_text (stmt, 0);
*r_value = xtrystrdup (s? s : "");
if (!*r_value)
err = gpg_error_from_syserror ();
else
err = 0;
}
else if (gpg_err_code (err) == GPG_ERR_SQL_DONE)
err = gpg_error (GPG_ERR_NOT_FOUND);
else
log_assert (err); /* We'll never see 0 here. */
sqlite3_finalize (stmt);
return err;
}
/* Insert or update a value in the config table. */
static gpg_error_t
set_config_value (const char *name, const char *value)
{
gpg_error_t err;
sqlite3_stmt *stmt;
err = run_sql_prepare ("INSERT OR REPLACE INTO config(name,value)"
" VALUES(?1,?2)", NULL, NULL, &stmt);
if (err)
return err;
err = run_sql_bind_text (stmt, 1, name);
if (!err)
err = run_sql_bind_text (stmt, 2, value);
if (!err)
err = run_sql_step (stmt);
sqlite3_finalize (stmt);
return err;
}
/* 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];
+ const char *s;
+ size_t n;
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, " ORDER BY p.ubid", &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, " ORDER BY p.ubid", &ctx->select_stmt);
if (!err)
- err = run_sql_bind_text (ctx->select_stmt, 1, desc[descidx].u.name);
+ {
+ s = desc[descidx].u.name;
+ if (s && *s == '<' && s[1])
+ { /* It is common that the indicator for exact addrspec
+ * search has not been removed. We do this here. */
+ s++;
+ n = strlen (s);
+ if (n > 1 && s[n-1] == '>')
+ n--;
+ }
+ else
+ n = s? strlen (s):0;
+ err = run_sql_bind_ntext (ctx->select_stmt, 1, s, n);
+ }
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, " ORDER BY p.ubid", &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, " ORDER BY p.ubid", &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, " ORDER BY p.ubid", &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, " ORDER BY p.ubid",
&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, " ORDER BY p.ubid", &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, " ORDER BY p.ubid", &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, " ORDER BY p.ubid", &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, " ORDER BY p.ubid", &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, " ORDER BY p.ubid", &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, NULL, &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, NULL, &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;
ctx->lastubid_valid = 0;
err = 0;
goto leave;
}
if (ctx->select_eof)
{
/* Still in EOF state. */
err = gpg_error (GPG_ERR_EOF);
goto leave;
}
/* Start a global transaction if needed. */
if (!opt.active_transaction && opt.in_transaction)
{
err = run_sql_statement ("begin transaction");
if (err)
goto leave;
opt.active_transaction = 1;
}
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;
}
if (ctx->lastubid_valid && !memcmp (ctx->lastubid, ubid, UBID_LEN))
{
/* The search has already returned this blob and thus we may
* not return this again. Consider the case that we are
* searching for user id "foo" and a keyblock or certificate
* has several userids with "foo" in it (or with even a full
* mail address in it but with other extra parts). The code
* in gpg and gpgsm expects to see only a single block and
* not several of them. Whether the UIDNO makes any sense
* in this case is questionable and we ignore that because
* we currently are not able to return several UIDNOs. */
goto again;
}
memcpy (ctx->lastubid, ubid, UBID_LEN);
ctx->lastubid_valid = 1;
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)
{
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, 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, 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, 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, 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; */
if (!opt.active_transaction)
{
err = run_sql_statement ("begin transaction");
if (err)
goto leave;
if (opt.in_transaction)
opt.active_transaction = 1;
}
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)
{
if (opt.active_transaction)
; /* We are in a global transaction. */
else
err = run_sql_statement ("commit");
}
else if (in_transaction)
{
if (opt.active_transaction)
; /* We are in a global transaction. */
else 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; */
if (!opt.active_transaction)
{
err = run_sql_statement ("begin transaction");
if (err)
goto leave;
if (opt.in_transaction)
opt.active_transaction = 1;
}
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)
{
if (opt.active_transaction)
; /* We are in a global transaction. */
else
err = run_sql_statement ("commit");
}
else if (in_transaction)
{
if (opt.active_transaction)
; /* We are in a global transaction. */
else if (run_sql_statement ("rollback"))
log_error ("Warning: database rollback failed - should not happen!\n");
}
release_mutex ();
return err;
}
diff --git a/kbx/kbxserver.c b/kbx/kbxserver.c
index 8a75035a5..55b478586 100644
--- a/kbx/kbxserver.c
+++ b/kbx/kbxserver.c
@@ -1,1000 +1,1000 @@
/* 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 <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0+
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include "keyboxd.h"
#include <assuan.h>
#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
{
/* We keep a list of all active sessions with the anchor at
* SESSION_LIST (see below). This field is used for linking. */
struct server_local_s *next_session;
/* The pid of the client. */
pid_t client_pid;
/* 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;
};
/* To keep track of all running sessions, we link all active server
* contexts and anchor them at this variable. */
static struct server_local_s *session_list;
/* 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] [--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. 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, 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);
+ err = classify_user_id (line, &ctrl->server_local->search_desc, 1);
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 <ubid> \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_transaction[] =
"TRANSACTION [begin|commit|rollback]\n"
"\n"
"For bulk import of data it is often useful to run everything\n"
"in one transaction. This can be achieved with this command.\n"
"If the last connection of client is closed before a commit\n"
"or rollback an implicit rollback is done. With no argument\n"
"the status of the current transaction is returned.";
static gpg_error_t
cmd_transaction (assuan_context_t ctx, char *line)
{
gpg_error_t err = 0;
line = skip_options (line);
if (!strcmp (line, "begin"))
{
/* Note that we delay the actual transaction until we have to
* use SQL. */
if (opt.in_transaction)
err = set_error (GPG_ERR_CONFLICT, "already in a transaction");
else
{
opt.in_transaction = 1;
opt.transaction_pid = assuan_get_pid (ctx);
}
}
else if (!strcmp (line, "commit"))
{
if (!opt.in_transaction)
err = set_error (GPG_ERR_CONFLICT, "not in a transaction");
else if (opt.transaction_pid != assuan_get_pid (ctx))
err = set_error (GPG_ERR_CONFLICT, "other client is in a transaction");
else
err = kbxd_commit ();
}
else if (!strcmp (line, "rollback"))
{
if (!opt.in_transaction)
err = set_error (GPG_ERR_CONFLICT, "not in a transaction");
else if (opt.transaction_pid != assuan_get_pid (ctx))
err = set_error (GPG_ERR_CONFLICT, "other client is in a transaction");
else
err = kbxd_rollback ();
}
else if (!*line)
{
if (opt.in_transaction && opt.transaction_pid == assuan_get_pid (ctx))
err = assuan_set_okay_line (ctx, opt.active_transaction?
"active transaction" :
"pending transaction");
else if (opt.in_transaction)
err = assuan_set_okay_line (ctx, opt.active_transaction?
"active transaction on other client" :
"pending transaction on other client");
else
err = set_error (GPG_ERR_FALSE, "no transaction");
}
else
{
err = set_error (GPG_ERR_ASS_PARAMETER, "unknown transaction command");
}
return leave_cmd (ctx, err);
}
static const char hlp_getinfo[] =
"GETINFO <what>\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 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"
"\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 },
{ "TRANSACTION",cmd_transaction,hlp_transaction },
{ "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;
}
ctrl->server_local->client_pid = ASSUAN_INVALID_PID;
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;
/* Put the session int a list. */
ctrl->server_local->next_session = session_list;
session_list = ctrl->server_local;
/* 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
ctrl->server_local->client_pid = assuan_get_pid (ctx);
rc = assuan_process (ctx);
if (rc)
{
log_info (_("Assuan processing failed: %s\n"), gpg_strerror (rc));
continue;
}
}
if (opt.in_transaction
&& opt.transaction_pid == ctrl->server_local->client_pid)
{
struct server_local_s *sl;
pid_t thispid = ctrl->server_local->client_pid;
int npids = 0;
/* Only if this is the last connection rollback the transaction. */
for (sl = session_list; sl; sl = sl->next_session)
if (sl->client_pid == thispid)
npids++;
if (npids == 1)
kbxd_rollback ();
}
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
{
if (session_list == ctrl->server_local)
session_list = ctrl->server_local->next_session;
else
{
struct server_local_s *sl;
for (sl=session_list; sl->next_session; sl = sl->next_session)
if (sl->next_session == ctrl->server_local)
break;
if (!sl->next_session)
BUG ();
sl->next_session = ctrl->server_local->next_session;
}
xfree (ctrl->server_local->multi_search_desc);
xfree (ctrl->server_local);
ctrl->server_local = NULL;
}
}
diff --git a/sm/keydb.c b/sm/keydb.c
index e092e4a01..990f63a61 100644
--- a/sm/keydb.c
+++ b/sm/keydb.c
@@ -1,2133 +1,2134 @@
/* keydb.c - key database dispatcher
* Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc.
* Copyright (C) 2014, 2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "gpgsm.h"
#include <assuan.h>
#include "../kbx/keybox.h"
#include "keydb.h"
#include "../common/i18n.h"
#include "../common/asshelp.h"
#include "../kbx/kbx-client-util.h"
typedef enum {
KEYDB_RESOURCE_TYPE_NONE = 0,
KEYDB_RESOURCE_TYPE_KEYBOX
} KeydbResourceType;
#define MAX_KEYDB_RESOURCES 20
struct resource_item {
KeydbResourceType type;
union {
KEYBOX_HANDLE kr;
} u;
void *token;
};
/* Data used to keep track of keybox daemon sessions. This allows us
* to use several sessions with the keyboxd and also to re-use already
* established sessions. Note that gpgdm.h defines the type
* keydb_local_t for this structure. */
struct keydb_local_s
{
/* Link to other keyboxd contexts which are used simultaneously. */
struct keydb_local_s *next;
/* The active Assuan context. */
assuan_context_t ctx;
/* The client data helper context. */
kbx_client_data_t kcd;
/* I/O buffer with the last search result or NULL. Used if
* D-lines are used to convey the keyblocks. */
struct {
char *buf;
size_t len;
} search_result;
/* This flag set while an operation is running on this context. */
unsigned int is_active : 1;
/* Flag indicating that a search reset is required. */
unsigned int need_search_reset : 1;
};
static struct resource_item all_resources[MAX_KEYDB_RESOURCES];
static int used_resources;
/* Whether we have successfully registered any resource. */
static int any_registered;
/* Number of active handles. */
static int active_handles;
struct keydb_handle {
/* CTRL object passed to keydb_new. */
ctrl_t ctrl;
/* If set the keyboxdd is used instead of the local files. */
int use_keyboxd;
/* BEGIN USE_KEYBOXD */
/* (These fields are only valid if USE_KEYBOXD is set.) */
/* Connection info which also keeps the local state. (This points
* into the CTRL->keybox_local list.) */
keydb_local_t kbl;
/* Various flags. */
unsigned int last_ubid_valid:1;
unsigned int last_is_ephemeral; /* Last found key is ephemeral. */
/* The UBID of the last returned keyblock. */
unsigned char last_ubid[UBID_LEN];
/* END USE_KEYBOXD */
/* BEGIN !USE_KEYBOXD */
/* (The remaining fields are only valid if USE_KEYBOXD is cleared.) */
/* If this flag is set the resources is locked. */
int locked;
/* If this flag is set a lock will only be released by
* keydb_release. */
int keep_lock;
int found;
int saved_found;
int current;
int is_ephemeral;
int used; /* items in active */
struct resource_item active[MAX_KEYDB_RESOURCES];
/* END !USE_KEYBOXD */
};
static int lock_all (KEYDB_HANDLE hd);
static void unlock_all (KEYDB_HANDLE hd);
/* Deinitialize all session resources pertaining to the keyboxd. */
void
gpgsm_keydb_deinit_session_data (ctrl_t ctrl)
{
keydb_local_t kbl;
while ((kbl = ctrl->keydb_local))
{
ctrl->keydb_local = kbl->next;
if (kbl->is_active)
log_error ("oops: trying to cleanup an active keydb context\n");
else
{
kbx_client_data_release (kbl->kcd);
kbl->kcd = NULL;
assuan_release (kbl->ctx);
kbl->ctx = NULL;
}
xfree (kbl);
}
}
static void
try_make_homedir (const char *fname)
{
if ( opt.dry_run || opt.no_homedir_creation )
return;
gnupg_maybe_make_homedir (fname, opt.quiet);
}
/* Handle the creation of a keybox if it does not yet exist. Take
into account that other processes might have the keybox already
locked. This lock check does not work if the directory itself is
not yet available. If R_CREATED is not NULL it will be set to true
if the function created a new keybox. */
static gpg_error_t
maybe_create_keybox (char *filename, int force, int *r_created)
{
gpg_err_code_t ec;
dotlock_t lockhd = NULL;
estream_t fp;
int rc;
mode_t oldmask;
char *last_slash_in_filename;
int save_slash;
if (r_created)
*r_created = 0;
/* A quick test whether the filename already exists. */
if (!gnupg_access (filename, F_OK))
return !gnupg_access (filename, R_OK)? 0 : gpg_error (GPG_ERR_EACCES);
/* If we don't want to create a new file at all, there is no need to
go any further - bail out right here. */
if (!force)
return gpg_error (GPG_ERR_ENOENT);
/* First of all we try to create the home directory. Note, that we
don't do any locking here because any sane application of gpg
would create the home directory by itself and not rely on gpg's
tricky auto-creation which is anyway only done for some home
directory name patterns. */
last_slash_in_filename = strrchr (filename, DIRSEP_C);
#if HAVE_W32_SYSTEM
{
/* Windows may either have a slash or a backslash. Take care of it. */
char *p = strrchr (filename, '/');
if (!last_slash_in_filename || p > last_slash_in_filename)
last_slash_in_filename = p;
}
#endif /*HAVE_W32_SYSTEM*/
if (!last_slash_in_filename)
return gpg_error (GPG_ERR_ENOENT); /* No slash at all - should
not happen though. */
save_slash = *last_slash_in_filename;
*last_slash_in_filename = 0;
if (gnupg_access(filename, F_OK))
{
static int tried;
if (!tried)
{
tried = 1;
try_make_homedir (filename);
}
if ((ec = gnupg_access (filename, F_OK)))
{
rc = gpg_error (ec);
*last_slash_in_filename = save_slash;
goto leave;
}
}
*last_slash_in_filename = save_slash;
/* To avoid races with other instances of gpg trying to create or
update the keybox (it is removed during an update for a short
time), we do the next stuff in a locked state. */
lockhd = dotlock_create (filename, 0);
if (!lockhd)
{
/* A reason for this to fail is that the directory is not
writable. However, this whole locking stuff does not make
sense if this is the case. An empty non-writable directory
with no keyring is not really useful at all. */
if (opt.verbose)
log_info ("can't allocate lock for '%s'\n", filename );
if (!force)
return gpg_error (GPG_ERR_ENOENT);
else
return gpg_error (GPG_ERR_GENERAL);
}
if ( dotlock_take (lockhd, -1) )
{
/* This is something bad. Probably a stale lockfile. */
log_info ("can't lock '%s'\n", filename);
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
/* Now the real test while we are locked. */
if (!access(filename, F_OK))
{
rc = 0; /* Okay, we may access the file now. */
goto leave;
}
/* The file does not yet exist, create it now. */
oldmask = umask (077);
fp = es_fopen (filename, "wb");
if (!fp)
{
rc = gpg_error_from_syserror ();
umask (oldmask);
log_error (_("error creating keybox '%s': %s\n"),
filename, gpg_strerror (rc));
goto leave;
}
umask (oldmask);
/* Make sure that at least one record is in a new keybox file, so
that the detection magic for OpenPGP keyboxes works the next time
it is used. */
rc = _keybox_write_header_blob (fp, 0);
if (rc)
{
es_fclose (fp);
log_error (_("error creating keybox '%s': %s\n"),
filename, gpg_strerror (rc));
goto leave;
}
if (!opt.quiet)
log_info (_("keybox '%s' created\n"), filename);
if (r_created)
*r_created = 1;
es_fclose (fp);
rc = 0;
leave:
if (lockhd)
{
dotlock_release (lockhd);
dotlock_destroy (lockhd);
}
return rc;
}
/*
* Register a resource (which currently may only be a keybox file).
* The first keybox which is added by this function is created if it
* does not exist. If AUTO_CREATED is not NULL it will be set to true
* if the function has created a new keybox.
*/
gpg_error_t
keydb_add_resource (ctrl_t ctrl, const char *url, int force, int *auto_created)
{
const char *resname = url;
char *filename = NULL;
gpg_error_t err = 0;
KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE;
if (auto_created)
*auto_created = 0;
/* Do we have an URL?
gnupg-kbx:filename := this is a plain keybox
filename := See what it is, but create as plain keybox.
*/
if (strlen (resname) > 10)
{
if (!strncmp (resname, "gnupg-kbx:", 10) )
{
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
resname += 10;
}
#if !defined(HAVE_DRIVE_LETTERS) && !defined(__riscos__)
else if (strchr (resname, ':'))
{
log_error ("invalid key resource URL '%s'\n", url );
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
#endif /* !HAVE_DRIVE_LETTERS && !__riscos__ */
}
if (*resname != DIRSEP_C )
{ /* do tilde expansion etc */
if (strchr(resname, DIRSEP_C) )
filename = make_filename (resname, NULL);
else
filename = make_filename (gnupg_homedir (), resname, NULL);
}
else
filename = xstrdup (resname);
if (!force)
force = !any_registered;
/* see whether we can determine the filetype */
if (rt == KEYDB_RESOURCE_TYPE_NONE)
{
estream_t fp;
fp = es_fopen( filename, "rb" );
if (fp)
{
u32 magic;
/* FIXME: check for the keybox magic */
if (es_fread (&magic, 4, 1, fp) == 1 )
{
if (magic == 0x13579ace || magic == 0xce9a5713)
; /* GDBM magic - no more support */
else
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
}
else /* maybe empty: assume keybox */
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
es_fclose (fp);
}
else /* no file yet: create keybox */
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
}
switch (rt)
{
case KEYDB_RESOURCE_TYPE_NONE:
log_error ("unknown type of key resource '%s'\n", url );
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = maybe_create_keybox (filename, force, auto_created);
if (err)
goto leave;
/* Now register the file */
{
void *token;
err = keybox_register_file (filename, 0, &token);
if (gpg_err_code (err) == GPG_ERR_EEXIST)
; /* Already registered - ignore. */
else if (err)
; /* Other error. */
else if (used_resources >= MAX_KEYDB_RESOURCES)
err = gpg_error (GPG_ERR_RESOURCE_LIMIT);
else
{
KEYBOX_HANDLE kbxhd;
all_resources[used_resources].type = rt;
all_resources[used_resources].u.kr = NULL; /* Not used here */
all_resources[used_resources].token = token;
/* Do a compress run if needed and the keybox is not locked. */
kbxhd = keybox_new_x509 (token, 0);
if (kbxhd)
{
if (!keybox_lock (kbxhd, 1, 0))
{
keybox_compress (kbxhd);
keybox_lock (kbxhd, 0, 0);
}
keybox_release (kbxhd);
}
used_resources++;
}
}
break;
default:
log_error ("resource type of '%s' not supported\n", url);
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
/* fixme: check directory permissions and print a warning */
leave:
if (err)
{
log_error ("keyblock resource '%s': %s\n", filename, gpg_strerror (err));
gpgsm_status_with_error (ctrl, STATUS_ERROR,
"add_keyblock_resource", err);
}
else
any_registered = 1;
xfree (filename);
return err;
}
/* Print a warning if the server's version number is less than our
version number. Returns an error code on a connection problem. */
static gpg_error_t
warn_version_mismatch (ctrl_t ctrl, assuan_context_t ctx,
const char *servername)
{
return warn_server_version_mismatch (ctx, servername, 0,
gpgsm_status2, ctrl,
!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 (ctrl, ctx, KEYBOXD_NAME)))
{
/* Place to emit global options. */
}
if (err)
assuan_release (ctx);
else
*r_ctx = ctx;
return err;
}
/* Get a context for accessing keyboxd. If no context is available a
* new one is created and if necessary keyboxd is started. R_KBL
* receives a pointer to the local context object. */
static gpg_error_t
open_context (ctrl_t ctrl, keydb_local_t *r_kbl)
{
gpg_error_t err;
keydb_local_t kbl;
*r_kbl = NULL;
for (;;)
{
for (kbl = ctrl->keydb_local; kbl && kbl->is_active; kbl = kbl->next)
;
if (kbl)
{
/* Found an inactive keyboxd session - return that. */
log_assert (!kbl->is_active);
kbl->is_active = 1;
kbl->need_search_reset = 1;
*r_kbl = kbl;
return 0;
}
/* None found. Create a new session and retry. */
kbl = xtrycalloc (1, sizeof *kbl);
if (!kbl)
return gpg_error_from_syserror ();
err = create_new_context (ctrl, &kbl->ctx);
if (err)
{
xfree (kbl);
return err;
}
err = kbx_client_data_new (&kbl->kcd, kbl->ctx, 1);
if (err)
{
assuan_release (kbl->ctx);
xfree (kbl);
return err;
}
/* For thread-saftey we add it to the list and retry; this is
* easier than to employ a lock. */
kbl->next = ctrl->keydb_local;
ctrl->keydb_local = kbl;
}
/*NOTREACHED*/
}
KEYDB_HANDLE
keydb_new (ctrl_t ctrl)
{
gpg_error_t err;
KEYDB_HANDLE hd;
int rc, i, j;
if (DBG_CLOCK)
log_clock ("%s: enter\n", __func__);
hd = xcalloc (1, sizeof *hd);
hd->found = -1;
hd->saved_found = -1;
hd->use_keyboxd = opt.use_keyboxd;
hd->ctrl = ctrl;
if (hd->use_keyboxd)
{
err = open_context (ctrl, &hd->kbl);
if (err)
{
log_error (_("error opening key DB: %s\n"), gpg_strerror (err));
xfree (hd);
hd = NULL;
if (!(rc = gpg_err_code_to_errno (err)))
rc = gpg_err_code_to_errno (GPG_ERR_EIO);
gpg_err_set_errno (rc);
goto leave;
}
}
else /* Use the local keybox. */
{
log_assert (used_resources <= MAX_KEYDB_RESOURCES);
for (i=j=0; i < used_resources; i++)
{
switch (all_resources[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE: /* ignore */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
hd->active[j].type = all_resources[i].type;
hd->active[j].token = all_resources[i].token;
hd->active[j].u.kr = keybox_new_x509 (all_resources[i].token, 0);
if (!hd->active[j].u.kr)
{
xfree (hd);
return NULL; /* fixme: free all previously allocated handles*/
}
j++;
break;
}
}
hd->used = j;
}
active_handles++;
leave:
if (DBG_CLOCK)
log_clock ("%s: leave (hd=%p)\n", __func__, hd);
return hd;
}
void
keydb_release (KEYDB_HANDLE hd)
{
keydb_local_t kbl;
int i;
if (!hd)
return;
if (DBG_CLOCK)
log_clock ("%s: enter (hd=%p)\n", __func__, hd);
log_assert (active_handles > 0);
active_handles--;
if (hd->use_keyboxd)
{
kbl = hd->kbl;
if (DBG_CLOCK)
log_clock ("close_context (found)");
if (!kbl->is_active)
log_fatal ("closing inactive keyboxd context %p\n", kbl);
kbl->is_active = 0;
hd->kbl = NULL;
}
else
{
hd->keep_lock = 0;
unlock_all (hd);
for (i=0; i < hd->used; i++)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_release (hd->active[i].u.kr);
break;
}
}
}
xfree (hd);
if (DBG_CLOCK)
log_clock ("%s: leave\n", __func__);
}
/* Return the name of the current resource. This is function first
looks for the last found found, then for the current search
position, and last returns the first available resource. The
returned string is only valid as long as the handle exists. This
function does only return NULL if no handle is specified, in all
other error cases an empty string is returned. */
const char *
keydb_get_resource_name (KEYDB_HANDLE hd)
{
int idx;
const char *s = NULL;
if (!hd)
return NULL;
if (hd->use_keyboxd)
return "[keyboxd]";
if ( hd->found >= 0 && hd->found < hd->used)
idx = hd->found;
else if ( hd->current >= 0 && hd->current < hd->used)
idx = hd->current;
else
idx = 0;
switch (hd->active[idx].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
s = NULL;
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
s = keybox_get_resource_name (hd->active[idx].u.kr);
break;
}
return s? s: "";
}
/* Switch the handle into ephemeral mode and return the original value. */
int
keydb_set_ephemeral (KEYDB_HANDLE hd, int yes)
{
int i;
if (!hd)
return 0;
if (hd->use_keyboxd)
return 0; /* FIXME: No support yet. */
yes = !!yes;
if (hd->is_ephemeral != yes)
{
for (i=0; i < hd->used; i++)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_set_ephemeral (hd->active[i].u.kr, yes);
break;
}
}
}
i = hd->is_ephemeral;
hd->is_ephemeral = yes;
return i;
}
/* If the keyring has not yet been locked, lock it now. This
* operation is required before any update operation; it is optional
* for an insert operation. The lock is kept until a keydb_release so
* that internal unlock_all calls have no effect. */
gpg_error_t
keydb_lock (KEYDB_HANDLE hd)
{
gpg_error_t err;
if (!hd)
return gpg_error (GPG_ERR_INV_HANDLE);
if (hd->use_keyboxd)
return 0;
if (DBG_CLOCK)
log_clock ("%s: enter (hd=%p)\n", __func__, hd);
err = lock_all (hd);
if (!err)
hd->keep_lock = 1;
if (DBG_CLOCK)
log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
return err;
}
static int
lock_all (KEYDB_HANDLE hd)
{
int i, rc = 0;
if (hd->use_keyboxd)
return 0;
/* Fixme: This locking scheme may lead to deadlock if the resources
are not added in the same order by all processes. We are
currently only allowing one resource so it is not a problem. */
for (i=0; i < hd->used; i++)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
rc = keybox_lock (hd->active[i].u.kr, 1, -1);
break;
}
if (rc)
break;
}
if (rc)
{
/* Revert the already set locks. */
for (i--; i >= 0; i--)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_lock (hd->active[i].u.kr, 0, 0);
break;
}
}
}
else
hd->locked = 1;
return rc;
}
static void
unlock_all (KEYDB_HANDLE hd)
{
int i;
if (hd->use_keyboxd)
return;
if (!hd->locked || hd->keep_lock)
return;
for (i=hd->used-1; i >= 0; i--)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_lock (hd->active[i].u.kr, 0, 0);
break;
}
}
hd->locked = 0;
}
/* Push the last found state if any. */
void
keydb_push_found_state (KEYDB_HANDLE hd)
{
if (!hd)
return;
if (hd->use_keyboxd)
return; /* FIXME: Do we need this? */
if (hd->found < 0 || hd->found >= hd->used)
{
hd->saved_found = -1;
return;
}
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_push_found_state (hd->active[hd->found].u.kr);
break;
}
hd->saved_found = hd->found;
hd->found = -1;
if (DBG_CLOCK)
log_clock ("%s: done (hd=%p)\n", __func__, hd);
}
/* Pop the last found state. */
void
keydb_pop_found_state (KEYDB_HANDLE hd)
{
if (!hd)
return;
if (hd->use_keyboxd)
return; /* FIXME: Do we need this? */
hd->found = hd->saved_found;
hd->saved_found = -1;
if (hd->found < 0 || hd->found >= hd->used)
return;
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_pop_found_state (hd->active[hd->found].u.kr);
break;
}
if (DBG_CLOCK)
log_clock ("%s: done (hd=%p)\n", __func__, hd);
}
/* Return the last found certificate. Caller must free it. */
int
keydb_get_cert (KEYDB_HANDLE hd, ksba_cert_t *r_cert)
{
int err = 0;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (DBG_CLOCK)
log_clock ("%s: enter (hd=%p)\n", __func__, hd);
if (hd->use_keyboxd)
{
ksba_cert_t cert;
/* Fixme: We should clear that also in non-keyboxd mode but we
* did not in the past and thus all code should be checked
* whether this is okay. If we run into error in keyboxd mode,
* this is a not as severe because keyboxd is currently
* experimental. */
*r_cert = NULL;
if (!hd->kbl->search_result.buf || !hd->kbl->search_result.len)
{
err = gpg_error (GPG_ERR_VALUE_NOT_FOUND);
goto leave;
}
err = ksba_cert_new (&cert);
if (err)
goto leave;
err = ksba_cert_init_from_mem (cert,
hd->kbl->search_result.buf,
hd->kbl->search_result.len);
if (err)
{
ksba_cert_release (cert);
goto leave;
}
xfree (hd->kbl->search_result.buf);
hd->kbl->search_result.buf = NULL;
hd->kbl->search_result.len = 0;
*r_cert = cert;
goto leave;
}
if ( hd->found < 0 || hd->found >= hd->used)
{
/* Fixme: It would be better to use GPG_ERR_VALUE_NOT_FOUND here
* but for now we use NOT_FOUND because that is our standard
* replacement for the formerly used (-1). */
err = gpg_error (GPG_ERR_NOT_FOUND); /* nothing found */
goto leave;
}
err = GPG_ERR_BUG;
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = keybox_get_cert (hd->active[hd->found].u.kr, r_cert);
break;
}
leave:
if (DBG_CLOCK)
log_clock ("%s: leave (rc=%d)\n", __func__, err);
return err;
}
/* Return a flag of the last found object. WHICH is the flag requested;
it should be one of the KEYBOX_FLAG_ values. If the operation is
successful, the flag value will be stored at the address given by
VALUE. Return 0 on success or an error code. */
gpg_error_t
keydb_get_flags (KEYDB_HANDLE hd, int which, int idx, unsigned int *value)
{
gpg_error_t err;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (DBG_CLOCK)
log_clock ("%s: enter (hd=%p)\n", __func__, hd);
if (hd->use_keyboxd)
{
/* FIXME */
*value = 0;
err = 0;
goto leave;
}
if ( hd->found < 0 || hd->found >= hd->used)
{
err = gpg_error (GPG_ERR_NOTHING_FOUND);
goto leave;
}
err = gpg_error (GPG_ERR_BUG);
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = keybox_get_flags (hd->active[hd->found].u.kr, which, idx, value);
break;
}
leave:
if (DBG_CLOCK)
log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
return err;
}
/* Set a flag of the last found object. WHICH is the flag to be set; it
should be one of the KEYBOX_FLAG_ values. If the operation is
successful, the flag value will be stored in the keybox. Note,
that some flag values can't be updated and thus may return an
error, some other flag values may be masked out before an update.
Returns 0 on success or an error code. */
gpg_error_t
keydb_set_flags (KEYDB_HANDLE hd, int which, int idx, unsigned int value)
{
gpg_error_t err = 0;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (DBG_CLOCK)
log_clock ("%s: enter (hd=%p)\n", __func__, hd);
if (hd->use_keyboxd)
{
/* FIXME */
goto leave;
}
if ( hd->found < 0 || hd->found >= hd->used)
{
err = gpg_error (GPG_ERR_NOTHING_FOUND);
goto leave;
}
if (!hd->locked)
{
err = gpg_error (GPG_ERR_NOT_LOCKED);
goto leave;
}
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = keybox_set_flags (hd->active[hd->found].u.kr, which, idx, value);
break;
}
leave:
if (DBG_CLOCK)
log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
return err;
}
/* Communication object for Keyboxd STORE commands. */
struct store_parm_s
{
assuan_context_t ctx;
const void *data; /* The certificate in X.509 binary format. */
size_t datalen; /* The length of DATA. */
};
/* Handle the inquiries from the STORE command. */
static gpg_error_t
store_inq_cb (void *opaque, const char *line)
{
struct store_parm_s *parm = opaque;
gpg_error_t err = 0;
if (has_leading_keyword (line, "BLOB"))
{
if (parm->data)
err = assuan_send_data (parm->ctx, parm->data, parm->datalen);
}
else
return gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
return err;
}
/*
* Insert a new Certificate into one of the resources.
*/
gpg_error_t
keydb_insert_cert (KEYDB_HANDLE hd, ksba_cert_t cert)
{
gpg_error_t err;
int idx;
unsigned char digest[20];
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (opt.dry_run)
return 0;
if (DBG_CLOCK)
log_clock ("%s: enter (hd=%p)\n", __func__, hd);
if (hd->use_keyboxd)
{
struct store_parm_s parm;
parm.ctx = hd->kbl->ctx;
parm.data = ksba_cert_get_image (cert, &parm.datalen);
if (!parm.data)
{
log_debug ("broken ksba cert object\n");
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
err = assuan_transact (hd->kbl->ctx, "STORE --insert",
NULL, NULL,
store_inq_cb, &parm,
NULL, NULL);
goto leave;
}
if ( hd->found >= 0 && hd->found < hd->used)
idx = hd->found;
else if ( hd->current >= 0 && hd->current < hd->used)
idx = hd->current;
else
{
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
if (!hd->locked)
{
err = gpg_error (GPG_ERR_NOT_LOCKED);
goto leave;
}
gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/
err = gpg_error (GPG_ERR_BUG);
switch (hd->active[idx].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = keybox_insert_cert (hd->active[idx].u.kr, cert, digest);
break;
}
unlock_all (hd);
leave:
if (DBG_CLOCK)
log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
return err;
}
/* Update the current keyblock with KB. */
/* Note: This function is currently not called. */
gpg_error_t
keydb_update_cert (KEYDB_HANDLE hd, ksba_cert_t cert)
{
(void)hd;
(void)cert;
return GPG_ERR_BUG;
#if 0
gpg_error_t err;
unsigned char digest[20];
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if ( hd->found < 0 || hd->found >= hd->used)
return gpg_error (GPG_ERR_NOT_FOUND);
if (opt.dry_run)
return 0;
if (DBG_CLOCK)
log_clock ("%s: enter (hd=%p)\n", __func__, hd);
if (hd->use_keyboxd)
{
/* FIXME */
goto leave;
}
err = lock_all (hd);
if (err)
goto leave;
gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/
err = gpg_error (GPG_ERR_BUG);
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = keybox_update_cert (hd->active[hd->found].u.kr, cert, digest);
break;
}
unlock_all (hd);
leave:
if (DBG_CLOCK)
log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
return err;
#endif /*0*/
}
/*
* The current keyblock or cert will be deleted.
*/
gpg_error_t
keydb_delete (KEYDB_HANDLE hd)
{
gpg_error_t err;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (!hd->use_keyboxd && (hd->found < 0 || hd->found >= hd->used))
return gpg_error (GPG_ERR_NOT_FOUND);
if (opt.dry_run)
return 0;
if (DBG_CLOCK)
log_clock ("%s: enter (hd=%p)\n", __func__, hd);
if (hd->use_keyboxd)
{
unsigned char hexubid[UBID_LEN * 2 + 1];
char line[ASSUAN_LINELENGTH];
if (!hd->last_ubid_valid)
{
err = gpg_error (GPG_ERR_VALUE_NOT_FOUND);
goto leave;
}
bin2hex (hd->last_ubid, UBID_LEN, hexubid);
snprintf (line, sizeof line, "DELETE %s", hexubid);
err = assuan_transact (hd->kbl->ctx, line,
NULL, NULL,
NULL, NULL,
NULL, NULL);
goto leave;
}
if (!hd->locked)
{
err = gpg_error (GPG_ERR_NOT_LOCKED);
goto leave;
}
err = gpg_error (GPG_ERR_BUG);
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = keybox_delete (hd->active[hd->found].u.kr);
break;
}
unlock_all (hd);
leave:
if (DBG_CLOCK)
log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
return err;
}
/*
* Locate the default writable key resource, so that the next
* operation (which is only relevant for inserts) will be done on this
* resource.
*/
static gpg_error_t
keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved)
{
int rc;
(void)reserved;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (hd->use_keyboxd)
return 0; /* Not required. */
rc = keydb_search_reset (hd); /* this does reset hd->current */
if (rc)
return rc;
for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++)
{
switch (hd->active[hd->current].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
BUG();
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
if (keybox_is_writable (hd->active[hd->current].token))
return 0; /* found (hd->current is set to it) */
break;
}
}
return gpg_error (GPG_ERR_NOT_FOUND);
}
/*
* Rebuild the caches of all key resources.
*/
void
keydb_rebuild_caches (void)
{
int i;
/* This function does nothing and thus we don't need to handle keyboxd in a
* special way. */
for (i=0; i < used_resources; i++)
{
switch (all_resources[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE: /* ignore */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
/* rc = keybox_rebuild_cache (all_resources[i].token); */
/* if (rc) */
/* log_error (_("failed to rebuild keybox cache: %s\n"), */
/* g10_errstr (rc)); */
break;
}
}
}
/*
* Start the next search on this handle right at the beginning
*/
gpg_error_t
keydb_search_reset (KEYDB_HANDLE hd)
{
gpg_error_t err = 0;
int i;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (DBG_CLOCK)
log_clock ("%s: enter (hd=%p)\n", __func__, hd);
hd->current = 0;
hd->found = -1;
if (hd->use_keyboxd)
{
/* All we need is to tell search that a reset is pending. Note that
* keydb_new sets this flag as well. To comply with the
* specification of keydb_delete_keyblock we also need to clear the
* ubid flag so that after a reset a delete can't be performed. */
hd->kbl->need_search_reset = 1;
hd->last_ubid_valid = 0;
}
else
{
/* Reset all resources */
for (i=0; !err && i < hd->used; i++)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = keybox_search_reset (hd->active[i].u.kr);
break;
}
}
}
if (DBG_CLOCK)
log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
return err;
}
char *
keydb_search_desc_dump (struct keydb_search_desc *desc)
{
char *fpr;
char *result;
switch (desc->mode)
{
case KEYDB_SEARCH_MODE_EXACT:
return xasprintf ("EXACT: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_SUBSTR:
return xasprintf ("SUBSTR: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_MAIL:
return xasprintf ("MAIL: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_MAILSUB:
return xasprintf ("MAILSUB: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_MAILEND:
return xasprintf ("MAILEND: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_WORDS:
return xasprintf ("WORDS: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_SHORT_KID:
return xasprintf ("SHORT_KID: '%08lX'", (ulong)desc->u.kid[1]);
case KEYDB_SEARCH_MODE_LONG_KID:
return xasprintf ("LONG_KID: '%08lX%08lX'",
(ulong)desc->u.kid[0], (ulong)desc->u.kid[1]);
case KEYDB_SEARCH_MODE_FPR:
fpr = bin2hexcolon (desc->u.fpr, desc->fprlen, NULL);
result = xasprintf ("FPR%02d: '%s'", desc->fprlen, fpr);
xfree (fpr);
return result;
case KEYDB_SEARCH_MODE_ISSUER:
return xasprintf ("ISSUER: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_ISSUER_SN:
return xasprintf ("ISSUER_SN: '#%.*s/%s'",
(int)desc->snlen,desc->sn, desc->u.name);
case KEYDB_SEARCH_MODE_SN:
return xasprintf ("SN: '%.*s'",
(int)desc->snlen, desc->sn);
case KEYDB_SEARCH_MODE_SUBJECT:
return xasprintf ("SUBJECT: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_KEYGRIP:
return xasprintf ("KEYGRIP: %s", desc->u.grip);
case KEYDB_SEARCH_MODE_FIRST:
return xasprintf ("FIRST");
case KEYDB_SEARCH_MODE_NEXT:
return xasprintf ("NEXT");
default:
return xasprintf ("Bad search mode (%d)", desc->mode);
}
}
/* Status callback for SEARCH and NEXT operaions. */
static gpg_error_t
search_status_cb (void *opaque, const char *line)
{
KEYDB_HANDLE hd = opaque;
gpg_error_t err = 0;
const char *s;
unsigned int n;
if ((s = has_leading_keyword (line, "PUBKEY_INFO")))
{
if (atoi (s) != PUBKEY_TYPE_X509)
err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
else
{
hd->last_ubid_valid = 0;
while (*s && !spacep (s))
s++;
if (!(n=hex2fixedbuf (s, hd->last_ubid, sizeof hd->last_ubid)))
err = gpg_error (GPG_ERR_INV_VALUE);
else
{
hd->last_ubid_valid = 1;
s += n;
hd->last_is_ephemeral = (*s == 'e');
}
}
}
return err;
}
/* Search through all keydb resources, starting at the current
* position, for a keyblock which contains one of the keys described
* in the DESC array. In keyboxd mode the search is instead delegated
* to the keyboxd.
*
* DESC is an array of search terms with NDESC entries. The search
* terms are or'd together. That is, the next entry in the DB that
* matches any of the descriptions will be returned.
*
* Note: this function resumes searching where the last search left
* off (i.e., at the current file position). If you want to search
* from the start of the database, then you need to first call
* keydb_search_reset().
*
* If no key matches the search description, the error code
* GPG_ERR_NOT_FOUND is retruned. If there was a match, 0 is
* returned. If an error occurred, that error code is returned.
*
* The returned key is considered to be selected and the certificate
* can be detched via keydb_get_cert. */
gpg_error_t
keydb_search (ctrl_t ctrl, KEYDB_HANDLE hd,
KEYDB_SEARCH_DESC *desc, size_t ndesc)
{
gpg_error_t err = gpg_error (GPG_ERR_EOF);
unsigned long skipped;
int i;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (!any_registered && !hd->use_keyboxd)
{
gpgsm_status_with_error (ctrl, STATUS_ERROR, "keydb_search",
gpg_error (GPG_ERR_KEYRING_OPEN));
return gpg_error (GPG_ERR_NOT_FOUND);
}
if (DBG_CLOCK)
log_clock ("%s: enter (hd=%p)\n", __func__, hd);
if (DBG_LOOKUP)
{
log_debug ("%s: %zd search description(s):\n", __func__, ndesc);
for (i = 0; i < ndesc; i ++)
{
char *t = keydb_search_desc_dump (&desc[i]);
log_debug ("%s: %d: %s\n", __func__, i, t);
xfree (t);
}
}
if (hd->use_keyboxd)
{
char line[ASSUAN_LINELENGTH];
/* Clear the result objects. */
if (hd->kbl->search_result.buf)
{
xfree (hd->kbl->search_result.buf);
hd->kbl->search_result.buf = NULL;
hd->kbl->search_result.len = 0;
}
/* Check whether this is a NEXT search. */
if (!hd->kbl->need_search_reset)
{
/* A reset was not requested thus continue the search. The
* keyboxd keeps the context of the search and thus the NEXT
* operates on the last search pattern. This is the way how
* we always used the keydb functions. In theory we were
* able to modify the search pattern between searches but
* that is not anymore supported by keyboxd and a cursory
* check does not show that we actually made use of that
* misfeature. */
snprintf (line, sizeof line, "NEXT --x509");
goto do_search;
}
hd->kbl->need_search_reset = 0;
if (!ndesc)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
/* FIXME: Implement --multi */
switch (desc->mode)
{
case KEYDB_SEARCH_MODE_EXACT:
snprintf (line, sizeof line, "SEARCH --x509 =%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_SUBSTR:
snprintf (line, sizeof line, "SEARCH --x509 *%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_MAIL:
- snprintf (line, sizeof line, "SEARCH --x509 <%s", desc[0].u.name);
+ snprintf (line, sizeof line, "SEARCH --x509 <%s",
+ desc[0].u.name + (desc[0].u.name[0] == '<'));
break;
case KEYDB_SEARCH_MODE_MAILSUB:
snprintf (line, sizeof line, "SEARCH --x509 @%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_MAILEND:
snprintf (line, sizeof line, "SEARCH --x509 .%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_WORDS:
snprintf (line, sizeof line, "SEARCH --x509 +%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
snprintf (line, sizeof line, "SEARCH --x509 0x%08lX",
(ulong)desc->u.kid[1]);
break;
case KEYDB_SEARCH_MODE_LONG_KID:
snprintf (line, sizeof line, "SEARCH --x509 0x%08lX%08lX",
(ulong)desc->u.kid[0], (ulong)desc->u.kid[1]);
break;
case KEYDB_SEARCH_MODE_FPR:
{
unsigned char hexfpr[MAX_FINGERPRINT_LEN * 2 + 1];
log_assert (desc[0].fprlen <= MAX_FINGERPRINT_LEN);
bin2hex (desc[0].u.fpr, desc[0].fprlen, hexfpr);
snprintf (line, sizeof line, "SEARCH --x509 0x%s", hexfpr);
}
break;
case KEYDB_SEARCH_MODE_ISSUER:
snprintf (line, sizeof line, "SEARCH --x509 #/%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_ISSUER_SN:
if (desc[0].snhex)
snprintf (line, sizeof line, "SEARCH --x509 #%.*s/%s",
(int)desc[0].snlen, desc[0].sn, desc[0].u.name);
else
{
char *hexsn = bin2hex (desc[0].sn, desc[0].snlen, NULL);
if (!hexsn)
{
err = gpg_error_from_syserror ();
goto leave;
}
snprintf (line, sizeof line, "SEARCH --x509 #%s/%s",
hexsn, desc[0].u.name);
xfree (hexsn);
}
break;
case KEYDB_SEARCH_MODE_SN:
snprintf (line, sizeof line, "SEARCH --x509 #%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_SUBJECT:
snprintf (line, sizeof line, "SEARCH --x509 /%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_KEYGRIP:
{
unsigned char hexgrip[KEYGRIP_LEN * 2 + 1];
bin2hex (desc[0].u.grip, KEYGRIP_LEN, hexgrip);
snprintf (line, sizeof line, "SEARCH --x509 &%s", hexgrip);
}
break;
case KEYDB_SEARCH_MODE_UBID:
{
unsigned char hexubid[UBID_LEN * 2 + 1];
bin2hex (desc[0].u.ubid, UBID_LEN, hexubid);
snprintf (line, sizeof line, "SEARCH --x509 ^%s", hexubid);
}
break;
case KEYDB_SEARCH_MODE_FIRST:
snprintf (line, sizeof line, "SEARCH --x509");
break;
case KEYDB_SEARCH_MODE_NEXT:
log_debug ("%s: mode next - we should not get to here!\n", __func__);
snprintf (line, sizeof line, "NEXT --x509");
break;
default:
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
do_search:
hd->last_ubid_valid = 0;
/* To avoid silent truncation we error out on a too long line. */
if (strlen (line) + 5 >= sizeof line)
err = gpg_error (GPG_ERR_ASS_LINE_TOO_LONG);
else
err = kbx_client_data_cmd (hd->kbl->kcd, line, search_status_cb, hd);
if (!err && !(err = kbx_client_data_wait (hd->kbl->kcd,
&hd->kbl->search_result.buf,
&hd->kbl->search_result.len)))
{
/* if (hd->last_ubid_valid) */
/* log_printhex (hd->last_ubid, 20, "found UBID%s:", */
/* hd->last_is_ephemeral? "(ephemeral)":""); */
}
}
else /* Local keyring search. */
{
while (gpg_err_code (err) == GPG_ERR_EOF
&& hd->current >= 0 && hd->current < hd->used)
{
switch (hd->active[hd->current].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
BUG(); /* we should never see it here */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = keybox_search (hd->active[hd->current].u.kr, desc, ndesc,
KEYBOX_BLOBTYPE_X509,
NULL, &skipped);
if (err == -1) /* Map legacy code. */
err = gpg_error (GPG_ERR_EOF);
break;
}
if (DBG_LOOKUP)
log_debug ("%s: searched %s (resource %d of %d) => %s\n",
__func__,
hd->active[hd->current].type==KEYDB_RESOURCE_TYPE_KEYBOX
? "keybox" : "unknown type",
hd->current, hd->used, gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_EOF)
{ /* EOF -> switch to next resource */
hd->current++;
}
else if (!err)
hd->found = hd->current;
}
}
leave:
/* The NOTHING_FOUND error is triggered by a NEXT command. */
if (gpg_err_code (err) == GPG_ERR_EOF
|| gpg_err_code (err) == GPG_ERR_NOTHING_FOUND)
err = gpg_error (GPG_ERR_NOT_FOUND);
if (DBG_CLOCK)
log_clock ("%s: leave (%s)\n", __func__, gpg_strerror (err));
return err;
}
int
keydb_search_first (ctrl_t ctrl, KEYDB_HANDLE hd)
{
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_FIRST;
return keydb_search (ctrl, hd, &desc, 1);
}
int
keydb_search_next (ctrl_t ctrl, KEYDB_HANDLE hd)
{
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_NEXT;
return keydb_search (ctrl, hd, &desc, 1);
}
int
keydb_search_kid (ctrl_t ctrl, KEYDB_HANDLE hd, u32 *kid)
{
KEYDB_SEARCH_DESC desc;
(void)kid;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_LONG_KID;
desc.u.kid[0] = kid[0];
desc.u.kid[1] = kid[1];
return keydb_search (ctrl, hd, &desc, 1);
}
int
keydb_search_fpr (ctrl_t ctrl, KEYDB_HANDLE hd, const byte *fpr)
{
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_FPR;
memcpy (desc.u.fpr, fpr, 20);
desc.fprlen = 20;
return keydb_search (ctrl, hd, &desc, 1);
}
int
keydb_search_issuer (ctrl_t ctrl, KEYDB_HANDLE hd, const char *issuer)
{
KEYDB_SEARCH_DESC desc;
int rc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_ISSUER;
desc.u.name = issuer;
rc = keydb_search (ctrl, hd, &desc, 1);
return rc;
}
int
keydb_search_issuer_sn (ctrl_t ctrl, KEYDB_HANDLE hd,
const char *issuer, ksba_const_sexp_t serial)
{
KEYDB_SEARCH_DESC desc;
int rc;
const unsigned char *s;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_ISSUER_SN;
s = serial;
if (*s !='(')
return gpg_error (GPG_ERR_INV_VALUE);
s++;
for (desc.snlen = 0; digitp (s); s++)
desc.snlen = 10*desc.snlen + atoi_1 (s);
if (*s !=':')
return gpg_error (GPG_ERR_INV_VALUE);
desc.sn = s+1;
desc.u.name = issuer;
rc = keydb_search (ctrl, hd, &desc, 1);
return rc;
}
int
keydb_search_subject (ctrl_t ctrl, KEYDB_HANDLE hd, const char *name)
{
KEYDB_SEARCH_DESC desc;
int rc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_SUBJECT;
desc.u.name = name;
rc = keydb_search (ctrl, hd, &desc, 1);
return rc;
}
/* Store the certificate in the key DB but make sure that it does not
already exists. We do this simply by comparing the fingerprint.
If EXISTED is not NULL it will be set to true if the certificate
was already in the DB. */
int
keydb_store_cert (ctrl_t ctrl, ksba_cert_t cert, int ephemeral, int *existed)
{
KEYDB_HANDLE kh;
int rc;
unsigned char fpr[20];
if (existed)
*existed = 0;
if (!gpgsm_get_fingerprint (cert, 0, fpr, NULL))
{
log_error (_("failed to get the fingerprint\n"));
return gpg_error (GPG_ERR_GENERAL);
}
kh = keydb_new (ctrl);
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
return gpg_error (GPG_ERR_ENOMEM);;
}
/* Set the ephemeral flag so that the search looks at all
records. */
keydb_set_ephemeral (kh, 1);
if (!kh->use_keyboxd)
{
rc = lock_all (kh);
if (rc)
return rc;
}
rc = keydb_search_fpr (ctrl, kh, fpr);
if (gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
{
keydb_release (kh);
if (!rc)
{
if (existed)
*existed = 1;
if (!ephemeral)
{
/* Remove ephemeral flags from existing certificate to "store"
it permanently. */
rc = keydb_set_cert_flags (ctrl, cert, 1, KEYBOX_FLAG_BLOB, 0,
KEYBOX_FLAG_BLOB_EPHEMERAL, 0);
if (rc)
{
log_error ("clearing ephemeral flag failed: %s\n",
gpg_strerror (rc));
return rc;
}
}
return 0; /* okay */
}
log_error (_("problem looking for existing certificate: %s\n"),
gpg_strerror (rc));
return rc;
}
/* Reset the ephemeral flag if not requested. */
if (!ephemeral)
keydb_set_ephemeral (kh, 0);
rc = keydb_locate_writable (kh, 0);
if (rc)
{
log_error (_("error finding writable keyDB: %s\n"), gpg_strerror (rc));
keydb_release (kh);
return rc;
}
rc = keydb_insert_cert (kh, cert);
if (rc)
{
log_error (_("error storing certificate: %s\n"), gpg_strerror (rc));
keydb_release (kh);
return rc;
}
keydb_release (kh);
return 0;
}
/* This is basically keydb_set_flags but it implements a complete
transaction by locating the certificate in the DB and updating the
flags. */
gpg_error_t
keydb_set_cert_flags (ctrl_t ctrl, ksba_cert_t cert, int ephemeral,
int which, int idx,
unsigned int mask, unsigned int value)
{
KEYDB_HANDLE kh;
gpg_error_t err;
unsigned char fpr[20];
unsigned int old_value;
if (!gpgsm_get_fingerprint (cert, 0, fpr, NULL))
{
log_error (_("failed to get the fingerprint\n"));
return gpg_error (GPG_ERR_GENERAL);
}
kh = keydb_new (ctrl);
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
return gpg_error (GPG_ERR_ENOMEM);;
}
if (ephemeral)
keydb_set_ephemeral (kh, 1);
if (!kh->use_keyboxd)
{
err = keydb_lock (kh);
if (err)
{
log_error (_("error locking keybox: %s\n"), gpg_strerror (err));
keydb_release (kh);
return err;
}
}
err = keydb_search_fpr (ctrl, kh, fpr);
if (err)
{
if (gpg_err_code (err) != gpg_error (GPG_ERR_NOT_FOUND))
log_error (_("problem re-searching certificate: %s\n"),
gpg_strerror (err));
keydb_release (kh);
return err;
}
err = keydb_get_flags (kh, which, idx, &old_value);
if (err)
{
log_error (_("error getting stored flags: %s\n"), gpg_strerror (err));
keydb_release (kh);
return err;
}
value = ((old_value & ~mask) | (value & mask));
if (value != old_value)
{
err = keydb_set_flags (kh, which, idx, value);
if (err)
{
log_error (_("error storing flags: %s\n"), gpg_strerror (err));
keydb_release (kh);
return err;
}
}
keydb_release (kh);
return 0;
}
/* Reset all the certificate flags we have stored with the certificates
for performance reasons. */
void
keydb_clear_some_cert_flags (ctrl_t ctrl, strlist_t names)
{
gpg_error_t err;
KEYDB_HANDLE hd = NULL;
KEYDB_SEARCH_DESC *desc = NULL;
int ndesc;
strlist_t sl;
int rc=0;
unsigned int old_value, value;
(void)ctrl;
hd = keydb_new (ctrl);
if (!hd)
{
log_error ("keydb_new failed\n");
goto leave;
}
if (!names)
ndesc = 1;
else
{
for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++)
;
}
desc = xtrycalloc (ndesc, sizeof *desc);
if (!ndesc)
{
log_error ("allocating memory failed: %s\n",
gpg_strerror (out_of_core ()));
goto leave;
}
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
else
{
for (ndesc=0, sl=names; sl; sl = sl->next)
{
rc = classify_user_id (sl->d, desc+ndesc, 0);
if (rc)
log_error ("key '%s' not found: %s\n", sl->d, gpg_strerror (rc));
else
ndesc++;
}
}
if (!hd->use_keyboxd)
{
err = keydb_lock (hd);
if (err)
{
log_error (_("error locking keybox: %s\n"), gpg_strerror (err));
goto leave;
}
}
while (!(rc = keydb_search (ctrl, hd, desc, ndesc)))
{
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
err = keydb_get_flags (hd, KEYBOX_FLAG_VALIDITY, 0, &old_value);
if (err)
{
log_error (_("error getting stored flags: %s\n"),
gpg_strerror (err));
goto leave;
}
value = (old_value & ~VALIDITY_REVOKED);
if (value != old_value)
{
err = keydb_set_flags (hd, KEYBOX_FLAG_VALIDITY, 0, value);
if (err)
{
log_error (_("error storing flags: %s\n"), gpg_strerror (err));
goto leave;
}
}
}
if (rc && gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
log_error ("keydb_search failed: %s\n", gpg_strerror (rc));
leave:
xfree (desc);
keydb_release (hd);
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Tue, Sep 16, 11:58 AM (1 d, 22 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
ab/b2/e90d00ef1fe39fc3a0396f8eed17
Attached To
rG GnuPG
Event Timeline
Log In to Comment