summaryrefslogtreecommitdiffstats
path: root/kbx
diff options
context:
space:
mode:
authorWerner Koch <wk@gnupg.org>2020-09-02 15:05:44 +0200
committerWerner Koch <wk@gnupg.org>2020-09-02 15:16:29 +0200
commit497db0b5bcd688c4e2144ba167bd2ac485069d1b (patch)
treed5389aec8f4564500e5cc1d76f931d9892e61861 /kbx
parentkeyboxd: Fix user id based queries (diff)
downloadgnupg2-497db0b5bcd688c4e2144ba167bd2ac485069d1b.tar.xz
gnupg2-497db0b5bcd688c4e2144ba167bd2ac485069d1b.zip
keyboxd: Restructure client access code.
* kbx/kbx-client-util.c: New. * kbx/kbx-client-util.h: New. * kbx/Makefile.am (client_sources): New. * g10/keydb.c (parse_keyblock_image): Rename to keydb_parse_keyblock and make global. * g10/call-keyboxd.c: Include kbx-client-util.h. (struct keyboxd_local_s): Remove struct datastream. Add field kcd. Remove per_session_init_done. (lock_datastream, unlock_datastream): Remove. (prepare_data_pipe, datastream_thread): Remove. (keydb_get_keyblock_do_parse): Remove. (gpg_keyboxd_deinit_session_data): Release the KCD object. (open_context): Use of kbx_client_data_new. (keydb_get_keyblock): Simplify. (keydb_search): Use kbx_client_data_cmd and _wait. -- The data specific part of the code has been moved from gpg to a new module in kbx/ so that it can also be used by gpgsm. The OpenPGP parsing while reading the data has been replaced by storing the data in memory and parse it later. That makes a nice interface and abstracts the fd-passing/D-lines handling away. Signed-off-by: Werner Koch <wk@gnupg.org>
Diffstat (limited to 'kbx')
-rw-r--r--kbx/Makefile.am8
-rw-r--r--kbx/kbx-client-util.c450
-rw-r--r--kbx/kbx-client-util.h41
3 files changed, 497 insertions, 2 deletions
diff --git a/kbx/Makefile.am b/kbx/Makefile.am
index 242e373a6..fe72860e9 100644
--- a/kbx/Makefile.am
+++ b/kbx/Makefile.am
@@ -57,9 +57,13 @@ common_sources = \
keybox-openpgp.c \
keybox-dump.c
+client_sources = \
+ kbx-client-util.h \
+ kbx-client-util.c
-libkeybox_a_SOURCES = $(common_sources)
-libkeybox509_a_SOURCES = $(common_sources)
+
+libkeybox_a_SOURCES = $(common_sources) $(client_sources)
+libkeybox509_a_SOURCES = $(common_sources) $(client_sources)
libkeybox_a_CFLAGS = $(AM_CFLAGS)
libkeybox509_a_CFLAGS = $(AM_CFLAGS) -DKEYBOX_WITH_X509=1
diff --git a/kbx/kbx-client-util.c b/kbx/kbx-client-util.c
new file mode 100644
index 000000000..ba356b3c5
--- /dev/null
+++ b/kbx/kbx-client-util.c
@@ -0,0 +1,450 @@
+/* kbx-client-util.c - Utility functions to implement a keyboxd client
+ * Copyright (C) 2020 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <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 <npth.h>
+#include <assuan.h>
+
+#include "../common/util.h"
+#include "../common/membuf.h"
+#include "../common/i18n.h"
+#include "../common/asshelp.h"
+#include "../common/exechelp.h"
+#include "../common/sysutils.h"
+#include "../common/host2net.h"
+#include "kbx-client-util.h"
+
+
+#define MAX_DATABLOB_SIZE (16*1024*1024)
+
+
+
+/* This object is used to implement a client to the keyboxd. */
+struct kbx_client_data_s
+{
+ /* The used assuan context. */
+ assuan_context_t ctx;
+
+ /* A stream used to receive data. If this is NULL D-lines are used
+ * to receive the data. */
+ estream_t fp;
+
+ /* Condition variable to sync the datastream with the command. */
+ npth_mutex_t mutex;
+ npth_cond_t cond;
+
+ /* The data received from the keyboxd and an error code if there was
+ * a problem (in which case DATA is also set to NULL. This is only
+ * used if FP is not NULL. */
+ char *data;
+ size_t datalen;
+ gpg_error_t dataerr;
+
+ /* Helper variables in case D-lines are used (FP is NULL) */
+ char *dlinedata;
+ size_t dlinedatalen;
+ gpg_error_t dlineerr;
+};
+
+
+
+static void *datastream_thread (void *arg);
+
+
+
+static void
+lock_datastream (kbx_client_data_t kcd)
+{
+ int rc = npth_mutex_lock (&kcd->mutex);
+ if (rc)
+ log_fatal ("%s: failed to acquire mutex: %s\n", __func__,
+ gpg_strerror (gpg_error_from_errno (rc)));
+}
+
+
+static void
+unlock_datastream (kbx_client_data_t kcd)
+{
+ int rc = npth_mutex_unlock (&kcd->mutex);
+ if (rc)
+ log_fatal ("%s: failed to release mutex: %s\n", __func__,
+ gpg_strerror (gpg_error_from_errno (rc)));
+}
+
+
+
+/* Setup the pipe used for receiving data from the keyboxd. Store the
+ * info on KCD. */
+static gpg_error_t
+prepare_data_pipe (kbx_client_data_t kcd)
+{
+ gpg_error_t err;
+ int rc;
+ int inpipe[2];
+ estream_t infp;
+ npth_t thread;
+ npth_attr_t tattr;
+
+ kcd->fp = NULL;
+ kcd->data = NULL;
+ kcd->datalen = 0;
+ kcd->dataerr = 0;
+
+ err = gnupg_create_inbound_pipe (inpipe, &infp, 0);
+ if (err)
+ {
+ log_error ("error creating inbound pipe: %s\n", gpg_strerror (err));
+ return err; /* That should not happen. */
+ }
+
+ err = assuan_sendfd (kcd->ctx, INT2FD (inpipe[1]));
+ if (err)
+ {
+ log_error ("sending sending fd %d to keyboxd: %s <%s>\n",
+ inpipe[1], gpg_strerror (err), gpg_strsource (err));
+ es_fclose (infp);
+ gnupg_close_pipe (inpipe[1]);
+ return 0; /* Server may not support fd-passing. */
+ }
+
+ err = assuan_transact (kcd->ctx, "OUTPUT FD",
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (err)
+ {
+ log_info ("keyboxd does not accept our fd: %s <%s>\n",
+ gpg_strerror (err), gpg_strsource (err));
+ es_fclose (infp);
+ return 0;
+ }
+
+ kcd->fp = infp;
+
+
+ rc = npth_attr_init (&tattr);
+ if (rc)
+ {
+ err = gpg_error_from_errno (rc);
+ log_error ("error preparing thread for keyboxd: %s\n",gpg_strerror (err));
+ es_fclose (infp);
+ kcd->fp = NULL;
+ return err;
+ }
+ npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
+ rc = npth_create (&thread, &tattr, datastream_thread, kcd);
+ if (rc)
+ {
+ err = gpg_error_from_errno (rc);
+ log_error ("error spawning thread for keyboxd: %s\n", gpg_strerror (err));
+ npth_attr_destroy (&tattr);
+ es_fclose (infp);
+ kcd->fp = NULL;
+ return err;
+ }
+
+ return 0;
+}
+
+
+/* The thread used to read from the data stream. This is running as
+ * long as the connection and its datastream exists. */
+static void *
+datastream_thread (void *arg)
+{
+ kbx_client_data_t kcd = arg;
+ gpg_error_t err;
+ int rc;
+ unsigned char lenbuf[4];
+ size_t nread, datalen;
+ int pk_no, uid_no;
+ char *data, *tmpdata;
+
+ /* log_debug ("%s: started\n", __func__); */
+ while (kcd->fp)
+ {
+ /* log_debug ("%s: waiting ...\n", __func__); */
+ if (es_read (kcd->fp, lenbuf, 4, &nread))
+ {
+ err = gpg_error_from_syserror ();
+ if (gpg_err_code (err) == GPG_ERR_EAGAIN)
+ continue;
+ log_error ("error reading data length from keyboxd: %s\n",
+ gpg_strerror (err));
+ gnupg_sleep (1);
+ continue;
+ }
+ if (nread != 4)
+ {
+ err = gpg_error (GPG_ERR_EIO);
+ log_error ("error reading data length from keyboxd: %s\n",
+ "short read");
+ continue;
+ }
+
+ datalen = buf32_to_size_t (lenbuf);
+ /* log_debug ("keyboxd announced %zu bytes\n", datalen); */
+ if (!datalen)
+ {
+ log_info ("ignoring empty blob received from keyboxd\n");
+ continue;
+ }
+
+ if (datalen > MAX_DATABLOB_SIZE)
+ {
+ err = gpg_error (GPG_ERR_TOO_LARGE);
+ /* Drop connection or what shall we do? */
+ }
+ else if (!(data = xtrymalloc (datalen+1)))
+ {
+ err = gpg_error_from_syserror ();
+ }
+ else if (es_read (kcd->fp, data, datalen, &nread))
+ {
+ err = gpg_error_from_syserror ();
+ }
+ else if (datalen != nread)
+ {
+ err = gpg_error (GPG_ERR_TOO_SHORT);
+ }
+ else
+ err = 0;
+
+ if (err)
+ {
+ log_error ("error reading data from keyboxd: %s <%s>\n",
+ gpg_strerror (err), gpg_strsource (err));
+ xfree (data);
+ data = NULL;
+ datalen = 0;
+ }
+ else
+ {
+ /* log_debug ("parsing datastream succeeded\n"); */
+ pk_no = uid_no = 0; /* FIXME: Get this from the keyboxd. */
+ }
+
+ /* Thread-safe assignment to the result var: */
+ tmpdata = kcd->data;
+ kcd->data = data;
+ kcd->datalen = datalen;
+ kcd->dataerr = err;
+ xfree (tmpdata);
+ data = NULL;
+
+ /* Tell the main thread. */
+ lock_datastream (kcd);
+ rc = npth_cond_signal (&kcd->cond);
+ if (rc)
+ {
+ err = gpg_error_from_errno (rc);
+ log_error ("%s: signaling condition failed: %s\n",
+ __func__, gpg_strerror (err));
+ }
+ unlock_datastream (kcd);
+ }
+ /* log_debug ("%s: finished\n", __func__); */
+
+ return NULL;
+}
+
+
+
+/* Create a new keyboxd client data object and return it at R_KCD.
+ * CTX is the assuan context to be used for connecting the
+ * keyboxd. */
+gpg_error_t
+kbx_client_data_new (kbx_client_data_t *r_kcd, assuan_context_t ctx)
+{
+ kbx_client_data_t kcd;
+ int rc;
+ gpg_error_t err;
+
+ kcd = xtrycalloc (1, sizeof *kcd);
+ if (!kcd)
+ return gpg_error_from_syserror ();
+
+ kcd->ctx = ctx;
+
+ rc = npth_mutex_init (&kcd->mutex, NULL);
+ if (rc)
+ {
+ err = gpg_error_from_errno (rc);
+ log_error ("error initializing mutex: %s\n", gpg_strerror (err));
+ xfree (kcd);
+ return err;
+ }
+ rc = npth_cond_init (&kcd->cond, NULL);
+ if (rc)
+ {
+ err = gpg_error_from_errno (rc);
+ log_error ("error initializing condition: %s\n", gpg_strerror (err));
+ npth_mutex_destroy (&kcd->mutex);
+ xfree (kcd);
+ return err;
+ }
+
+ err = prepare_data_pipe (kcd);
+ if (err)
+ {
+ npth_cond_destroy (&kcd->cond);
+ npth_mutex_destroy (&kcd->mutex);
+ xfree (kcd);
+ return err;
+ }
+
+ *r_kcd = kcd;
+ return 0;
+}
+
+
+void
+kbx_client_data_release (kbx_client_data_t kcd)
+{
+ estream_t fp;
+
+ if (!kcd)
+ return;
+ fp = kcd->fp;
+ kcd->fp = NULL;
+ es_fclose (fp); /* That close should let the thread run into an error. */
+ /* FIXME: Make thread killing explicit. Otherwise we run in a
+ * log_fatal due to the destroyed mutex. */
+ npth_cond_destroy (&kcd->cond);
+ npth_mutex_destroy (&kcd->mutex);
+ xfree (kcd);
+}
+
+
+/* Send the COMMAND down to the keyboxd associated with KCD.
+ * STATUS_CB and STATUS_CB_VALUE are the usual status callback as used
+ * by assuan_transact. After this function has returned success
+ * kbx_client_data_wait needs to be called to actually return the
+ * data. */
+gpg_error_t
+kbx_client_data_cmd (kbx_client_data_t kcd, const char *command,
+ gpg_error_t (*status_cb)(void *opaque, const char *line),
+ void *status_cb_value)
+{
+ gpg_error_t err;
+
+ xfree (kcd->dlinedata);
+ kcd->dlinedata = NULL;
+ kcd->dlinedatalen = 0;
+ kcd->dlineerr = 0;
+
+ if (kcd->fp)
+ {
+ /* log_debug ("%s: sending command '%s'\n", __func__, command); */
+ err = assuan_transact (kcd->ctx, command,
+ NULL, NULL,
+ NULL, NULL,
+ status_cb, status_cb_value);
+ if (err)
+ {
+ if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
+ log_debug ("%s: finished command with error: %s\n",
+ __func__, gpg_strerror (err));
+ /* Fixme: On unexpected errors we need a way to cancel the
+ * data stream. Probably it will be best to close and
+ * reopen it. */
+ }
+ }
+ else /* Slower D-line version if fd-passing is not available. */
+ {
+ membuf_t mb;
+ size_t len;
+
+ /* log_debug ("%s: sending command '%s' (no fd-passing)\n", */
+ /* __func__, command); */
+ init_membuf (&mb, 8192);
+ err = assuan_transact (kcd->ctx, command,
+ put_membuf_cb, &mb,
+ NULL, NULL,
+ status_cb, status_cb_value);
+ if (err)
+ {
+ if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
+ log_debug ("%s: finished command with error: %s\n",
+ __func__, gpg_strerror (err));
+ xfree (get_membuf (&mb, &len));
+ kcd->dlineerr = err;
+ goto leave;
+ }
+
+ kcd->dlinedata = get_membuf (&mb, &kcd->dlinedatalen);
+ if (!kcd->dlinedata)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ }
+
+ leave:
+ return err;
+}
+
+
+
+/* Wait for the data from the server and on success return it at
+ * (R_DATA, R_DATALEN). */
+gpg_error_t
+kbx_client_data_wait (kbx_client_data_t kcd, char **r_data, size_t *r_datalen)
+{
+ gpg_error_t err = 0;
+ int rc;
+
+ *r_data = NULL;
+ *r_datalen = 0;
+ if (kcd->fp)
+ {
+ lock_datastream (kcd);
+ if (!kcd->data && !kcd->dataerr)
+ {
+ /* log_debug ("%s: waiting on datastream_cond ...\n", __func__); */
+ rc = npth_cond_wait (&kcd->cond, &kcd->mutex);
+ if (rc)
+ {
+ err = gpg_error_from_errno (rc);
+ log_error ("%s: waiting on condition failed: %s\n",
+ __func__, gpg_strerror (err));
+ }
+ /* else */
+ /* log_debug ("%s: waiting on datastream.cond done\n", __func__); */
+ }
+ *r_data = kcd->data;
+ kcd->data = NULL;
+ *r_datalen = kcd->datalen;
+ err = err? err : kcd->dataerr;
+
+ unlock_datastream (kcd);
+ }
+ else
+ {
+ *r_data = kcd->dlinedata;
+ kcd->dlinedata = NULL;
+ *r_datalen = kcd->dlinedatalen;
+ err = kcd->dlineerr;
+ }
+
+ return err;
+}
diff --git a/kbx/kbx-client-util.h b/kbx/kbx-client-util.h
new file mode 100644
index 000000000..db6cb9475
--- /dev/null
+++ b/kbx/kbx-client-util.h
@@ -0,0 +1,41 @@
+/* kbx-client-util.c - Defs for utility functions for a keyboxd client
+ * Copyright (C) 2020 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#ifndef GNUPG_KBX_CLIENT_UTIL_H
+#define GNUPG_KBX_CLIENT_UTIL_H 1
+
+
+struct kbx_client_data_s;
+typedef struct kbx_client_data_s *kbx_client_data_t;
+
+gpg_error_t kbx_client_data_new (kbx_client_data_t *r_kcd,
+ assuan_context_t ctx);
+void kbx_client_data_release (kbx_client_data_t kcd);
+gpg_error_t kbx_client_data_cmd (kbx_client_data_t kcd, const char *command,
+ gpg_error_t (*status_cb)(void *opaque,
+ const char *line),
+ void *status_cb_value);
+gpg_error_t kbx_client_data_wait (kbx_client_data_t kcd,
+ char **r_data, size_t *r_datalen);
+
+
+
+
+#endif /*GNUPG_KBX_CLIENT_UTIL_H*/