summaryrefslogtreecommitdiffstats
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
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>
-rw-r--r--g10/call-keyboxd.c451
-rw-r--r--g10/keydb-private.h4
-rw-r--r--g10/keydb.c9
-rw-r--r--kbx/Makefile.am8
-rw-r--r--kbx/kbx-client-util.c450
-rw-r--r--kbx/kbx-client-util.h41
6 files changed, 525 insertions, 438 deletions
diff --git a/g10/call-keyboxd.c b/g10/call-keyboxd.c
index e09fa20e3..31bccefec 100644
--- a/g10/call-keyboxd.c
+++ b/g10/call-keyboxd.c
@@ -40,6 +40,7 @@
#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 */
@@ -57,20 +58,8 @@ struct keyboxd_local_s
/* The active Assuan context. */
assuan_context_t ctx;
- /* This object is used if fd-passing is used to convey the
- * keyblocks. */
- struct {
- /* NULL or a stream used to receive data. */
- estream_t fp;
-
- /* Condition variable to sync the datastream with the command. */
- npth_mutex_t mutex;
- npth_cond_t cond;
-
- /* The found keyblock or the parsing error. */
- kbnode_t found_keyblock;
- gpg_error_t found_err;
- } datastream;
+ /* 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. */
@@ -79,41 +68,14 @@ struct keyboxd_local_s
/* This flag set while an operation is running on this context. */
unsigned int is_active : 1;
- /* This flag is set to record that the standard per session init has
- * been done. */
- unsigned int per_session_init_done : 1;
-
/* Flag indicating that a search reset is required. */
unsigned int need_search_reset : 1;
};
-/* Local prototypes. */
-static void *datastream_thread (void *arg);
-
-static void
-lock_datastream (keyboxd_local_t kbl)
-{
- int rc = npth_mutex_lock (&kbl->datastream.mutex);
- if (rc)
- log_fatal ("%s: failed to acquire mutex: %s\n", __func__,
- gpg_strerror (gpg_error_from_errno (rc)));
-}
-
-
-static void
-unlock_datastream (keyboxd_local_t kbl)
-{
- int rc = npth_mutex_unlock (&kbl->datastream.mutex);
- if (rc)
- log_fatal ("%s: failed to release mutex: %s\n", __func__,
- gpg_strerror (gpg_error_from_errno (rc)));
-}
-
-
/* Deinitialize all session resources pertaining to the keyboxd. */
void
gpg_keyboxd_deinit_session_data (ctrl_t ctrl)
@@ -127,8 +89,8 @@ gpg_keyboxd_deinit_session_data (ctrl_t ctrl)
log_error ("oops: trying to cleanup an active keyboxd context\n");
else
{
- es_fclose (kbl->datastream.fp);
- kbl->datastream.fp = NULL;
+ kbx_client_data_release (kbl->kcd);
+ kbl->kcd = NULL;
assuan_release (kbl->ctx);
kbl->ctx = NULL;
}
@@ -189,73 +151,6 @@ create_new_context (ctrl_t ctrl, assuan_context_t *r_ctx)
-/* Setup the pipe used for receiving data from the keyboxd. Store the
- * info on KBL. */
-static gpg_error_t
-prepare_data_pipe (keyboxd_local_t kbl)
-{
- gpg_error_t err;
- int rc;
- int inpipe[2];
- estream_t infp;
- npth_t thread;
- npth_attr_t tattr;
-
- 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 (kbl->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);
- close (inpipe[1]);
- return 0; /* Server may not support fd-passing. */
- }
-
- err = assuan_transact (kbl->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;
- }
-
- kbl->datastream.fp = infp;
- kbl->datastream.found_keyblock = NULL;
- kbl->datastream.found_err = 0;
-
- 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);
- kbl->datastream.fp = NULL;
- return err;
- }
- npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
- rc = npth_create (&thread, &tattr, datastream_thread, kbl);
- 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);
- kbl->datastream.fp = NULL;
- return err;
- }
-
- return 0;
-}
-
/* Get a context for accessing keyboxd. If no context is available a
* new one is created and if necessary keyboxd is started. R_KBL
@@ -264,7 +159,6 @@ static gpg_error_t
open_context (ctrl_t ctrl, keyboxd_local_t *r_kbl)
{
gpg_error_t err;
- int rc;
keyboxd_local_t kbl;
*r_kbl = NULL;
@@ -277,15 +171,6 @@ open_context (ctrl_t ctrl, keyboxd_local_t *r_kbl)
/* Found an inactive keyboxd session - return that. */
log_assert (!kbl->is_active);
- /* But first do the per session init if not yet done. */
- if (!kbl->per_session_init_done)
- {
- err = prepare_data_pipe (kbl);
- if (err)
- return err;
- kbl->per_session_init_done = 1;
- }
-
kbl->is_active = 1;
kbl->need_search_reset = 1;
@@ -298,29 +183,17 @@ open_context (ctrl_t ctrl, keyboxd_local_t *r_kbl)
if (!kbl)
return gpg_error_from_syserror ();
- rc = npth_mutex_init (&kbl->datastream.mutex, NULL);
- if (rc)
- {
- err = gpg_error_from_errno (rc);
- log_error ("error initializing mutex: %s\n", gpg_strerror (err));
- xfree (kbl);
- return err;
- }
- rc = npth_cond_init (&kbl->datastream.cond, NULL);
- if (rc)
+ err = create_new_context (ctrl, &kbl->ctx);
+ if (err)
{
- err = gpg_error_from_errno (rc);
- log_error ("error initializing condition: %s\n", gpg_strerror (err));
- npth_mutex_destroy (&kbl->datastream.mutex);
xfree (kbl);
return err;
}
- err = create_new_context (ctrl, &kbl->ctx);
+ err = kbx_client_data_new (&kbl->kcd, kbl->ctx);
if (err)
{
- npth_cond_destroy (&kbl->datastream.cond);
- npth_mutex_destroy (&kbl->datastream.mutex);
+ assuan_release (kbl->ctx);
xfree (kbl);
return err;
}
@@ -427,224 +300,6 @@ keydb_lock (KEYDB_HANDLE hd)
}
-
-/* FIXME: This helper is duplicates code of partse_keyblock_image. */
-static gpg_error_t
-keydb_get_keyblock_do_parse (iobuf_t iobuf, int pk_no, int uid_no,
- kbnode_t *r_keyblock)
-{
- gpg_error_t err;
- struct parse_packet_ctx_s parsectx;
- PACKET *pkt;
- kbnode_t keyblock = NULL;
- kbnode_t node, *tail;
- int in_cert, save_mode;
- int pk_count, uid_count;
-
- *r_keyblock = NULL;
-
- pkt = xtrymalloc (sizeof *pkt);
- if (!pkt)
- return gpg_error_from_syserror ();
- init_packet (pkt);
- init_parse_packet (&parsectx, iobuf);
- save_mode = set_packet_list_mode (0);
- in_cert = 0;
- tail = NULL;
- pk_count = uid_count = 0;
- while ((err = parse_packet (&parsectx, pkt)) != -1)
- {
- if (gpg_err_code (err) == GPG_ERR_UNKNOWN_PACKET)
- {
- free_packet (pkt, &parsectx);
- init_packet (pkt);
- continue;
- }
- if (err)
- {
- es_fflush (es_stdout);
- log_error ("parse_keyblock_image: read error: %s\n",
- gpg_strerror (err));
- if (gpg_err_code (err) == GPG_ERR_INV_PACKET)
- {
- free_packet (pkt, &parsectx);
- init_packet (pkt);
- continue;
- }
- err = gpg_error (GPG_ERR_INV_KEYRING);
- break;
- }
-
- /* Filter allowed packets. */
- switch (pkt->pkttype)
- {
- case PKT_PUBLIC_KEY:
- case PKT_PUBLIC_SUBKEY:
- case PKT_SECRET_KEY:
- case PKT_SECRET_SUBKEY:
- case PKT_USER_ID:
- case PKT_ATTRIBUTE:
- case PKT_SIGNATURE:
- case PKT_RING_TRUST:
- break; /* Allowed per RFC. */
-
- default:
- log_info ("skipped packet of type %d in keybox\n", (int)pkt->pkttype);
- free_packet(pkt, &parsectx);
- init_packet(pkt);
- continue;
- }
-
- /* Other sanity checks. */
- if (!in_cert && pkt->pkttype != PKT_PUBLIC_KEY)
- {
- log_error ("parse_keyblock_image: first packet in a keybox blob "
- "is not a public key packet\n");
- err = gpg_error (GPG_ERR_INV_KEYRING);
- break;
- }
- if (in_cert && (pkt->pkttype == PKT_PUBLIC_KEY
- || pkt->pkttype == PKT_SECRET_KEY))
- {
- log_error ("parse_keyblock_image: "
- "multiple keyblocks in a keybox blob\n");
- err = gpg_error (GPG_ERR_INV_KEYRING);
- break;
- }
- in_cert = 1;
-
- node = new_kbnode (pkt);
-
- switch (pkt->pkttype)
- {
- case PKT_PUBLIC_KEY:
- case PKT_PUBLIC_SUBKEY:
- case PKT_SECRET_KEY:
- case PKT_SECRET_SUBKEY:
- if (++pk_count == pk_no)
- node->flag |= 1;
- break;
-
- case PKT_USER_ID:
- if (++uid_count == uid_no)
- node->flag |= 2;
- break;
-
- default:
- break;
- }
-
- if (!keyblock)
- keyblock = node;
- else
- *tail = node;
- tail = &node->next;
- pkt = xtrymalloc (sizeof *pkt);
- if (!pkt)
- {
- err = gpg_error_from_syserror ();
- break;
- }
- init_packet (pkt);
- }
- set_packet_list_mode (save_mode);
-
- if (err == -1 && keyblock)
- err = 0; /* Got the entire keyblock. */
-
- if (err)
- release_kbnode (keyblock);
- else
- {
- *r_keyblock = keyblock;
- }
- free_packet (pkt, &parsectx);
- deinit_parse_packet (&parsectx);
- xfree (pkt);
- return err;
-}
-
-
-/* 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)
-{
- keyboxd_local_t kbl = arg;
- gpg_error_t err;
- int rc;
- unsigned char lenbuf[4];
- size_t nread, datalen;
- iobuf_t iobuf;
- int pk_no, uid_no;
- kbnode_t keyblock, tmpkeyblock;
-
-
- log_debug ("Datastream_thread started\n");
- while (kbl->datastream.fp)
- {
- /* log_debug ("Datastream_thread waiting ...\n"); */
- if (es_read (kbl->datastream.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); */
-
- iobuf = iobuf_esopen (kbl->datastream.fp, "rb", 1, datalen);
- pk_no = uid_no = 0; /* FIXME: Get this from the keyboxd. */
- err = keydb_get_keyblock_do_parse (iobuf, pk_no, uid_no, &keyblock);
- iobuf_close (iobuf);
- if (!err)
- {
- /* log_debug ("parsing datastream succeeded\n"); */
-
- /* Thread-safe assignment to the result var: */
- tmpkeyblock = kbl->datastream.found_keyblock;
- kbl->datastream.found_keyblock = keyblock;
- release_kbnode (tmpkeyblock);
- }
- else
- {
- /* log_debug ("parsing datastream failed: %s <%s>\n", */
- /* gpg_strerror (err), gpg_strsource (err)); */
- tmpkeyblock = kbl->datastream.found_keyblock;
- kbl->datastream.found_keyblock = NULL;
- kbl->datastream.found_err = err;
- release_kbnode (tmpkeyblock);
- }
-
- /* Tell the main thread. */
- lock_datastream (kbl);
- rc = npth_cond_signal (&kbl->datastream.cond);
- if (rc)
- {
- err = gpg_error_from_errno (rc);
- log_error ("%s: signaling condition failed: %s\n",
- __func__, gpg_strerror (err));
- }
- unlock_datastream (kbl);
- }
- log_debug ("Datastream_thread finished\n");
-
- return NULL;
-}
-
-
/* Return the keyblock last found by keydb_search() in *RET_KB.
*
* On success, the function returns 0 and the caller must free *RET_KB
@@ -677,19 +332,13 @@ keydb_get_keyblock (KEYDB_HANDLE hd, kbnode_t *ret_kb)
if (hd->kbl->search_result)
{
pk_no = uid_no = 0; /*FIXME: Get this from the keyboxd. */
- err = keydb_get_keyblock_do_parse (hd->kbl->search_result,
- pk_no, uid_no, ret_kb);
+ err = keydb_parse_keyblock (hd->kbl->search_result, pk_no, uid_no,
+ ret_kb);
/* In contrast to the old code we close the iobuf here and thus
* this function may be called only once to get a keyblock. */
iobuf_close (hd->kbl->search_result);
hd->kbl->search_result = NULL;
}
- else if (hd->kbl->datastream.found_keyblock)
- {
- *ret_kb = hd->kbl->datastream.found_keyblock;
- hd->kbl->datastream.found_keyblock = NULL;
- err = 0;
- }
else
{
err = gpg_error (GPG_ERR_VALUE_NOT_FOUND);
@@ -976,7 +625,8 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
gpg_error_t err;
int i;
char line[ASSUAN_LINELENGTH];
-
+ char *buffer;
+ size_t len;
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
@@ -989,7 +639,7 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
if (DBG_LOOKUP)
{
- log_debug ("%s: %zd search descriptions:\n", __func__, ndesc);
+ log_debug ("%s: %zu search descriptions:\n", __func__, ndesc);
for (i = 0; i < ndesc; i ++)
{
char *t = keydb_search_desc_dump (&desc[i]);
@@ -1010,11 +660,6 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
iobuf_close (hd->kbl->search_result);
hd->kbl->search_result = NULL;
}
- if (hd->kbl->datastream.found_keyblock)
- {
- release_kbnode (hd->kbl->datastream.found_keyblock);
- hd->kbl->datastream.found_keyblock = NULL;
- }
/* Check whether this is a NEXT search. */
if (!hd->kbl->need_search_reset)
@@ -1129,72 +774,14 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
do_search:
hd->last_ubid_valid = 0;
- if (hd->kbl->datastream.fp)
- {
- /* log_debug ("Sending command '%s'\n", line); */
- err = assuan_transact (hd->kbl->ctx, line,
- NULL, NULL,
- NULL, NULL,
- search_status_cb, hd);
- if (err)
- {
- /* log_debug ("Finished command with error: %s\n", 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
- {
- int rc;
-
- /* log_debug ("Finished command .. telling data stream\n"); */
- lock_datastream (hd->kbl);
- if (!hd->kbl->datastream.found_keyblock)
- {
- /* log_debug ("%s: waiting on datastream_cond ...\n", __func__); */
- rc = npth_cond_wait (&hd->kbl->datastream.cond,
- &hd->kbl->datastream.mutex);
- /* log_debug ("%s: waiting on datastream.cond done\n", __func__); */
- if (rc)
- {
- err = gpg_error_from_errno (rc);
- log_error ("%s: waiting on condition failed: %s\n",
- __func__, gpg_strerror (err));
- }
- }
- unlock_datastream (hd->kbl);
- }
- }
- else /* Slower D-line version if fd-passing was not successful. */
+ 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)))
{
- membuf_t data;
- void *buffer;
- size_t len;
-
- init_membuf (&data, 8192);
- err = assuan_transact (hd->kbl->ctx, line,
- put_membuf_cb, &data,
- NULL, NULL,
- search_status_cb, hd);
- if (err)
- {
- xfree (get_membuf (&data, &len));
- goto leave;
- }
-
- buffer = get_membuf (&data, &len);
- if (!buffer)
- {
- err = gpg_error_from_syserror ();
- goto leave;
- }
-
hd->kbl->search_result = iobuf_temp_with_content (buffer, len);
xfree (buffer);
- }
-
- /* if (hd->last_ubid_valid) */
- /* log_printhex (hd->last_ubid, 20, "found UBID:"); */
+ /* if (hd->last_ubid_valid) */
+ /* log_printhex (hd->last_ubid, 20, "found UBID:"); */
+ }
leave:
if (DBG_CLOCK)
diff --git a/g10/keydb-private.h b/g10/keydb-private.h
index fdc905edf..c54e73f69 100644
--- a/g10/keydb-private.h
+++ b/g10/keydb-private.h
@@ -154,6 +154,10 @@ struct keydb_handle_s
/*-- keydb.c --*/
+
+gpg_error_t keydb_parse_keyblock (iobuf_t iobuf, int pk_no, int uid_no,
+ kbnode_t *r_keyblock);
+
/* These are the functions call-keyboxd diverts to if the keyboxd is
* not used. */
diff --git a/g10/keydb.c b/g10/keydb.c
index 15291f307..ef56885bd 100644
--- a/g10/keydb.c
+++ b/g10/keydb.c
@@ -1136,8 +1136,9 @@ keydb_pop_found_state (KEYDB_HANDLE hd)
-static gpg_error_t
-parse_keyblock_image (iobuf_t iobuf, int pk_no, int uid_no,
+/* Parse the keyblock in IOBUF and return at R_KEYBLOCK. */
+gpg_error_t
+keydb_parse_keyblock (iobuf_t iobuf, int pk_no, int uid_no,
kbnode_t *r_keyblock)
{
gpg_error_t err;
@@ -1300,7 +1301,7 @@ internal_keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb)
}
else
{
- err = parse_keyblock_image (hd->keyblock_cache.iobuf,
+ err = keydb_parse_keyblock (hd->keyblock_cache.iobuf,
hd->keyblock_cache.pk_no,
hd->keyblock_cache.uid_no,
ret_kb);
@@ -1332,7 +1333,7 @@ internal_keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb)
&iobuf, &pk_no, &uid_no);
if (!err)
{
- err = parse_keyblock_image (iobuf, pk_no, uid_no, ret_kb);
+ err = keydb_parse_keyblock (iobuf, pk_no, uid_no, ret_kb);
if (!err && hd->keyblock_cache.state == KEYBLOCK_CACHE_PREPARED)
{
hd->keyblock_cache.state = KEYBLOCK_CACHE_FILLED;
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*/