aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/asshelp.c331
-rw-r--r--common/asshelp.h18
-rw-r--r--common/asshelp2.c56
-rw-r--r--common/homedir.c18
-rw-r--r--common/iobuf.c37
-rw-r--r--common/iobuf.h6
-rw-r--r--common/t-mbox-util.c6
-rw-r--r--common/userids.c14
-rw-r--r--common/util.h6
-rw-r--r--configure.ac18
-rw-r--r--doc/DETAILS12
-rw-r--r--doc/tools.texi11
-rw-r--r--g10/Makefile.am38
-rw-r--r--g10/call-keyboxd.c1112
-rw-r--r--g10/decrypt-data.c2
-rw-r--r--g10/delkey.c6
-rw-r--r--g10/export.c2
-rw-r--r--g10/getkey.c34
-rw-r--r--g10/gpg.c35
-rw-r--r--g10/gpg.h12
-rw-r--r--g10/gpgcompose.c4
-rw-r--r--g10/import.c17
-rw-r--r--g10/keydb-private.h171
-rw-r--r--g10/keydb.c252
-rw-r--r--g10/keydb.h74
-rw-r--r--g10/keyedit.c6
-rw-r--r--g10/keygen.c4
-rw-r--r--g10/keylist.c2
-rw-r--r--g10/keyserver.c2
-rw-r--r--g10/main.h2
-rw-r--r--g10/misc.c4
-rw-r--r--g10/objcache.c4
-rw-r--r--g10/options.h3
-rw-r--r--g10/photoid.c4
-rw-r--r--g10/revoke.c4
-rw-r--r--g10/sign.c12
-rw-r--r--g10/t-keydb-get-keyblock.c6
-rw-r--r--g10/t-keydb.c7
-rw-r--r--g10/tofu.c2
-rw-r--r--g10/trustdb.c2
-rw-r--r--kbx/Makefile.am35
-rw-r--r--kbx/backend-cache.c1190
-rw-r--r--kbx/backend-kbx.c328
-rw-r--r--kbx/backend-support.c171
-rw-r--r--kbx/backend.h140
-rw-r--r--kbx/frontend.c385
-rw-r--r--kbx/frontend.h36
-rw-r--r--kbx/kbxserver.c775
-rw-r--r--kbx/keybox-blob.c21
-rw-r--r--kbx/keybox-dump.c46
-rw-r--r--kbx/keybox-file.c18
-rw-r--r--kbx/keybox-search-desc.h11
-rw-r--r--kbx/keybox-search.c96
-rw-r--r--kbx/keybox-update.c4
-rw-r--r--kbx/keybox.h6
-rw-r--r--kbx/keyboxd-w32info.rc50
-rw-r--r--kbx/keyboxd.c1845
-rw-r--r--kbx/keyboxd.h156
-rw-r--r--po/Makevars1
-rw-r--r--sm/keydb.c2
-rw-r--r--tools/gpg-connect-agent.c29
61 files changed, 7194 insertions, 507 deletions
diff --git a/common/asshelp.c b/common/asshelp.c
index 88d18478d..83c378786 100644
--- a/common/asshelp.c
+++ b/common/asshelp.c
@@ -56,9 +56,11 @@
operation after we started them before giving up. */
#ifdef HAVE_W32CE_SYSTEM
# define SECS_TO_WAIT_FOR_AGENT 30
+# define SECS_TO_WAIT_FOR_KEYBOXD 30
# define SECS_TO_WAIT_FOR_DIRMNGR 30
#else
# define SECS_TO_WAIT_FOR_AGENT 5
+# define SECS_TO_WAIT_FOR_KEYBOXD 5
# define SECS_TO_WAIT_FOR_DIRMNGR 5
#endif
@@ -308,17 +310,15 @@ unlock_spawning (lock_spawn_t *lock, const char *name)
}
-/* Helper for start_new_gpg_agent and start_new_dirmngr.
- * Values for WHICH are:
- * 0 - Start gpg-agent
- * 1 - Start dirmngr
- * SECS give the number of seconds to wait. SOCKNAME is the name of
- * the socket to connect. VERBOSE is the usual verbose flag. CTX is
- * the assuan context. DID_SUCCESS_MSG will be set to 1 if a success
- * messages has been printed.
+/* Helper to start a service. SECS gives the number of seconds to
+ * wait. SOCKNAME is the name of the socket to connect. VERBOSE is
+ * the usual verbose flag. CTX is the assuan context. CONNECT_FLAGS
+ * are the assuan connect flags. DID_SUCCESS_MSG will be set to 1 if
+ * a success messages has been printed.
*/
static gpg_error_t
-wait_for_sock (int secs, int which, const char *sockname,
+wait_for_sock (int secs, int module_name_id, const char *sockname,
+ unsigned int connect_flags,
int verbose, assuan_context_t ctx, int *did_success_msg)
{
gpg_error_t err = 0;
@@ -343,8 +343,10 @@ wait_for_sock (int secs, int which, const char *sockname,
/* next_sleep_us); */
if (secsleft < lastalert)
{
- log_info (which == 1?
+ log_info (module_name_id == GNUPG_MODULE_NAME_DIRMNGR?
_("waiting for the dirmngr to come up ... (%ds)\n"):
+ module_name_id == GNUPG_MODULE_NAME_KEYBOXD?
+ _("waiting for the keyboxd to come up ... (%ds)\n"):
_("waiting for the agent to come up ... (%ds)\n"),
secsleft);
lastalert = secsleft;
@@ -352,13 +354,15 @@ wait_for_sock (int secs, int which, const char *sockname,
}
gnupg_usleep (next_sleep_us);
elapsed_us += next_sleep_us;
- err = assuan_socket_connect (ctx, sockname, 0, 0);
+ err = assuan_socket_connect (ctx, sockname, 0, connect_flags);
if (!err)
{
if (verbose)
{
- log_info (which == 1?
+ log_info (module_name_id == GNUPG_MODULE_NAME_DIRMNGR?
_("connection to the dirmngr established\n"):
+ module_name_id == GNUPG_MODULE_NAME_KEYBOXD?
+ _("connection to the keyboxd established\n"):
_("connection to the agent established\n"));
*did_success_msg = 1;
}
@@ -372,25 +376,35 @@ wait_for_sock (int secs, int which, const char *sockname,
}
-/* Try to connect to the agent via socket or start it if it is not
- running and AUTOSTART is set. Handle the server's initial
- greeting. Returns a new assuan context at R_CTX or an error
- code. */
-gpg_error_t
-start_new_gpg_agent (assuan_context_t *r_ctx,
- gpg_err_source_t errsource,
- const char *agent_program,
- const char *opt_lc_ctype,
- const char *opt_lc_messages,
- session_env_t session_env,
- int autostart, int verbose, int debug,
- gpg_error_t (*status_cb)(ctrl_t, int, ...),
- ctrl_t status_cb_arg)
+/* Try to connect to a new service via socket or start it if it is not
+ * running and AUTOSTART is set. Handle the server's initial
+ * greeting. Returns a new assuan context at R_CTX or an error code.
+ * MODULE_NAME_ID is one of:
+ * GNUPG_MODULE_NAME_AGENT
+ * GNUPG_MODULE_NAME_DIRMNGR
+ */
+static gpg_error_t
+start_new_service (assuan_context_t *r_ctx,
+ int module_name_id,
+ gpg_err_source_t errsource,
+ const char *program_name,
+ const char *opt_lc_ctype,
+ const char *opt_lc_messages,
+ session_env_t session_env,
+ int autostart, int verbose, int debug,
+ gpg_error_t (*status_cb)(ctrl_t, int, ...),
+ ctrl_t status_cb_arg)
{
gpg_error_t err;
assuan_context_t ctx;
int did_success_msg = 0;
char *sockname;
+ const char *printed_name;
+ const char *lock_name;
+ const char *status_start_line;
+ int no_service_err;
+ int seconds_to_wait;
+ unsigned int connect_flags = 0;
const char *argv[6];
*r_ctx = NULL;
@@ -402,15 +416,40 @@ start_new_gpg_agent (assuan_context_t *r_ctx,
return err;
}
- sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
- if (!sockname)
+ switch (module_name_id)
{
- err = gpg_err_make (errsource, gpg_err_code_from_syserror ());
+ case GNUPG_MODULE_NAME_AGENT:
+ sockname = make_filename (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
+ lock_name = "agent";
+ printed_name = "gpg-agent";
+ status_start_line = "starting_agent ? 0 0";
+ no_service_err = GPG_ERR_NO_AGENT;
+ seconds_to_wait = SECS_TO_WAIT_FOR_AGENT;
+ break;
+ case GNUPG_MODULE_NAME_DIRMNGR:
+ sockname = make_filename (gnupg_socketdir (), DIRMNGR_SOCK_NAME, NULL);
+ lock_name = "dirmngr";
+ printed_name = "dirmngr";
+ status_start_line = "starting_dirmngr ? 0 0";
+ no_service_err = GPG_ERR_NO_DIRMNGR;
+ seconds_to_wait = SECS_TO_WAIT_FOR_DIRMNGR;
+ break;
+ case GNUPG_MODULE_NAME_KEYBOXD:
+ sockname = make_filename (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL);
+ lock_name = "keyboxd";
+ printed_name = "keyboxd";
+ status_start_line = "starting_keyboxd ? 0 0";
+ no_service_err = GPG_ERR_NO_KEYBOXD;
+ seconds_to_wait = SECS_TO_WAIT_FOR_KEYBOXD;
+ connect_flags |= ASSUAN_SOCKET_CONNECT_FDPASSING;
+ break;
+ default:
+ err = gpg_error (GPG_ERR_INV_ARG);
assuan_release (ctx);
return err;
}
- err = assuan_socket_connect (ctx, sockname, 0, 0);
+ err = assuan_socket_connect (ctx, sockname, 0, connect_flags);
if (err && autostart)
{
char *abs_homedir;
@@ -422,12 +461,12 @@ start_new_gpg_agent (assuan_context_t *r_ctx,
int i;
/* With no success start a new server. */
- if (!agent_program || !*agent_program)
- agent_program = gnupg_module_name (GNUPG_MODULE_NAME_AGENT);
- else if ((s=strchr (agent_program, '|')) && s[1] == '-' && s[2]=='-')
+ if (!program_name || !*program_name)
+ program_name = gnupg_module_name (module_name_id);
+ else if ((s=strchr (program_name, '|')) && s[1] == '-' && s[2]=='-')
{
/* Hack to insert an additional option on the command line. */
- program = xtrystrdup (agent_program);
+ program = xtrystrdup (program_name);
if (!program)
{
gpg_error_t tmperr = gpg_err_make (errsource,
@@ -442,22 +481,21 @@ start_new_gpg_agent (assuan_context_t *r_ctx,
}
if (verbose)
- log_info (_("no running gpg-agent - starting '%s'\n"),
- agent_program);
+ log_info (_("no running %s - starting '%s'\n"),
+ printed_name, program_name);
if (status_cb)
- status_cb (status_cb_arg, STATUS_PROGRESS,
- "starting_agent ? 0 0", NULL);
+ status_cb (status_cb_arg, STATUS_PROGRESS, status_start_line, NULL);
- /* We better pass an absolute home directory to the agent just
- in case gpg-agent does not convert the passed name to an
- absolute one (which it should do). */
+ /* We better pass an absolute home directory to the service just
+ * in case the service does not convert the passed name to an
+ * absolute one (which it should do). */
abs_homedir = make_absfilename_try (gnupg_homedir (), NULL);
if (!abs_homedir)
{
gpg_error_t tmperr = gpg_err_make (errsource,
gpg_err_code_from_syserror ());
- log_error ("error building filename: %s\n",gpg_strerror (tmperr));
+ log_error ("error building filename: %s\n", gpg_strerror (tmperr));
xfree (sockname);
assuan_release (ctx);
xfree (program);
@@ -468,8 +506,7 @@ start_new_gpg_agent (assuan_context_t *r_ctx,
{
gpg_error_t tmperr = gpg_err_make (errsource,
gpg_err_code_from_syserror ());
- log_error ("error flushing pending output: %s\n",
- strerror (errno));
+ log_error ("error flushing pending output: %s\n", strerror (errno));
xfree (sockname);
assuan_release (ctx);
xfree (abs_homedir);
@@ -477,42 +514,42 @@ start_new_gpg_agent (assuan_context_t *r_ctx,
return tmperr;
}
- /* If the agent has been configured for use with a standard
- socket, an environment variable is not required and thus
- we can safely start the agent here. */
i = 0;
argv[i++] = "--homedir";
argv[i++] = abs_homedir;
- argv[i++] = "--use-standard-socket";
+ if (module_name_id == GNUPG_MODULE_NAME_AGENT)
+ argv[i++] = "--use-standard-socket";
if (program_arg)
argv[i++] = program_arg;
argv[i++] = "--daemon";
argv[i++] = NULL;
- if (!(err = lock_spawning (&lock, gnupg_homedir (), "agent", verbose))
- && assuan_socket_connect (ctx, sockname, 0, 0))
+ if (!(err = lock_spawning (&lock, gnupg_homedir (), lock_name, verbose))
+ && assuan_socket_connect (ctx, sockname, 0, connect_flags))
{
#ifdef HAVE_W32_SYSTEM
- err = gnupg_spawn_process_detached (program? program : agent_program,
+ err = gnupg_spawn_process_detached (program? program : program_name,
argv, NULL);
-#else
+#else /*!W32*/
pid_t pid;
- err = gnupg_spawn_process_fd (program? program : agent_program,
+ err = gnupg_spawn_process_fd (program? program : program_name,
argv, -1, -1, -1, &pid);
if (!err)
- err = gnupg_wait_process (program? program : agent_program,
+ err = gnupg_wait_process (program? program : program_name,
pid, 1, NULL);
-#endif
+#endif /*!W32*/
if (err)
- log_error ("failed to start agent '%s': %s\n",
- agent_program, gpg_strerror (err));
+ log_error ("failed to start %s '%s': %s\n",
+ printed_name, program? program : program_name,
+ gpg_strerror (err));
else
- err = wait_for_sock (SECS_TO_WAIT_FOR_AGENT, 0,
- sockname, verbose, ctx, &did_success_msg);
+ err = wait_for_sock (seconds_to_wait, module_name_id,
+ sockname, connect_flags,
+ verbose, ctx, &did_success_msg);
}
- unlock_spawning (&lock, "agent");
+ unlock_spawning (&lock, lock_name);
xfree (abs_homedir);
xfree (program);
}
@@ -520,17 +557,21 @@ start_new_gpg_agent (assuan_context_t *r_ctx,
if (err)
{
if (autostart || gpg_err_code (err) != GPG_ERR_ASS_CONNECT_FAILED)
- log_error ("can't connect to the agent: %s\n", gpg_strerror (err));
+ log_error ("can't connect to the %s: %s\n",
+ printed_name, gpg_strerror (err));
assuan_release (ctx);
- return gpg_err_make (errsource, GPG_ERR_NO_AGENT);
+ return gpg_err_make (errsource, no_service_err);
}
if (debug && !did_success_msg)
- log_debug ("connection to the agent established\n");
+ log_debug ("connection to the %s established\n", printed_name);
+
+ if (module_name_id == GNUPG_MODULE_NAME_AGENT)
+ err = assuan_transact (ctx, "RESET",
+ NULL, NULL, NULL, NULL, NULL, NULL);
- err = assuan_transact (ctx, "RESET",
- NULL, NULL, NULL, NULL, NULL, NULL);
- if (!err)
+ if (!err
+ && module_name_id == GNUPG_MODULE_NAME_AGENT)
{
err = send_pinentry_environment (ctx, errsource,
opt_lc_ctype, opt_lc_messages,
@@ -538,7 +579,7 @@ start_new_gpg_agent (assuan_context_t *r_ctx,
if (gpg_err_code (err) == GPG_ERR_FORBIDDEN
&& gpg_err_source (err) == GPG_ERR_SOURCE_GPGAGENT)
{
- /* Check whether we are in restricted mode. */
+ /* Check whether the agent is in restricted mode. */
if (!assuan_transact (ctx, "GETINFO restricted",
NULL, NULL, NULL, NULL, NULL, NULL))
{
@@ -559,126 +600,64 @@ start_new_gpg_agent (assuan_context_t *r_ctx,
}
+/* Try to connect tothe agent or start a new one. */
+gpg_error_t
+start_new_gpg_agent (assuan_context_t *r_ctx,
+ gpg_err_source_t errsource,
+ const char *agent_program,
+ const char *opt_lc_ctype,
+ const char *opt_lc_messages,
+ session_env_t session_env,
+ int autostart, int verbose, int debug,
+ gpg_error_t (*status_cb)(ctrl_t, int, ...),
+ ctrl_t status_cb_arg)
+{
+ return start_new_service (r_ctx, GNUPG_MODULE_NAME_AGENT,
+ errsource, agent_program,
+ opt_lc_ctype, opt_lc_messages, session_env,
+ autostart, verbose, debug,
+ status_cb, status_cb_arg);
+}
+
+
/* Try to connect to the dirmngr via a socket. On platforms
supporting it, start it up if needed and if AUTOSTART is true.
Returns a new assuan context at R_CTX or an error code. */
gpg_error_t
-start_new_dirmngr (assuan_context_t *r_ctx,
+start_new_keyboxd (assuan_context_t *r_ctx,
gpg_err_source_t errsource,
- const char *dirmngr_program,
- int autostart,
- int verbose, int debug,
+ const char *keyboxd_program,
+ int autostart, int verbose, int debug,
gpg_error_t (*status_cb)(ctrl_t, int, ...),
ctrl_t status_cb_arg)
{
- gpg_error_t err;
- assuan_context_t ctx;
- const char *sockname;
- int did_success_msg = 0;
-
- *r_ctx = NULL;
-
- err = assuan_new (&ctx);
- if (err)
- {
- log_error ("error allocating assuan context: %s\n", gpg_strerror (err));
- return err;
- }
-
- sockname = dirmngr_socket_name ();
- err = assuan_socket_connect (ctx, sockname, 0, 0);
-
-#ifdef USE_DIRMNGR_AUTO_START
- if (err && autostart)
- {
- lock_spawn_t lock;
- const char *argv[4];
- char *abs_homedir;
-
- /* No connection: Try start a new Dirmngr. */
- if (!dirmngr_program || !*dirmngr_program)
- dirmngr_program = gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR);
-
- if (verbose)
- log_info (_("no running dirmngr - starting '%s'\n"),
- dirmngr_program);
-
- if (status_cb)
- status_cb (status_cb_arg, STATUS_PROGRESS,
- "starting_dirmngr ? 0 0", NULL);
-
- abs_homedir = make_absfilename (gnupg_homedir (), NULL);
- if (!abs_homedir)
- {
- gpg_error_t tmperr = gpg_err_make (errsource,
- gpg_err_code_from_syserror ());
- log_error ("error building filename: %s\n",gpg_strerror (tmperr));
- assuan_release (ctx);
- return tmperr;
- }
-
- if (fflush (NULL))
- {
- gpg_error_t tmperr = gpg_err_make (errsource,
- gpg_err_code_from_syserror ());
- log_error ("error flushing pending output: %s\n",
- strerror (errno));
- assuan_release (ctx);
- return tmperr;
- }
+ return start_new_service (r_ctx, GNUPG_MODULE_NAME_KEYBOXD,
+ errsource, keyboxd_program,
+ NULL, NULL, NULL,
+ autostart, verbose, debug,
+ status_cb, status_cb_arg);
+}
- argv[0] = "--daemon";
- /* Try starting the daemon. Versions of dirmngr < 2.1.15 do
- * this only if the home directory is given on the command line. */
- argv[1] = "--homedir";
- argv[2] = abs_homedir;
- argv[3] = NULL;
- if (!(err = lock_spawning (&lock, gnupg_homedir (), "dirmngr", verbose))
- && assuan_socket_connect (ctx, sockname, 0, 0))
- {
-#ifdef HAVE_W32_SYSTEM
- err = gnupg_spawn_process_detached (dirmngr_program, argv, NULL);
-#else
- pid_t pid;
-
- err = gnupg_spawn_process_fd (dirmngr_program, argv,
- -1, -1, -1, &pid);
- if (!err)
- err = gnupg_wait_process (dirmngr_program, pid, 1, NULL);
+/* Try to connect to the dirmngr via a socket. On platforms
+ supporting it, start it up if needed and if AUTOSTART is true.
+ Returns a new assuan context at R_CTX or an error code. */
+gpg_error_t
+start_new_dirmngr (assuan_context_t *r_ctx,
+ gpg_err_source_t errsource,
+ const char *dirmngr_program,
+ int autostart, int verbose, int debug,
+ gpg_error_t (*status_cb)(ctrl_t, int, ...),
+ ctrl_t status_cb_arg)
+{
+#ifndef USE_DIRMNGR_AUTO_START
+ autostart = 0;
#endif
- if (err)
- log_error ("failed to start the dirmngr '%s': %s\n",
- dirmngr_program, gpg_strerror (err));
- else
- err = wait_for_sock (SECS_TO_WAIT_FOR_DIRMNGR, 1,
- sockname, verbose, ctx, &did_success_msg);
- }
-
- unlock_spawning (&lock, "dirmngr");
- xfree (abs_homedir);
- }
-#else
- (void)dirmngr_program;
- (void)verbose;
- (void)status_cb;
- (void)status_cb_arg;
-#endif /*USE_DIRMNGR_AUTO_START*/
-
- if (err)
- {
- if (autostart || gpg_err_code (err) != GPG_ERR_ASS_CONNECT_FAILED)
- log_error ("connecting dirmngr at '%s' failed: %s\n",
- sockname, gpg_strerror (err));
- assuan_release (ctx);
- return gpg_err_make (errsource, GPG_ERR_NO_DIRMNGR);
- }
-
- if (debug && !did_success_msg)
- log_debug ("connection to the dirmngr established\n");
-
- *r_ctx = ctx;
- return 0;
+ return start_new_service (r_ctx, GNUPG_MODULE_NAME_DIRMNGR,
+ errsource, dirmngr_program,
+ NULL, NULL, NULL,
+ autostart, verbose, debug,
+ status_cb, status_cb_arg);
}
diff --git a/common/asshelp.h b/common/asshelp.h
index bf1bd1705..b2f4e538f 100644
--- a/common/asshelp.h
+++ b/common/asshelp.h
@@ -65,6 +65,16 @@ start_new_gpg_agent (assuan_context_t *r_ctx,
gpg_error_t (*status_cb)(ctrl_t, int, ...),
ctrl_t status_cb_arg);
+/* This function is used to connect to the keyboxd. If needed the
+ * keyboxd is started. */
+gpg_error_t
+start_new_keyboxd (assuan_context_t *r_ctx,
+ gpg_err_source_t errsource,
+ const char *keyboxd_program,
+ int autostart, int verbose, int debug,
+ gpg_error_t (*status_cb)(ctrl_t, int, ...),
+ ctrl_t status_cb_arg);
+
/* This function is used to connect to the dirmngr. On some platforms
the function is able starts a dirmngr process if needed. */
gpg_error_t
@@ -82,8 +92,16 @@ gpg_error_t get_assuan_server_version (assuan_context_t ctx,
/*-- asshelp2.c --*/
+void set_assuan_context_func (assuan_context_t (*func)(ctrl_t ctrl));
+
/* Helper function to print an assuan status line using a printf
format string. */
+
+gpg_error_t status_printf (ctrl_t ctrl, const char *keyword, const char *format,
+ ...) GPGRT_ATTR_PRINTF(3,4);
+gpg_error_t status_no_printf (ctrl_t ctrl, int no, const char *format,
+ ...) GPGRT_ATTR_PRINTF(3,4);
+
gpg_error_t print_assuan_status (assuan_context_t ctx,
const char *keyword,
const char *format,
diff --git a/common/asshelp2.c b/common/asshelp2.c
index 0a7c4549d..8410808e3 100644
--- a/common/asshelp2.c
+++ b/common/asshelp2.c
@@ -36,6 +36,24 @@
#include "util.h"
#include "asshelp.h"
+#include "status.h"
+
+
+/* A variable with a function to be used to return the current assuan
+ * context for a CTRL variable. This needs to be set using the
+ * set_assuan_ctx_func function. */
+static assuan_context_t (*the_assuan_ctx_func)(ctrl_t ctrl);
+
+
+/* Set FUNC to be used as a mapping fucntion from CTRL to an assuan
+ * context. Pass NULL for FUNC to disable the use of the assuan
+ * context in this module. */
+void
+set_assuan_context_func (assuan_context_t (*func)(ctrl_t ctrl))
+{
+ the_assuan_ctx_func = func;
+}
+
/* Helper function to print an assuan status line using a printf
format string. */
@@ -134,3 +152,41 @@ print_assuan_status_strings (assuan_context_t ctx, const char *keyword, ...)
va_end (arg_ptr);
return err;
}
+
+
+/* This function is similar to print_assuan_status but takes a CTRL
+ * arg instead of an assuan context as first argument. */
+gpg_error_t
+status_printf (ctrl_t ctrl, const char *keyword, const char *format, ...)
+{
+ gpg_error_t err;
+ va_list arg_ptr;
+ assuan_context_t ctx;
+
+ if (!ctrl || !the_assuan_ctx_func || !(ctx = the_assuan_ctx_func (ctrl)))
+ return 0;
+
+ va_start (arg_ptr, format);
+ err = vprint_assuan_status (ctx, keyword, format, arg_ptr);
+ va_end (arg_ptr);
+ return err;
+}
+
+
+/* Same as sytus_printf but takes a status number instead of a
+ * keyword. */
+gpg_error_t
+status_no_printf (ctrl_t ctrl, int no, const char *format, ...)
+{
+ gpg_error_t err;
+ va_list arg_ptr;
+ assuan_context_t ctx;
+
+ if (!ctrl || !the_assuan_ctx_func || !(ctx = the_assuan_ctx_func (ctrl)))
+ return 0;
+
+ va_start (arg_ptr, format);
+ err = vprint_assuan_status (ctx, get_status_string (no), format, arg_ptr);
+ va_end (arg_ptr);
+ return err;
+}
diff --git a/common/homedir.c b/common/homedir.c
index e9e75d01e..4425d7811 100644
--- a/common/homedir.c
+++ b/common/homedir.c
@@ -945,6 +945,17 @@ gnupg_cachedir (void)
}
+/* Return the standard socket name used by gpg-agent. */
+const char *
+gpg_agent_socket_name (void)
+{
+ static char *name;
+
+ if (!name)
+ name = make_filename (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
+ return name;
+}
+
/* Return the user socket name used by DirMngr. */
const char *
dirmngr_socket_name (void)
@@ -1104,6 +1115,13 @@ gnupg_module_name (int which)
X(bindir, "dirmngr", DIRMNGR_NAME);
#endif
+ case GNUPG_MODULE_NAME_KEYBOXD:
+#ifdef GNUPG_DEFAULT_KEYBOXD
+ return GNUPG_DEFAULT_KEYBOXD;
+#else
+ X(bindir, "keyboxd", KEYBOXD_NAME);
+#endif
+
case GNUPG_MODULE_NAME_PROTECT_TOOL:
#ifdef GNUPG_DEFAULT_PROTECT_TOOL
return GNUPG_DEFAULT_PROTECT_TOOL;
diff --git a/common/iobuf.c b/common/iobuf.c
index 05944255f..db5d062cd 100644
--- a/common/iobuf.c
+++ b/common/iobuf.c
@@ -106,6 +106,8 @@ typedef struct
int keep_open;
int no_cache;
int eof_seen;
+ int use_readlimit; /* Take care of the readlimit. */
+ size_t readlimit; /* Number of bytes left to read. */
int print_only_name; /* Flags indicating that fname is not a real file. */
char fname[1]; /* Name of the file. */
} file_es_filter_ctx_t;
@@ -635,6 +637,34 @@ file_es_filter (void *opaque, int control, iobuf_t chain, byte * buf,
rc = -1;
*ret_len = 0;
}
+ else if (a->use_readlimit)
+ {
+ nbytes = 0;
+ if (!a->readlimit)
+ { /* eof */
+ a->eof_seen = 1;
+ rc = -1;
+ }
+ else
+ {
+ if (size > a->readlimit)
+ size = a->readlimit;
+ rc = es_read (f, buf, size, &nbytes);
+ if (rc == -1)
+ { /* error */
+ rc = gpg_error_from_syserror ();
+ log_error ("%s: read error: %s\n", a->fname,strerror (errno));
+ }
+ else if (!nbytes)
+ { /* eof */
+ a->eof_seen = 1;
+ rc = -1;
+ }
+ else
+ a->readlimit -= nbytes;
+ }
+ *ret_len = nbytes;
+ }
else
{
nbytes = 0;
@@ -1412,7 +1442,8 @@ iobuf_fdopen_nc (int fd, const char *mode)
iobuf_t
-iobuf_esopen (estream_t estream, const char *mode, int keep_open)
+iobuf_esopen (estream_t estream, const char *mode, int keep_open,
+ size_t readlimit)
{
iobuf_t a;
file_es_filter_ctx_t *fcx;
@@ -1424,7 +1455,9 @@ iobuf_esopen (estream_t estream, const char *mode, int keep_open)
fcx->fp = estream;
fcx->print_only_name = 1;
fcx->keep_open = keep_open;
- sprintf (fcx->fname, "[fd %p]", estream);
+ fcx->readlimit = readlimit;
+ fcx->use_readlimit = !!readlimit;
+ snprintf (fcx->fname, 30, "[fd %p]", estream);
a->filter = file_es_filter;
a->filter_ov = fcx;
file_es_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len);
diff --git a/common/iobuf.h b/common/iobuf.h
index 16156383c..9c9650c61 100644
--- a/common/iobuf.h
+++ b/common/iobuf.h
@@ -328,8 +328,10 @@ iobuf_t iobuf_fdopen_nc (int fd, const char *mode);
letter 'w', creates an output filter. Otherwise, creates an input
filter. If KEEP_OPEN is TRUE, then the stream is not closed when
the filter is destroyed. Otherwise, the stream is closed when the
- filter is destroyed. */
-iobuf_t iobuf_esopen (estream_t estream, const char *mode, int keep_open);
+ filter is destroyed. If READLIMIT is not 0 this gives a limit on
+ the number of bytes to read from estream. */
+iobuf_t iobuf_esopen (estream_t estream, const char *mode, int keep_open,
+ size_t readlimit);
/* Create a filter using an existing socket. On Windows creates a
special socket filter. On non-Windows systems simply, this simply
diff --git a/common/t-mbox-util.c b/common/t-mbox-util.c
index ae717f96f..e777e03e2 100644
--- a/common/t-mbox-util.c
+++ b/common/t-mbox-util.c
@@ -77,6 +77,12 @@ run_mbox_test (void)
{ "<fo()[email protected]> ()", "fo()[email protected]" },
{ "fo()[email protected]", NULL},
+ { "Surname, Forename | company <[email protected]>", "[email protected]"},
+ /* The next one is for sure not RFC-822 correct but nevertheless
+ * the way gpg does it. We won't change it because the user-id
+ * is only rfc-822 alike and not compliant (think only of our
+ * utf-8 requirement). */
{ NULL, NULL }
};
int idx;
diff --git a/common/userids.c b/common/userids.c
index 55bd85546..eb714a9af 100644
--- a/common/userids.c
+++ b/common/userids.c
@@ -65,6 +65,9 @@
* (note that you can't search for these characters). Compare
* is not case sensitive.
* - If the userid starts with a '&' a 40 hex digits keygrip is expected.
+ * - If the userid starts with a '^' followed by 40 hex digits it describes
+ * a Unique-Blob-ID (UBID) which is the hash of keyblob or certificate as
+ * stored in the database. This is used in the IPC of the keyboxd.
*/
gpg_error_t
@@ -251,6 +254,17 @@ classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack)
}
break;
+ case '^': /* UBID */
+ {
+ if (hex2bin (s+1, desc->u.ubid, 20) < 0)
+ {
+ rc = gpg_error (GPG_ERR_INV_USER_ID); /* Invalid. */
+ goto out;
+ }
+ mode = KEYDB_SEARCH_MODE_UBID;
+ }
+ break;
+
default:
if (s[0] == '0' && s[1] == 'x')
{
diff --git a/common/util.h b/common/util.h
index bd6cd1ff5..8f8a06a41 100644
--- a/common/util.h
+++ b/common/util.h
@@ -43,6 +43,10 @@
#define GPG_ERR_NO_AUTH 314
#define GPG_ERR_BAD_AUTH 315
#endif /*GPG_ERROR_VERSION_NUMBER*/
+#if GPG_ERROR_VERSION_NUMBER < 0x012500 /* 1.37 */
+#define GPG_ERR_NO_KEYBOXD 316
+#define GPG_ERR_KEYBOXD 317
+#endif /*GPG_ERROR_VERSION_NUMBER*/
/* Hash function used with libksba. */
#define HASH_FNC ((void (*)(void *, const void*,size_t))gcry_md_write)
@@ -243,6 +247,7 @@ const char *gnupg_libdir (void);
const char *gnupg_datadir (void);
const char *gnupg_localedir (void);
const char *gnupg_cachedir (void);
+const char *gpg_agent_socket_name (void);
const char *dirmngr_socket_name (void);
char *_gnupg_socketdir_internal (int skip_checks, unsigned *r_info);
@@ -261,6 +266,7 @@ char *_gnupg_socketdir_internal (int skip_checks, unsigned *r_info);
#define GNUPG_MODULE_NAME_GPGCONF 10
#define GNUPG_MODULE_NAME_DIRMNGR_LDAP 11
#define GNUPG_MODULE_NAME_GPGV 12
+#define GNUPG_MODULE_NAME_KEYBOXD 13
const char *gnupg_module_name (int which);
void gnupg_module_name_flush_some (void);
void gnupg_set_builddir (const char *newdir);
diff --git a/configure.ac b/configure.ac
index 7ce90aa46..5bb366e76 100644
--- a/configure.ac
+++ b/configure.ac
@@ -56,7 +56,7 @@ AC_DEFINE_UNQUOTED(GNUPG_SWDB_TAG, "gnupg24", [swdb tag for this branch])
NEED_GPG_ERROR_VERSION=1.29
NEED_LIBGCRYPT_API=1
-NEED_LIBGCRYPT_VERSION=1.7.0
+NEED_LIBGCRYPT_VERSION=1.8.0
NEED_LIBASSUAN_API=2
NEED_LIBASSUAN_VERSION=2.5.0
@@ -191,6 +191,14 @@ AM_CONDITIONAL(GNUPG_DIRMNGR_PGM, test -n "$GNUPG_DIRMNGR_PGM")
show_gnupg_dirmngr_pgm="(default)"
test -n "$GNUPG_DIRMNGR_PGM" && show_gnupg_dirmngr_pgm="$GNUPG_DIRMNGR_PGM"
+AC_ARG_WITH(keyboxd-pgm,
+ [ --with-keyboxd-pgm=PATH Use PATH as the default for the keyboxd)],
+ GNUPG_KEYBOXD_PGM="$withval", GNUPG_KEYBOXD_PGM="" )
+AC_SUBST(GNUPG_KEYBOXD_PGM)
+AM_CONDITIONAL(GNUPG_KEYBOXD_PGM, test -n "$GNUPG_KEYBOXD_PGM")
+show_gnupg_keyboxd_pgm="(default)"
+test -n "$GNUPG_KEYBOXD_PGM" && show_gnupg_keyboxd_pgm="$GNUPG_KEYBOXD_PGM"
+
AC_ARG_WITH(protect-tool-pgm,
[ --with-protect-tool-pgm=PATH Use PATH as the default for the protect-tool)],
GNUPG_PROTECT_TOOL_PGM="$withval", GNUPG_PROTECT_TOOL_PGM="" )
@@ -508,6 +516,7 @@ AH_BOTTOM([
#define GNUPG_DEFAULT_HOMEDIR "~/.gnupg"
#endif
#define GNUPG_PRIVATE_KEYS_DIR "private-keys-v1.d"
+#define GNUPG_PUBLIC_KEYS_DIR "public-keys.d"
#define GNUPG_OPENPGP_REVOC_DIR "openpgp-revocs.d"
#define GNUPG_CACHE_DIR "cache.d"
@@ -1888,6 +1897,10 @@ AC_DEFINE_UNQUOTED(DIRMNGR_NAME, "dirmngr", [The name of the dirmngr])
AC_DEFINE_UNQUOTED(DIRMNGR_DISP_NAME, "DirMngr",
[The displayed name of dirmngr])
+AC_DEFINE_UNQUOTED(KEYBOXD_NAME, "keyboxd", [The name of the keyboxd])
+AC_DEFINE_UNQUOTED(KEYBOXD_DISP_NAME, "Keyboxd",
+ [The displayed name of keyboxd])
+
AC_DEFINE_UNQUOTED(G13_NAME, "g13", [The name of the g13 tool])
AC_DEFINE_UNQUOTED(G13_DISP_NAME, "G13", [The displayed name of g13])
@@ -1909,6 +1922,8 @@ AC_DEFINE_UNQUOTED(DIRMNGR_INFO_NAME, "DIRMNGR_INFO",
[The name of the dirmngr info envvar])
AC_DEFINE_UNQUOTED(SCDAEMON_SOCK_NAME, "S.scdaemon",
[The name of the SCdaemon socket])
+AC_DEFINE_UNQUOTED(KEYBOXD_SOCK_NAME, "S.keyboxd",
+ [The name of the keyboxd socket])
AC_DEFINE_UNQUOTED(DIRMNGR_SOCK_NAME, "S.dirmngr",
[The name of the dirmngr socket])
AC_DEFINE_UNQUOTED(DIRMNGR_DEFAULT_KEYSERVER,
@@ -2106,6 +2121,7 @@ echo "
Default agent: $show_gnupg_agent_pgm
Default pinentry: $show_gnupg_pinentry_pgm
Default scdaemon: $show_gnupg_scdaemon_pgm
+ Default keyboxd: $show_gnupg_keyboxd_pgm
Default dirmngr: $show_gnupg_dirmngr_pgm
Dirmngr auto start: $dirmngr_auto_start
diff --git a/doc/DETAILS b/doc/DETAILS
index f1abda930..315f56e31 100644
--- a/doc/DETAILS
+++ b/doc/DETAILS
@@ -1138,6 +1138,18 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB:
*** BEGIN_STREAM, END_STREAM
Used to issued by the experimental pipemode.
+** Inter-component codes
+ Status codes are also used between the components of the GnuPG
+ system via the Assuan S lines. Some of them are documented here:
+
+*** PUBKEY_INFO <n> <ubid>
+ The type of the public key in the following D-lines or
+ communicated via a pipe. <n> is the value of =enum pubkey_types=
+ and <ubid> the Unique Blob ID (UBID) which is a SHA-1 digest the
+ entire blob here formatted in hex. The consumer of this status
+ line should be prepared to see a <ubid> of up to 64 characters.
+ Note that the keyboxd SEARCH command can be used to lookup the
+ public key using the <ubid> prefixed with a caret (^).
* Format of the --attribute-fd output
diff --git a/doc/tools.texi b/doc/tools.texi
index 460030038..0893c65a5 100644
--- a/doc/tools.texi
+++ b/doc/tools.texi
@@ -1328,11 +1328,22 @@ Specify the directory manager (keyserver client) program to be started
if none is running. This has only an effect if used together with the
option @option{--dirmngr}.
+@item --keyboxd-program @var{file}
+@opindex keyboxd-program
+Specify the keybox daemon program to be started if none is running.
+This has only an effect if used together with the option
+@option{--keyboxd}.
+
@item --dirmngr
@opindex dirmngr
Connect to a running directory manager (keyserver client) instead of
to the gpg-agent. If a dirmngr is not running, start it.
+@item --keyboxd
+@opindex keyboxd
+Connect to a running keybox daemon instead of
+to the gpg-agent. If a keyboxd is not running, start it.
+
@item -S
@itemx --raw-socket @var{name}
@opindex raw-socket
diff --git a/g10/Makefile.am b/g10/Makefile.am
index ba297cfc9..2b92daf33 100644
--- a/g10/Makefile.am
+++ b/g10/Makefile.am
@@ -29,9 +29,9 @@ AM_CPPFLAGS =
include $(top_srcdir)/am/cmacros.am
AM_CFLAGS = $(SQLITE3_CFLAGS) $(LIBGCRYPT_CFLAGS) \
- $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS)
+ $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) $(GPG_ERROR_CFLAGS)
-needed_libs = ../kbx/libkeybox.a $(libcommon)
+needed_libs = ../kbx/libkeybox.a $(libcommonpth)
# Because there are no program specific transform macros we need to
# work around that to allow installing gpg as gpg2.
@@ -50,9 +50,9 @@ noinst_PROGRAMS = gpg
if !HAVE_W32CE_SYSTEM
noinst_PROGRAMS += gpgv
endif
-if MAINTAINER_MODE
-noinst_PROGRAMS += gpgcompose
-endif
+#if MAINTAINER_MODE
+#noinst_PROGRAMS += gpgcompose
+#endif
noinst_PROGRAMS += $(module_tests)
TESTS = $(module_tests)
TESTS_ENVIRONMENT = \
@@ -100,7 +100,10 @@ common_source = \
free-packet.c \
getkey.c \
expand-group.c \
- keydb.c keydb.h \
+ keydb.h \
+ keydb-private.h \
+ call-keyboxd.c \
+ keydb.c \
keyring.c keyring.h \
seskey.c \
kbnode.c \
@@ -161,7 +164,7 @@ gpg_SOURCES = gpg.c \
keyedit.c keyedit.h \
$(gpg_sources)
-gpgcompose_SOURCES = gpgcompose.c $(gpg_sources)
+#gpgcompose_SOURCES = gpgcompose.c $(gpg_sources)
gpgv_SOURCES = gpgv.c \
$(common_source) \
verify.c
@@ -176,33 +179,36 @@ gpgv_SOURCES = gpgv.c \
LDADD = $(needed_libs) ../common/libgpgrl.a \
$(ZLIBS) $(LIBINTL) $(CAPLIBS) $(NETLIBS)
gpg_LDADD = $(LDADD) $(SQLITE3_LIBS) $(LIBGCRYPT_LIBS) $(LIBREADLINE) \
- $(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \
+ $(LIBASSUAN_LIBS) $(NPTH_LIBS) $(GPG_ERROR_LIBS) \
$(LIBICONV) $(resource_objs) $(extra_sys_libs)
gpg_LDFLAGS = $(extra_bin_ldflags)
gpgv_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) \
- $(GPG_ERROR_LIBS) \
+ $(LIBASSUAN_LIBS) $(NPTH_LIBS) $(GPG_ERROR_LIBS) \
$(LIBICONV) $(resource_objs) $(extra_sys_libs)
gpgv_LDFLAGS = $(extra_bin_ldflags)
-gpgcompose_LDADD = $(LDADD) $(SQLITE3_LIBS) $(LIBGCRYPT_LIBS) $(LIBREADLINE) \
- $(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \
- $(LIBICONV) $(resource_objs) $(extra_sys_libs)
-gpgcompose_LDFLAGS = $(extra_bin_ldflags)
+#gpgcompose_LDADD = $(LDADD) $(SQLITE3_LIBS) $(LIBGCRYPT_LIBS) $(LIBREADLINE) \
+# $(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \
+# $(LIBICONV) $(resource_objs) $(extra_sys_libs)
+#gpgcompose_LDFLAGS = $(extra_bin_ldflags)
t_common_ldadd =
module_tests = t-rmd160 t-keydb t-keydb-get-keyblock t-stutter
t_rmd160_SOURCES = t-rmd160.c rmd160.c
t_rmd160_LDADD = $(t_common_ldadd)
t_keydb_SOURCES = t-keydb.c test-stubs.c $(common_source)
-t_keydb_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
+t_keydb_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) \
+ $(LIBASSUAN_LIBS) $(NPTH_LIBS) $(GPG_ERROR_LIBS) \
$(LIBICONV) $(t_common_ldadd)
t_keydb_get_keyblock_SOURCES = t-keydb-get-keyblock.c test-stubs.c \
$(common_source)
-t_keydb_get_keyblock_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
+t_keydb_get_keyblock_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) \
+ $(LIBASSUAN_LIBS) $(NPTH_LIBS) $(GPG_ERROR_LIBS) \
$(LIBICONV) $(t_common_ldadd)
t_stutter_SOURCES = t-stutter.c test-stubs.c \
$(common_source)
-t_stutter_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
+t_stutter_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) \
+ $(LIBASSUAN_LIBS) $(NPTH_LIBS) $(GPG_ERROR_LIBS) \
$(LIBICONV) $(t_common_ldadd)
diff --git a/g10/call-keyboxd.c b/g10/call-keyboxd.c
new file mode 100644
index 000000000..88ad07817
--- /dev/null
+++ b/g10/call-keyboxd.c
@@ -0,0 +1,1112 @@
+/* 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 "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;
+
+ /* 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;
+
+ /* 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;
+
+ /* 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)
+{
+ keyboxd_local_t kbl;
+
+ while ((kbl = ctrl->keyboxd_local))
+ {
+ ctrl->keyboxd_local = kbl->next;
+ if (kbl->is_active)
+ log_error ("oops: trying to cleanup an active keyboxd context\n");
+ else
+ {
+ es_fclose (kbl->datastream.fp);
+ kbl->datastream.fp = NULL;
+ assuan_release (kbl->ctx);
+ kbl->ctx = NULL;
+ }
+ xfree (kbl);
+ }
+}
+
+
+/* Print a warning if the server's version number is less than our
+ version number. Returns an error code on a connection problem. */
+static gpg_error_t
+warn_version_mismatch (assuan_context_t ctx, const char *servername)
+{
+ gpg_error_t err;
+ char *serverversion;
+ const char *myversion = strusage (13);
+
+ err = get_assuan_server_version (ctx, 0, &serverversion);
+ if (err)
+ log_error (_("error getting version from '%s': %s\n"),
+ servername, gpg_strerror (err));
+ else if (compare_version_strings (serverversion, myversion) < 0)
+ {
+ char *warn;
+
+ warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"),
+ servername, serverversion, myversion);
+ if (!warn)
+ err = gpg_error_from_syserror ();
+ else
+ {
+ log_info (_("WARNING: %s\n"), warn);
+ if (!opt.quiet)
+ {
+ log_info (_("Note: Outdated servers may lack important"
+ " security fixes.\n"));
+ log_info (_("Note: Use the command \"%s\" to restart them.\n"),
+ "gpgconf --kill all");
+ }
+
+ write_status_strings (STATUS_WARNING, "server_version_mismatch 0",
+ " ", warn, NULL);
+ xfree (warn);
+ }
+ }
+ xfree (serverversion);
+ return err;
+}
+
+
+/* Connect to the keybox daemon and launch it if necessary. Handle
+ * the server's initial greeting and set global options. Returns a
+ * new assuan context or an error. */
+static gpg_error_t
+create_new_context (ctrl_t ctrl, assuan_context_t *r_ctx)
+{
+ gpg_error_t err;
+ assuan_context_t ctx;
+
+ *r_ctx = NULL;
+
+ err = start_new_keyboxd (&ctx,
+ GPG_ERR_SOURCE_DEFAULT,
+ opt.keyboxd_program,
+ opt.autostart, opt.verbose, DBG_IPC,
+ NULL, ctrl);
+ if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_KEYBOXD)
+ {
+ static int shown;
+
+ if (!shown)
+ {
+ shown = 1;
+ log_info (_("no keyboxd running in this session\n"));
+ }
+ }
+ else if (!err && !(err = warn_version_mismatch (ctx, KEYBOXD_NAME)))
+ {
+ /* Place to emit global options. */
+ }
+
+ if (err)
+ assuan_release (ctx);
+ else
+ *r_ctx = ctx;
+
+ return err;
+}
+
+
+
+/* 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
+ * 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;
+ int rc;
+ 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);
+
+ /* 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;
+
+ *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 ();
+
+ 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 = 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);
+ if (err)
+ {
+ npth_cond_destroy (&kbl->datastream.cond);
+ npth_mutex_destroy (&kbl->datastream.mutex);
+ 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;
+}
+
+
+
+/* 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
+ * using release_kbnode(). Otherwise, the function returns an error
+ * code.
+ *
+ * The returned keyblock has the kbnode flag bit 0 set for the node
+ * with the public key used to locate the keyblock or flag bit 1 set
+ * for the user ID node. */
+gpg_error_t
+keydb_get_keyblock (KEYDB_HANDLE hd, kbnode_t *ret_kb)
+{
+ gpg_error_t err;
+ int pk_no, uid_no;
+
+ *ret_kb = NULL;
+
+ if (!hd)
+ return gpg_error (GPG_ERR_INV_ARG);
+
+ if (DBG_CLOCK)
+ log_clock ("%s enter", __func__);
+
+ if (!hd->use_keyboxd)
+ {
+ err = internal_keydb_get_keyblock (hd, ret_kb);
+ goto leave;
+ }
+
+ if (hd->kbl->search_result)
+ {
+ pk_no = uid_no = 0; /*FIXME: Get this from the keyboxd. */
+ err = keydb_get_keyblock_do_parse (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);
+ goto leave;
+ }
+
+ leave:
+ if (DBG_CLOCK)
+ log_clock ("%s leave%s", __func__, err? " (failed)":"");
+ 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;
+
+ 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;
+ }
+
+ err = GPG_ERR_NOT_IMPLEMENTED;
+
+ leave:
+ return err;
+}
+
+
+/* Insert a keyblock into one of the underlying keyrings or keyboxes.
+ *
+ * Be 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).
+ *
+ * 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;
+
+ if (!hd)
+ return gpg_error (GPG_ERR_INV_ARG);
+
+ if (!hd->use_keyboxd)
+ {
+ err = internal_keydb_insert_keyblock (hd, kb);
+ goto leave;
+ }
+
+ err = GPG_ERR_NOT_IMPLEMENTED;
+
+ leave:
+ 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 occured. */
+gpg_error_t
+keydb_delete_keyblock (KEYDB_HANDLE hd)
+{
+ gpg_error_t err;
+
+ if (!hd)
+ return gpg_error (GPG_ERR_INV_ARG);
+
+ if (!hd->use_keyboxd)
+ {
+ err = internal_keydb_delete_keyblock (hd);
+ goto leave;
+ }
+
+ err = GPG_ERR_NOT_IMPLEMENTED;
+
+ 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 todo is to tell search that a reset is pending. Noet
+ * that keydb_new sets this flag as well. */
+ hd->kbl->need_search_reset = 1;
+ err = 0;
+
+ leave:
+ 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];
+
+
+ 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: %zd 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;
+ }
+ 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)
+ {
+ /* No reset requested thus continue the search. The keyboxd
+ * keeps the context of the search and thus the NEXT operates on
+ * the last search pattern. This is how we always used the
+ * keydb.c functions. In theory we were able to modify the
+ * search pattern between searches but that is not anymore
+ * supported by keyboxd and a cursory check does not show that
+ * we actually made used of that misfeature. */
+ snprintf (line, sizeof line, "NEXT");
+ goto do_search;
+ }
+
+ hd->kbl->need_search_reset = 0;
+
+ if (!ndesc)
+ {
+ err = gpg_error (GPG_ERR_INV_ARG);
+ goto leave;
+ }
+
+ /* FIXME: Implement --multi */
+ switch (desc->mode)
+ {
+ case KEYDB_SEARCH_MODE_EXACT:
+ snprintf (line, sizeof line, "SEARCH =%s", desc[0].u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_SUBSTR:
+ snprintf (line, sizeof line, "SEARCH *%s", desc[0].u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_MAIL:
+ snprintf (line, sizeof line, "SEARCH <%s", desc[0].u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_MAILSUB:
+ snprintf (line, sizeof line, "SEARCH @%s", desc[0].u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_MAILEND:
+ snprintf (line, sizeof line, "SEARCH .%s", desc[0].u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_WORDS:
+ snprintf (line, sizeof line, "SEARCH +%s", desc[0].u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_SHORT_KID:
+ snprintf (line, sizeof line, "SEARCH 0x%08lX",
+ (ulong)desc->u.kid[1]);
+ break;
+
+ case KEYDB_SEARCH_MODE_LONG_KID:
+ snprintf (line, sizeof line, "SEARCH 0x%08lX%08lX",
+ (ulong)desc->u.kid[0], (ulong)desc->u.kid[1]);
+ break;
+
+ case KEYDB_SEARCH_MODE_FPR:
+ {
+ unsigned char hexfpr[MAX_FINGERPRINT_LEN * 2 + 1];
+ log_assert (desc[0].fprlen <= MAX_FINGERPRINT_LEN);
+ bin2hex (desc[0].u.fpr, desc[0].fprlen, hexfpr);
+ snprintf (line, sizeof line, "SEARCH 0x%s", hexfpr);
+ }
+ break;
+
+ case KEYDB_SEARCH_MODE_ISSUER:
+ snprintf (line, sizeof line, "SEARCH #/%s", desc[0].u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_ISSUER_SN:
+ case KEYDB_SEARCH_MODE_SN:
+ snprintf (line, sizeof line, "SEARCH #%s", desc[0].u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_SUBJECT:
+ snprintf (line, sizeof line, "SEARCH /%s", desc[0].u.name);
+ break;
+
+ case KEYDB_SEARCH_MODE_KEYGRIP:
+ {
+ unsigned char hexgrip[KEYGRIP_LEN * 2 + 1];
+ bin2hex (desc[0].u.grip, KEYGRIP_LEN, hexgrip);
+ snprintf (line, sizeof line, "SEARCH &%s", hexgrip);
+ }
+ break;
+
+ case KEYDB_SEARCH_MODE_UBID:
+ {
+ unsigned char hexubid[20 * 2 + 1];
+ bin2hex (desc[0].u.grip, 20, hexubid);
+ snprintf (line, sizeof line, "SEARCH ^%s", hexubid);
+ }
+ break;
+
+ case KEYDB_SEARCH_MODE_FIRST:
+ snprintf (line, sizeof line, "SEARCH");
+ break;
+
+ case KEYDB_SEARCH_MODE_NEXT:
+ log_debug ("%s: mode next - we should not get to here!\n", __func__);
+ snprintf (line, sizeof line, "NEXT");
+ break;
+
+ default:
+ err = gpg_error (GPG_ERR_INV_ARG);
+ goto leave;
+ }
+
+ do_search:
+ if (hd->kbl->datastream.fp)
+ {
+ /* log_debug ("Sending command '%s'\n", line); */
+ err = assuan_transact (hd->kbl->ctx, line,
+ NULL, NULL,
+ NULL, NULL,
+ NULL, NULL);
+ if (err)
+ {
+ /* log_debug ("Finished command with error: %s\n", gpg_strerror (err)); */
+ /* Fixme: On unexpected errors we need a way to cancek the
+ * data stream. Probly it will be best to closeand 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. */
+ {
+ 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,
+ NULL, NULL);
+ 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);
+ }
+
+
+ leave:
+ if (DBG_CLOCK)
+ log_clock ("%s leave (%sfound)", __func__, err? "not ":"");
+ return err;
+}
diff --git a/g10/decrypt-data.c b/g10/decrypt-data.c
index c73d5fb45..5fd458845 100644
--- a/g10/decrypt-data.c
+++ b/g10/decrypt-data.c
@@ -475,7 +475,7 @@ decrypt_data (ctrl_t ctrl, void *procctx, PKT_encrypted *ed, DEK *dek)
rc = get_output_file ("", 0, ed->buf, &filename, &fp);
if (! rc)
{
- iobuf_t output = iobuf_esopen (fp, "w", 0);
+ iobuf_t output = iobuf_esopen (fp, "w", 0, 0);
armor_filter_context_t *afx = NULL;
if (opt.armor)
diff --git a/g10/delkey.c b/g10/delkey.c
index b5ab47434..8a7144ace 100644
--- a/g10/delkey.c
+++ b/g10/delkey.c
@@ -65,7 +65,7 @@ do_delete_key (ctrl_t ctrl, const char *username, int secret, int force,
*r_sec_avail = 0;
- hd = keydb_new ();
+ hd = keydb_new (ctrl);
if (!hd)
return gpg_error_from_syserror ();
@@ -131,7 +131,7 @@ do_delete_key (ctrl_t ctrl, const char *username, int secret, int force,
if (!secret && !force)
{
- if (have_secret_key_with_kid (keyid))
+ if (have_secret_key_with_kid (ctrl, keyid))
{
*r_sec_avail = 1;
err = gpg_error (GPG_ERR_EOF);
@@ -141,7 +141,7 @@ do_delete_key (ctrl_t ctrl, const char *username, int secret, int force,
err = 0;
}
- if (secret && !have_secret_key_with_kid (keyid))
+ if (secret && !have_secret_key_with_kid (ctrl, keyid))
{
err = gpg_error (GPG_ERR_NOT_FOUND);
log_error (_("key \"%s\" not found\n"), username);
diff --git a/g10/export.c b/g10/export.c
index 98ed3b0e6..3517be72c 100644
--- a/g10/export.c
+++ b/g10/export.c
@@ -1877,7 +1877,7 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
stats = &dummystats;
*any = 0;
init_packet (&pkt);
- kdbhd = keydb_new ();
+ kdbhd = keydb_new (ctrl);
if (!kdbhd)
return gpg_error_from_syserror ();
diff --git a/g10/getkey.c b/g10/getkey.c
index 2bf42a677..de5024198 100644
--- a/g10/getkey.c
+++ b/g10/getkey.c
@@ -403,7 +403,7 @@ get_pubkey (ctrl_t ctrl, PKT_public_key * pk, u32 * keyid)
}
else
{
- ctx.kr_handle = keydb_new ();
+ ctx.kr_handle = keydb_new (ctrl);
if (!ctx.kr_handle)
{
rc = gpg_error_from_syserror ();
@@ -448,7 +448,7 @@ leave:
* Return the public key in *PK. The resources in *PK should be
* released using release_public_key_parts(). */
int
-get_pubkey_fast (PKT_public_key * pk, u32 * keyid)
+get_pubkey_fast (ctrl_t ctrl, PKT_public_key * pk, u32 * keyid)
{
int rc = 0;
KEYDB_HANDLE hd;
@@ -476,7 +476,7 @@ get_pubkey_fast (PKT_public_key * pk, u32 * keyid)
}
#endif
- hd = keydb_new ();
+ hd = keydb_new (ctrl);
if (!hd)
return gpg_error_from_syserror ();
rc = keydb_search_kid (hd, keyid);
@@ -550,7 +550,7 @@ get_pubkeyblock (ctrl_t ctrl, u32 * keyid)
memset (&ctx, 0, sizeof ctx);
/* No need to set exact here because we want the entire block. */
ctx.not_allocated = 1;
- ctx.kr_handle = keydb_new ();
+ ctx.kr_handle = keydb_new (ctrl);
if (!ctx.kr_handle)
return NULL;
ctx.nitems = 1;
@@ -592,7 +592,7 @@ get_seckey (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid)
memset (&ctx, 0, sizeof ctx);
ctx.exact = 1; /* Use the key ID exactly as given. */
ctx.not_allocated = 1;
- ctx.kr_handle = keydb_new ();
+ ctx.kr_handle = keydb_new (ctrl);
if (!ctx.kr_handle)
return gpg_error_from_syserror ();
ctx.nitems = 1;
@@ -807,7 +807,7 @@ key_byname (ctrl_t ctrl, GETKEY_CTX *retctx, strlist_t namelist,
}
ctx->want_secret = want_secret;
- ctx->kr_handle = keydb_new ();
+ ctx->kr_handle = keydb_new (ctrl);
if (!ctx->kr_handle)
{
rc = gpg_error_from_syserror ();
@@ -1448,7 +1448,7 @@ get_best_pubkey_byname (ctrl_t ctrl, enum get_pubkey_modes mode,
err = gpg_error_from_syserror ();
else
{
- ctx->kr_handle = keydb_new ();
+ ctx->kr_handle = keydb_new (ctrl);
if (! ctx->kr_handle)
{
err = gpg_error_from_syserror ();
@@ -1594,7 +1594,7 @@ get_pubkey_byfprint (ctrl_t ctrl, PKT_public_key *pk, kbnode_t *r_keyblock,
ctx.not_allocated = 1;
/* FIXME: We should get the handle from the cache like we do in
* get_pubkey. */
- ctx.kr_handle = keydb_new ();
+ ctx.kr_handle = keydb_new (ctrl);
if (!ctx.kr_handle)
return gpg_error_from_syserror ();
@@ -1632,13 +1632,14 @@ get_pubkey_byfprint (ctrl_t ctrl, PKT_public_key *pk, kbnode_t *r_keyblock,
* Like get_pubkey_byfprint, PK may be NULL. In that case, this
* function effectively just checks for the existence of the key. */
gpg_error_t
-get_pubkey_byfprint_fast (PKT_public_key * pk,
+get_pubkey_byfprint_fast (ctrl_t ctrl, PKT_public_key * pk,
const byte * fprint, size_t fprint_len)
{
gpg_error_t err;
KBNODE keyblock;
- err = get_keyblock_byfprint_fast (&keyblock, NULL, fprint, fprint_len, 0);
+ err = get_keyblock_byfprint_fast (ctrl,
+ &keyblock, NULL, fprint, fprint_len, 0);
if (!err)
{
if (pk)
@@ -1658,7 +1659,8 @@ get_pubkey_byfprint_fast (PKT_public_key * pk,
* it may have a value of NULL, though. This allows to do an insert
* operation on a locked keydb handle. */
gpg_error_t
-get_keyblock_byfprint_fast (kbnode_t *r_keyblock, KEYDB_HANDLE *r_hd,
+get_keyblock_byfprint_fast (ctrl_t ctrl,
+ kbnode_t *r_keyblock, KEYDB_HANDLE *r_hd,
const byte *fprint, size_t fprint_len, int lock)
{
gpg_error_t err;
@@ -1675,7 +1677,7 @@ get_keyblock_byfprint_fast (kbnode_t *r_keyblock, KEYDB_HANDLE *r_hd,
for (i = 0; i < MAX_FINGERPRINT_LEN && i < fprint_len; i++)
fprbuf[i] = fprint[i];
- hd = keydb_new ();
+ hd = keydb_new (ctrl);
if (!hd)
return gpg_error_from_syserror ();
@@ -1757,7 +1759,7 @@ parse_def_secret_key (ctrl_t ctrl)
if (! hd)
{
- hd = keydb_new ();
+ hd = keydb_new (ctrl);
if (!hd)
return NULL;
}
@@ -2732,7 +2734,7 @@ merge_selfsigs_main (ctrl_t ctrl, kbnode_t keyblock, int *r_revoked,
* reason to check that an ultimately trusted key is
* still valid - if it has been revoked the user
* should also remove the ultimate trust flag. */
- if (get_pubkey_fast (ultimate_pk, sig->keyid) == 0
+ if (get_pubkey_fast (ctrl, ultimate_pk, sig->keyid) == 0
&& check_key_signature2 (ctrl,
keyblock, k, ultimate_pk,
NULL, NULL, NULL, NULL) == 0
@@ -4117,7 +4119,7 @@ key_origin_string (int origin)
the secret key is valid; this check merely indicates whether there
is some secret key with the specified key id. */
int
-have_secret_key_with_kid (u32 *keyid)
+have_secret_key_with_kid (ctrl_t ctrl, u32 *keyid)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd;
@@ -4126,7 +4128,7 @@ have_secret_key_with_kid (u32 *keyid)
kbnode_t node;
int result = 0;
- kdbhd = keydb_new ();
+ kdbhd = keydb_new (ctrl);
if (!kdbhd)
return 0;
memset (&desc, 0, sizeof desc);
diff --git a/g10/gpg.c b/g10/gpg.c
index 6da36c2d4..b9b6de4e6 100644
--- a/g10/gpg.c
+++ b/g10/gpg.c
@@ -36,6 +36,7 @@
# endif
# include <windows.h>
#endif
+#include <npth.h>
#define INCLUDED_BY_MAIN_MODULE 1
#include "gpg.h"
@@ -361,6 +362,7 @@ enum cmd_and_opt_values
oUseAgent,
oNoUseAgent,
oGpgAgentInfo,
+ oUseKeyboxd,
oMergeOnly,
oTryAllSecrets,
oTrustedKey,
@@ -378,6 +380,7 @@ enum cmd_and_opt_values
oPersonalDigestPreferences,
oPersonalCompressPreferences,
oAgentProgram,
+ oKeyboxdProgram,
oDirmngrProgram,
oDisableDirmngr,
oDisplay,
@@ -850,6 +853,7 @@ static ARGPARSE_OPTS opts[] = {
ARGPARSE_s_s (oPersonalCompressPreferences, "personal-compress-prefs", "@"),
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
+ ARGPARSE_s_s (oKeyboxdProgram, "keyboxd-program", "@"),
ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"),
ARGPARSE_s_n (oDisableDirmngr, "disable-dirmngr", "@"),
ARGPARSE_s_s (oDisplay, "display", "@"),
@@ -896,6 +900,7 @@ static ARGPARSE_OPTS opts[] = {
ARGPARSE_s_n (oNoAutoKeyLocate, "no-auto-key-locate", "@"),
ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"),
ARGPARSE_s_n (oNoSymkeyCache, "no-symkey-cache", "@"),
+ ARGPARSE_s_n (oUseKeyboxd, "use-keyboxd", "@"),
/* Options which can be used in special circumstances. They are not
* published and we hope they are never required. */
@@ -983,6 +988,9 @@ static void add_keyserver_url( const char *string, int which );
static void emergency_cleanup (void);
static void read_sessionkey_from_fd (int fd);
+/* NPth wrapper function definitions. */
+ASSUAN_SYSTEM_NPTH_IMPL;
+
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
@@ -2250,6 +2258,7 @@ gpg_deinit_default_ctrl (ctrl_t ctrl)
gpg_dirmngr_deinit_session_data (ctrl);
keydb_release (ctrl->cached_getkey_kdb);
+ gpg_keyboxd_deinit_session_data (ctrl);
}
@@ -2742,6 +2751,11 @@ main (int argc, char **argv)
case oGpgAgentInfo:
obsolete_option (configname, configlineno, "gpg-agent-info");
break;
+
+ case oUseKeyboxd:
+ opt.use_keyboxd = 1;
+ break;
+
case oReaderPort:
obsolete_scdaemon_option (configname, configlineno, "reader-port");
break;
@@ -3499,6 +3513,7 @@ main (int argc, char **argv)
pers_compress_list=pargs.r.ret_str;
break;
case oAgentProgram: opt.agent_program = pargs.r.ret_str; break;
+ case oKeyboxdProgram: opt.keyboxd_program = pargs.r.ret_str; break;
case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break;
case oDisableDirmngr: opt.disable_dirmngr = 1; break;
case oWeakDigest:
@@ -3738,6 +3753,11 @@ main (int argc, char **argv)
}
#endif
+ /* Init threading which is used by some helper functions. */
+ npth_init ();
+ assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
+ gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
+
/* FIXME: We should use logging to a file only in server mode;
however we have not yet implemetyed that. Thus we try to get
away with --batch as indication for logging to file
@@ -3745,7 +3765,9 @@ main (int argc, char **argv)
if (logfile && opt.batch)
{
log_set_file (logfile);
- log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID);
+ log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
+ | GPGRT_LOG_WITH_TIME
+ | GPGRT_LOG_WITH_PID ));
}
if (opt.verbose > 2)
@@ -4120,8 +4142,10 @@ main (int argc, char **argv)
/* Add the keyrings, but not for some special commands. We always
* need to add the keyrings if we are running under SELinux, this
* is so that the rings are added to the list of secured files.
- * We do not add any keyring if --no-keyring has been used. */
- if (default_keyring >= 0
+ * We do not add any keyring if --no-keyring or --use-keyboxd has
+ * been used. */
+ if (!opt.use_keyboxd
+ && default_keyring >= 0
&& (ALWAYS_ADD_KEYRINGS
|| (cmd != aDeArmor && cmd != aEnArmor && cmd != aGPGConfTest)))
{
@@ -4133,9 +4157,8 @@ main (int argc, char **argv)
}
FREE_STRLIST(nrings);
+ /* In loopback mode, never ask for the password multiple times. */
if (opt.pinentry_mode == PINENTRY_MODE_LOOPBACK)
- /* In loopback mode, never ask for the password multiple
- times. */
{
opt.passphrase_repeat = 0;
}
@@ -5089,7 +5112,7 @@ main (int argc, char **argv)
policy = parse_tofu_policy (argv[0]);
- hd = keydb_new ();
+ hd = keydb_new (ctrl);
if (! hd)
{
write_status_failure ("tofu-driver", gpg_error(GPG_ERR_GENERAL));
diff --git a/g10/gpg.h b/g10/gpg.h
index 28a77b6df..adb919d7d 100644
--- a/g10/gpg.h
+++ b/g10/gpg.h
@@ -60,16 +60,19 @@
/* Object used to keep state locally to server.c . */
struct server_local_s;
+/* Object used to keep state locally to call-keyboxd.c . */
+struct keyboxd_local_s;
+typedef struct keyboxd_local_s *keyboxd_local_t;
+
/* Object used to keep state locally to call-dirmngr.c . */
struct dirmngr_local_s;
typedef struct dirmngr_local_s *dirmngr_local_t;
/* Object used to describe a keyblock node. */
-typedef struct kbnode_struct *KBNODE; /* Deprecated use kbnode_t. */
-typedef struct kbnode_struct *kbnode_t;
+typedef struct kbnode_struct *KBNODE; /* Deprecated use kbnode_t. */typedef struct kbnode_struct *kbnode_t;
/* The handle for keydb operations. */
-typedef struct keydb_handle *KEYDB_HANDLE;
+typedef struct keydb_handle_s *KEYDB_HANDLE;
/* TOFU database meta object. */
struct tofu_dbs_s;
@@ -96,6 +99,9 @@ struct server_control_s
/* Local data for call-dirmngr.c */
dirmngr_local_t dirmngr_local;
+ /* Local data for call-keyboxd.c */
+ keyboxd_local_t keyboxd_local;
+
/* Local data for tofu.c */
struct {
tofu_dbs_t dbs;
diff --git a/g10/gpgcompose.c b/g10/gpgcompose.c
index 7b7e1dc9a..43cecb90e 100644
--- a/g10/gpgcompose.c
+++ b/g10/gpgcompose.c
@@ -614,7 +614,7 @@ pk_search_terms (const char *option, int argc, char *argv[], void *cookie)
if (err)
log_fatal ("search terms '%s': %s\n", argv[0], gpg_strerror (err));
- hd = keydb_new ();
+ hd = keydb_new (ctrl);
err = keydb_search (hd, &desc, 1, NULL);
if (err)
@@ -810,7 +810,7 @@ sig_issuer (const char *option, int argc, char *argv[], void *cookie)
if (err)
log_fatal ("search terms '%s': %s\n", argv[0], gpg_strerror (err));
- hd = keydb_new ();
+ hd = keydb_new (ctrl);
err = keydb_search (hd, &desc, 1, NULL);
if (err)
diff --git a/g10/import.c b/g10/import.c
index 867a9de29..47014b948 100644
--- a/g10/import.c
+++ b/g10/import.c
@@ -550,7 +550,7 @@ import_keys_es_stream (ctrl_t ctrl, estream_t fp,
gpg_error_t err;
iobuf_t inp;
- inp = iobuf_esopen (fp, "rb", 1);
+ inp = iobuf_esopen (fp, "rb", 1, 0);
if (!inp)
{
err = gpg_error_from_syserror ();
@@ -2022,7 +2022,7 @@ import_one_real (ctrl_t ctrl,
goto leave;
/* Do we have this key already in one of our pubrings ? */
- err = get_keyblock_byfprint_fast (&keyblock_orig, &hd,
+ err = get_keyblock_byfprint_fast (ctrl, &keyblock_orig, &hd,
fpr2, fpr2len, 1/*locked*/);
if ((err
&& gpg_err_code (err) != GPG_ERR_NO_PUBKEY
@@ -2310,13 +2310,13 @@ import_one_real (ctrl_t ctrl,
if (mod_key)
{
revocation_present (ctrl, keyblock_orig);
- if (!from_sk && have_secret_key_with_kid (keyid))
+ if (!from_sk && have_secret_key_with_kid (ctrl, keyid))
check_prefs (ctrl, keyblock_orig);
}
else if (new_key)
{
revocation_present (ctrl, keyblock);
- if (!from_sk && have_secret_key_with_kid (keyid))
+ if (!from_sk && have_secret_key_with_kid (ctrl, keyid))
check_prefs (ctrl, keyblock);
}
@@ -3372,7 +3372,7 @@ import_revoke_cert (ctrl_t ctrl, kbnode_t node, unsigned int options,
}
/* Read the original keyblock. */
- hd = keydb_new ();
+ hd = keydb_new (ctrl);
if (!hd)
{
rc = gpg_error_from_syserror ();
@@ -3768,7 +3768,8 @@ delete_inv_parts (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid,
else if (node->pkt->pkttype == PKT_SIGNATURE
&& !node->pkt->pkt.signature->flags.exportable
&& !(options&IMPORT_LOCAL_SIGS)
- && !have_secret_key_with_kid (node->pkt->pkt.signature->keyid))
+ && !have_secret_key_with_kid (ctrl,
+ node->pkt->pkt.signature->keyid))
{
/* here we violate the rfc a bit by still allowing
* to import non-exportable signature when we have the
@@ -4089,7 +4090,7 @@ revocation_present (ctrl_t ctrl, kbnode_t keyblock)
* itself? */
gpg_error_t err;
- err = get_pubkey_byfprint_fast (NULL,
+ err = get_pubkey_byfprint_fast (ctrl, NULL,
sig->revkey[idx].fpr,
sig->revkey[idx].fprlen);
if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY
@@ -4111,7 +4112,7 @@ revocation_present (ctrl_t ctrl, kbnode_t keyblock)
opt.keyserver, 0);
/* Do we have it now? */
- err = get_pubkey_byfprint_fast (NULL,
+ err = get_pubkey_byfprint_fast (ctrl, NULL,
sig->revkey[idx].fpr,
sig->revkey[idx].fprlen);
}
diff --git a/g10/keydb-private.h b/g10/keydb-private.h
new file mode 100644
index 000000000..efef82289
--- /dev/null
+++ b/g10/keydb-private.h
@@ -0,0 +1,171 @@
+/* keydb-private.h - Common definitions for keydb.c and call-keyboxd.c
+ * 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
+ */
+
+#ifndef G10_KEYDB_PRIVATE_H
+#define G10_KEYDB_PRIVATE_H
+
+#include <assuan.h>
+#include "../common/membuf.h"
+
+
+/* Ugly forward declarations. */
+struct keyring_handle;
+typedef struct keyring_handle *KEYRING_HANDLE;
+struct keybox_handle;
+typedef struct keybox_handle *KEYBOX_HANDLE;
+
+
+/* This is for keydb.c and only used in non-keyboxd mode. */
+#define MAX_KEYDB_RESOURCES 40
+
+/* This is for keydb.c and only used in non-keyboxd mode. */
+typedef enum
+ {
+ KEYDB_RESOURCE_TYPE_NONE = 0,
+ KEYDB_RESOURCE_TYPE_KEYRING,
+ KEYDB_RESOURCE_TYPE_KEYBOX
+ } KeydbResourceType;
+
+/* This is for keydb.c and only used in non-keyboxd mode. */
+struct resource_item
+{
+ KeydbResourceType type;
+ union {
+ KEYRING_HANDLE kr;
+ KEYBOX_HANDLE kb;
+ } u;
+ void *token;
+};
+
+
+/* This is a simple cache used to return the last result of a
+ * successful fingerprint search. This works only for keybox
+ * resources because (due to lack of a copy_keyblock function) we need
+ * to store an image of the keyblock which is fortunately instantly
+ * available for keyboxes. Only used in non-keyboxd mode. */
+enum keyblock_cache_states {
+ KEYBLOCK_CACHE_EMPTY,
+ KEYBLOCK_CACHE_PREPARED,
+ KEYBLOCK_CACHE_FILLED
+};
+
+struct keyblock_cache {
+ enum keyblock_cache_states state;
+ byte fpr[MAX_FINGERPRINT_LEN];
+ byte fprlen;
+ iobuf_t iobuf; /* Image of the keyblock. */
+ int pk_no;
+ int uid_no;
+ /* Offset of the record in the keybox. */
+ int resource;
+ off_t offset;
+};
+
+
+/* The definition of the KEYDB_HANDLE as used internally by keydb.c and
+ * the newer call-keyboxd. */
+struct keydb_handle_s
+{
+ /* Flag set if this handles pertains to call-keyboxd.c. */
+ int use_keyboxd;
+
+ /* BEGIN USE_KEYBOXD */
+ /* (These fields are only valid if USE_KEYBOXD is set.) */
+
+ /* A shallow pointer with the CTRL used to create this handle. */
+ ctrl_t ctrl;
+
+ /* Connection info which also keep the local state. (This is points
+ * into the CTRL->keybox_local list.) */
+ keyboxd_local_t kbl;
+
+ /* END USE_KEYBOXD */
+
+ /* BEGIN !USE_KEYBOXD */
+ /* (The remaining fields are only valid if USE_KEYBOXD is cleared.) */
+
+ /* When we locked all of the resources in ACTIVE (using keyring_lock
+ * / keybox_lock, as appropriate). */
+ int locked;
+
+ /* If this flag is set a lock will only be released by
+ * keydb_release. */
+ int keep_lock;
+
+ /* The index into ACTIVE of the resources in which the last search
+ result was found. Initially -1. */
+ int found;
+
+ /* Initially -1 (invalid). This is used to save a search result and
+ later restore it as the selected result. */
+ int saved_found;
+
+ /* The number of skipped long blobs since the last search
+ (keydb_search_reset). */
+ unsigned long skipped_long_blobs;
+
+ /* If set, this disables the use of the keyblock cache. */
+ int no_caching;
+
+ /* Whether the next search will be from the beginning of the
+ database (and thus consider all records). */
+ int is_reset;
+
+ /* The "file position." In our case, this is index of the current
+ resource in ACTIVE. */
+ int current;
+
+ /* The number of resources in ACTIVE. */
+ int used;
+
+ /* Cache of the last found and parsed key block (only used for
+ keyboxes, not keyrings). */
+ struct keyblock_cache keyblock_cache;
+
+ /* Copy of ALL_RESOURCES when keydb_new is called. */
+ struct resource_item active[MAX_KEYDB_RESOURCES];
+
+ /* END !USE_KEYBOXD */
+};
+
+
+/*-- keydb.c --*/
+
+/* These are the functions call-keyboxd diverts to if the keyboxd is
+ * not used. */
+
+gpg_error_t internal_keydb_init (KEYDB_HANDLE hd);
+void internal_keydb_deinit (KEYDB_HANDLE hd);
+gpg_error_t internal_keydb_lock (KEYDB_HANDLE hd);
+
+gpg_error_t internal_keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb);
+gpg_error_t internal_keydb_update_keyblock (ctrl_t ctrl,
+ KEYDB_HANDLE hd, kbnode_t kb);
+gpg_error_t internal_keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb);
+gpg_error_t internal_keydb_delete_keyblock (KEYDB_HANDLE hd);
+gpg_error_t internal_keydb_search_reset (KEYDB_HANDLE hd);
+gpg_error_t internal_keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
+ size_t ndesc, size_t *descindex);
+
+
+
+
+
+#endif /*G10_KEYDB_PRIVATE_H*/
diff --git a/g10/keydb.c b/g10/keydb.c
index 92e5faae8..aeb62abfc 100644
--- a/g10/keydb.c
+++ b/g10/keydb.c
@@ -37,25 +37,10 @@
#include "keydb.h"
#include "../common/i18n.h"
-static int active_handles;
+#include "keydb-private.h" /* For struct keydb_handle_s */
-typedef enum
- {
- KEYDB_RESOURCE_TYPE_NONE = 0,
- KEYDB_RESOURCE_TYPE_KEYRING,
- KEYDB_RESOURCE_TYPE_KEYBOX
- } KeydbResourceType;
-#define MAX_KEYDB_RESOURCES 40
+static int active_handles;
-struct resource_item
-{
- KeydbResourceType type;
- union {
- KEYRING_HANDLE kr;
- KEYBOX_HANDLE kb;
- } u;
- void *token;
-};
static struct resource_item all_resources[MAX_KEYDB_RESOURCES];
static int used_resources;
@@ -67,74 +52,6 @@ static void *primary_keydb;
/* Whether we have successfully registered any resource. */
static int any_registered;
-/* This is a simple cache used to return the last result of a
- successful fingerprint search. This works only for keybox resources
- because (due to lack of a copy_keyblock function) we need to store
- an image of the keyblock which is fortunately instantly available
- for keyboxes. */
-enum keyblock_cache_states {
- KEYBLOCK_CACHE_EMPTY,
- KEYBLOCK_CACHE_PREPARED,
- KEYBLOCK_CACHE_FILLED
-};
-
-struct keyblock_cache {
- enum keyblock_cache_states state;
- byte fpr[MAX_FINGERPRINT_LEN];
- byte fprlen;
- iobuf_t iobuf; /* Image of the keyblock. */
- int pk_no;
- int uid_no;
- /* Offset of the record in the keybox. */
- int resource;
- off_t offset;
-};
-
-
-struct keydb_handle
-{
- /* When we locked all of the resources in ACTIVE (using keyring_lock
- / keybox_lock, as appropriate). */
- int locked;
-
- /* If this flag is set a lock will only be released by
- * keydb_release. */
- int keep_lock;
-
- /* The index into ACTIVE of the resources in which the last search
- result was found. Initially -1. */
- int found;
-
- /* Initially -1 (invalid). This is used to save a search result and
- later restore it as the selected result. */
- int saved_found;
-
- /* The number of skipped long blobs since the last search
- (keydb_search_reset). */
- unsigned long skipped_long_blobs;
-
- /* If set, this disables the use of the keyblock cache. */
- int no_caching;
-
- /* Whether the next search will be from the beginning of the
- database (and thus consider all records). */
- int is_reset;
-
- /* The "file position." In our case, this is index of the current
- resource in ACTIVE. */
- int current;
-
- /* The number of resources in ACTIVE. */
- int used;
-
- /* Cache of the last found and parsed key block (only used for
- keyboxes, not keyrings). */
- struct keyblock_cache keyblock_cache;
-
- /* Copy of ALL_RESOURCES when keydb_new is called. */
- struct resource_item active[MAX_KEYDB_RESOURCES];
-};
-
/* Looking up keys is expensive. To hide the cost, we cache whether
keys exist in the key database. Then, if we know a key does not
exist, we don't have to spend time looking it up. This
@@ -273,7 +190,7 @@ kid_not_found_flush (void)
static void
-keyblock_cache_clear (struct keydb_handle *hd)
+keyblock_cache_clear (struct keydb_handle_s *hd)
{
hd->keyblock_cache.state = KEYBLOCK_CACHE_EMPTY;
iobuf_close (hd->keyblock_cache.iobuf);
@@ -448,7 +365,7 @@ maybe_create_keyring_or_box (char *filename, int is_box, int force_create)
rc = gpg_error_from_syserror ();
else
{
- rc = _keybox_write_header_blob (fp, 1);
+ rc = _keybox_write_header_blob (fp, NULL, 1);
fclose (fp);
}
if (rc)
@@ -539,6 +456,10 @@ keydb_search_desc_dump (struct keydb_search_desc *desc)
char b[MAX_FORMATTED_FINGERPRINT_LEN + 1];
char fpr[2 * MAX_FINGERPRINT_LEN + 1];
+#if MAX_FINGERPRINT_LEN < 20
+#error MAX_FINGERPRINT_LEN shorter than GRIP and UBID length/
+#endif
+
switch (desc->mode)
{
case KEYDB_SEARCH_MODE_EXACT:
@@ -578,7 +499,11 @@ keydb_search_desc_dump (struct keydb_search_desc *desc)
case KEYDB_SEARCH_MODE_SUBJECT:
return xasprintf ("SUBJECT: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_KEYGRIP:
- return xasprintf ("KEYGRIP: %s", desc->u.grip);
+ bin2hex (desc[0].u.grip, 20, fpr);
+ return xasprintf ("KEYGRIP: %s", fpr);
+ case KEYDB_SEARCH_MODE_UBID:
+ bin2hex (desc[0].u.ubid, 20, fpr);
+ return xasprintf ("UBID: %s", fpr);
case KEYDB_SEARCH_MODE_FIRST:
return xasprintf ("FIRST");
case KEYDB_SEARCH_MODE_NEXT:
@@ -888,26 +813,17 @@ keydb_dump_stats (void)
}
-/* 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. */
-KEYDB_HANDLE
-keydb_new (void)
+/* keydb_new diverts to here in non-keyboxd mode. HD is just the
+ * calloced structure with the handle type intialized. */
+gpg_error_t
+internal_keydb_init (KEYDB_HANDLE hd)
{
- KEYDB_HANDLE hd;
+ gpg_error_t err = 0;
int i, j;
int die = 0;
int reterrno;
- if (DBG_CLOCK)
- log_clock ("keydb_new");
-
- hd = xtrycalloc (1, sizeof *hd);
- if (!hd)
- goto leave;
+ log_assert (!hd->use_keyboxd);
hd->found = -1;
hd->saved_found = -1;
hd->is_reset = 1;
@@ -949,28 +865,21 @@ keydb_new (void)
keydb_stats.handles++;
if (die)
- {
- keydb_release (hd);
- gpg_err_set_errno (reterrno);
- hd = NULL;
- }
-
- leave:
- if (!hd)
- log_error (_("error opening key DB: %s\n"),
- gpg_strerror (gpg_error_from_syserror()));
+ err = gpg_error_from_errno (reterrno);
- return hd;
+ return err;
}
+/* Free all non-keyboxd resources owned by the database handle.
+ * keydb_release diverts to here. */
void
-keydb_release (KEYDB_HANDLE hd)
+internal_keydb_deinit (KEYDB_HANDLE hd)
{
int i;
- if (!hd)
- return;
+ log_assert (!hd->use_keyboxd);
+
log_assert (active_handles > 0);
active_handles--;
@@ -992,19 +901,17 @@ keydb_release (KEYDB_HANDLE hd)
}
keyblock_cache_clear (hd);
- xfree (hd);
}
/* Take a lock on the files immediately and not only during insert or
* update. This lock is released with keydb_release. */
gpg_error_t
-keydb_lock (KEYDB_HANDLE hd)
+internal_keydb_lock (KEYDB_HANDLE hd)
{
gpg_error_t err;
- if (!hd)
- return gpg_error (GPG_ERR_INV_ARG);
+ log_assert (!hd->use_keyboxd);
err = lock_all (hd);
if (!err)
@@ -1020,7 +927,7 @@ keydb_lock (KEYDB_HANDLE hd)
void
keydb_disable_caching (KEYDB_HANDLE hd)
{
- if (hd)
+ if (hd && !hd->use_keyboxd)
hd->no_caching = 1;
}
@@ -1042,6 +949,9 @@ keydb_get_resource_name (KEYDB_HANDLE hd)
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)
@@ -1165,6 +1075,8 @@ unlock_all (KEYDB_HANDLE hd)
* Note: it is only possible to save a single save state at a time.
* In other words, the save stack only has room for a single
* instance of the state. */
+/* FIXME(keyboxd): This function is used only at one place - see how
+ * we can avoid it. */
void
keydb_push_found_state (KEYDB_HANDLE hd)
{
@@ -1196,6 +1108,8 @@ keydb_push_found_state (KEYDB_HANDLE hd)
/* Restore the previous save state. If the saved state is NULL or
invalid, this is a NOP. */
+/* FIXME(keyboxd): This function is used only at one place - see how
+ * we can avoid it. */
void
keydb_pop_found_state (KEYDB_HANDLE hd)
{
@@ -1360,6 +1274,7 @@ parse_keyblock_image (iobuf_t iobuf, int pk_no, int uid_no,
/* Return the keyblock last found by keydb_search() in *RET_KB.
+ * keydb_get_keyblock divert to here in the non-keyboxd mode.
*
* On success, the function returns 0 and the caller must free *RET_KB
* using release_kbnode(). Otherwise, the function returns an error
@@ -1369,17 +1284,11 @@ parse_keyblock_image (iobuf_t iobuf, int pk_no, int uid_no,
* 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 *ret_kb)
+internal_keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb)
{
gpg_error_t err = 0;
- *ret_kb = NULL;
-
- if (!hd)
- return gpg_error (GPG_ERR_INV_ARG);
-
- if (DBG_CLOCK)
- log_clock ("keydb_get_keybock enter");
+ log_assert (!hd->use_keyboxd);
if (hd->keyblock_cache.state == KEYBLOCK_CACHE_FILLED)
{
@@ -1398,8 +1307,7 @@ keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb)
if (err)
keyblock_cache_clear (hd);
if (DBG_CLOCK)
- log_clock (err? "keydb_get_keyblock leave (cached, failed)"
- : "keydb_get_keyblock leave (cached)");
+ log_clock ("%s leave (cached mode)", __func__);
return err;
}
}
@@ -1447,9 +1355,6 @@ keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb)
if (!err)
keydb_stats.get_keyblocks++;
- if (DBG_CLOCK)
- log_clock (err? "keydb_get_keyblock leave (failed)"
- : "keydb_get_keyblock leave");
return err;
}
@@ -1498,6 +1403,7 @@ build_keyblock_image (kbnode_t keyblock, iobuf_t *r_iobuf)
/* Update the keyblock KB (i.e., extract the fingerprint and find the
* corresponding keyblock in the keyring).
+ * keydb_update_keyblock diverts to here in the non-keyboxd mode.
*
* This doesn't do anything if --dry-run was specified.
*
@@ -1512,20 +1418,16 @@ build_keyblock_image (kbnode_t keyblock, iobuf_t *r_iobuf)
* 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)
+internal_keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb)
{
gpg_error_t err;
PKT_public_key *pk;
KEYDB_SEARCH_DESC desc;
size_t len;
- log_assert (kb);
- log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
+ log_assert (!hd->use_keyboxd);
pk = kb->pkt->pkt.public_key;
- if (!hd)
- return gpg_error (GPG_ERR_INV_ARG);
-
kid_not_found_flush ();
keyblock_cache_clear (hd);
@@ -1588,6 +1490,7 @@ keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb)
/* Insert a keyblock into one of the underlying keyrings or keyboxes.
+ * keydb_insert_keyblock diverts to here in the non-keyboxd mode.
*
* Be default, the keyring / keybox from which the last search result
* came is used. If there was no previous search result (or
@@ -1598,13 +1501,12 @@ keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb)
*
* Returns 0 on success. Otherwise, it returns an error code. */
gpg_error_t
-keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
+internal_keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
{
gpg_error_t err;
int idx;
- if (!hd)
- return gpg_error (GPG_ERR_INV_ARG);
+ log_assert (!hd->use_keyboxd);
kid_not_found_flush ();
keyblock_cache_clear (hd);
@@ -1663,12 +1565,11 @@ keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
*
* Returns 0 on success or an error code, if an error occurs. */
gpg_error_t
-keydb_delete_keyblock (KEYDB_HANDLE hd)
+internal_keydb_delete_keyblock (KEYDB_HANDLE hd)
{
gpg_error_t rc;
- if (!hd)
- return gpg_error (GPG_ERR_INV_ARG);
+ log_assert (!hd->use_keyboxd);
kid_not_found_flush ();
keyblock_cache_clear (hd);
@@ -1721,6 +1622,9 @@ keydb_locate_writable (KEYDB_HANDLE hd)
if (!hd)
return GPG_ERR_INV_ARG;
+ if (hd->use_keyboxd)
+ return 0; /* No need for this here. */
+
rc = keydb_search_reset (hd); /* this does reset hd->current */
if (rc)
return rc;
@@ -1772,6 +1676,9 @@ keydb_rebuild_caches (ctrl_t ctrl, int noisy)
{
int i, rc;
+ if (opt.use_keyboxd)
+ return; /* No need for this here. */
+
for (i=0; i < used_resources; i++)
{
if (!keyring_is_writable (all_resources[i].token))
@@ -1794,38 +1701,33 @@ keydb_rebuild_caches (ctrl_t ctrl, int noisy)
}
-/* Return the number of skipped blocks (because they were to large to
+/* Return the number of skipped blocks (because they were too large to
read from a keybox) since the last search reset. */
unsigned long
keydb_get_skipped_counter (KEYDB_HANDLE hd)
{
- return hd ? hd->skipped_long_blobs : 0;
+ /*FIXME(keyboxd): Do we need this? */
+ return hd && !hd->use_keyboxd? hd->skipped_long_blobs : 0;
}
/* Clears the current search result and resets the handle's position
* so that the next search starts at the beginning of the database
* (the start of the first resource).
+ * keydb_search_reset diverts to here in the non-keyboxd mode.
*
* Returns 0 on success and an error code if an error occurred.
* (Currently, this function always returns 0 if HD is valid.) */
gpg_error_t
-keydb_search_reset (KEYDB_HANDLE hd)
+internal_keydb_search_reset (KEYDB_HANDLE hd)
{
gpg_error_t rc = 0;
int i;
- if (!hd)
- return gpg_error (GPG_ERR_INV_ARG);
+ log_assert (!hd->use_keyboxd);
keyblock_cache_clear (hd);
- if (DBG_CLOCK)
- log_clock ("keydb_search_reset");
-
- if (DBG_CACHE)
- log_debug ("keydb_search: reset (hd=%p)", hd);
-
hd->skipped_long_blobs = 0;
hd->current = 0;
hd->found = -1;
@@ -1853,6 +1755,7 @@ keydb_search_reset (KEYDB_HANDLE hd)
/* Search the database for keys matching the search description. If
* the DB contains any legacy keys, these are silently ignored.
+ * keydb_search diverts to here in the non-keyboxd mode.
*
* 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
@@ -1870,21 +1773,16 @@ keydb_search_reset (KEYDB_HANDLE hd)
* 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)
+internal_keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
+ size_t ndesc, size_t *descindex)
{
- int i;
gpg_error_t rc;
int was_reset = hd->is_reset;
/* If an entry is already in the cache, then don't add it again. */
int already_in_cache = 0;
int fprlen;
- if (descindex)
- *descindex = 0; /* Make sure it is always set on return. */
-
- if (!hd)
- return gpg_error (GPG_ERR_INV_ARG);
+ log_assert (!hd->use_keyboxd);
if (!any_registered)
{
@@ -1892,26 +1790,11 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
return gpg_error (GPG_ERR_NOT_FOUND);
}
- if (DBG_CLOCK)
- log_clock ("keydb_search enter");
-
- if (DBG_LOOKUP)
- {
- log_debug ("%s: %zd 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 (ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID
&& (already_in_cache = kid_not_found_p (desc[0].u.kid)) == 1 )
{
if (DBG_CLOCK)
- log_clock ("keydb_search leave (not found, cached)");
+ log_clock ("%s leave (not found, cached)", __func__);
keydb_stats.notfound_cached++;
return gpg_error (GPG_ERR_NOT_FOUND);
}
@@ -1939,7 +1822,7 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
{
/* (DESCINDEX is already set). */
if (DBG_CLOCK)
- log_clock ("keydb_search leave (cached)");
+ log_clock ("%s leave (cached)", __func__);
hd->current = hd->keyblock_cache.resource;
/* HD->KEYBLOCK_CACHE.OFFSET is the last byte in the record.
@@ -2029,9 +1912,6 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
&& !already_in_cache)
kid_not_found_insert (desc[0].u.kid);
- if (DBG_CLOCK)
- log_clock (rc? "keydb_search leave (not found)"
- : "keydb_search leave (found)");
if (!rc)
keydb_stats.found++;
else
diff --git a/g10/keydb.h b/g10/keydb.h
index 6fbc432fd..f94b14659 100644
--- a/g10/keydb.h
+++ b/g10/keydb.h
@@ -169,6 +169,41 @@ is_in_klist (struct key_item *k, PKT_signature *sig)
}
+/*-- call-keyboxd.c --*/
+
+/* Release all open contexts to the keyboxd. */
+void gpg_keyboxd_deinit_session_data (ctrl_t ctrl);
+
+/* Create a new database handle. Returns NULL on error, sets ERRNO,
+ * and prints an error diagnostic. */
+KEYDB_HANDLE keydb_new (ctrl_t ctrl);
+
+/* Release a keydb handle. */
+void keydb_release (KEYDB_HANDLE hd);
+
+/* Take a lock if we are not using the keyboxd. */
+gpg_error_t keydb_lock (KEYDB_HANDLE hd);
+
+/* Return the keyblock last found by keydb_search. */
+gpg_error_t keydb_get_keyblock (KEYDB_HANDLE hd, kbnode_t *ret_kb);
+
+/* Update the keyblock KB. */
+gpg_error_t keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb);
+
+/* Insert a keyblock into one of the storage system. */
+gpg_error_t keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb);
+
+/* Delete the currently selected keyblock. */
+gpg_error_t keydb_delete_keyblock (KEYDB_HANDLE hd);
+
+/* Clears the current search result and resets the handle's position. */
+gpg_error_t keydb_search_reset (KEYDB_HANDLE hd);
+
+/* Search the database for keys matching the search description. */
+gpg_error_t keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
+ size_t ndesc, size_t *descindex);
+
+
/*-- keydb.c --*/
@@ -187,17 +222,6 @@ gpg_error_t keydb_add_resource (const char *url, unsigned int flags);
/* Dump some statistics to the log. */
void keydb_dump_stats (void);
-/* Create a new database handle. Returns NULL on error, sets ERRNO,
- and prints an error diagnostic. */
-KEYDB_HANDLE keydb_new (void);
-
-/* Free all resources owned by the database handle. */
-void keydb_release (KEYDB_HANDLE hd);
-
-/* Take a lock on the files immediately and not only during insert or
- * update. This lock is released with keydb_release. */
-gpg_error_t keydb_lock (KEYDB_HANDLE hd);
-
/* Set a flag on the handle to suppress use of cached results. This
is required for updating a keyring and for key listings. Fixme:
Using a new parameter for keydb_new might be a better solution. */
@@ -212,18 +236,6 @@ void keydb_pop_found_state (KEYDB_HANDLE hd);
/* Return the file name of the resource. */
const char *keydb_get_resource_name (KEYDB_HANDLE hd);
-/* Return the keyblock last found by keydb_search. */
-gpg_error_t keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb);
-
-/* Update the keyblock KB. */
-gpg_error_t keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb);
-
-/* Insert a keyblock into one of the underlying keyrings or keyboxes. */
-gpg_error_t keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb);
-
-/* Delete the currently selected keyblock. */
-gpg_error_t keydb_delete_keyblock (KEYDB_HANDLE hd);
-
/* Find the first writable resource. */
gpg_error_t keydb_locate_writable (KEYDB_HANDLE hd);
@@ -234,13 +246,6 @@ void keydb_rebuild_caches (ctrl_t ctrl, int noisy);
read from a keybox) since the last search reset. */
unsigned long keydb_get_skipped_counter (KEYDB_HANDLE hd);
-/* Clears the current search result and resets the handle's position. */
-gpg_error_t keydb_search_reset (KEYDB_HANDLE hd);
-
-/* Search the database for keys matching the search description. */
-gpg_error_t keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
- size_t ndesc, size_t *descindex);
-
/* Return the first non-legacy key in the database. */
gpg_error_t keydb_search_first (KEYDB_HANDLE hd);
@@ -331,7 +336,7 @@ int get_pubkey (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid);
/* Similar to get_pubkey, but it does not take PK->REQ_USAGE into
account nor does it merge in the self-signed data. This function
also only considers primary keys. */
-int get_pubkey_fast (PKT_public_key *pk, u32 *keyid);
+int get_pubkey_fast (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid);
/* Return the entire keyblock used to create SIG. This is a
* specialized version of get_pubkeyblock. */
@@ -391,13 +396,14 @@ int get_pubkey_byfprint (ctrl_t ctrl, PKT_public_key *pk, kbnode_t *r_keyblock,
/* This function is similar to get_pubkey_byfprint, but it doesn't
merge the self-signed data into the public key and subkeys or into
the user ids. */
-gpg_error_t get_pubkey_byfprint_fast (PKT_public_key *pk,
+gpg_error_t get_pubkey_byfprint_fast (ctrl_t ctrl, PKT_public_key *pk,
const byte *fprint, size_t fprint_len);
/* This function is similar to get_pubkey_byfprint, but it doesn't
merge the self-signed data into the public key and subkeys or into
the user ids. */
-gpg_error_t get_keyblock_byfprint_fast (kbnode_t *r_keyblock,
+gpg_error_t get_keyblock_byfprint_fast (ctrl_t ctrl,
+ kbnode_t *r_keyblock,
KEYDB_HANDLE *r_hd,
const byte *fprint, size_t fprint_len,
int lock);
@@ -405,7 +411,7 @@ gpg_error_t get_keyblock_byfprint_fast (kbnode_t *r_keyblock,
/* Returns true if a secret key is available for the public key with
key id KEYID. */
-int have_secret_key_with_kid (u32 *keyid);
+int have_secret_key_with_kid (ctrl_t ctrl, u32 *keyid);
/* Parse the --default-key parameter. Returns the last key (in terms
of when the option is given) that is available. */
diff --git a/g10/keyedit.c b/g10/keyedit.c
index b66ae9548..2ac52d315 100644
--- a/g10/keyedit.c
+++ b/g10/keyedit.c
@@ -2289,7 +2289,7 @@ quick_find_keyblock (ctrl_t ctrl, const char *username,
*r_keyblock = NULL;
/* Search the key; we don't want the whole getkey stuff here. */
- kdbhd = keydb_new ();
+ kdbhd = keydb_new (ctrl);
if (!kdbhd)
{
/* Note that keydb_new has already used log_error. */
@@ -5761,7 +5761,7 @@ menu_revsig (ctrl_t ctrl, kbnode_t keyblock)
}
else if (!skip && node->pkt->pkttype == PKT_SIGNATURE
&& ((sig = node->pkt->pkt.signature),
- have_secret_key_with_kid (sig->keyid)))
+ have_secret_key_with_kid (ctrl, sig->keyid)))
{
if ((sig->sig_class & ~3) == 0x10)
{
@@ -5800,7 +5800,7 @@ menu_revsig (ctrl_t ctrl, kbnode_t keyblock)
}
else if (!skip && node->pkt->pkttype == PKT_SIGNATURE
&& ((sig = node->pkt->pkt.signature),
- have_secret_key_with_kid (sig->keyid)))
+ have_secret_key_with_kid (ctrl, sig->keyid)))
{
if ((sig->sig_class & ~3) == 0x10)
{
diff --git a/g10/keygen.c b/g10/keygen.c
index 1e5722fb2..61682158e 100644
--- a/g10/keygen.c
+++ b/g10/keygen.c
@@ -4502,7 +4502,7 @@ quick_generate_keypair (ctrl_t ctrl, const char *uid, const char *algostr,
desc.mode = KEYDB_SEARCH_MODE_EXACT;
desc.u.name = uid;
- kdbhd = keydb_new ();
+ kdbhd = keydb_new (ctrl);
if (!kdbhd)
goto leave;
@@ -5371,7 +5371,7 @@ do_generate_keypair (ctrl_t ctrl, struct para_data_s *para,
{
KEYDB_HANDLE pub_hd;
- pub_hd = keydb_new ();
+ pub_hd = keydb_new (ctrl);
if (!pub_hd)
err = gpg_error_from_syserror ();
else
diff --git a/g10/keylist.c b/g10/keylist.c
index bbe66831c..dacc13788 100644
--- a/g10/keylist.c
+++ b/g10/keylist.c
@@ -524,7 +524,7 @@ list_all (ctrl_t ctrl, int secret, int mark_secret)
if (opt.check_sigs)
listctx.check_sigs = 1;
- hd = keydb_new ();
+ hd = keydb_new (ctrl);
if (!hd)
rc = gpg_error_from_syserror ();
else
diff --git a/g10/keyserver.c b/g10/keyserver.c
index c63f61c30..c2e304f09 100644
--- a/g10/keyserver.c
+++ b/g10/keyserver.c
@@ -1195,7 +1195,7 @@ keyidlist (ctrl_t ctrl, strlist_t users, KEYDB_SEARCH_DESC **klist,
*klist=xmalloc(sizeof(KEYDB_SEARCH_DESC)*num);
- kdbhd = keydb_new ();
+ kdbhd = keydb_new (ctrl);
if (!kdbhd)
{
rc = gpg_error_from_syserror ();
diff --git a/g10/main.h b/g10/main.h
index f45b03909..981315a4a 100644
--- a/g10/main.h
+++ b/g10/main.h
@@ -157,7 +157,7 @@ struct expando_args
const byte *namehash;
};
-char *pct_expando(const char *string,struct expando_args *args);
+char *pct_expando (ctrl_t ctrl, const char *string,struct expando_args *args);
void deprecated_warning(const char *configname,unsigned int configlineno,
const char *option,const char *repl1,const char *repl2);
void deprecated_command (const char *name);
diff --git a/g10/misc.c b/g10/misc.c
index 0541d2b77..c2d5bbb8e 100644
--- a/g10/misc.c
+++ b/g10/misc.c
@@ -895,7 +895,7 @@ get_signature_count (PKT_public_key *pk)
/* Expand %-strings. Returns a string which must be xfreed. Returns
NULL if the string cannot be expanded (too large). */
char *
-pct_expando(const char *string,struct expando_args *args)
+pct_expando (ctrl_t ctrl, const char *string,struct expando_args *args)
{
const char *ch=string;
int idx=0,maxlen=0,done=0;
@@ -1017,7 +1017,7 @@ pct_expando(const char *string,struct expando_args *args)
PKT_public_key *pk=
xmalloc_clear(sizeof(PKT_public_key));
- if (!get_pubkey_fast (pk,args->pksk->main_keyid))
+ if (!get_pubkey_fast (ctrl, pk,args->pksk->main_keyid))
fingerprint_from_pk (pk, array, &len);
else
memset (array, 0, (len=MAX_FINGERPRINT_LEN));
diff --git a/g10/objcache.c b/g10/objcache.c
index adb0717d7..a90b4d9d8 100644
--- a/g10/objcache.c
+++ b/g10/objcache.c
@@ -30,8 +30,8 @@
#include "options.h"
#include "objcache.h"
-/* Note that max value for uid_items is actually a the threshold when
- * we start to look for ietms which can be removed. */
+/* Note that max value for uid_items is actually the threshold when
+ * we start to look for items which can be removed. */
#define NO_OF_UID_ITEM_BUCKETS 107
#define MAX_UID_ITEMS_PER_BUCKET 20
diff --git a/g10/options.h b/g10/options.h
index 6285542b8..26c8439b6 100644
--- a/g10/options.h
+++ b/g10/options.h
@@ -126,6 +126,7 @@ struct
int completes_needed;
int max_cert_depth;
const char *agent_program;
+ const char *keyboxd_program;
const char *dirmngr_program;
int disable_dirmngr;
@@ -287,6 +288,8 @@ struct
int only_sign_text_ids;
int no_symkey_cache; /* Disable the cache used for --symmetric. */
+
+ int use_keyboxd; /* Use the external keyboxd as storage backend. */
} opt;
/* CTRL is used to keep some global variables we currently can't
diff --git a/g10/photoid.c b/g10/photoid.c
index 477b641c6..f7ada2729 100644
--- a/g10/photoid.c
+++ b/g10/photoid.c
@@ -667,8 +667,8 @@ show_photos (ctrl_t ctrl, const struct user_attribute *attrs, int count,
int offset = attrs[i].len-len;
/* make command grow */
- command = pct_expando (opt.photo_viewer, &args);
- if (!command)
+ command = pct_expando (ctrl, opt.photo_viewer,&args);
+ if(!command)
goto fail;
name = xmalloc (1 + 16 + strlen(EXTSEP_S)
diff --git a/g10/revoke.c b/g10/revoke.c
index 0a93c31f9..0e39eca61 100644
--- a/g10/revoke.c
+++ b/g10/revoke.c
@@ -217,7 +217,7 @@ gen_desig_revoke (ctrl_t ctrl, const char *uname, strlist_t locusr)
afx = new_armor_context ();
- kdbhd = keydb_new ();
+ kdbhd = keydb_new (ctrl);
if (!kdbhd)
{
rc = gpg_error_from_syserror ();
@@ -641,7 +641,7 @@ gen_revoke (ctrl_t ctrl, const char *uname)
}
/* Search the userid; we don't want the whole getkey stuff here. */
- kdbhd = keydb_new ();
+ kdbhd = keydb_new (ctrl);
if (!kdbhd)
{
rc = gpg_error_from_syserror ();
diff --git a/g10/sign.c b/g10/sign.c
index 691c3d6d1..250c0cfeb 100644
--- a/g10/sign.c
+++ b/g10/sign.c
@@ -70,7 +70,7 @@ typedef struct pt_extra_hash_data_s *pt_extra_hash_data_t;
* a valid NAME=VALUE format.
*/
static void
-mk_notation_policy_etc (PKT_signature *sig,
+mk_notation_policy_etc (ctrl_t ctrl, PKT_signature *sig,
PKT_public_key *pk, PKT_public_key *pksk)
{
const char *string;
@@ -99,7 +99,7 @@ mk_notation_policy_etc (PKT_signature *sig,
for (item = nd; item; item = item->next)
{
- item->altvalue = pct_expando (item->value,&args);
+ item->altvalue = pct_expando (ctrl, item->value,&args);
if (!item->altvalue)
log_error (_("WARNING: unable to %%-expand notation "
"(too large). Using unexpanded.\n"));
@@ -126,7 +126,7 @@ mk_notation_policy_etc (PKT_signature *sig,
{
string = pu->d;
- p = pct_expando (string, &args);
+ p = pct_expando (ctrl, string, &args);
if (!p)
{
log_error(_("WARNING: unable to %%-expand policy URL "
@@ -149,7 +149,7 @@ mk_notation_policy_etc (PKT_signature *sig,
{
string = pu->d;
- p = pct_expando (string, &args);
+ p = pct_expando (ctrl, string, &args);
if (!p)
{
log_error (_("WARNING: unable to %%-expand preferred keyserver URL"
@@ -838,7 +838,7 @@ write_signature_packets (ctrl_t ctrl,
BUG ();
build_sig_subpkt_from_sig (sig, pk);
- mk_notation_policy_etc (sig, NULL, pk);
+ mk_notation_policy_etc (ctrl, sig, NULL, pk);
hash_sigversion_to_magic (md, sig, extrahash);
gcry_md_final (md);
@@ -1664,7 +1664,7 @@ make_keysig_packet (ctrl_t ctrl,
sig->sig_class = sigclass;
build_sig_subpkt_from_sig (sig, pksk);
- mk_notation_policy_etc (sig, pk, pksk);
+ mk_notation_policy_etc (ctrl, sig, pk, pksk);
/* Crucial that the call to mksubpkt comes LAST before the calls
* to finalize the sig as that makes it possible for the mksubpkt
diff --git a/g10/t-keydb-get-keyblock.c b/g10/t-keydb-get-keyblock.c
index 167a9bbee..90ce6e9a6 100644
--- a/g10/t-keydb-get-keyblock.c
+++ b/g10/t-keydb-get-keyblock.c
@@ -21,9 +21,11 @@
#include "keydb.h"
+
static void
do_test (int argc, char *argv[])
{
+ ctrl_t ctrl;
char *fname;
int rc;
KEYDB_HANDLE hd1;
@@ -33,6 +35,7 @@ do_test (int argc, char *argv[])
(void) argc;
(void) argv;
+ ctrl = xcalloc (1, sizeof *ctrl);
/* t-keydb-get-keyblock.gpg contains two keys: a modern key followed
by a legacy key. If we get the keyblock for the modern key, we
shouldn't get
@@ -44,7 +47,7 @@ do_test (int argc, char *argv[])
if (rc)
ABORT ("Failed to open keyring.");
- hd1 = keydb_new ();
+ hd1 = keydb_new (ctrl);
if (!hd1)
ABORT ("");
@@ -62,4 +65,5 @@ do_test (int argc, char *argv[])
keydb_release (hd1);
release_kbnode (kb1);
+ xfree (ctrl);
}
diff --git a/g10/t-keydb.c b/g10/t-keydb.c
index 5eb8d0154..4c78dac48 100644
--- a/g10/t-keydb.c
+++ b/g10/t-keydb.c
@@ -25,6 +25,7 @@ static void
do_test (int argc, char *argv[])
{
int rc;
+ ctrl_t ctrl;
KEYDB_HANDLE hd1, hd2;
KEYDB_SEARCH_DESC desc1, desc2;
KBNODE kb1, kb2, p;
@@ -35,16 +36,17 @@ do_test (int argc, char *argv[])
(void) argc;
(void) argv;
+ ctrl = xcalloc (1, sizeof *ctrl);
fname = prepend_srcdir ("t-keydb-keyring.kbx");
rc = keydb_add_resource (fname, 0);
test_free (fname);
if (rc)
ABORT ("Failed to open keyring.");
- hd1 = keydb_new ();
+ hd1 = keydb_new (ctrl);
if (!hd1)
ABORT ("");
- hd2 = keydb_new ();
+ hd2 = keydb_new (ctrl);
if (!hd2)
ABORT ("");
@@ -101,4 +103,5 @@ do_test (int argc, char *argv[])
release_kbnode (kb2);
keydb_release (hd1);
keydb_release (hd2);
+ xfree (ctrl);
}
diff --git a/g10/tofu.c b/g10/tofu.c
index e78da15c1..2eda1ff30 100644
--- a/g10/tofu.c
+++ b/g10/tofu.c
@@ -2131,7 +2131,7 @@ build_conflict_set (ctrl_t ctrl, tofu_dbs_t dbs,
/* If two keys have cross signatures, then they are controlled by
* the same person and thus are not in conflict. */
kb_all = xcalloc (sizeof (kb_all[0]), conflict_set_count);
- hd = keydb_new ();
+ hd = keydb_new (ctrl);
for (i = 0, iter = conflict_set;
i < conflict_set_count;
i ++, iter = iter->next)
diff --git a/g10/trustdb.c b/g10/trustdb.c
index bd1dad8f5..64e6ec349 100644
--- a/g10/trustdb.c
+++ b/g10/trustdb.c
@@ -2017,7 +2017,7 @@ validate_keys (ctrl_t ctrl, int interactive)
trust. */
keydb_rebuild_caches (ctrl, 0);
- kdb = keydb_new ();
+ kdb = keydb_new (ctrl);
if (!kdb)
return gpg_error_from_syserror ();
diff --git a/kbx/Makefile.am b/kbx/Makefile.am
index 8fca24afb..42c3c4be8 100644
--- a/kbx/Makefile.am
+++ b/kbx/Makefile.am
@@ -18,16 +18,20 @@
## Process this file with automake to produce Makefile.in
-EXTRA_DIST = mkerrors
+EXTRA_DIST = mkerrors keyboxd-w32info.rc
AM_CPPFLAGS =
include $(top_srcdir)/am/cmacros.am
+if HAVE_W32_SYSTEM
+resource_objs += keyboxd-w32info.o
+endif
AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS)
noinst_LIBRARIES = libkeybox.a libkeybox509.a
bin_PROGRAMS = kbxutil
+libexec_PROGRAMS = keyboxd
if HAVE_W32CE_SYSTEM
extra_libs = $(LIBASSUAN_LIBS)
@@ -35,6 +39,9 @@ else
extra_libs =
endif
+common_libs = $(libcommon)
+commonpth_libs = $(libcommonpth)
+
common_sources = \
keybox.h keybox-defs.h keybox-search-desc.h \
keybox-util.c \
@@ -59,9 +66,31 @@ libkeybox509_a_CFLAGS = $(AM_CFLAGS) -DKEYBOX_WITH_X509=1
# to do it this way.
kbxutil_SOURCES = kbxutil.c $(common_sources)
kbxutil_CFLAGS = $(AM_CFLAGS) -DKEYBOX_WITH_X509=1
-kbxutil_LDADD = ../common/libcommon.a \
+kbxutil_LDADD = $(common_libs) \
$(KSBA_LIBS) $(LIBGCRYPT_LIBS) $(extra_libs) \
$(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV) $(W32SOCKLIBS) \
$(NETLIBS)
-$(PROGRAMS) : ../common/libcommon.a
+
+keyboxd_SOURCES = \
+ keyboxd.c keyboxd.h \
+ kbxserver.c \
+ frontend.c frontend.h \
+ backend.h backend-support.c \
+ backend-cache.c \
+ backend-kbx.c \
+ $(common_sources)
+
+keyboxd_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) \
+ $(INCICONV)
+keyboxd_LDADD = $(commonpth_libs) \
+ $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \
+ $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV) \
+ $(resource_objs)
+keyboxd_LDFLAGS = $(extra_bin_ldflags)
+keyboxd_DEPENDENCIES = $(resource_objs)
+
+
+# Make sure that all libs are build before we use them. This is
+# important for things like make -j2.
+$(PROGRAMS): $(common_libs) $(commonpth_libs)
diff --git a/kbx/backend-cache.c b/kbx/backend-cache.c
new file mode 100644
index 000000000..10a6f6bd9
--- /dev/null
+++ b/kbx/backend-cache.c
@@ -0,0 +1,1190 @@
+/* backend-cache.c - Cache backend for keyboxd
+ * Copyright (C) 2019 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/*
+ * This cache backend is designed to be queried first and to deliver
+ * cached items (which may also be not-found). A set a maintenance
+ * functions is used used by the frontend to fill the cache.
+ * FIXME: Support x.509
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "keyboxd.h"
+#include "../common/i18n.h"
+#include "../common/host2net.h"
+#include "backend.h"
+#include "keybox-defs.h"
+
+
+/* Standard values for the number of buckets and the threshold we use
+ * to flush items. */
+#define NO_OF_KEY_ITEM_BUCKETS 383
+#define KEY_ITEMS_PER_BUCKET_THRESHOLD 40
+#define NO_OF_BLOB_BUCKETS 383
+#define BLOBS_PER_BUCKET_THRESHOLD 20
+
+
+/* Our definition of the backend handle. */
+struct backend_handle_s
+{
+ enum database_types db_type; /* Always DB_TYPE_CACHE. */
+ unsigned int backend_id; /* Always the id the backend. */
+};
+
+
+/* The object holding a blob. */
+typedef struct blob_s
+{
+ struct blob_s *next;
+ enum pubkey_types pktype;
+ unsigned int refcount;
+ unsigned int usecount;
+ unsigned int datalen;
+ unsigned char *data; /* The actual data of length DATALEN. */
+ unsigned char ubid[20];
+} *blob_t;
+
+
+static blob_t *blob_table; /* Hash table with the blobs. */
+static size_t blob_table_size; /* Number of allocated buckets. */
+static unsigned int blob_table_threshold; /* Max. # of items per bucket. */
+static unsigned int blob_table_added; /* Number of items added. */
+static unsigned int blob_table_dropped; /* Number of items dropped. */
+static blob_t blob_attic; /* List of freed blobs. */
+
+
+/* A list item to blob data. This is so that a next operation on a
+ * cached key item can actually work. Things are complicated because
+ * we do not want to force caching all object before we get a next
+ * request from the client. To accomplish this we keep a flag
+ * indicating that the search needs to continue instead of delivering
+ * the previous item from the cache. */
+typedef struct bloblist_s
+{
+ struct bloblist_s *next;
+ unsigned int final_kid:1; /* The final blob for KID searches. */
+ unsigned int final_fpr:1; /* The final blob for FPR searches. */
+ unsigned int ubid_valid:1; /* The blobid below is valid. */
+ unsigned int subkey:1; /* The entry is for a subkey. */
+ unsigned int fprlen:8; /* The length of the fingerprint or 0. */
+ char fpr[32]; /* The buffer for the fingerprint. */
+ unsigned char ubid[20]; /* The Unique-Blob-ID of the blob. */
+} *bloblist_t;
+
+static bloblist_t bloblist_attic; /* List of freed items. */
+
+/* The cache object. For indexing we could use the fingerprint
+ * directly as a hash value. However, we use the keyid instead
+ * because the keyid is used by OpenPGP in encrypted packets and older
+ * signatures to identify a key. Since v4 OpenPGP keys the keyid is
+ * anyway a part of the fingerprint so it quickly extracted from a
+ * fingerprint. Note that v3 keys are not supported by gpg.
+ * FIXME: Add support for X.509.
+ */
+typedef struct key_item_s
+{
+ struct key_item_s *next;
+ bloblist_t blist; /* List of blobs or NULL for not-found. */
+ unsigned int usecount;
+ unsigned int refcount; /* Reference counter for this item. */
+ u32 kid_h; /* Upper 4 bytes of the keyid. */
+ u32 kid_l; /* Lower 4 bytes of the keyid. */
+} *key_item_t;
+
+static key_item_t *key_table; /* Hash table with the keys. */
+static size_t key_table_size; /* Number of allocated buckets. */
+static unsigned int key_table_threshold; /* Max. # of items per bucket. */
+static unsigned int key_table_added; /* Number of items added. */
+static unsigned int key_table_dropped; /* Number of items dropped. */
+static key_item_t key_item_attic; /* List of freed items. */
+
+
+
+
+/* The hash function we use for the key_table. Must not call a system
+ * function. */
+static inline unsigned int
+blob_table_hasher (const unsigned char *ubid)
+{
+ return (ubid[0] << 16 | ubid[1]) % blob_table_size;
+}
+
+
+/* Runtime allocation of the key table. This allows us to eventually
+ * add an option to control the size. */
+static gpg_error_t
+blob_table_init (void)
+{
+ if (blob_table)
+ return 0;
+ blob_table_size = NO_OF_BLOB_BUCKETS;
+ blob_table_threshold = BLOBS_PER_BUCKET_THRESHOLD;
+ blob_table = xtrycalloc (blob_table_size, sizeof *blob_table);
+ if (!blob_table)
+ return gpg_error_from_syserror ();
+ return 0;
+}
+
+/* Free a blob. This is done by moving it to the attic list. */
+static void
+blob_unref (blob_t blob)
+{
+ void *p;
+
+ if (!blob)
+ return;
+ log_assert (blob->refcount);
+ if (!--blob->refcount)
+ {
+ p = blob->data;
+ blob->data = NULL;
+ blob->next = blob_attic;
+ blob_attic = blob;
+ xfree (p);
+ }
+}
+
+
+/* Given the hash value and the ubid, find the blob in the bucket.
+ * Returns NULL if not found or the blob item if found. Always
+ * returns the the number of items searched, which is in the case of a
+ * not-found the length of the chain. */
+static blob_t
+find_blob (unsigned int hash, const unsigned char *ubid,
+ unsigned int *r_count)
+{
+ blob_t b;
+ unsigned int count = 0;
+
+ for (b = blob_table[hash]; b; b = b->next, count++)
+ if (!memcmp (b->ubid, ubid, 20))
+ break;
+ if (r_count)
+ *r_count = count;
+ return b;
+}
+
+
+/* Helper for the qsort in key_table_put. */
+static int
+compare_blobs (const void *arg_a, const void *arg_b)
+{
+ const blob_t a = *(const blob_t *)arg_a;
+ const blob_t b = *(const blob_t *)arg_b;
+
+ /* Reverse sort on the usecount. */
+ if (a->usecount > b->usecount)
+ return -1;
+ else if (a->usecount == b->usecount)
+ return 0;
+ else
+ return 1;
+}
+
+
+/* Put the blob (BLOBDATA, BLOBDATALEN) into the cache using UBID as
+ * the index. If it is already in the cache nothing happens. */
+static void
+blob_table_put (const unsigned char *ubid, enum pubkey_types pktype,
+ const void *blobdata, unsigned int blobdatalen)
+{
+ unsigned int hash;
+ blob_t b;
+ unsigned int count, n;
+ void *blobdatacopy = NULL;
+
+ hash = blob_table_hasher (ubid);
+ find_again:
+ b = find_blob (hash, ubid, &count);
+ if (b)
+ {
+ xfree (blobdatacopy);
+ return; /* Already got this blob. */
+ }
+
+ /* Create a copy of the blob if not yet done. */
+ if (!blobdatacopy)
+ {
+ blobdatacopy = xtrymalloc (blobdatalen);
+ if (!blobdatacopy)
+ {
+ log_info ("Note: malloc failed while copying blob to the cache: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+ return; /* Out of core - ignore. */
+ }
+ memcpy (blobdatacopy, blobdata, blobdatalen);
+ }
+
+ /* If the bucket is full remove a couple of items. */
+ if (count >= blob_table_threshold)
+ {
+ blob_t list_head, *list_tailp, b_next;
+ blob_t *array;
+ int narray, idx;
+
+ /* Unlink from the global list so that other threads don't
+ * disturb us. If another thread adds or removes something only
+ * one will be the winner. Bad luck for the dropped cache items
+ * but after all it is just a cache. */
+ list_head = blob_table[hash];
+ blob_table[hash] = NULL;
+
+ /* Put all items into an array for sorting. */
+ array = xtrycalloc (count, sizeof *array);
+ if (!array)
+ {
+ /* That's bad; give up all items of the bucket. */
+ log_info ("Note: malloc failed while purging blobs from the "
+ "cache: %s\n", gpg_strerror (gpg_error_from_syserror ()));
+ goto leave_drop;
+ }
+ narray = 0;
+ for (b = list_head; b; b = b_next)
+ {
+ b_next = b->next;
+ array[narray++] = b;
+ b->next = NULL;
+ }
+ log_assert (narray == count);
+
+ /* Sort the array and put half of it onto a new list. */
+ qsort (array, narray, sizeof *array, compare_blobs);
+ list_head = NULL;
+ list_tailp = &list_head;
+ for (idx=0; idx < narray/2; idx++)
+ {
+ *list_tailp = array[idx];
+ list_tailp = &array[idx]->next;
+ }
+
+ /* Put the new list into the bucket. */
+ b = blob_table[hash];
+ blob_table[hash] = list_head;
+ list_head = b;
+
+ /* Free the remaining items and the array. */
+ for (; idx < narray; idx++)
+ {
+ blob_unref (array[idx]);
+ blob_table_dropped++;
+ }
+ xfree (array);
+
+ leave_drop:
+ /* Free any items added in the meantime by other threads. This
+ * is also used in case of a malloc problem (which won't update
+ * the counters, though). */
+ for ( ; list_head; list_head = b_next)
+ {
+ b_next = list_head->next;
+ blob_unref (list_head);
+ }
+ }
+
+ /* Add an item to the bucket. We allocate a whole block of items
+ * for cache performance reasons. */
+ if (!blob_attic)
+ {
+ blob_t b_block;
+ int b_blocksize = 256;
+
+ b_block = xtrymalloc (b_blocksize * sizeof *b_block);
+ if (!b_block)
+ {
+ log_info ("Note: malloc failed while adding blob to the cache: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+ xfree (blobdatacopy);
+ return; /* Out of core - ignore. */
+ }
+ for (n = 0; n < b_blocksize; n++)
+ {
+ b = b_block + n;
+ b->next = blob_attic;
+ blob_attic = b;
+ }
+
+ /* During the malloc another thread might have changed the
+ * bucket. Thus we need to start over. */
+ goto find_again;
+ }
+
+ /* We now know that there is an item in the attic. Put it into the
+ * chain. Note that we may not use any system call here. */
+ b = blob_attic;
+ blob_attic = b->next;
+ b->next = NULL;
+ b->pktype = pktype;
+ b->data = blobdatacopy;
+ b->datalen = blobdatalen;
+ memcpy (b->ubid, ubid, 20);
+ b->usecount = 1;
+ b->refcount = 1;
+ b->next = blob_table[hash];
+ blob_table[hash] = b;
+ blob_table_added++;
+}
+
+
+/* Given the UBID return a cached blob item. The caller must
+ * release that item using blob_unref. */
+static blob_t
+blob_table_get (const unsigned char *ubid)
+{
+ unsigned int hash;
+ blob_t b;
+
+ hash = blob_table_hasher (ubid);
+ b = find_blob (hash, ubid, NULL);
+ if (b)
+ {
+ b->usecount++;
+ b->refcount++;
+ return b; /* Found */
+ }
+
+ return NULL;
+}
+
+
+
+/* The hash function we use for the key_table. Must not call a system
+ * function. */
+static inline unsigned int
+key_table_hasher (u32 kid_l)
+{
+ return kid_l % key_table_size;
+}
+
+
+/* Runtime allocation of the key table. This allows us to eventually
+ * add an option to control the size. */
+static gpg_error_t
+key_table_init (void)
+{
+ if (key_table)
+ return 0;
+ key_table_size = NO_OF_KEY_ITEM_BUCKETS;
+ key_table_threshold = KEY_ITEMS_PER_BUCKET_THRESHOLD;
+ key_table = xtrycalloc (key_table_size, sizeof *key_table);
+ if (!key_table)
+ return gpg_error_from_syserror ();
+ return 0;
+}
+
+/* Free a key_item. This is done by moving it to the attic list. */
+static void
+key_item_unref (key_item_t ki)
+{
+ bloblist_t bl, bl2;
+
+ if (!ki)
+ return;
+ log_assert (ki->refcount);
+ if (!--ki->refcount)
+ {
+ bl = ki->blist;
+ ki->blist = NULL;
+ ki->next = key_item_attic;
+ key_item_attic = ki;
+
+ if (bl)
+ {
+ for (bl2 = bl; bl2->next; bl2 = bl2->next)
+ ;
+ bl2->next = bloblist_attic;
+ bloblist_attic = bl;
+ }
+ }
+}
+
+
+/* Given the hash value and the search info, find the key item in the
+ * bucket. Return NULL if not found or the key item if fount. Always
+ * returns the the number of items searched, which is in the case of a
+ * not-found the length of the chain. Note that FPR may only be NULL
+ * if FPRLEN is 0. */
+static key_item_t
+find_in_chain (unsigned int hash, u32 kid_h, u32 kid_l,
+ unsigned int *r_count)
+{
+ key_item_t ki = key_table[hash];
+ unsigned int count = 0;
+
+ for (; ki; ki = ki->next, count++)
+ if (ki->kid_h == kid_h && ki->kid_l == kid_l)
+ break;
+ if (r_count)
+ *r_count = count;
+ return ki;
+}
+
+
+/* Helper for the qsort in key_table_put. */
+static int
+compare_key_items (const void *arg_a, const void *arg_b)
+{
+ const key_item_t a = *(const key_item_t *)arg_a;
+ const key_item_t b = *(const key_item_t *)arg_b;
+
+ /* Reverse sort on the usecount. */
+ if (a->usecount > b->usecount)
+ return -1;
+ else if (a->usecount == b->usecount)
+ return 0;
+ else
+ return 1;
+}
+
+
+/* Allocate new key items. They are put to the attic so that the
+ * caller can take them from there. On allocation failure a note
+ * is printed and an error returned. */
+static gpg_error_t
+alloc_more_key_items (void)
+{
+ gpg_error_t err;
+ key_item_t kiblock, ki;
+ int kiblocksize = 256;
+ unsigned int n;
+
+ kiblock = xtrymalloc (kiblocksize * sizeof *kiblock);
+ if (!kiblock)
+ {
+ err = gpg_error_from_syserror ();
+ log_info ("Note: malloc failed while adding to the cache: %s\n",
+ gpg_strerror (err));
+ return err;
+ }
+ for (n = 0; n < kiblocksize; n++)
+ {
+ ki = kiblock + n;
+ ki->next = key_item_attic;
+ key_item_attic = ki;
+ }
+ return 0;
+}
+
+
+/* Allocate new bloblist items. They are put to the attic so that the
+ * caller can take them from there. On allocation failure a note is
+ * printed and an error returned. */
+static gpg_error_t
+alloc_more_bloblist_items (void)
+{
+ gpg_error_t err;
+ bloblist_t bl;
+ bloblist_t blistblock;
+ int blistblocksize = 256;
+ unsigned int n;
+
+ blistblock = xtrymalloc (blistblocksize * sizeof *blistblock);
+ if (!blistblock)
+ {
+ err = gpg_error_from_syserror ();
+ log_info ("Note: malloc failed while adding to the cache: %s\n",
+ gpg_strerror (err));
+ return err;
+ }
+ for (n = 0; n < blistblocksize; n++)
+ {
+ bl = blistblock + n;
+ bl->next = bloblist_attic;
+ bloblist_attic = bl;
+ }
+ return 0;
+}
+
+
+/* Helper for key_table_put. This function assumes that
+ * bloblist_attaci is not NULL. Returns a new bloblist item. Be
+ * aware that no system calls may be done - even not log
+ * functions! */
+static bloblist_t
+new_bloblist_item (const unsigned char *fpr, unsigned int fprlen,
+ const unsigned char *ubid, int subkey)
+{
+ bloblist_t bl;
+
+ bl = bloblist_attic;
+ bloblist_attic = bl->next;
+ bl->next = NULL;
+
+ if (ubid)
+ memcpy (bl->ubid, ubid, 20);
+ else
+ memset (bl->ubid, 0, 20);
+ bl->ubid_valid = 1;
+ bl->final_kid = 0;
+ bl->final_fpr = 0;
+ bl->subkey = !!subkey;
+ bl->fprlen = fprlen;
+ memcpy (bl->fpr, fpr, fprlen);
+ return bl;
+}
+
+
+/* If the list of key item in the bucken HASH is full remove a couple
+ * of them. On error a diagnostic is printed and an error code
+ * return. Note that the error code GPG_ERR_TRUE is returned if any
+ * flush and thus system calls were done.
+ */
+static gpg_error_t
+maybe_flush_some_key_buckets (unsigned int hash, unsigned int count)
+{
+ gpg_error_t err;
+ key_item_t ki, list_head, *list_tailp, ki_next;
+ key_item_t *array;
+ int narray, idx;
+
+ if (count < key_table_threshold)
+ return 0; /* Nothing to do. */
+
+ /* Unlink from the global list so that other threads don't disturb
+ * us. If another thread adds or removes something only one will be
+ * the winner. Bad luck for the dropped cache items but after all
+ * it is just a cache. */
+ list_head = key_table[hash];
+ key_table[hash] = NULL;
+
+ /* Put all items into an array for sorting. */
+ array = xtrycalloc (count, sizeof *array);
+ if (!array)
+ {
+ /* That's bad; give up all items of the bucket. */
+ err = gpg_error_from_syserror ();
+ log_info ("Note: malloc failed while purging from the cache: %s\n",
+ gpg_strerror (err));
+ goto leave;
+ }
+ narray = 0;
+ for (ki = list_head; ki; ki = ki_next)
+ {
+ ki_next = ki->next;
+ array[narray++] = ki;
+ ki->next = NULL;
+ }
+ log_assert (narray == count);
+
+ /* Sort the array and put half of it onto a new list. */
+ qsort (array, narray, sizeof *array, compare_key_items);
+ list_head = NULL;
+ list_tailp = &list_head;
+ for (idx=0; idx < narray/2; idx++)
+ {
+ *list_tailp = array[idx];
+ list_tailp = &array[idx]->next;
+ }
+
+ /* Put the new list into the bucket. */
+ ki = key_table[hash];
+ key_table[hash] = list_head;
+ list_head = ki;
+
+ /* Free the remaining items and the array. */
+ for (; idx < narray; idx++)
+ {
+ key_item_unref (array[idx]);
+ key_table_dropped++;
+ }
+ xfree (array);
+ err = gpg_error (GPG_ERR_TRUE);
+
+ leave:
+ /* Free any items added in the meantime by other threads. This is
+ * also used in case of a malloc problem (which won't update the
+ * counters, though). */
+ for ( ; list_head; list_head = ki_next)
+ {
+ ki_next = list_head->next;
+ key_item_unref (list_head);
+ }
+ return err;
+}
+
+
+/* Thsi is the core of
+ * key_table_put,
+ * key_table_put_no_fpr,
+ * key_table_put_no_kid.
+ */
+static void
+do_key_table_put (u32 kid_h, u32 kid_l,
+ const unsigned char *fpr, unsigned int fprlen,
+ const unsigned char *ubid, int subkey)
+{
+ unsigned int hash;
+ key_item_t ki;
+ bloblist_t bl, bl_tail;
+ unsigned int count;
+ int do_find_again;
+ int mark_not_found = !fpr;
+
+ hash = key_table_hasher (kid_l);
+ find_again:
+ do_find_again = 0;
+ ki = find_in_chain (hash, kid_h, kid_l, &count);
+ if (ki)
+ {
+ if (mark_not_found)
+ return; /* Can't put the mark because meanwhile a entry was
+ * added. */
+
+ for (bl = ki->blist; bl; bl = bl->next)
+ if (bl->fprlen
+ && bl->fprlen == fprlen
+ && !memcmp (bl->fpr, fpr, fprlen))
+ break;
+ if (bl)
+ return; /* Already in the bloblist for the keyid */
+
+ /* Append to the list. */
+ if (!bloblist_attic)
+ {
+ if (alloc_more_bloblist_items ())
+ return; /* Out of core - ignore. */
+ goto find_again; /* Need to start over due to the malloc. */
+ }
+ for (bl_tail = NULL, bl = ki->blist; bl; bl_tail = bl, bl = bl->next)
+ ;
+ bl = new_bloblist_item (fpr, fprlen, ubid, subkey);
+ if (bl_tail)
+ bl_tail->next = bl;
+ else
+ ki->blist = bl;
+
+ return;
+ }
+
+ /* If the bucket is full remove a couple of items. */
+ if (maybe_flush_some_key_buckets (hash, count))
+ {
+ /* During the fucntion call another thread might have changed
+ * the bucket. Thus we need to start over. */
+ do_find_again = 1;
+ }
+
+ if (!key_item_attic)
+ {
+ if (alloc_more_key_items ())
+ return; /* Out of core - ignore. */
+ do_find_again = 1;
+ }
+
+ if (!bloblist_attic)
+ {
+ if (alloc_more_bloblist_items ())
+ return; /* Out of core - ignore. */
+ do_find_again = 1;
+ }
+
+ if (do_find_again)
+ goto find_again;
+
+ /* We now know that there are items in the attics. Put them into
+ * the chain. Note that we may not use any system call here. */
+ ki = key_item_attic;
+ key_item_attic = ki->next;
+ ki->next = NULL;
+
+ if (mark_not_found)
+ ki->blist = NULL;
+ else
+ ki->blist = new_bloblist_item (fpr, fprlen, ubid, subkey);
+
+ ki->kid_h = kid_h;
+ ki->kid_l = kid_l;
+ ki->usecount = 1;
+ ki->refcount = 1;
+
+ ki->next = key_table[hash];
+ key_table[hash] = ki;
+ key_table_added++;
+}
+
+
+/* Given the fingerprint (FPR,FPRLEN) put the UBID into the cache.
+ * SUBKEY indicates that the fingerprint is from a subkey. */
+static void
+key_table_put (const unsigned char *fpr, unsigned int fprlen,
+ const unsigned char *ubid, int subkey)
+{
+ u32 kid_h, kid_l;
+
+ if (fprlen < 20 || fprlen > 32)
+ return; /* No support for v3 keys or unknown key versions. */
+
+ if (fprlen == 20) /* v4 key */
+ {
+ kid_h = buf32_to_u32 (fpr+12);
+ kid_l = buf32_to_u32 (fpr+16);
+ }
+ else /* v5 or later key */
+ {
+ kid_h = buf32_to_u32 (fpr);
+ kid_l = buf32_to_u32 (fpr+4);
+ }
+ do_key_table_put (kid_h, kid_l, fpr, fprlen, ubid, subkey);
+}
+
+
+/* Given the fingerprint (FPR,FPRLEN) put a flag into the cache that
+ * this fingerprint was not found. */
+static void
+key_table_put_no_fpr (const unsigned char *fpr, unsigned int fprlen)
+{
+ u32 kid_h, kid_l;
+
+ if (fprlen < 20 || fprlen > 32)
+ return; /* No support for v3 keys or unknown key versions. */
+
+ if (fprlen == 20) /* v4 key */
+ {
+ kid_h = buf32_to_u32 (fpr+12);
+ kid_l = buf32_to_u32 (fpr+16);
+ }
+ else /* v5 or later key */
+ {
+ kid_h = buf32_to_u32 (fpr);
+ kid_l = buf32_to_u32 (fpr+4);
+ }
+ /* Note that our not-found chaching is only based on the keyid. */
+ do_key_table_put (kid_h, kid_l, NULL, 0, NULL, 0);
+}
+
+
+/* Given the keyid (KID_H, KID_L) put a flag into the cache that this
+ * keyid was not found. */
+static void
+key_table_put_no_kid (u32 kid_h, u32 kid_l)
+{
+ do_key_table_put (kid_h, kid_l, NULL, 0, NULL, 0);
+}
+
+
+/* Given the keyid or the fingerprint return the key item from the
+ * cache. The caller must release the result using key_item_unref.
+ * NULL is returned if not found. */
+static key_item_t
+key_table_get (u32 kid_h, u32 kid_l)
+{
+ unsigned int hash;
+ key_item_t ki;
+
+ hash = key_table_hasher (kid_l);
+ ki = find_in_chain (hash, kid_h, kid_l, NULL);
+ if (ki)
+ {
+ ki->usecount++;
+ ki->refcount++;
+ return ki; /* Found */
+ }
+
+ return NULL;
+}
+
+
+/* Return a key item by searching for the keyid. The caller must use
+ * key_item_unref on it. */
+static key_item_t
+query_by_kid (u32 kid_h, u32 kid_l)
+{
+ return key_table_get (kid_h, kid_l);
+}
+
+
+/* Return a key item by searching for the fingerprint. The caller
+ * must use key_item_unref on it. Note that the returned key item may
+ * not actually carry the fingerprint; the caller needs to scan the
+ * bloblist of the keyitem. We can't do that here because the
+ * reference counting is done on the keyitem s and thus this needs to
+ * be returned. */
+static key_item_t
+query_by_fpr (const unsigned char *fpr, unsigned int fprlen)
+{
+ u32 kid_h, kid_l;
+
+ if (fprlen < 20 || fprlen > 32 )
+ return NULL; /* No support for v3 keys or unknown key versions. */
+
+ if (fprlen == 20) /* v4 key */
+ {
+ kid_h = buf32_to_u32 (fpr+12);
+ kid_l = buf32_to_u32 (fpr+16);
+ }
+ else /* v5 or later key */
+ {
+ kid_h = buf32_to_u32 (fpr);
+ kid_l = buf32_to_u32 (fpr+4);
+ }
+
+ return key_table_get (kid_h, kid_l);
+}
+
+
+
+
+
+/* Install a new resource and return a handle for that backend. */
+gpg_error_t
+be_cache_add_resource (ctrl_t ctrl, backend_handle_t *r_hd)
+{
+ gpg_error_t err;
+ backend_handle_t hd;
+
+ (void)ctrl;
+
+ *r_hd = NULL;
+ hd = xtrycalloc (1, sizeof *hd);
+ if (!hd)
+ return gpg_error_from_syserror ();
+ hd->db_type = DB_TYPE_CACHE;
+
+ hd->backend_id = be_new_backend_id ();
+
+ err = blob_table_init ();
+ if (err)
+ goto leave;
+
+ err = key_table_init ();
+ if (err)
+ goto leave;
+
+ *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_cache_release_resource (ctrl_t ctrl, backend_handle_t hd)
+{
+ (void)ctrl;
+
+ if (!hd)
+ return;
+ hd->db_type = DB_TYPE_NONE;
+
+ /* Fixme: Free the key_table. */
+
+ xfree (hd);
+}
+
+
+/* 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. On a cache hit either 0 or
+ * GPG_ERR_NOT_FOUND is returned. The former returns the item; the
+ * latter indicates that the cache has known that the item won't be
+ * found in any databases. On a cache miss GPG_ERR_EOF is
+ * returned. */
+gpg_error_t
+be_cache_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 reqpart;
+ unsigned int n;
+ blob_t b;
+ key_item_t ki;
+ bloblist_t bl;
+ int not_found = 0;
+ int descidx = 0;
+ int found_bykid = 0;
+
+ log_assert (backend_hd && backend_hd->db_type == DB_TYPE_CACHE);
+ log_assert (request);
+
+ err = be_find_request_part (backend_hd, request, &reqpart);
+ if (err)
+ goto leave;
+
+ if (!desc)
+ {
+ /* Reset operation. */
+ request->last_cached_valid = 0;
+ request->last_cached_final = 0;
+ reqpart->cache_seqno.fpr = 0;
+ reqpart->cache_seqno.kid = 0;
+ reqpart->cache_seqno.grip = 0;
+ reqpart->cache_seqno.ubid = 0;
+ err = 0;
+ goto leave;
+ }
+
+ for (ki = NULL, n=0; n < ndesc && !ki; n++)
+ {
+ descidx = n;
+ switch (desc[n].mode)
+ {
+ case KEYDB_SEARCH_MODE_LONG_KID:
+ ki = query_by_kid (desc[n].u.kid[0], desc[n].u.kid[1]);
+ if (ki && ki->blist)
+ {
+ not_found = 0;
+ /* Note that in a bloblist all keyids are the same. */
+ for (n=0, bl = ki->blist; bl; bl = bl->next)
+ if (n++ == reqpart->cache_seqno.kid)
+ break;
+ if (!bl)
+ {
+ key_item_unref (ki);
+ ki = NULL;
+ }
+ else
+ {
+ found_bykid = 1;
+ reqpart->cache_seqno.kid++;
+ }
+ }
+ else if (ki)
+ not_found = 1;
+ break;
+
+ case KEYDB_SEARCH_MODE_FPR:
+ ki = query_by_fpr (desc[n].u.fpr, desc[n].fprlen);
+ if (ki && ki->blist)
+ {
+ not_found = 0;
+ for (n=0, bl = ki->blist; bl; bl = bl->next)
+ if (bl->fprlen
+ && bl->fprlen == desc[n].fprlen
+ && !memcmp (bl->fpr, desc[n].u.fpr, desc[n].fprlen)
+ && n++ == reqpart->cache_seqno.fpr)
+ break;
+ if (!bl)
+ {
+ key_item_unref (ki);
+ ki = NULL;
+ }
+ else
+ reqpart->cache_seqno.fpr++;
+ }
+ else if (ki)
+ not_found = 1;
+ break;
+
+ /* case KEYDB_SEARCH_MODE_KEYGRIP: */
+ /* ki = query_by_grip (desc[n].u.fpr, desc[n].fprlen); */
+ /* break; */
+
+ case KEYDB_SEARCH_MODE_UBID:
+ /* This is the quite special UBID mode: If this is
+ * encountered in the search list we will return just this
+ * one and obviously look only into the blob cache. */
+ if (reqpart->cache_seqno.ubid)
+ err = gpg_error (GPG_ERR_NOT_FOUND);
+ else
+ {
+ b = blob_table_get (desc[n].u.ubid);
+ if (b)
+ {
+ err = be_return_pubkey (ctrl, b->data, b->datalen,
+ b->pktype, desc[n].u.ubid);
+ blob_unref (b);
+ reqpart->cache_seqno.ubid++;
+ }
+ else
+ err = gpg_error (GPG_ERR_EOF);
+ }
+ goto leave;
+
+ default:
+ ki = NULL;
+ break;
+ }
+ }
+
+ if (not_found)
+ {
+ err = gpg_error (GPG_ERR_NOT_FOUND);
+ key_item_unref (ki);
+ }
+ else if (ki)
+ {
+ if (bl && bl->ubid_valid)
+ {
+ memcpy (request->last_cached_ubid, bl->ubid, 20);
+ request->last_cached_valid = 1;
+ request->last_cached_fprlen = desc[descidx].fprlen;
+ memcpy (request->last_cached_fpr,
+ desc[descidx].u.fpr, desc[descidx].fprlen);
+ request->last_cached_kid_h = ki->kid_h;
+ request->last_cached_kid_l = ki->kid_l;
+ request->last_cached_valid = 1;
+ if ((bl->final_kid && found_bykid)
+ || (bl->final_fpr && !found_bykid))
+ request->last_cached_final = 1;
+ else
+ request->last_cached_final = 0;
+
+ b = blob_table_get (bl->ubid);
+ if (b)
+ {
+ err = be_return_pubkey (ctrl, b->data, b->datalen,
+ PUBKEY_TYPE_OPGP, bl->ubid);
+ blob_unref (b);
+ }
+ else
+ {
+ /* FIXME - return a different code so that the caller
+ * can lookup using the UBID. */
+ err = gpg_error (GPG_ERR_MISSING_VALUE);
+ }
+ }
+ else if (bl)
+ err = gpg_error (GPG_ERR_MISSING_VALUE);
+ else
+ err = gpg_error (GPG_ERR_NOT_FOUND);
+ key_item_unref (ki);
+ }
+ else
+ err = gpg_error (GPG_ERR_EOF);
+
+ leave:
+ return err;
+}
+
+
+/* Mark the last cached item as the final item. This is called when
+ * the actual database returned EOF in respond to a restart from the
+ * last cached UBID. */
+void
+be_cache_mark_final (ctrl_t ctrl, db_request_t request)
+{
+ key_item_t ki;
+ bloblist_t bl, blfound;
+
+ (void)ctrl;
+
+ log_assert (request);
+
+ if (!request->last_cached_valid)
+ return;
+
+ if (!request->last_cached_fprlen) /* Was cached via keyid. */
+ {
+ ki = query_by_kid (request->last_cached_kid_h,
+ request->last_cached_kid_l);
+ if (ki && (bl = ki->blist))
+ {
+ for (blfound=NULL; bl; bl = bl->next)
+ blfound = bl;
+ if (blfound)
+ blfound->final_kid = 1;
+ }
+ key_item_unref (ki);
+ }
+ else /* Was cached via fingerprint. */
+ {
+ ki = query_by_fpr (request->last_cached_fpr,
+ request->last_cached_fprlen);
+ if (ki && (bl = ki->blist))
+ {
+ for (blfound=NULL; bl; bl = bl->next)
+ if (bl->fprlen
+ && bl->fprlen == request->last_cached_fprlen
+ && !memcmp (bl->fpr, request->last_cached_fpr,
+ request->last_cached_fprlen))
+ blfound = bl;
+ if (blfound)
+ blfound->final_fpr = 1;
+ }
+ key_item_unref (ki);
+ }
+
+ request->last_cached_valid = 0;
+}
+
+
+/* Put the key (BLOB,BLOBLEN) of PUBKEY_TYPE into the cache. */
+void
+be_cache_pubkey (ctrl_t ctrl, const unsigned char *ubid,
+ const void *blob, unsigned int bloblen,
+ enum pubkey_types pubkey_type)
+{
+ gpg_error_t err;
+
+ (void)ctrl;
+
+ if (pubkey_type == PUBKEY_TYPE_OPGP)
+ {
+ struct _keybox_openpgp_info info;
+ struct _keybox_openpgp_key_info *kinfo;
+
+ err = _keybox_parse_openpgp (blob, bloblen, NULL, &info);
+ if (err)
+ {
+ log_info ("cache: error parsing OpenPGP blob: %s\n",
+ gpg_strerror (err));
+ return;
+ }
+
+ blob_table_put (ubid, pubkey_type, blob, bloblen);
+
+ kinfo = &info.primary;
+ key_table_put (kinfo->fpr, kinfo->fprlen, ubid, 0);
+ if (info.nsubkeys)
+ for (kinfo = &info.subkeys; kinfo; kinfo = kinfo->next)
+ key_table_put (kinfo->fpr, kinfo->fprlen, ubid, 1);
+
+ _keybox_destroy_openpgp_info (&info);
+ }
+
+}
+
+
+/* Put the a non-found mark for PUBKEY_TYPE into the cache. The
+ * indices are taken from the search descriptors (DESC,NDESC). */
+void
+be_cache_not_found (ctrl_t ctrl, enum pubkey_types pubkey_type,
+ KEYDB_SEARCH_DESC *desc, unsigned int ndesc)
+{
+ unsigned int n;
+
+ (void)ctrl;
+ (void)pubkey_type;
+
+ for (n=0; n < ndesc; n++)
+ {
+ switch (desc->mode)
+ {
+ case KEYDB_SEARCH_MODE_LONG_KID:
+ key_table_put_no_kid (desc[n].u.kid[0], desc[n].u.kid[1]);
+ break;
+
+ case KEYDB_SEARCH_MODE_FPR:
+ key_table_put_no_fpr (desc[n].u.fpr, desc[n].fprlen);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
diff --git a/kbx/backend-kbx.c b/kbx/backend-kbx.c
new file mode 100644
index 000000000..438d300b0
--- /dev/null
+++ b/kbx/backend-kbx.c
@@ -0,0 +1,328 @@
+/* backend-kbx.c - Keybox format backend for keyboxd
+ * Copyright (C) 2019 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <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 "keyboxd.h"
+#include "../common/i18n.h"
+#include "backend.h"
+#include "keybox.h"
+
+
+/* Our definition of the backend handle. */
+struct backend_handle_s
+{
+ enum database_types db_type; /* Always DB_TYPE_KBX. */
+ unsigned int backend_id; /* Always the id of the backend. */
+
+ void *token; /* Used to create a new KEYBOX_HANDLE. */
+ char filename[1];
+};
+
+
+/* Check that the file FILENAME is a valid keybox file which can be
+ * used here. Common return codes:
+ *
+ * 0 := Valid keybox file
+ * GPG_ERR_ENOENT := No such file
+ * GPG_ERR_NO_OBJ := File exists with size zero.
+ * GPG_ERR_INV_OBJ:= File exists but is not a keybox file.
+ */
+static gpg_error_t
+check_kbx_file_magic (const char *filename)
+{
+ gpg_error_t err;
+ u32 magic;
+ unsigned char verbuf[4];
+ estream_t fp;
+
+ fp = es_fopen (filename, "rb");
+ if (!fp)
+ return gpg_error_from_syserror ();
+
+ err = gpg_error (GPG_ERR_INV_OBJ);
+ if (es_fread (&magic, 4, 1, fp) == 1 )
+ {
+ if (es_fread (&verbuf, 4, 1, fp) == 1
+ && verbuf[0] == 1
+ && es_fread (&magic, 4, 1, fp) == 1
+ && !memcmp (&magic, "KBXf", 4))
+ {
+ err = 0;
+ }
+ }
+ else /* Maybe empty: Let's create it. */
+ err = gpg_error (GPG_ERR_NO_OBJ);
+
+ es_fclose (fp);
+ return err;
+}
+
+
+/* Create new keybox file. This can also be used if the keybox
+ * already exists but has a length of zero. Do not use it in any
+ * other cases. */
+static gpg_error_t
+create_keybox (const char *filename)
+{
+ gpg_error_t err;
+ dotlock_t lockhd = NULL;
+ estream_t fp;
+
+ /* To avoid races with other temporary instances of keyboxd trying
+ * to create or update the keybox, we do the next stuff in a locked
+ * state. */
+ lockhd = dotlock_create (filename, 0);
+ if (!lockhd)
+ {
+ 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 keybox is not really useful at all. */
+ if (opt.verbose)
+ log_info ("can't allocate lock for '%s': %s\n",
+ filename, gpg_strerror (err));
+ return err;
+ }
+
+ if ( dotlock_take (lockhd, -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;
+ }
+
+ /* Make sure that at least one record is in a new keybox file, so
+ * that the detection magic will work the next time it is used.
+ * We always set the OpenPGP blobs maybe availabale flag. */
+ fp = es_fopen (filename, "w+b,mode=-rw-------");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("error creating keybox '%s': %s\n"),
+ filename, gpg_strerror (err));
+ goto leave;
+ }
+ err = _keybox_write_header_blob (NULL, fp, 1);
+ es_fclose (fp);
+ if (err)
+ {
+ log_error (_("error creating keybox '%s': %s\n"),
+ filename, gpg_strerror (err));
+ goto leave;
+ }
+
+ if (!opt.quiet)
+ log_info (_("keybox '%s' created\n"), filename);
+ err = 0;
+
+ leave:
+ if (lockhd)
+ {
+ dotlock_release (lockhd);
+ dotlock_destroy (lockhd);
+ }
+ return err;
+}
+
+
+
+/* Install a new resource and return a handle for that backend. */
+gpg_error_t
+be_kbx_add_resource (ctrl_t ctrl, backend_handle_t *r_hd,
+ const char *filename, int readonly)
+{
+ gpg_error_t err;
+ backend_handle_t hd;
+ void *token;
+
+ (void)ctrl;
+
+ *r_hd = NULL;
+ hd = xtrycalloc (1, sizeof *hd + strlen (filename));
+ if (!hd)
+ return gpg_error_from_syserror ();
+ hd->db_type = DB_TYPE_KBX;
+ strcpy (hd->filename, filename);
+
+ err = check_kbx_file_magic (filename);
+ switch (gpg_err_code (err))
+ {
+ case 0:
+ break;
+ case GPG_ERR_ENOENT:
+ case GPG_ERR_NO_OBJ:
+ if (readonly)
+ {
+ err = gpg_error (GPG_ERR_ENOENT);
+ goto leave;
+ }
+ err = create_keybox (filename);
+ if (err)
+ goto leave;
+ break;
+ default:
+ goto leave;
+ }
+
+ err = keybox_register_file (filename, 0, &token);
+ if (err)
+ goto leave;
+
+ hd->backend_id = be_new_backend_id ();
+ hd->token = token;
+
+ *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_kbx_release_resource (ctrl_t ctrl, backend_handle_t hd)
+{
+ (void)ctrl;
+
+ if (!hd)
+ return;
+ hd->db_type = DB_TYPE_NONE;
+
+ xfree (hd);
+}
+
+
+void
+be_kbx_release_kbx_hd (KEYBOX_HANDLE kbx_hd)
+{
+ keybox_release (kbx_hd);
+}
+
+
+/* Helper for be_find_request_part to initialize a kbx request part. */
+gpg_error_t
+be_kbx_init_request_part (backend_handle_t backend_hd, db_request_part_t part)
+{
+ part->kbx_hd = keybox_new_openpgp (backend_hd->token, 0);
+ if (!part->kbx_hd)
+ return gpg_error_from_syserror ();
+ return 0;
+}
+
+
+/* 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_kbx_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;
+ size_t descindex;
+ unsigned long skipped_long_blobs;
+
+ log_assert (backend_hd && backend_hd->db_type == DB_TYPE_KBX);
+ log_assert (request);
+
+ /* Find the specific request part or allocate it. */
+ err = be_find_request_part (backend_hd, request, &part);
+ if (err)
+ goto leave;
+
+ if (!desc)
+ err = keybox_search_reset (part->kbx_hd);
+ else
+ err = keybox_search (part->kbx_hd, desc, ndesc, KEYBOX_BLOBTYPE_PGP,
+ &descindex, &skipped_long_blobs);
+ if (err == -1)
+ err = gpg_error (GPG_ERR_EOF);
+
+ if (desc && !err)
+ {
+ /* Successful search operation. */
+ void *buffer;
+ size_t buflen;
+ enum pubkey_types pubkey_type;
+ unsigned char blobid[20];
+
+ err = keybox_get_data (part->kbx_hd, &buffer, &buflen, &pubkey_type);
+ if (err)
+ goto leave;
+ gcry_md_hash_buffer (GCRY_MD_SHA1, blobid, buffer, buflen);
+ err = be_return_pubkey (ctrl, buffer, buflen, pubkey_type, blobid);
+ if (!err)
+ be_cache_pubkey (ctrl, blobid, buffer, buflen, pubkey_type);
+ xfree (buffer);
+ }
+
+ leave:
+ return err;
+}
+
+
+/* Seek in the keybox to the given UBID. BACKEND_HD is the handle for
+ * this backend and REQUEST is the current database request object.
+ * This does a dummy read so that the next search operation starts
+ * right after that UBID. */
+gpg_error_t
+be_kbx_seek (ctrl_t ctrl, backend_handle_t backend_hd,
+ db_request_t request, unsigned char *ubid)
+{
+ gpg_error_t err;
+ db_request_part_t part;
+ size_t descindex;
+ unsigned long skipped_long_blobs;
+ KEYDB_SEARCH_DESC desc;
+
+ (void)ctrl;
+
+ log_assert (backend_hd && backend_hd->db_type == DB_TYPE_KBX);
+ log_assert (request);
+
+ memset (&desc, 0, sizeof desc);
+ desc.mode = KEYDB_SEARCH_MODE_UBID;
+ memcpy (desc.u.ubid, ubid, 20);
+
+ /* Find the specific request part or allocate it. */
+ err = be_find_request_part (backend_hd, request, &part);
+ if (err)
+ goto leave;
+
+ err = keybox_search_reset (part->kbx_hd);
+ if (!err)
+ err = keybox_search (part->kbx_hd, &desc, 1, 0,
+ &descindex, &skipped_long_blobs);
+ if (err == -1)
+ err = gpg_error (GPG_ERR_EOF);
+
+ leave:
+ return err;
+}
diff --git a/kbx/backend-support.c b/kbx/backend-support.c
new file mode 100644
index 000000000..62551cafa
--- /dev/null
+++ b/kbx/backend-support.c
@@ -0,0 +1,171 @@
+/* backend-support.c - Supporting functions for the backend.
+ * 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 "keyboxd.h"
+#include "../common/i18n.h"
+#include "../common/asshelp.h"
+#include "backend.h"
+#include "keybox.h"
+
+
+/* Common definition part of all backend handle. All definitions of
+ * this structure must start with these fields. */
+struct backend_handle_s
+{
+ enum database_types db_type;
+ unsigned int backend_id;
+};
+
+
+
+/* Return a string with the name of the database type T. */
+const char *
+strdbtype (enum database_types t)
+{
+ switch (t)
+ {
+ case DB_TYPE_NONE: return "none";
+ case DB_TYPE_CACHE:return "cache";
+ case DB_TYPE_KBX: return "keybox";
+ }
+ return "?";
+}
+
+
+/* Return a new backend ID. Backend IDs are used to identify backends
+ * without using the actual object. The number of backend resources
+ * is limited because they are specified in the config file. Thus an
+ * overflow check is not required. */
+unsigned int
+be_new_backend_id (void)
+{
+ static unsigned int last;
+
+ return ++last;
+}
+
+
+/* Release the backend described by HD. This is a generic function
+ * which dispatches to the the actual backend. */
+void
+be_generic_release_backend (ctrl_t ctrl, backend_handle_t hd)
+{
+ if (!hd)
+ return;
+ switch (hd->db_type)
+ {
+ case DB_TYPE_NONE:
+ xfree (hd);
+ break;
+ case DB_TYPE_CACHE:
+ be_cache_release_resource (ctrl, hd);
+ break;
+ case DB_TYPE_KBX:
+ be_kbx_release_resource (ctrl, hd);
+ break;
+ default:
+ log_error ("%s: faulty backend handle of type %d given\n",
+ __func__, hd->db_type);
+ }
+}
+
+
+/* Release the request object REQ. */
+void
+be_release_request (db_request_t req)
+{
+ db_request_part_t part, partn;
+
+ if (!req)
+ return;
+
+ for (part = req->part; part; part = partn)
+ {
+ partn = part->next;
+ be_kbx_release_kbx_hd (part->kbx_hd);
+ xfree (part);
+ }
+}
+
+
+/* Given the backend handle BACKEND_HD and the REQUEST find or
+ * allocate a request part for that backend and store it at R_PART.
+ * On error R_PART is set to NULL and an error returned. */
+gpg_error_t
+be_find_request_part (backend_handle_t backend_hd, db_request_t request,
+ db_request_part_t *r_part)
+{
+ gpg_error_t err;
+ db_request_part_t part;
+
+ for (part = request->part; part; part = part->next)
+ if (part->backend_id == backend_hd->backend_id)
+ break;
+ if (!part)
+ {
+ part = xtrycalloc (1, sizeof *part);
+ if (!part)
+ return gpg_error_from_syserror ();
+ part->backend_id = backend_hd->backend_id;
+ if (backend_hd->db_type == DB_TYPE_KBX)
+ {
+ err = be_kbx_init_request_part (backend_hd, part);
+ if (err)
+ {
+ xfree (part);
+ return err;
+ }
+ }
+ part->next = request->part;
+ request->part = part;
+ }
+ *r_part = part;
+ return 0;
+}
+
+
+/* Return the public key (BUFFER,BUFLEN) which has the type
+ * PUBKEY_TYPE to the caller. */
+gpg_error_t
+be_return_pubkey (ctrl_t ctrl, const void *buffer, size_t buflen,
+ enum pubkey_types pubkey_type, const unsigned char *ubid)
+{
+ gpg_error_t err;
+ char hexubid[41];
+
+ bin2hex (ubid, 20, hexubid);
+ err = status_printf (ctrl, "PUBKEY_INFO", "%d %s", pubkey_type, hexubid);
+ if (err)
+ goto leave;
+
+ if (ctrl->no_data_return)
+ err = 0;
+ else
+ err = kbxd_write_data_line(ctrl, buffer, buflen);
+
+ leave:
+ return err;
+}
diff --git a/kbx/backend.h b/kbx/backend.h
new file mode 100644
index 000000000..675ec213d
--- /dev/null
+++ b/kbx/backend.h
@@ -0,0 +1,140 @@
+/* backend.h - Definitions for keyboxd backends
+ * 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/>.
+ */
+
+#ifndef KBX_BACKEND_H
+#define KBX_BACKEND_H
+
+#include "keybox-search-desc.h"
+
+/* Forward declaration of the keybox handle type. */
+struct keybox_handle;
+typedef struct keybox_handle *KEYBOX_HANDLE;
+
+
+/* The types of the backends. */
+enum database_types
+ {
+ DB_TYPE_NONE, /* No database at all (unitialized etc.). */
+ DB_TYPE_CACHE, /* The cache backend (backend-cache.c). */
+ DB_TYPE_KBX /* Keybox type database (backend-kbx.c). */
+ };
+
+
+/* Declaration of the backend handle. Each backend uses its own
+ * hidden handle structure with the only common thing being that the
+ * first field is the database_type to help with debugging. */
+struct backend_handle_s;
+typedef struct backend_handle_s *backend_handle_t;
+
+
+/* Object to store backend specific database information per database
+ * handle. */
+struct db_request_part_s
+{
+ struct db_request_part_s *next;
+
+ /* Id of the backend instance this object pertains to. */
+ unsigned int backend_id;
+
+ /* The handle used for a KBX backend or NULL. */
+ KEYBOX_HANDLE kbx_hd;
+
+ /* For the CACHE backend the indices into the bloblist for each
+ * index type. */
+ struct {
+ unsigned int fpr;
+ unsigned int kid;
+ unsigned int grip;
+ unsigned int ubid;
+ } cache_seqno;
+};
+typedef struct db_request_part_s *db_request_part_t;
+
+
+/* A database request handle. This keeps per session search
+ * information as well as a list of per-backend infos. */
+struct db_request_s
+{
+ unsigned int any_search:1; /* Any search has been done. */
+ unsigned int any_found:1; /* Any object has been found. */
+ unsigned int last_cached_valid:1; /* see below */
+ unsigned int last_cached_final:1; /* see below */
+ unsigned int last_cached_fprlen:8;/* see below */
+
+ db_request_part_t part;
+
+ /* Counter to track the next to be searched database index. */
+ unsigned int next_dbidx;
+
+ /* The last UBID found in the cache and the corresponding keyid and,
+ * if found via fpr, the fingerprint. For the LAST_CACHE_FPRLEN see
+ * above. The entry here is only valid if LAST_CACHE_VALID is set;
+ * if LAST_CACHE_FINAL is also set, this indicates that no further
+ * database searches are required. */
+ unsigned char last_cached_ubid[20];
+ u32 last_cached_kid_h;
+ u32 last_cached_kid_l;
+ unsigned char last_cached_fpr[32];
+};
+
+
+
+/*-- backend-support.c --*/
+const char *strdbtype (enum database_types t);
+unsigned int be_new_backend_id (void);
+void be_generic_release_backend (ctrl_t ctrl, backend_handle_t hd);
+void be_release_request (db_request_t req);
+gpg_error_t be_find_request_part (backend_handle_t backend_hd,
+ db_request_t request,
+ db_request_part_t *r_part);
+gpg_error_t be_return_pubkey (ctrl_t ctrl, const void *buffer, size_t buflen,
+ enum pubkey_types pubkey_type,
+ const unsigned char *ubid);
+
+
+/*-- backend-cache.c --*/
+gpg_error_t be_cache_add_resource (ctrl_t ctrl, backend_handle_t *r_hd);
+void be_cache_release_resource (ctrl_t ctrl, backend_handle_t hd);
+gpg_error_t be_cache_search (ctrl_t ctrl, backend_handle_t backend_hd,
+ db_request_t request,
+ KEYDB_SEARCH_DESC *desc, unsigned int ndesc);
+void be_cache_mark_final (ctrl_t ctrl, db_request_t request);
+void be_cache_pubkey (ctrl_t ctrl, const unsigned char *ubid,
+ const void *blob, unsigned int bloblen,
+ enum pubkey_types pubkey_type);
+void be_cache_not_found (ctrl_t ctrl, enum pubkey_types pubkey_type,
+ KEYDB_SEARCH_DESC *desc, unsigned int ndesc);
+
+
+/*-- backend-kbx.c --*/
+gpg_error_t be_kbx_add_resource (ctrl_t ctrl, backend_handle_t *r_hd,
+ const char *filename, int readonly);
+void be_kbx_release_resource (ctrl_t ctrl, backend_handle_t hd);
+
+void be_kbx_release_kbx_hd (KEYBOX_HANDLE kbx_hd);
+gpg_error_t be_kbx_init_request_part (backend_handle_t backend_hd,
+ db_request_part_t part);
+gpg_error_t be_kbx_search (ctrl_t ctrl, backend_handle_t hd,
+ db_request_t request,
+ KEYDB_SEARCH_DESC *desc, unsigned int ndesc);
+gpg_error_t be_kbx_seek (ctrl_t ctrl, backend_handle_t backend_hd,
+ db_request_t request, unsigned char *ubid);
+
+
+#endif /*KBX_BACKEND_H*/
diff --git a/kbx/frontend.c b/kbx/frontend.c
new file mode 100644
index 000000000..6e0cbcb11
--- /dev/null
+++ b/kbx/frontend.c
@@ -0,0 +1,385 @@
+/* frontend.c - Database fronend code for keyboxd
+ * Copyright (C) 2019 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <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 "keyboxd.h"
+#include <assuan.h>
+#include "../common/i18n.h"
+#include "../common/userids.h"
+#include "backend.h"
+#include "frontend.h"
+
+
+/* An object to describe a single database. */
+struct db_desc_s
+{
+ enum database_types db_type;
+ backend_handle_t backend_handle;
+};
+typedef struct db_desc_s *db_desc_t;
+
+
+/* The table of databases and the size of that table. */
+static db_desc_t databases;
+static unsigned int no_of_databases;
+
+
+
+
+/* Take a lock for reading the databases. */
+static void
+take_read_lock (ctrl_t ctrl)
+{
+ /* FIXME */
+ (void)ctrl;
+}
+
+
+/* Take a lock for reading and writing the databases. */
+/* static void */
+/* take_read_write_lock (ctrl_t ctrl) */
+/* { */
+/* /\* FIXME *\/ */
+/* (void)ctrl; */
+/* } */
+
+
+/* Release a lock. It is valid to call this even if no lock has been
+ * taken in which case this is a nop. */
+static void
+release_lock (ctrl_t ctrl)
+{
+ /* FIXME */
+ (void)ctrl;
+}
+
+
+/* Add a new resource to the database. Depending on the FILENAME
+ * suffix we decide which one to use. This function must be called at
+ * daemon startup because it employs no locking. If FILENAME has no
+ * directory separator, the file is expected or created below
+ * "$GNUPGHOME/public-keys-v1.d/". In READONLY mode the file must
+ * exists; otherwise it is created. */
+gpg_error_t
+kbxd_add_resource (ctrl_t ctrl, const char *filename_arg, int readonly)
+{
+ gpg_error_t err;
+ char *filename;
+ enum database_types db_type = 0;
+ backend_handle_t handle = NULL;
+ unsigned int n, dbidx;
+
+ /* Do tilde expansion etc. */
+ if (!strcmp (filename_arg, "[cache]"))
+ {
+ filename = xstrdup (filename_arg);
+ db_type = DB_TYPE_CACHE;
+ }
+ else if (strchr (filename_arg, DIRSEP_C)
+#ifdef HAVE_W32_SYSTEM
+ || strchr (filename_arg, '/') /* Windows also accepts a slash. */
+#endif
+ )
+ filename = make_filename (filename_arg, NULL);
+ else
+ filename = make_filename (gnupg_homedir (), GNUPG_PUBLIC_KEYS_DIR,
+ filename_arg, NULL);
+
+ /* If this is the first call to the function and the request is not
+ * for the cache backend, add the cache backend so that it will
+ * always be the first to be queried. */
+ if (!no_of_databases && !db_type)
+ {
+ err = kbxd_add_resource (ctrl, "[cache]", 0);
+ if (err)
+ goto leave;
+ }
+
+ n = strlen (filename);
+ if (db_type)
+ ; /* We already know it. */
+ else if (n > 4 && !strcmp (filename + n - 4, ".kbx"))
+ db_type = DB_TYPE_KBX;
+ else
+ {
+ log_error (_("can't use file '%s': %s\n"), filename, _("unknown suffix"));
+ err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+ goto leave;
+ }
+
+ err = gpg_error (GPG_ERR_BUG);
+ switch (db_type)
+ {
+ case DB_TYPE_NONE: /* NOTREACHED */
+ break;
+
+ case DB_TYPE_CACHE:
+ err = be_cache_add_resource (ctrl, &handle);
+ break;
+
+ case DB_TYPE_KBX:
+ err = be_kbx_add_resource (ctrl, &handle, filename, readonly);
+ break;
+ }
+ if (err)
+ goto leave;
+
+ /* All good, create an entry in the table. */
+ for (dbidx = 0; dbidx < no_of_databases; dbidx++)
+ if (!databases[dbidx].db_type)
+ break;
+ if (dbidx == no_of_databases)
+ {
+ /* No table yet or table is full. */
+ if (!databases)
+ {
+ /* Create first set of databases. Note that the initial
+ * type for all entries is DB_TYPE_NONE. */
+ dbidx = 4;
+ databases = xtrycalloc (dbidx, sizeof *databases);
+ if (!databases)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ no_of_databases = dbidx;
+ dbidx = 0; /* Put into first slot. */
+ }
+ else
+ {
+ db_desc_t newdb;
+
+ dbidx = no_of_databases + (no_of_databases == 4? 12 : 16);
+ newdb = xtrycalloc (dbidx, sizeof *databases);
+ if (!databases)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ for (n=0; n < no_of_databases; n++)
+ newdb[n] = databases[n];
+ xfree (databases);
+ databases = newdb;
+ n = no_of_databases;
+ no_of_databases = dbidx;
+ dbidx = n; /* Put into first new slot. */
+ }
+ }
+
+ databases[dbidx].db_type = db_type;
+ databases[dbidx].backend_handle = handle;
+ handle = NULL;
+
+ leave:
+ if (err)
+ {
+ log_error ("error adding resource '%s': %s\n",
+ filename, gpg_strerror (err));
+ be_generic_release_backend (ctrl, handle);
+ }
+ xfree (filename);
+ return err;
+}
+
+
+/* Release all per session objects. */
+void
+kbxd_release_session_info (ctrl_t ctrl)
+{
+ if (!ctrl)
+ return;
+ be_release_request (ctrl->opgp_req);
+ ctrl->opgp_req = NULL;
+ be_release_request (ctrl->x509_req);
+ ctrl->x509_req = NULL;
+}
+
+
+
+/* Search for the keys described by (DESC,NDESC) and return them to
+ * the caller. If RESET is set, the search state is first reset. */
+gpg_error_t
+kbxd_search (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, unsigned int ndesc,
+ int reset)
+{
+ gpg_error_t err;
+ int i;
+ unsigned int dbidx;
+ db_desc_t db;
+ db_request_t request;
+ int start_at_ubid = 0;
+
+ if (DBG_CLOCK)
+ log_clock ("%s: enter", __func__);
+
+ if (DBG_LOOKUP)
+ {
+ log_debug ("%s: %u search descriptions:\n", __func__, ndesc);
+ for (i = 0; i < ndesc; i ++)
+ {
+ /* char *t = keydb_search_desc_dump (&desc[i]); */
+ /* log_debug ("%s %d: %s\n", __func__, i, t); */
+ /* xfree (t); */
+ }
+ }
+
+ take_read_lock (ctrl);
+
+ /* Allocate a handle object if none exists for this context. */
+ if (!ctrl->opgp_req)
+ {
+ ctrl->opgp_req = xtrycalloc (1, sizeof *ctrl->opgp_req);
+ if (!ctrl->opgp_req)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ }
+ request = ctrl->opgp_req;
+
+ /* If requested do a reset. Using the reset flag is faster than
+ * letting the caller do a separate call for an intial reset. */
+ if (!desc || reset)
+ {
+ for (dbidx=0; dbidx < no_of_databases; dbidx++)
+ {
+ db = databases + dbidx;
+ if (!db->db_type)
+ continue; /* Empty slot. */
+
+ switch (db->db_type)
+ {
+ case DB_TYPE_NONE: /* NOTREACHED */
+ break;
+
+ case DB_TYPE_CACHE:
+ err = 0; /* Nothing to do. */
+ break;
+
+ case DB_TYPE_KBX:
+ err = be_kbx_search (ctrl, db->backend_handle, request, NULL, 0);
+ break;
+ }
+ if (err)
+ {
+ log_error ("error during the %ssearch reset: %s\n",
+ reset? "initial ":"", gpg_strerror (err));
+ goto leave;
+ }
+ }
+ request->any_search = 0;
+ request->any_found = 0;
+ request->next_dbidx = 0;
+ if (!desc) /* Reset only mode */
+ {
+ err = 0;
+ goto leave;
+ }
+ }
+
+
+ /* Move to the next non-empty slot. */
+ next_db:
+ for (dbidx=request->next_dbidx; (dbidx < no_of_databases
+ && !databases[dbidx].db_type); dbidx++)
+ ;
+ request->next_dbidx = dbidx;
+ if (!(dbidx < no_of_databases))
+ {
+ /* All databases have been searched. Put the non-found mark
+ * into the cache for all descriptors.
+ * FIXME: We need to see which pubkey type we need to insert. */
+ be_cache_not_found (ctrl, PUBKEY_TYPE_UNKNOWN, desc, ndesc);
+ err = gpg_error (GPG_ERR_NOT_FOUND);
+ goto leave;
+ }
+ db = databases + dbidx;
+
+ /* Divert to the backend for the actual search. */
+ switch (db->db_type)
+ {
+ case DB_TYPE_NONE:
+ /* NOTREACHED */
+ err = gpg_error (GPG_ERR_INTERNAL);
+ break;
+
+ case DB_TYPE_CACHE:
+ err = be_cache_search (ctrl, db->backend_handle, request,
+ desc, ndesc);
+ /* Expected error codes from the cache lookup are:
+ * 0 - found and returned via the cache
+ * GPG_ERR_NOT_FOUND - marked in the cache as not available
+ * GPG_ERR_EOF - cache miss. */
+ break;
+
+ case DB_TYPE_KBX:
+ if (start_at_ubid)
+ {
+ /* We need to set the startpoint for the search. */
+ err = be_kbx_seek (ctrl, db->backend_handle, request,
+ request->last_cached_ubid);
+ if (err)
+ {
+ log_debug ("%s: seeking %s to an UBID failed: %s\n",
+ __func__, strdbtype (db->db_type), gpg_strerror (err));
+ break;
+ }
+ }
+ err = be_kbx_search (ctrl, db->backend_handle, request,
+ desc, ndesc);
+ if (start_at_ubid && gpg_err_code (err) == GPG_ERR_EOF)
+ be_cache_mark_final (ctrl, request);
+ break;
+ }
+
+ if (DBG_LOOKUP)
+ log_debug ("%s: searched %s (db %u of %u) => %s\n",
+ __func__, strdbtype (db->db_type), dbidx, no_of_databases,
+ gpg_strerror (err));
+ request->any_search = 1;
+ start_at_ubid = 0;
+ if (!err)
+ {
+ request->any_found = 1;
+ }
+ else if (gpg_err_code (err) == GPG_ERR_EOF)
+ {
+ if (db->db_type == DB_TYPE_CACHE && request->last_cached_valid)
+ {
+ if (request->last_cached_final)
+ goto leave;
+ start_at_ubid = 1;
+ }
+ request->next_dbidx++;
+ goto next_db;
+ }
+
+
+ leave:
+ release_lock (ctrl);
+ if (DBG_CLOCK)
+ log_clock ("%s: leave (%s)", __func__, err? "not found" : "found");
+ return err;
+}
diff --git a/kbx/frontend.h b/kbx/frontend.h
new file mode 100644
index 000000000..55d041fb0
--- /dev/null
+++ b/kbx/frontend.h
@@ -0,0 +1,36 @@
+/* frontend.h - Definitions for the keyboxd frontend
+ * 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/>.
+ */
+
+#ifndef KBX_FRONTEND_H
+#define KBX_FRONTEND_H
+
+#include "keybox-search-desc.h"
+
+
+gpg_error_t kbxd_add_resource (ctrl_t ctrl,
+ const char *filename_arg, int readonly);
+
+void kbxd_release_session_info (ctrl_t ctrl);
+
+gpg_error_t kbxd_search (ctrl_t ctrl,
+ KEYDB_SEARCH_DESC *desc, unsigned int ndesc,
+ int reset);
+
+
+#endif /*KBX_FRONTEND_H*/
diff --git a/kbx/kbxserver.c b/kbx/kbxserver.c
new file mode 100644
index 000000000..929ee6116
--- /dev/null
+++ b/kbx/kbxserver.c
@@ -0,0 +1,775 @@
+/* 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
+{
+ /* Data used to associate an Assuan context with local server data */
+ assuan_context_t assuan_ctx;
+
+ /* The session id (a counter). */
+ unsigned int session_id;
+
+ /* If this flag is set to true this process will be terminated after
+ * the end of this session. */
+ int stopme;
+
+ /* If the first both flags are set the assuan logging of data lines
+ * is suppressed. The count variable is used to show the number of
+ * non-logged bytes. */
+ size_t inhibit_data_logging_count;
+ unsigned int inhibit_data_logging : 1;
+ unsigned int inhibit_data_logging_now : 1;
+
+ /* This flag is set if the last search command was called with --more. */
+ unsigned int search_expecting_more : 1;
+
+ /* This flag is set if the last search command was successful. */
+ unsigned int search_any_found : 1;
+
+ /* The first is the current search description as parsed by the
+ * cmd_search. If more than one pattern is required, cmd_search
+ * also allocates and sets multi_search_desc and
+ * multi_search_desc_len. If a search description has ever been
+ * allocated the allocated size is stored at
+ * multi_search_desc_size. */
+ KEYBOX_SEARCH_DESC search_desc;
+ KEYBOX_SEARCH_DESC *multi_search_desc;
+ unsigned int multi_search_desc_size;
+ unsigned int multi_search_desc_len;
+
+ /* If not NULL write output to this stream instead of using D lines. */
+ estream_t outstream;
+};
+
+
+
+
+/* Return the assuan contxt from the local server info in CTRL. */
+static assuan_context_t
+get_assuan_ctx_from_ctrl (ctrl_t ctrl)
+{
+ if (!ctrl || !ctrl->server_local)
+ return NULL;
+ return ctrl->server_local->assuan_ctx;
+}
+
+
+/* If OUTPUT has been used prepare the output FD for use. This needs
+ * to be called by all functions which will in any way use
+ * kbxd_write_data_line later. Whether the output goes to the output
+ * stream is decided by this function. */
+static gpg_error_t
+prepare_outstream (ctrl_t ctrl)
+{
+ int fd;
+
+ log_assert (ctrl && ctrl->server_local);
+
+ if (ctrl->server_local->outstream)
+ return 0; /* Already enabled. */
+
+ fd = translate_sys2libc_fd
+ (assuan_get_output_fd (get_assuan_ctx_from_ctrl (ctrl)), 1);
+ if (fd == -1)
+ return 0; /* No Output command active. */
+
+ ctrl->server_local->outstream = es_fdopen_nc (fd, "w");
+ if (!ctrl->server_local->outstream)
+ return gpg_err_code_from_syserror ();
+ return 0;
+}
+
+
+/* The usual writen function; here with diagnostic output. */
+static gpg_error_t
+kbxd_writen (estream_t fp, const void *buffer, size_t length)
+{
+ gpg_error_t err;
+ size_t nwritten;
+
+ if (es_write (fp, buffer, length, &nwritten))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error writing OUTPUT: %s\n", gpg_strerror (err));
+ }
+ else if (length != nwritten)
+ {
+ err = gpg_error (GPG_ERR_EIO);
+ log_error ("error writing OUTPUT: %s\n", "short write");
+ }
+ else
+ err = 0;
+
+ return err;
+}
+
+
+/* A wrapper around assuan_send_data which makes debugging the output
+ * in verbose mode easier. It also takes CTRL as argument. */
+gpg_error_t
+kbxd_write_data_line (ctrl_t ctrl, const void *buffer_arg, size_t size)
+{
+ const char *buffer = buffer_arg;
+ assuan_context_t ctx = get_assuan_ctx_from_ctrl (ctrl);
+ gpg_error_t err;
+
+ if (!ctx) /* Oops - no assuan context. */
+ return gpg_error (GPG_ERR_NOT_PROCESSED);
+
+ /* Write toa file descriptor if enabled. */
+ if (ctrl && ctrl->server_local && ctrl->server_local->outstream)
+ {
+ unsigned char lenbuf[4];
+
+ ulongtobuf (lenbuf, size);
+ err = kbxd_writen (ctrl->server_local->outstream, lenbuf, 4);
+ if (!err)
+ err = kbxd_writen (ctrl->server_local->outstream, buffer, size);
+ if (!err && es_fflush (ctrl->server_local->outstream))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error writing OUTPUT: %s\n", gpg_strerror (err));
+ }
+
+ goto leave;
+ }
+
+ /* If we do not want logging, enable it here. */
+ if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging)
+ ctrl->server_local->inhibit_data_logging_now = 1;
+
+ if (0 && opt.verbose && buffer && size)
+ {
+ /* Ease reading of output by limiting the line length. */
+ size_t n, nbytes;
+
+ nbytes = size;
+ do
+ {
+ n = nbytes > 64? 64 : nbytes;
+ err = assuan_send_data (ctx, buffer, n);
+ if (err)
+ {
+ gpg_err_set_errno (EIO);
+ goto leave;
+ }
+ buffer += n;
+ nbytes -= n;
+ if (nbytes && (err=assuan_send_data (ctx, NULL, 0))) /* Flush line. */
+ {
+ gpg_err_set_errno (EIO);
+ goto leave;
+ }
+ }
+ while (nbytes);
+ }
+ else
+ {
+ err = assuan_send_data (ctx, buffer, size);
+ if (err)
+ {
+ gpg_err_set_errno (EIO); /* For use by data_line_cookie_write. */
+ goto leave;
+ }
+ }
+
+ leave:
+ if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging)
+ {
+ ctrl->server_local->inhibit_data_logging_count += size;
+ ctrl->server_local->inhibit_data_logging_now = 0;
+ }
+
+ return err;
+}
+
+
+
+/* Helper to print a message while leaving a command. */
+static gpg_error_t
+leave_cmd (assuan_context_t ctx, gpg_error_t err)
+{
+ if (err && opt.verbose)
+ {
+ const char *name = assuan_get_command_name (ctx);
+ if (!name)
+ name = "?";
+ if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
+ log_error ("command '%s' failed: %s\n", name,
+ gpg_strerror (err));
+ else
+ log_error ("command '%s' failed: %s <%s>\n", name,
+ gpg_strerror (err), gpg_strsource (err));
+ }
+ return err;
+}
+
+
+
+/* Handle OPTION commands. */
+static gpg_error_t
+option_handler (assuan_context_t ctx, const char *key, const char *value)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err = 0;
+
+ if (!strcmp (key, "lc-messages"))
+ {
+ if (ctrl->lc_messages)
+ xfree (ctrl->lc_messages);
+ ctrl->lc_messages = xtrystrdup (value);
+ if (!ctrl->lc_messages)
+ return out_of_core ();
+ }
+ else
+ err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
+
+ return err;
+}
+
+
+
+static const char hlp_search[] =
+ "SEARCH [--no-data] [[--more] PATTERN]\n"
+ "\n"
+ "Search for the keys identified by PATTERN. With --more more\n"
+ "patterns to be used for the search are expected with the next\n"
+ "command. With --no-data only the search status is returned but\n"
+ "not the actual data. See also \"NEXT\".";
+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;
+ gpg_error_t err;
+ unsigned int n, k;
+
+ opt_no_data = has_option (line, "--no-data");
+ opt_more = has_option (line, "--more");
+ line = skip_options (line);
+
+ ctrl->server_local->search_any_found = 0;
+
+ if (!*line)
+ {
+ if (opt_more)
+ {
+ err = set_error (GPG_ERR_INV_ARG, "--more but no pattern");
+ goto leave;
+ }
+ else if (!*line && ctrl->server_local->search_expecting_more)
+ {
+ /* It would be too surprising to first set a pattern but
+ * finally add no pattern to search the entire DB. */
+ err = set_error (GPG_ERR_INV_ARG, "--more pending but no pattern");
+ goto leave;
+ }
+ else /* No pattern - return the first item. */
+ {
+ memset (&ctrl->server_local->search_desc, 0,
+ sizeof ctrl->server_local->search_desc);
+ ctrl->server_local->search_desc.mode = KEYDB_SEARCH_MODE_FIRST;
+ }
+ }
+ else
+ {
+ err = classify_user_id (line, &ctrl->server_local->search_desc, 0);
+ if (err)
+ goto leave;
+ }
+
+ if (opt_more || ctrl->server_local->search_expecting_more)
+ {
+ /* More pattern are expected - store the current one and return
+ * success. */
+ if (!ctrl->server_local->multi_search_desc_size)
+ {
+ n = 10;
+ ctrl->server_local->multi_search_desc
+ = xtrycalloc (n, sizeof *ctrl->server_local->multi_search_desc);
+ if (!ctrl->server_local->multi_search_desc)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ ctrl->server_local->multi_search_desc_size = n;
+ }
+
+ if (ctrl->server_local->multi_search_desc_len
+ == ctrl->server_local->multi_search_desc_size)
+ {
+ KEYBOX_SEARCH_DESC *desc;
+ n = ctrl->server_local->multi_search_desc_size + 10;
+ desc = xtrycalloc (n, sizeof *desc);
+ if (!desc)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ for (k=0; k < ctrl->server_local->multi_search_desc_size; k++)
+ desc[k] = ctrl->server_local->multi_search_desc[k];
+ xfree (ctrl->server_local->multi_search_desc);
+ ctrl->server_local->multi_search_desc = desc;
+ ctrl->server_local->multi_search_desc_size = n;
+ }
+ /* Actually store. */
+ ctrl->server_local->multi_search_desc
+ [ctrl->server_local->multi_search_desc_len++]
+ = ctrl->server_local->search_desc;
+
+ if (opt_more)
+ {
+ /* We need to be called aagain with more pattern. */
+ ctrl->server_local->search_expecting_more = 1;
+ goto leave;
+ }
+ ctrl->server_local->search_expecting_more = 0;
+ /* Continue with the actual search. */
+ }
+ else
+ ctrl->server_local->multi_search_desc_len = 0;
+
+ ctrl->server_local->inhibit_data_logging = 1;
+ ctrl->server_local->inhibit_data_logging_now = 0;
+ ctrl->server_local->inhibit_data_logging_count = 0;
+ ctrl->no_data_return = opt_no_data;
+ 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 previus 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_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 gpg_error (GPG_ERR_EOF);
+}
+
+
+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 },
+ { "GETINFO", cmd_getinfo, hlp_getinfo },
+ { "OUTPUT", NULL, hlp_output },
+ { "KILLKEYBOXD",cmd_killkeyboxd,hlp_killkeyboxd },
+ { "RELOADKEYBOXD",cmd_reloadkeyboxd,hlp_reloadkeyboxd },
+ { NULL, NULL }
+ };
+ int i, j, rc;
+
+ for (i=j=0; table[i].name; i++)
+ {
+ rc = assuan_register_command (ctx, table[i].name, table[i].handler,
+ table[i].help);
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
+
+/* Note that we do not reset the list of configured keyservers. */
+static gpg_error_t
+reset_notify (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+
+ (void)line;
+ (void)ctrl;
+
+ return 0;
+}
+
+
+/* This function is called by our assuan log handler to test whether a
+ * log message shall really be printed. The function must return
+ * false to inhibit the logging of MSG. CAT gives the requested log
+ * category. MSG might be NULL. */
+int
+kbxd_assuan_log_monitor (assuan_context_t ctx, unsigned int cat,
+ const char *msg)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+
+ (void)cat;
+ (void)msg;
+
+ if (!ctrl || !ctrl->server_local)
+ return 1; /* Can't decide - allow logging. */
+
+ if (!ctrl->server_local->inhibit_data_logging)
+ return 1; /* Not requested - allow logging. */
+
+ /* Disallow logging if *_now is true. */
+ return !ctrl->server_local->inhibit_data_logging_now;
+}
+
+
+/* Startup the server and run the main command loop. With FD = -1,
+ * use stdin/stdout. SESSION_ID is either 0 or a unique number
+ * identifying a session. */
+void
+kbxd_start_command_handler (ctrl_t ctrl, gnupg_fd_t fd, unsigned int session_id)
+{
+ static const char hello[] = "Keyboxd " VERSION " at your service";
+ static char *hello_line;
+ int rc;
+ assuan_context_t ctx;
+
+ ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
+ if (!ctrl->server_local)
+ {
+ log_error (_("can't allocate control structure: %s\n"),
+ gpg_strerror (gpg_error_from_syserror ()));
+ xfree (ctrl);
+ return;
+ }
+
+ rc = assuan_new (&ctx);
+ if (rc)
+ {
+ log_error (_("failed to allocate assuan context: %s\n"),
+ gpg_strerror (rc));
+ kbxd_exit (2);
+ }
+
+ if (fd == GNUPG_INVALID_FD)
+ {
+ assuan_fd_t filedes[2];
+
+ filedes[0] = assuan_fdopen (0);
+ filedes[1] = assuan_fdopen (1);
+ rc = assuan_init_pipe_server (ctx, filedes);
+ }
+ else
+ {
+ rc = assuan_init_socket_server (ctx, fd,
+ (ASSUAN_SOCKET_SERVER_ACCEPTED
+ |ASSUAN_SOCKET_SERVER_FDPASSING));
+ }
+
+ if (rc)
+ {
+ assuan_release (ctx);
+ log_error (_("failed to initialize the server: %s\n"),
+ gpg_strerror (rc));
+ kbxd_exit (2);
+ }
+
+ rc = register_commands (ctx);
+ if (rc)
+ {
+ log_error (_("failed to the register commands with Assuan: %s\n"),
+ gpg_strerror(rc));
+ kbxd_exit (2);
+ }
+
+
+ if (!hello_line)
+ {
+ hello_line = xtryasprintf
+ ("Home: %s\n"
+ "Config: %s\n"
+ "%s",
+ gnupg_homedir (),
+ /*opt.config_filename? opt.config_filename :*/ "[none]",
+ hello);
+ }
+
+ ctrl->server_local->assuan_ctx = ctx;
+ assuan_set_pointer (ctx, ctrl);
+
+ assuan_set_hello_line (ctx, hello_line);
+ assuan_register_option_handler (ctx, option_handler);
+ assuan_register_reset_notify (ctx, reset_notify);
+
+ ctrl->server_local->session_id = session_id;
+
+ /* The next call enable the use of status_printf. */
+ set_assuan_context_func (get_assuan_ctx_from_ctrl);
+
+ for (;;)
+ {
+ rc = assuan_accept (ctx);
+ if (rc == -1)
+ break;
+ if (rc)
+ {
+ log_info (_("Assuan accept problem: %s\n"), gpg_strerror (rc));
+ break;
+ }
+
+#ifndef HAVE_W32_SYSTEM
+ if (opt.verbose)
+ {
+ assuan_peercred_t peercred;
+
+ if (!assuan_get_peercred (ctx, &peercred))
+ log_info ("connection from process %ld (%ld:%ld)\n",
+ (long)peercred->pid, (long)peercred->uid,
+ (long)peercred->gid);
+ }
+#endif
+
+ rc = assuan_process (ctx);
+ if (rc)
+ {
+ log_info (_("Assuan processing failed: %s\n"), gpg_strerror (rc));
+ continue;
+ }
+ }
+
+ assuan_close_output_fd (ctx);
+
+ set_assuan_context_func (NULL);
+ ctrl->server_local->assuan_ctx = NULL;
+ assuan_release (ctx);
+
+ if (ctrl->server_local->stopme)
+ kbxd_exit (0);
+
+ if (ctrl->refcount)
+ log_error ("oops: connection control structure still referenced (%d)\n",
+ ctrl->refcount);
+ else
+ {
+ xfree (ctrl->server_local->multi_search_desc);
+ xfree (ctrl->server_local);
+ ctrl->server_local = NULL;
+ }
+}
diff --git a/kbx/keybox-blob.c b/kbx/keybox-blob.c
index 7e4dcfaaa..0fced7e1c 100644
--- a/kbx/keybox-blob.c
+++ b/kbx/keybox-blob.c
@@ -67,6 +67,7 @@
- u16 Blob flags
bit 0 = contains secret key material (not used)
bit 1 = ephemeral blob (e.g. used while querying external resources)
+ bit 2 = blob has an UBID field.
- u32 Offset to the OpenPGP keyblock or the X.509 DER encoded
certificate
- u32 The length of the keyblock or certificate
@@ -143,7 +144,10 @@
IDs go here.
- bN Space for the keyblock or certificate.
- bN RFU. This is the remaining space after keyblock and before
- the checksum. It is not covered by the checksum.
+ the checksum. Not part of the SHA-1 checksum.
+ - bN Only if blob flags bit 2 is set: 20 octet Unique Blob-ID (UBID).
+ This is the SHA-1 checksum of the keyblock or certificate.
+ This is not part of the SHA-1 checksum below.
- b20 SHA-1 checksum (useful for KS synchronization?)
Note, that KBX versions before GnuPG 2.1 used an MD5
checksum. However it was only created but never checked.
@@ -173,6 +177,10 @@
#include "../common/gettime.h"
+#include "../common/host2net.h"
+
+
+#define get32(a) buf32_to_ulong ((a))
/* special values of the signature status */
@@ -559,7 +567,7 @@ create_blob_header (KEYBOXBLOB blob, int blobtype, int as_ephemeral,
put32 ( a, 0 ); /* blob length, needs fixup */
put8 ( a, blobtype);
put8 ( a, want_fpr32? 2:1 ); /* blob type version */
- put16 ( a, as_ephemeral? 2:0 ); /* blob flags */
+ put16 ( a, as_ephemeral? 6:4 ); /* blob flags */
put32 ( a, 0 ); /* offset to the raw data, needs fixup */
put32 ( a, 0 ); /* length of the raw data, needs fixup */
@@ -686,8 +694,8 @@ create_blob_finish (KEYBOXBLOB blob)
unsigned char *pp;
size_t n;
- /* Write a placeholder for the checksum */
- put_membuf (a, NULL, 20);
+ /* Write placeholders for the UBID and the checksum */
+ put_membuf (a, NULL, 40);
/* get the memory area */
n = 0; /* (Just to avoid compiler warning.) */
@@ -721,8 +729,11 @@ create_blob_finish (KEYBOXBLOB blob)
blob->fixups = NULL;
}
+ /* Compute and store the UBID. (image_off) (image_len) */
+ gcry_md_hash_buffer (GCRY_MD_SHA1, p + n - 40, p + get32 (p+8), get32 (p+12));
+
/* Compute and store the SHA-1 checksum. */
- gcry_md_hash_buffer (GCRY_MD_SHA1, p + n - 20, p, n - 20);
+ gcry_md_hash_buffer (GCRY_MD_SHA1, p + n - 20, p, n - 40);
pp = xtrymalloc (n);
if ( !pp )
diff --git a/kbx/keybox-dump.c b/kbx/keybox-dump.c
index c595208d0..55a47a403 100644
--- a/kbx/keybox-dump.c
+++ b/kbx/keybox-dump.c
@@ -63,6 +63,41 @@ print_string (FILE *fp, const byte *p, size_t n, int delim)
}
+static void
+print_ubib (const byte *buffer, size_t length, FILE *fp)
+{
+ const byte *p;
+ int i;
+ size_t image_off, image_len;
+ unsigned char digest[20];
+
+ fprintf (fp, "UBIB: ");
+ if (length < 40)
+ {
+ fputs ("[blob too short for a stored UBIB]\n", fp);
+ return;
+ }
+
+ p = buffer + length - 40;
+ for (i=0; i < 20; p++, i++)
+ fprintf (fp, "%02X", *p);
+
+ image_off = get32 (buffer+8);
+ image_len = get32 (buffer+12);
+ if ((uint64_t)image_off+(uint64_t)image_len > (uint64_t)length)
+ {
+ fputs (" [image claims to be longer than the blob]\n", fp);
+ return;
+ }
+
+ gcry_md_hash_buffer (GCRY_MD_SHA1, digest, buffer+image_off,image_len);
+ if (memcmp (digest, buffer + length - 40, 20))
+ fputs (" [does not match the image]\n", fp);
+ else
+ fputc ('\n', fp);
+}
+
+
static int
print_checksum (const byte *buffer, size_t length, size_t unhashed, FILE *fp)
{
@@ -171,6 +206,7 @@ _keybox_dump_blob (KEYBOXBLOB blob, FILE *fp)
ulong unhashed;
const byte *p;
int is_fpr32; /* blob ersion 2 */
+ int have_ubib = 0;
buffer = _keybox_get_blob_image (blob, &length);
@@ -237,6 +273,14 @@ _keybox_dump_blob (KEYBOXBLOB blob, FILE *fp)
fputs ("ephemeral", fp);
any++;
}
+ if ((n & 4))
+ {
+ if (any)
+ putc (',', fp);
+ fputs ("ubid", fp);
+ any++;
+ have_ubib = 1;
+ }
putc (')', fp);
}
putc ('\n', fp);
@@ -422,6 +466,8 @@ _keybox_dump_blob (KEYBOXBLOB blob, FILE *fp)
n = get32 ( buffer + length - unhashed);
fprintf (fp, "Storage-Flags: %08lx\n", n );
}
+ if (have_ubib)
+ print_ubib (buffer, length, fp);
print_checksum (buffer, length, unhashed, fp);
return 0;
}
diff --git a/kbx/keybox-file.c b/kbx/keybox-file.c
index 046e32123..7a1b43aa7 100644
--- a/kbx/keybox-file.c
+++ b/kbx/keybox-file.c
@@ -146,9 +146,9 @@ _keybox_write_blob (KEYBOXBLOB blob, FILE *fp)
}
-/* Write a fresh header type blob. */
-int
-_keybox_write_header_blob (FILE *fp, int for_openpgp)
+/* Write a fresh header type blob. Either FP or STREAM must be used. */
+gpg_error_t
+_keybox_write_header_blob (FILE *fp, estream_t stream, int for_openpgp)
{
unsigned char image[32];
u32 val;
@@ -174,7 +174,15 @@ _keybox_write_header_blob (FILE *fp, int for_openpgp)
image[20+2] = (val >> 8);
image[20+3] = (val );
- if (fwrite (image, 32, 1, fp) != 1)
- return gpg_error_from_syserror ();
+ if (fp)
+ {
+ if (fwrite (image, 32, 1, fp) != 1)
+ return gpg_error_from_syserror ();
+ }
+ else
+ {
+ if (es_fwrite (image, 32, 1, stream) != 1)
+ return gpg_error_from_syserror ();
+ }
return 0;
}
diff --git a/kbx/keybox-search-desc.h b/kbx/keybox-search-desc.h
index 7286d2ae3..7fa97e97b 100644
--- a/kbx/keybox-search-desc.h
+++ b/kbx/keybox-search-desc.h
@@ -42,11 +42,21 @@ typedef enum {
KEYDB_SEARCH_MODE_SN,
KEYDB_SEARCH_MODE_SUBJECT,
KEYDB_SEARCH_MODE_KEYGRIP,
+ KEYDB_SEARCH_MODE_UBID,
KEYDB_SEARCH_MODE_FIRST,
KEYDB_SEARCH_MODE_NEXT
} KeydbSearchMode;
+/* Identifiers for the public key types we use in GnuPG. */
+enum pubkey_types
+ {
+ PUBKEY_TYPE_UNKNOWN = 0,
+ PUBKEY_TYPE_OPGP = 1,
+ PUBKEY_TYPE_X509 = 2
+ };
+
+
/* Forward declaration. See g10/packet.h. */
struct gpg_pkt_user_id_s;
typedef struct gpg_pkt_user_id_s *gpg_pkt_user_id_t;
@@ -70,6 +80,7 @@ struct keydb_search_desc
unsigned char fpr[32];
u32 kid[2]; /* Note that this is in native endianness. */
unsigned char grip[20];
+ unsigned char ubid[20];
} u;
byte fprlen; /* Only used with KEYDB_SEARCH_MODE_FPR. */
int exact; /* Use exactly this key ('!' suffix in gpg). */
diff --git a/kbx/keybox-search.c b/kbx/keybox-search.c
index 77469a24c..95ad07251 100644
--- a/kbx/keybox-search.c
+++ b/kbx/keybox-search.c
@@ -696,6 +696,35 @@ has_keygrip (KEYBOXBLOB blob, const unsigned char *grip)
return 0;
}
+static inline int
+has_ubid (KEYBOXBLOB blob, const unsigned char *ubid)
+{
+ size_t length;
+ const unsigned char *buffer;
+ size_t image_off, image_len;
+ unsigned char ubid_blob[20];
+
+ buffer = _keybox_get_blob_image (blob, &length);
+ if (length < 40)
+ return 0; /*GPG_ERR_TOO_SHORT*/
+
+ if ((get16 (buffer + 6) & 4))
+ {
+ /* The blob has a stored UBID. */
+ return !memcmp (ubid, buffer + length - 40, 20);
+ }
+ else
+ {
+ /* Need to compute the UBID. */
+ image_off = get32 (buffer+8);
+ image_len = get32 (buffer+12);
+ if ((uint64_t)image_off+(uint64_t)image_len > (uint64_t)length)
+ return 0; /*GPG_ERR_TOO_SHORT*/
+
+ gcry_md_hash_buffer (GCRY_MD_SHA1, ubid_blob, buffer+image_off,image_len);
+ return !memcmp (ubid, ubid_blob, 20);
+ }
+}
static inline int
has_issuer (KEYBOXBLOB blob, const char *name)
@@ -1119,6 +1148,10 @@ keybox_search (KEYBOX_HANDLE hd, KEYBOX_SEARCH_DESC *desc, size_t ndesc,
if (has_keygrip (blob, desc[n].u.grip))
goto found;
break;
+ case KEYDB_SEARCH_MODE_UBID:
+ if (has_ubid (blob, desc[n].u.ubid))
+ goto found;
+ break;
case KEYDB_SEARCH_MODE_FIRST:
goto found;
break;
@@ -1180,11 +1213,70 @@ keybox_search (KEYBOX_HANDLE hd, KEYBOX_SEARCH_DESC *desc, size_t ndesc,
a successful search operation.
*/
+/* Return the raw data from the last found blob. Caller must release
+ * the value stored at R_BUFFER. If called with NULL for R_BUFFER
+ * only the needed length for the buffer and the public key type is
+ * returned. */
+gpg_error_t
+keybox_get_data (KEYBOX_HANDLE hd, void **r_buffer, size_t *r_length,
+ enum pubkey_types *r_pubkey_type)
+{
+ const unsigned char *buffer;
+ size_t length;
+ size_t image_off, image_len;
+
+ if (r_buffer)
+ *r_buffer = NULL;
+ if (r_length)
+ *r_length = 0;
+ if (r_pubkey_type)
+ *r_pubkey_type = PUBKEY_TYPE_UNKNOWN;
+
+ if (!hd)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (!hd->found.blob)
+ return gpg_error (GPG_ERR_NOTHING_FOUND);
+
+ switch (blob_get_type (hd->found.blob))
+ {
+ case KEYBOX_BLOBTYPE_PGP:
+ if (r_pubkey_type)
+ *r_pubkey_type = PUBKEY_TYPE_OPGP;
+ break;
+ case KEYBOX_BLOBTYPE_X509:
+ if (r_pubkey_type)
+ *r_pubkey_type = PUBKEY_TYPE_X509;
+ break;
+ default:
+ return gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
+ }
+
+ buffer = _keybox_get_blob_image (hd->found.blob, &length);
+ if (length < 40)
+ return gpg_error (GPG_ERR_TOO_SHORT);
+ image_off = get32 (buffer+8);
+ image_len = get32 (buffer+12);
+ if ((uint64_t)image_off+(uint64_t)image_len > (uint64_t)length)
+ return gpg_error (GPG_ERR_TOO_SHORT);
+
+ if (r_length)
+ *r_length = image_len;
+ if (r_buffer)
+ {
+ *r_buffer = xtrymalloc (image_len);
+ if (!*r_buffer)
+ return gpg_error_from_syserror ();
+ memcpy (*r_buffer, buffer + image_off, image_len);
+ }
+
+ return 0;
+}
+
/* Return the last found keyblock. Returns 0 on success and stores a
* new iobuf at R_IOBUF. R_UID_NO and R_PK_NO are used to return the
- * number of the key or user id which was matched the search criteria;
- * if not known they are set to 0. */
+ * index of the key or user id which matched the search criteria; if
+ * not known they are set to 0. */
gpg_error_t
keybox_get_keyblock (KEYBOX_HANDLE hd, iobuf_t *r_iobuf,
int *r_pk_no, int *r_uid_no)
diff --git a/kbx/keybox-update.c b/kbx/keybox-update.c
index e25596b26..fbcaec7b9 100644
--- a/kbx/keybox-update.c
+++ b/kbx/keybox-update.c
@@ -182,7 +182,7 @@ blob_filecopy (int mode, const char *fname, KEYBOXBLOB blob,
if (!newfp )
return gpg_error_from_syserror ();
- rc = _keybox_write_header_blob (newfp, for_openpgp);
+ rc = _keybox_write_header_blob (newfp, NULL, for_openpgp);
if (rc)
{
fclose (newfp);
@@ -730,7 +730,7 @@ keybox_compress (KEYBOX_HANDLE hd)
}
/* The header blob is missing. Insert it. */
- rc = _keybox_write_header_blob (newfp, hd->for_openpgp);
+ rc = _keybox_write_header_blob (newfp, NULL, hd->for_openpgp);
if (rc)
break;
any_changes = 1;
diff --git a/kbx/keybox.h b/kbx/keybox.h
index 4d941571e..8a22580dd 100644
--- a/kbx/keybox.h
+++ b/kbx/keybox.h
@@ -81,9 +81,13 @@ gpg_error_t keybox_lock (KEYBOX_HANDLE hd, int yes, long timeout);
/*-- keybox-file.c --*/
/* Fixme: This function does not belong here: Provide a better
interface to create a new keybox file. */
-int _keybox_write_header_blob (FILE *fp, int openpgp_flag);
+gpg_error_t _keybox_write_header_blob (FILE *fp, estream_t stream,
+ int openpgp_flag);
/*-- keybox-search.c --*/
+gpg_error_t keybox_get_data (KEYBOX_HANDLE hd,
+ void **r_buffer, size_t *r_length,
+ enum pubkey_types *r_pubkey_type);
gpg_error_t keybox_get_keyblock (KEYBOX_HANDLE hd, iobuf_t *r_iobuf,
int *r_uid_no, int *r_pk_no);
#ifdef KEYBOX_WITH_X509
diff --git a/kbx/keyboxd-w32info.rc b/kbx/keyboxd-w32info.rc
new file mode 100644
index 000000000..421747499
--- /dev/null
+++ b/kbx/keyboxd-w32info.rc
@@ -0,0 +1,50 @@
+/* keyboxd-w32info.rc -*- c -*-
+ * Copyright (C) 2018 g10 Code GmbH
+ *
+ * This file is free software; as a special exception the author gives
+ * unlimited permission to copy and/or distribute it, with or without
+ * modifications, as long as this notice is preserved.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
+ * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include "afxres.h"
+#include "../common/w32info-rc.h"
+
+1 ICON "../common/gnupg.ico"
+
+1 VERSIONINFO
+ FILEVERSION W32INFO_VI_FILEVERSION
+ PRODUCTVERSION W32INFO_VI_PRODUCTVERSION
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/
+#else
+ FILEFLAGS 0x00L
+#endif
+ FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */
+ FILETYPE 0x1L /* VFT_APP (0x1) */
+ FILESUBTYPE 0x0L /* VFT2_UNKNOWN */
+ BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" /* US English (0409), Unicode (04b0) */
+ BEGIN
+ VALUE "FileDescription", L"GnuPG\x2019s public key daemon\0"
+ VALUE "InternalName", "keyboxd\0"
+ VALUE "OriginalFilename", "keyboxd.exe\0"
+ VALUE "ProductName", W32INFO_PRODUCTNAME
+ VALUE "ProductVersion", W32INFO_PRODUCTVERSION
+ VALUE "CompanyName", W32INFO_COMPANYNAME
+ VALUE "FileVersion", W32INFO_FILEVERSION
+ VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT
+ VALUE "Comments", W32INFO_COMMENTS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 0x4b0
+ END
+ END
diff --git a/kbx/keyboxd.c b/kbx/keyboxd.c
new file mode 100644
index 000000000..d39b35749
--- /dev/null
+++ b/kbx/keyboxd.c
@@ -0,0 +1,1845 @@
+/* keyboxd.c - The GnuPG Keybox Daemon
+ * Copyright (C) 2000-2007, 2009-2010 Free Software Foundation, Inc.
+ * Copyright (C) 2000-2018 Werner Koch
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0+
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <time.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#ifdef HAVE_W32_SYSTEM
+# ifndef WINVER
+# define WINVER 0x0500 /* Same as in common/sysutils.c */
+# endif
+# include <winsock2.h>
+#else /*!HAVE_W32_SYSTEM*/
+# include <sys/socket.h>
+# include <sys/un.h>
+#endif /*!HAVE_W32_SYSTEM*/
+#include <unistd.h>
+#ifdef HAVE_SIGNAL_H
+# include <signal.h>
+#endif
+#include <npth.h>
+
+#define GNUPG_COMMON_NEED_AFLOCAL
+#include "keyboxd.h"
+#include <assuan.h> /* Malloc hooks and socket wrappers. */
+
+#include "../common/i18n.h"
+#include "../common/sysutils.h"
+#include "../common/asshelp.h"
+#include "../common/init.h"
+#include "../common/gc-opt-flags.h"
+#include "../common/exechelp.h"
+#include "frontend.h"
+
+
+/* Urrgs: Put this into a separate header - but it needs assuan.h first. */
+extern int kbxd_assuan_log_monitor (assuan_context_t ctx, unsigned int cat,
+ const char *msg);
+
+
+enum cmd_and_opt_values
+ {
+ aNull = 0,
+ oQuiet = 'q',
+ oVerbose = 'v',
+
+ oNoVerbose = 500,
+ aGPGConfList,
+ aGPGConfTest,
+ oOptions,
+ oDebug,
+ oDebugAll,
+ oDebugWait,
+ oNoGreeting,
+ oNoOptions,
+ oHomedir,
+ oNoDetach,
+ oLogFile,
+ oServer,
+ oDaemon,
+ oBatch,
+ oFakedSystemTime,
+ oListenBacklog,
+ oDisableCheckOwnSocket,
+
+ oDummy
+ };
+
+
+static ARGPARSE_OPTS opts[] = {
+ ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
+ ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
+
+ ARGPARSE_group (301, N_("@Options:\n ")),
+
+ ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
+ ARGPARSE_s_n (oServer, "server", N_("run in server mode (foreground)")),
+ ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
+ ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
+ ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")),
+
+ ARGPARSE_s_s (oDebug, "debug", "@"),
+ ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
+ ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
+
+ ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"),
+ ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
+ ARGPARSE_s_s (oLogFile, "log-file", N_("use a log file for the server")),
+ ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
+
+ ARGPARSE_s_n (oBatch, "batch", "@"),
+ ARGPARSE_s_s (oHomedir, "homedir", "@"),
+
+ ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"),
+
+ ARGPARSE_end () /* End of list */
+};
+
+
+/* The list of supported debug flags. */
+static struct debug_flags_s debug_flags [] =
+ {
+ { DBG_MPI_VALUE , "mpi" },
+ { DBG_CRYPTO_VALUE , "crypto" },
+ { DBG_MEMORY_VALUE , "memory" },
+ { DBG_CACHE_VALUE , "cache" },
+ { DBG_MEMSTAT_VALUE, "memstat" },
+ { DBG_HASHING_VALUE, "hashing" },
+ { DBG_IPC_VALUE , "ipc" },
+ { DBG_CLOCK_VALUE , "clock" },
+ { DBG_LOOKUP_VALUE , "lookup" },
+ { 77, NULL } /* 77 := Do not exit on "help" or "?". */
+ };
+
+/* The timer tick used for housekeeping stuff. Note that on Windows
+ * we use a SetWaitableTimer seems to signal earlier than about 2
+ * seconds. Thus we use 4 seconds on all platforms except for
+ * Windowsce. CHECK_OWN_SOCKET_INTERVAL defines how often we check
+ * our own socket in standard socket mode. If that value is 0 we
+ * don't check at all. All values are in seconds. */
+#if defined(HAVE_W32CE_SYSTEM)
+# define TIMERTICK_INTERVAL (60)
+# define CHECK_OWN_SOCKET_INTERVAL (0) /* Never */
+#else
+# define TIMERTICK_INTERVAL (4)
+# define CHECK_OWN_SOCKET_INTERVAL (60)
+#endif
+
+/* The list of open file descriptors at startup. Note that this list
+ * has been allocated using the standard malloc. */
+#ifndef HAVE_W32_SYSTEM
+static int *startup_fd_list;
+#endif
+
+/* The signal mask at startup and a flag telling whether it is valid. */
+#ifdef HAVE_SIGPROCMASK
+static sigset_t startup_signal_mask;
+static int startup_signal_mask_valid;
+#endif
+
+/* Flag to indicate that a shutdown was requested. */
+static int shutdown_pending;
+
+/* Counter for the currently running own socket checks. */
+static int check_own_socket_running;
+
+/* Flag to indicate that we shall not watch our own socket. */
+static int disable_check_own_socket;
+
+/* Flag to inhibit socket removal in cleanup. */
+static int inhibit_socket_removal;
+
+/* Name of the communication socket used for client requests. */
+static char *socket_name;
+
+/* We need to keep track of the server's nonces (these are dummies for
+ * POSIX systems). */
+static assuan_sock_nonce_t socket_nonce;
+
+/* Value for the listen() backlog argument. We use the same value for
+ * all sockets - 64 is on current Linux half of the default maximum.
+ * Let's try this as default. Change at runtime with --listen-backlog. */
+static int listen_backlog = 64;
+
+/* Name of a config file, which will be reread on a HUP if it is not NULL. */
+static char *config_filename;
+
+/* Keep track of the current log file so that we can avoid updating
+ * the log file after a SIGHUP if it didn't changed. Malloced. */
+static char *current_logfile;
+
+/* This flag is true if the inotify mechanism for detecting the
+ * removal of the homedir is active. This flag is used to disable the
+ * alternative but portable stat based check. */
+static int have_homedir_inotify;
+
+/* Depending on how keyboxd was started, the homedir inotify watch may
+ * not be reliable. This flag is set if we assume that inotify works
+ * reliable. */
+static int reliable_homedir_inotify;
+
+/* Number of active connections. */
+static int active_connections;
+
+/* This object is used to dispatch progress messages from Libgcrypt to
+ * the right thread. Given that we will have at max only a few dozen
+ * connections at a time, using a linked list is the easiest way to
+ * handle this. */
+struct progress_dispatch_s
+{
+ struct progress_dispatch_s *next;
+ /* The control object of the connection. If this is NULL no
+ * connection is associated with this item and it is free for reuse
+ * by new connections. */
+ ctrl_t ctrl;
+
+ /* The thread id of (npth_self) of the connection. */
+ npth_t tid;
+
+ /* The callback set by the connection. This is similar to the
+ * Libgcrypt callback but with the control object passed as the
+ * first argument. */
+ void (*cb)(ctrl_t ctrl,
+ const char *what, int printchar,
+ int current, int total);
+};
+struct progress_dispatch_s *progress_dispatch_list;
+
+
+
+
+/*
+ * Local prototypes.
+ */
+
+static char *create_socket_name (char *standard_name, int with_homedir);
+static gnupg_fd_t create_server_socket (char *name, int cygwin,
+ assuan_sock_nonce_t *nonce);
+static void create_directories (void);
+
+static void kbxd_libgcrypt_progress_cb (void *data, const char *what,
+ int printchar,
+ int current, int total);
+static void kbxd_init_default_ctrl (ctrl_t ctrl);
+static void kbxd_deinit_default_ctrl (ctrl_t ctrl);
+
+static void handle_connections (gnupg_fd_t listen_fd);
+static void check_own_socket (void);
+static int check_for_running_kbxd (int silent);
+
+/* Pth wrapper function definitions. */
+ASSUAN_SYSTEM_NPTH_IMPL;
+
+
+/*
+ * Functions.
+ */
+
+/* Allocate a string describing a library version by calling a GETFNC.
+ * This function is expected to be called only once. GETFNC is
+ * expected to have a semantic like gcry_check_version (). */
+static char *
+make_libversion (const char *libname, const char *(*getfnc)(const char*))
+{
+ return xstrconcat (libname, " ", getfnc (NULL), NULL);
+}
+
+
+/* Return strings describing this program. The case values are
+ * described in common/argparse.c:strusage. The values here override
+ * the default values given by strusage. */
+static const char *
+my_strusage (int level)
+{
+ static char *ver_gcry;
+ const char *p;
+
+ switch (level)
+ {
+ case 11: p = "keyboxd (@GNUPG@)";
+ break;
+ case 13: p = VERSION; break;
+ case 17: p = PRINTABLE_OS_NAME; break;
+ /* TRANSLATORS: @EMAIL@ will get replaced by the actual bug
+ reporting address. This is so that we can change the
+ reporting address without breaking the translations. */
+ case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
+
+ case 20:
+ if (!ver_gcry)
+ ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
+ p = ver_gcry;
+ break;
+
+ case 1:
+ case 40: p = _("Usage: keyboxd [options] (-h for help)");
+ break;
+ case 41: p = _("Syntax: keyboxd [options] [command [args]]\n"
+ "Public key management for @GNUPG@\n");
+ break;
+
+ default: p = NULL;
+ }
+ return p;
+}
+
+
+
+/* Setup the debugging. Note that we don't fail here, because it is
+ * important to keep keyboxd running even after re-reading the options
+ * due to a SIGHUP. */
+static void
+set_debug (void)
+{
+ if (opt.debug && !opt.verbose)
+ opt.verbose = 1;
+ if (opt.debug && opt.quiet)
+ opt.quiet = 0;
+
+ if (opt.debug & DBG_MPI_VALUE)
+ gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
+ if (opt.debug & DBG_CRYPTO_VALUE )
+ gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
+ gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
+
+ if (opt.debug)
+ parse_debug_flag (NULL, &opt.debug, debug_flags);
+}
+
+
+/* Helper for cleanup to remove one socket with NAME. */
+static void
+remove_socket (char *name)
+{
+ if (name && *name)
+ {
+ gnupg_remove (name);
+ *name = 0;
+ }
+}
+
+
+/* Cleanup code for this program. This is either called has an atexit
+ handler or directly. */
+static void
+cleanup (void)
+{
+ static int done;
+
+ if (done)
+ return;
+ done = 1;
+ if (!inhibit_socket_removal)
+ remove_socket (socket_name);
+}
+
+
+/* Handle options which are allowed to be reset after program start.
+ * Return true when the current option in PARGS could be handled and
+ * false if not. As a special feature, passing a value of NULL for
+ * PARGS, resets the options to the default. REREAD should be set
+ * true if it is not the initial option parsing. */
+static int
+parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
+{
+ if (!pargs)
+ { /* reset mode */
+ opt.quiet = 0;
+ opt.verbose = 0;
+ opt.debug = 0;
+ disable_check_own_socket = 0;
+ return 1;
+ }
+
+ switch (pargs->r_opt)
+ {
+ case oQuiet: opt.quiet = 1; break;
+ case oVerbose: opt.verbose++; break;
+
+ case oDebug:
+ parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags);
+ break;
+ case oDebugAll: opt.debug = ~0; break;
+
+ case oLogFile:
+ if (!reread)
+ return 0; /* not handeld */
+ if (!current_logfile || !pargs->r.ret_str
+ || strcmp (current_logfile, pargs->r.ret_str))
+ {
+ log_set_file (pargs->r.ret_str);
+ xfree (current_logfile);
+ current_logfile = xtrystrdup (pargs->r.ret_str);
+ }
+ break;
+
+ case oDisableCheckOwnSocket: disable_check_own_socket = 1; break;
+
+ default:
+ return 0; /* not handled */
+ }
+
+ return 1; /* handled */
+}
+
+
+/* Fixup some options after all have been processed. */
+static void
+finalize_rereadable_options (void)
+{
+}
+
+
+static void
+thread_init_once (void)
+{
+ static int npth_initialized = 0;
+
+ if (!npth_initialized)
+ {
+ npth_initialized++;
+ npth_init ();
+ }
+ gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
+ /* Now that we have set the syscall clamp we need to tell Libgcrypt
+ * that it should get them from libgpg-error. Note that Libgcrypt
+ * has already been initialized but at that point nPth was not
+ * initialized and thus Libgcrypt could not set its system call
+ * clamp. */
+ gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0);
+}
+
+
+static void
+initialize_modules (void)
+{
+ thread_init_once ();
+ assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
+}
+
+
+/* The main entry point. */
+int
+main (int argc, char **argv )
+{
+ ARGPARSE_ARGS pargs;
+ int orig_argc;
+ char **orig_argv;
+ FILE *configfp = NULL;
+ char *configname = NULL;
+ unsigned configlineno;
+ int parse_debug = 0;
+ int default_config =1;
+ int pipe_server = 0;
+ int is_daemon = 0;
+ int nodetach = 0;
+ char *logfile = NULL;
+ int gpgconf_list = 0;
+ int debug_wait = 0;
+ struct assuan_malloc_hooks malloc_hooks;
+
+ early_system_init ();
+
+ /* Before we do anything else we save the list of currently open
+ * file descriptors and the signal mask. This info is required to
+ * do the exec call properly. We don't need it on Windows. */
+#ifndef HAVE_W32_SYSTEM
+ startup_fd_list = get_all_open_fds ();
+#endif /*!HAVE_W32_SYSTEM*/
+#ifdef HAVE_SIGPROCMASK
+ if (!sigprocmask (SIG_UNBLOCK, NULL, &startup_signal_mask))
+ startup_signal_mask_valid = 1;
+#endif /*HAVE_SIGPROCMASK*/
+
+ /* Set program name etc. */
+ set_strusage (my_strusage);
+ log_set_prefix ("keyboxd", GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_WITH_PID);
+
+ /* Make sure that our subsystems are ready. */
+ i18n_init ();
+ init_common_subsystems (&argc, &argv);
+ gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
+
+ malloc_hooks.malloc = gcry_malloc;
+ malloc_hooks.realloc = gcry_realloc;
+ malloc_hooks.free = gcry_free;
+ assuan_set_malloc_hooks (&malloc_hooks);
+ assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
+ assuan_sock_init ();
+ assuan_sock_set_system_hooks (ASSUAN_SYSTEM_NPTH);
+ setup_libassuan_logging (&opt.debug, kbxd_assuan_log_monitor);
+
+ setup_libgcrypt_logging ();
+ gcry_set_progress_handler (kbxd_libgcrypt_progress_cb, NULL);
+
+ /* Set default options. */
+ parse_rereadable_options (NULL, 0); /* Reset them to default values. */
+
+ /* Check whether we have a config file on the commandline */
+ orig_argc = argc;
+ orig_argv = argv;
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */
+ while (arg_parse( &pargs, opts))
+ {
+ if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll)
+ parse_debug++;
+ else if (pargs.r_opt == oOptions)
+ { /* Yes, a config file was given so we do not try the default
+ * one. Instead we read the config file when it is
+ * encountered during main parsing of the command line. */
+ default_config = 0;
+ }
+ else if (pargs.r_opt == oNoOptions)
+ default_config = 0; /* --no-options */
+ else if (pargs.r_opt == oHomedir)
+ gnupg_set_homedir (pargs.r.ret_str);
+ }
+
+ if (default_config)
+ configname = make_filename (gnupg_homedir (),
+ "keyboxd" EXTSEP_S "conf", NULL);
+
+ argc = orig_argc;
+ argv = orig_argv;
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= 1; /* do not remove the args */
+ next_pass:
+ if (configname)
+ {
+ configlineno = 0;
+ configfp = fopen (configname, "r");
+ if (!configfp)
+ {
+ if (default_config)
+ {
+ if (parse_debug)
+ log_info (_("Note: no default option file '%s'\n"), configname);
+ /* Save the default confif file name so that
+ * reread_configuration is able to test whether the
+ * config file has been created in the meantime. */
+ xfree (config_filename);
+ config_filename = configname;
+ configname = NULL;
+ }
+ else
+ {
+ log_error (_("option file '%s': %s\n"),
+ configname, strerror (errno));
+ exit (2);
+ }
+ xfree (configname);
+ configname = NULL;
+ }
+ if (parse_debug && configname )
+ log_info (_("reading options from '%s'\n"), configname);
+ default_config = 0;
+ }
+
+ while (optfile_parse (configfp, configname, &configlineno, &pargs, opts))
+ {
+ if (parse_rereadable_options (&pargs, 0))
+ continue; /* Already handled */
+ switch (pargs.r_opt)
+ {
+ case aGPGConfList: gpgconf_list = 1; break;
+ case aGPGConfTest: gpgconf_list = 2; break;
+ case oBatch: opt.batch=1; break;
+ case oDebugWait: debug_wait = pargs.r.ret_int; break;
+ case oOptions:
+ /* Config files may not be nested (silently ignore them). */
+ if (!configfp)
+ {
+ xfree (configname);
+ configname = xstrdup (pargs.r.ret_str);
+ goto next_pass;
+ }
+ break;
+ case oNoGreeting: /* Dummy option. */ break;
+ case oNoVerbose: opt.verbose = 0; break;
+ case oNoOptions: break; /* no-options */
+ case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
+ case oNoDetach: nodetach = 1; break;
+ case oLogFile: logfile = pargs.r.ret_str; break;
+ case oServer: pipe_server = 1; break;
+ case oDaemon: is_daemon = 1; break;
+ case oFakedSystemTime:
+ {
+ time_t faked_time = isotime2epoch (pargs.r.ret_str);
+ if (faked_time == (time_t)(-1))
+ faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
+ gnupg_set_time (faked_time, 0);
+ }
+ break;
+
+ case oListenBacklog:
+ listen_backlog = pargs.r.ret_int;
+ break;
+
+ default : pargs.err = configfp? 1:2; break;
+ }
+ }
+ if (configfp)
+ {
+ fclose (configfp);
+ configfp = NULL;
+ /* Keep a copy of the name so that it can be read on SIGHUP. */
+ if (config_filename != configname)
+ {
+ xfree (config_filename);
+ config_filename = configname;
+ }
+ configname = NULL;
+ goto next_pass;
+ }
+
+ xfree (configname);
+ configname = NULL;
+ if (log_get_errorcount(0))
+ exit (2);
+
+ finalize_rereadable_options ();
+
+ /* Print a warning if an argument looks like an option. */
+ if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
+ {
+ int i;
+
+ for (i=0; i < argc; i++)
+ if (argv[i][0] == '-' && argv[i][1] == '-')
+ log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
+ }
+
+#ifdef ENABLE_NLS
+ /* keyboxd usually does not output any messages because it runs in
+ * the background. For log files it is acceptable to have messages
+ * always encoded in utf-8. We switch here to utf-8, so that
+ * commands like --help still give native messages. It is far
+ * easier to switch only once instead of for every message and it
+ * actually helps when more then one thread is active (avoids an
+ * extra copy step). */
+ bind_textdomain_codeset (PACKAGE_GT, "UTF-8");
+#endif
+
+ if (!pipe_server && !is_daemon && !gpgconf_list)
+ {
+ /* We have been called without any command and thus we merely
+ * check whether an instance of us is already running. We do
+ * this right here so that we don't clobber a logfile with this
+ * check but print the status directly to stderr. */
+ opt.debug = 0;
+ set_debug ();
+ check_for_running_kbxd (0);
+ kbxd_exit (0);
+ }
+
+ set_debug ();
+
+ if (atexit (cleanup))
+ {
+ log_error ("atexit failed\n");
+ cleanup ();
+ exit (1);
+ }
+
+ /* Try to create missing directories. */
+ create_directories ();
+
+ if (debug_wait && pipe_server)
+ {
+ thread_init_once ();
+ log_debug ("waiting for debugger - my pid is %u .....\n",
+ (unsigned int)getpid());
+ gnupg_sleep (debug_wait);
+ log_debug ("... okay\n");
+ }
+
+ if (gpgconf_list == 2)
+ kbxd_exit (0);
+ else if (gpgconf_list)
+ {
+ char *filename;
+ char *filename_esc;
+
+ /* List options and default values in the gpgconf format. */
+ filename = make_filename (gnupg_homedir (),
+ "keyboxd" EXTSEP_S "conf", NULL);
+ filename_esc = percent_escape (filename, NULL);
+
+ es_printf ("%s-%s.conf:%lu:\"%s\n",
+ GPGCONF_NAME, "keyboxd", GC_OPT_FLAG_DEFAULT, filename_esc);
+ xfree (filename);
+ xfree (filename_esc);
+
+ es_printf ("verbose:%lu:\n"
+ "quiet:%lu:\n"
+ "log-file:%lu:\n",
+ GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME,
+ GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME,
+ GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME );
+
+ kbxd_exit (0);
+ }
+
+ /* Now start with logging to a file if this is desired. */
+ if (logfile)
+ {
+ log_set_file (logfile);
+ log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
+ | GPGRT_LOG_WITH_TIME
+ | GPGRT_LOG_WITH_PID));
+ current_logfile = xstrdup (logfile);
+ }
+
+ if (pipe_server)
+ {
+ /* This is the simple pipe based server */
+ ctrl_t ctrl;
+
+ initialize_modules ();
+
+ ctrl = xtrycalloc (1, sizeof *ctrl);
+ if (!ctrl)
+ {
+ log_error ("error allocating connection control data: %s\n",
+ strerror (errno) );
+ kbxd_exit (1);
+ }
+ kbxd_init_default_ctrl (ctrl);
+
+ kbxd_add_resource (ctrl, "pubring.kbx", 0);
+
+ kbxd_start_command_handler (ctrl, GNUPG_INVALID_FD, 0);
+ kbxd_deinit_default_ctrl (ctrl);
+ xfree (ctrl);
+ }
+ else if (!is_daemon)
+ ; /* NOTREACHED */
+ else
+ { /* Regular daemon mode. */
+ gnupg_fd_t fd;
+#ifndef HAVE_W32_SYSTEM
+ pid_t pid;
+#endif
+
+ /* Create the sockets. */
+ socket_name = create_socket_name (KEYBOXD_SOCK_NAME, 1);
+ fd = create_server_socket (socket_name, 0, &socket_nonce);
+
+ fflush (NULL);
+
+#ifdef HAVE_W32_SYSTEM
+
+ (void)nodetach;
+ initialize_modules ();
+
+#else /*!HAVE_W32_SYSTEM*/
+
+ pid = fork ();
+ if (pid == (pid_t)-1)
+ {
+ log_fatal ("fork failed: %s\n", strerror (errno) );
+ exit (1);
+ }
+ else if (pid)
+ { /* We are the parent */
+
+ /* Close the socket FD. */
+ close (fd);
+
+ /* The signal mask might not be correct right now and thus
+ * we restore it. That is not strictly necessary but some
+ * programs falsely assume a cleared signal mask. */
+
+#ifdef HAVE_SIGPROCMASK
+ if (startup_signal_mask_valid)
+ {
+ if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL))
+ log_error ("error restoring signal mask: %s\n",
+ strerror (errno));
+ }
+ else
+ log_info ("no saved signal mask\n");
+#endif /*HAVE_SIGPROCMASK*/
+
+ *socket_name = 0; /* Don't let cleanup() remove the socket -
+ the child should do this from now on */
+
+ exit (0);
+ /*NOTREACHED*/
+ } /* End parent */
+
+ /*
+ * This is the child
+ */
+
+ initialize_modules ();
+
+ /* Detach from tty and put process into a new session */
+ if (!nodetach)
+ {
+ int i;
+ unsigned int oldflags;
+
+ /* Close stdin, stdout and stderr unless it is the log stream */
+ for (i=0; i <= 2; i++)
+ {
+ if (!log_test_fd (i) && i != fd )
+ {
+ if ( ! close (i)
+ && open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
+ {
+ log_error ("failed to open '%s': %s\n",
+ "/dev/null", strerror (errno));
+ cleanup ();
+ exit (1);
+ }
+ }
+ }
+ if (setsid() == -1)
+ {
+ log_error ("setsid() failed: %s\n", strerror(errno) );
+ cleanup ();
+ exit (1);
+ }
+
+ log_get_prefix (&oldflags);
+ log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED);
+ opt.running_detached = 1;
+
+ /* Because we don't support running a program on the command
+ * line we can assume that the inotify things works and thus
+ * we can avoid the regular stat calls. */
+ reliable_homedir_inotify = 1;
+ }
+
+ {
+ struct sigaction sa;
+
+ sa.sa_handler = SIG_IGN;
+ sigemptyset (&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigaction (SIGPIPE, &sa, NULL);
+ }
+#endif /*!HAVE_W32_SYSTEM*/
+
+ if (gnupg_chdir (gnupg_daemon_rootdir ()))
+ {
+ log_error ("chdir to '%s' failed: %s\n",
+ gnupg_daemon_rootdir (), strerror (errno));
+ exit (1);
+ }
+
+ {
+ ctrl_t ctrl;
+
+ ctrl = xtrycalloc (1, sizeof *ctrl);
+ if (!ctrl)
+ {
+ log_error ("error allocating connection control data: %s\n",
+ strerror (errno) );
+ kbxd_exit (1);
+ }
+ kbxd_init_default_ctrl (ctrl);
+ kbxd_add_resource (ctrl, "pubring.kbx", 0);
+ kbxd_deinit_default_ctrl (ctrl);
+ xfree (ctrl);
+ }
+
+ log_info ("%s %s started\n", strusage(11), strusage(13) );
+ handle_connections (fd);
+ assuan_sock_close (fd);
+ }
+
+ return 0;
+}
+
+
+/* Exit entry point. This function should be called instead of a
+ plain exit. */
+void
+kbxd_exit (int rc)
+{
+ /* As usual we run our cleanup handler. */
+ cleanup ();
+
+ /* at this time a bit annoying */
+ if ((opt.debug & DBG_MEMSTAT_VALUE))
+ gcry_control (GCRYCTL_DUMP_MEMORY_STATS );
+ rc = rc? rc : log_get_errorcount(0)? 2 : 0;
+ exit (rc);
+}
+
+
+/* This is our callback function for gcrypt progress messages. It is
+ * set once at startup and dispatches progress messages to the
+ * corresponding threads of ours. */
+static void
+kbxd_libgcrypt_progress_cb (void *data, const char *what, int printchar,
+ int current, int total)
+{
+ struct progress_dispatch_s *dispatch;
+ npth_t mytid = npth_self ();
+
+ (void)data;
+
+ for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
+ if (dispatch->ctrl && dispatch->tid == mytid)
+ break;
+ if (dispatch && dispatch->cb)
+ dispatch->cb (dispatch->ctrl, what, printchar, current, total);
+}
+
+
+/* If a progress dispatcher callback has been associated with the
+ * current connection unregister it. */
+static void
+unregister_progress_cb (void)
+{
+ struct progress_dispatch_s *dispatch;
+ npth_t mytid = npth_self ();
+
+ for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
+ if (dispatch->ctrl && dispatch->tid == mytid)
+ break;
+ if (dispatch)
+ {
+ dispatch->ctrl = NULL;
+ dispatch->cb = NULL;
+ }
+}
+
+
+/* Setup a progress callback CB for the current connection. Using a
+ * CB of NULL disables the callback. */
+void
+kbxd_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what,
+ int printchar, int current, int total),
+ ctrl_t ctrl)
+{
+ struct progress_dispatch_s *dispatch, *firstfree;
+ npth_t mytid = npth_self ();
+
+ firstfree = NULL;
+ for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
+ {
+ if (dispatch->ctrl && dispatch->tid == mytid)
+ break;
+ if (!dispatch->ctrl && !firstfree)
+ firstfree = dispatch;
+ }
+ if (!dispatch) /* None allocated: Reuse or allocate a new one. */
+ {
+ if (firstfree)
+ {
+ dispatch = firstfree;
+ }
+ else if ((dispatch = xtrycalloc (1, sizeof *dispatch)))
+ {
+ dispatch->next = progress_dispatch_list;
+ progress_dispatch_list = dispatch;
+ }
+ else
+ {
+ log_error ("error allocating new progress dispatcher slot: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+ return;
+ }
+ dispatch->ctrl = ctrl;
+ dispatch->tid = mytid;
+ }
+
+ dispatch->cb = cb;
+}
+
+
+/* Each thread has its own local variables conveyed by a control
+ * structure usually identified by an argument named CTRL. This
+ * function is called immediately after allocating the control
+ * structure. Its purpose is to setup the default values for that
+ * structure. Note that some values may have already been set. */
+static void
+kbxd_init_default_ctrl (ctrl_t ctrl)
+{
+ ctrl->magic = SERVER_CONTROL_MAGIC;
+}
+
+
+/* Release all resources allocated by default in the control
+ structure. This is the counterpart to kbxd_init_default_ctrl. */
+static void
+kbxd_deinit_default_ctrl (ctrl_t ctrl)
+{
+ if (!ctrl)
+ return;
+ kbxd_release_session_info (ctrl);
+ ctrl->magic = 0xdeadbeef;
+ unregister_progress_cb ();
+ xfree (ctrl->lc_messages);
+}
+
+
+/* Reread parts of the configuration. Note, that this function is
+ * obviously not thread-safe and should only be called from the PTH
+ * signal handler.
+ *
+ * Fixme: Due to the way the argument parsing works, we create a
+ * memory leak here for all string type arguments. There is currently
+ * no clean way to tell whether the memory for the argument has been
+ * allocated or points into the process' original arguments. Unless
+ * we have a mechanism to tell this, we need to live on with this. */
+static void
+reread_configuration (void)
+{
+ ARGPARSE_ARGS pargs;
+ FILE *fp;
+ unsigned int configlineno = 0;
+ int dummy;
+
+ if (!config_filename)
+ return; /* No config file. */
+
+ fp = fopen (config_filename, "r");
+ if (!fp)
+ {
+ log_info (_("option file '%s': %s\n"),
+ config_filename, strerror(errno) );
+ return;
+ }
+
+ parse_rereadable_options (NULL, 1); /* Start from the default values. */
+
+ memset (&pargs, 0, sizeof pargs);
+ dummy = 0;
+ pargs.argc = &dummy;
+ pargs.flags = 1; /* do not remove the args */
+ while (optfile_parse (fp, config_filename, &configlineno, &pargs, opts) )
+ {
+ if (pargs.r_opt < -1)
+ pargs.err = 1; /* Print a warning. */
+ else /* Try to parse this option - ignore unchangeable ones. */
+ parse_rereadable_options (&pargs, 1);
+ }
+ fclose (fp);
+ finalize_rereadable_options ();
+ set_debug ();
+}
+
+
+/* Return the file name of the socket we are using for requests. */
+const char *
+get_kbxd_socket_name (void)
+{
+ const char *s = socket_name;
+
+ return (s && *s)? s : NULL;
+}
+
+
+/* Return the number of active connections. */
+int
+get_kbxd_active_connection_count (void)
+{
+ return active_connections;
+}
+
+
+/* Create a name for the socket in the home directory as using
+ * STANDARD_NAME. We also check for valid characters as well as
+ * against a maximum allowed length for a Unix domain socket is done.
+ * The function terminates the process in case of an error. The
+ * function returns a pointer to an allocated string with the absolute
+ * name of the socket used. */
+static char *
+create_socket_name (char *standard_name, int with_homedir)
+{
+ char *name;
+
+ if (with_homedir)
+ name = make_filename (gnupg_socketdir (), standard_name, NULL);
+ else
+ name = make_filename (standard_name, NULL);
+ if (strchr (name, PATHSEP_C))
+ {
+ log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S);
+ kbxd_exit (2);
+ }
+ return name;
+}
+
+
+
+/* Create a Unix domain socket with NAME. Returns the file descriptor
+ * or terminates the process in case of an error. If CYGWIN is set a
+ * Cygwin compatible socket is created (Windows only). */
+static gnupg_fd_t
+create_server_socket (char *name, int cygwin, assuan_sock_nonce_t *nonce)
+{
+ struct sockaddr *addr;
+ struct sockaddr_un *unaddr;
+ socklen_t len;
+ gnupg_fd_t fd;
+ int rc;
+
+ fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
+ if (fd == ASSUAN_INVALID_FD)
+ {
+ log_error (_("can't create socket: %s\n"), strerror (errno));
+ *name = 0; /* Inhibit removal of the socket by cleanup(). */
+ kbxd_exit (2);
+ }
+
+ if (cygwin)
+ assuan_sock_set_flag (fd, "cygwin", 1);
+
+ unaddr = xmalloc (sizeof *unaddr);
+ addr = (struct sockaddr*)unaddr;
+
+ if (assuan_sock_set_sockaddr_un (name, addr, NULL))
+ {
+ if (errno == ENAMETOOLONG)
+ log_error (_("socket name '%s' is too long\n"), name);
+ else
+ log_error ("error preparing socket '%s': %s\n",
+ name, gpg_strerror (gpg_error_from_syserror ()));
+ *name = 0; /* Inhibit removal of the socket by cleanup(). */
+ xfree (unaddr);
+ kbxd_exit (2);
+ }
+
+ len = SUN_LEN (unaddr);
+ rc = assuan_sock_bind (fd, addr, len);
+
+ /* Our error code mapping on W32CE returns EEXIST thus we also test
+ for this. */
+ if (rc == -1
+ && (errno == EADDRINUSE
+#ifdef HAVE_W32_SYSTEM
+ || errno == EEXIST
+#endif
+ ))
+ {
+ /* Check whether a keyboxd is already running. */
+ if (!check_for_running_kbxd (1))
+ {
+ log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX);
+ log_set_file (NULL);
+ log_error (_("a keyboxd is already running - "
+ "not starting a new one\n"));
+ *name = 0; /* Inhibit removal of the socket by cleanup(). */
+ assuan_sock_close (fd);
+ xfree (unaddr);
+ kbxd_exit (2);
+ }
+ gnupg_remove (unaddr->sun_path);
+ rc = assuan_sock_bind (fd, addr, len);
+ }
+ if (rc != -1 && (rc=assuan_sock_get_nonce (addr, len, nonce)))
+ log_error (_("error getting nonce for the socket\n"));
+ if (rc == -1)
+ {
+ /* We use gpg_strerror here because it allows us to get strings
+ for some W32 socket error codes. */
+ log_error (_("error binding socket to '%s': %s\n"),
+ unaddr->sun_path, gpg_strerror (gpg_error_from_syserror ()));
+
+ assuan_sock_close (fd);
+ *name = 0; /* Inhibit removal of the socket by cleanup(). */
+ xfree (unaddr);
+ kbxd_exit (2);
+ }
+
+ if (gnupg_chmod (unaddr->sun_path, "-rwx"))
+ log_error (_("can't set permissions of '%s': %s\n"),
+ unaddr->sun_path, strerror (errno));
+
+ if (listen (FD2INT(fd), listen_backlog ) == -1)
+ {
+ log_error ("listen(fd,%d) failed: %s\n", listen_backlog, strerror (errno));
+ *name = 0; /* Inhibit removal of the socket by cleanup(). */
+ assuan_sock_close (fd);
+ xfree (unaddr);
+ kbxd_exit (2);
+ }
+
+ if (opt.verbose)
+ log_info (_("listening on socket '%s'\n"), unaddr->sun_path);
+
+ xfree (unaddr);
+ return fd;
+}
+
+
+/* Check that the directory for storing the public keys exists and
+ * create it if not. This function won't fail as it is only a
+ * convenience function and not strictly necessary. */
+static void
+create_public_keys_directory (const char *home)
+{
+ char *fname;
+ struct stat statbuf;
+
+ fname = make_filename (home, GNUPG_PUBLIC_KEYS_DIR, NULL);
+ if (stat (fname, &statbuf) && errno == ENOENT)
+ {
+ if (gnupg_mkdir (fname, "-rwxr-x"))
+ log_error (_("can't create directory '%s': %s\n"),
+ fname, strerror (errno) );
+ else if (!opt.quiet)
+ log_info (_("directory '%s' created\n"), fname);
+ }
+ if (gnupg_chmod (fname, "-rwxr-x"))
+ log_error (_("can't set permissions of '%s': %s\n"),
+ fname, strerror (errno));
+ xfree (fname);
+}
+
+
+/* Create the directory only if the supplied directory name is the
+ * same as the default one. This way we avoid to create arbitrary
+ * directories when a non-default home directory is used. To cope
+ * with HOME, we compare only the suffix if we see that the default
+ * homedir does start with a tilde. We don't stop here in case of
+ * problems because other functions will throw an error anyway.*/
+static void
+create_directories (void)
+{
+ struct stat statbuf;
+ const char *defhome = standard_homedir ();
+ char *home;
+
+ home = make_filename (gnupg_homedir (), NULL);
+ if (stat (home, &statbuf))
+ {
+ if (errno == ENOENT)
+ {
+ if (
+#ifdef HAVE_W32_SYSTEM
+ ( !compare_filenames (home, defhome) )
+#else
+ (*defhome == '~'
+ && (strlen (home) >= strlen (defhome+1)
+ && !strcmp (home + strlen(home)
+ - strlen (defhome+1), defhome+1)))
+ || (*defhome != '~' && !strcmp (home, defhome) )
+#endif
+ )
+ {
+ if (gnupg_mkdir (home, "-rwx"))
+ log_error (_("can't create directory '%s': %s\n"),
+ home, strerror (errno) );
+ else
+ {
+ if (!opt.quiet)
+ log_info (_("directory '%s' created\n"), home);
+ }
+ }
+ }
+ else
+ log_error (_("stat() failed for '%s': %s\n"), home, strerror (errno));
+ }
+ else if ( !S_ISDIR(statbuf.st_mode))
+ {
+ log_error (_("can't use '%s' as home directory\n"), home);
+ }
+ else /* exists and is a directory. */
+ {
+ create_public_keys_directory (home);
+ }
+ xfree (home);
+}
+
+
+
+/* This is the worker for the ticker. It is called every few seconds
+ * and may only do fast operations. */
+static void
+handle_tick (void)
+{
+ static time_t last_minute;
+ struct stat statbuf;
+
+ if (!last_minute)
+ last_minute = time (NULL);
+
+ /* Code to be run from time to time. */
+#if CHECK_OWN_SOCKET_INTERVAL > 0
+ if (last_minute + CHECK_OWN_SOCKET_INTERVAL <= time (NULL))
+ {
+ check_own_socket ();
+ last_minute = time (NULL);
+ }
+#endif
+
+
+ /* Check whether the homedir is still available. */
+ if (!shutdown_pending
+ && (!have_homedir_inotify || !reliable_homedir_inotify)
+ && stat (gnupg_homedir (), &statbuf) && errno == ENOENT)
+ {
+ shutdown_pending = 1;
+ log_info ("homedir has been removed - shutting down\n");
+ }
+}
+
+
+/* A global function which allows us to call the reload stuff from
+ * other places too. This is only used when build for W32. */
+void
+kbxd_sighup_action (void)
+{
+ log_info ("SIGHUP received - "
+ "re-reading configuration and flushing cache\n");
+
+ reread_configuration ();
+}
+
+
+/* A helper function to handle SIGUSR2. */
+static void
+kbxd_sigusr2_action (void)
+{
+ if (opt.verbose)
+ log_info ("SIGUSR2 received - no action\n");
+ /* Nothing to do right now. */
+}
+
+
+#ifndef HAVE_W32_SYSTEM
+/* The signal handler for this program. It is expected to be run in
+ * its own thread and not in the context of a signal handler. */
+static void
+handle_signal (int signo)
+{
+ switch (signo)
+ {
+ case SIGHUP:
+ kbxd_sighup_action ();
+ break;
+
+ case SIGUSR1:
+ log_info ("SIGUSR1 received - printing internal information:\n");
+ /* Fixme: We need to see how to integrate pth dumping into our
+ logging system. */
+ /* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
+ break;
+
+ case SIGUSR2:
+ kbxd_sigusr2_action ();
+ break;
+
+ case SIGTERM:
+ if (!shutdown_pending)
+ log_info ("SIGTERM received - shutting down ...\n");
+ else
+ log_info ("SIGTERM received - still %i open connections\n",
+ active_connections);
+ shutdown_pending++;
+ if (shutdown_pending > 2)
+ {
+ log_info ("shutdown forced\n");
+ log_info ("%s %s stopped\n", strusage(11), strusage(13) );
+ cleanup ();
+ kbxd_exit (0);
+ }
+ break;
+
+ case SIGINT:
+ log_info ("SIGINT received - immediate shutdown\n");
+ log_info( "%s %s stopped\n", strusage(11), strusage(13));
+ cleanup ();
+ kbxd_exit (0);
+ break;
+
+ default:
+ log_info ("signal %d received - no action defined\n", signo);
+ }
+}
+#endif
+
+/* Check the nonce on a new connection. This is a NOP unless we
+ are using our Unix domain socket emulation under Windows. */
+static int
+check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce)
+{
+ if (assuan_sock_check_nonce (ctrl->thread_startup.fd, nonce))
+ {
+ log_info (_("error reading nonce on fd %d: %s\n"),
+ FD2INT(ctrl->thread_startup.fd), strerror (errno));
+ assuan_sock_close (ctrl->thread_startup.fd);
+ xfree (ctrl);
+ return -1;
+ }
+ else
+ return 0;
+}
+
+
+static void *
+do_start_connection_thread (ctrl_t ctrl)
+{
+ static unsigned int last_session_id;
+ unsigned int session_id;
+
+ active_connections++;
+ kbxd_init_default_ctrl (ctrl);
+ if (opt.verbose && !DBG_IPC)
+ log_info (_("handler 0x%lx for fd %d started\n"),
+ (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
+
+ session_id = ++last_session_id;
+ if (!session_id)
+ session_id = ++last_session_id;
+ kbxd_start_command_handler (ctrl, ctrl->thread_startup.fd, session_id);
+ if (opt.verbose && !DBG_IPC)
+ log_info (_("handler 0x%lx for fd %d terminated\n"),
+ (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
+
+ kbxd_deinit_default_ctrl (ctrl);
+ xfree (ctrl);
+ active_connections--;
+ return NULL;
+}
+
+
+/* This is the standard connection thread's main function. */
+static void *
+start_connection_thread (void *arg)
+{
+ ctrl_t ctrl = arg;
+
+ if (check_nonce (ctrl, &socket_nonce))
+ {
+ log_error ("handler 0x%lx nonce check FAILED\n",
+ (unsigned long) npth_self());
+ return NULL;
+ }
+
+ return do_start_connection_thread (ctrl);
+}
+
+
+/* Connection handler loop. Wait for connection requests and spawn a
+ * thread after accepting a connection. */
+static void
+handle_connections (gnupg_fd_t listen_fd)
+{
+ gpg_error_t err;
+ npth_attr_t tattr;
+ struct sockaddr_un paddr;
+ socklen_t plen;
+ fd_set fdset, read_fdset;
+ int ret;
+ gnupg_fd_t fd;
+ int nfd;
+ int saved_errno;
+ struct timespec abstime;
+ struct timespec curtime;
+ struct timespec timeout;
+#ifdef HAVE_W32_SYSTEM
+ HANDLE events[2];
+ unsigned int events_set;
+#endif
+ int sock_inotify_fd = -1;
+ int home_inotify_fd = -1;
+ struct {
+ const char *name;
+ void *(*func) (void *arg);
+ gnupg_fd_t l_fd;
+ } listentbl[] = {
+ { "std", start_connection_thread },
+ };
+
+
+ ret = npth_attr_init(&tattr);
+ if (ret)
+ log_fatal ("error allocating thread attributes: %s\n", strerror (ret));
+ npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
+
+#ifndef HAVE_W32_SYSTEM
+ npth_sigev_init ();
+ npth_sigev_add (SIGHUP);
+ npth_sigev_add (SIGUSR1);
+ npth_sigev_add (SIGUSR2);
+ npth_sigev_add (SIGINT);
+ npth_sigev_add (SIGTERM);
+ npth_sigev_fini ();
+#else
+# ifdef HAVE_W32CE_SYSTEM
+ /* Use a dummy event. */
+ sigs = 0;
+ ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo);
+# else
+ events[0] = INVALID_HANDLE_VALUE;
+# endif
+#endif
+
+ if (disable_check_own_socket)
+ sock_inotify_fd = -1;
+ else if ((err = gnupg_inotify_watch_socket (&sock_inotify_fd, socket_name)))
+ {
+ if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
+ log_info ("error enabling daemon termination by socket removal: %s\n",
+ gpg_strerror (err));
+ }
+
+ if (disable_check_own_socket)
+ home_inotify_fd = -1;
+ else if ((err = gnupg_inotify_watch_delete_self (&home_inotify_fd,
+ gnupg_homedir ())))
+ {
+ if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
+ log_info ("error enabling daemon termination by homedir removal: %s\n",
+ gpg_strerror (err));
+ }
+ else
+ have_homedir_inotify = 1;
+
+ FD_ZERO (&fdset);
+ FD_SET (FD2INT (listen_fd), &fdset);
+ nfd = FD2INT (listen_fd);
+ if (sock_inotify_fd != -1)
+ {
+ FD_SET (sock_inotify_fd, &fdset);
+ if (sock_inotify_fd > nfd)
+ nfd = sock_inotify_fd;
+ }
+ if (home_inotify_fd != -1)
+ {
+ FD_SET (home_inotify_fd, &fdset);
+ if (home_inotify_fd > nfd)
+ nfd = home_inotify_fd;
+ }
+
+ listentbl[0].l_fd = listen_fd;
+
+ npth_clock_gettime (&abstime);
+ abstime.tv_sec += TIMERTICK_INTERVAL;
+
+ for (;;)
+ {
+ /* Shutdown test. */
+ if (shutdown_pending)
+ {
+ if (!active_connections)
+ break; /* ready */
+
+ /* Do not accept new connections but keep on running the
+ * loop to cope with the timer events.
+ *
+ * Note that we do not close the listening socket because a
+ * client trying to connect to that socket would instead
+ * restart a new keyboxd instance - which is unlikely the
+ * intention of a shutdown. */
+ FD_ZERO (&fdset);
+ nfd = -1;
+ if (sock_inotify_fd != -1)
+ {
+ FD_SET (sock_inotify_fd, &fdset);
+ nfd = sock_inotify_fd;
+ }
+ if (home_inotify_fd != -1)
+ {
+ FD_SET (home_inotify_fd, &fdset);
+ if (home_inotify_fd > nfd)
+ nfd = home_inotify_fd;
+ }
+ }
+
+ read_fdset = fdset;
+
+ npth_clock_gettime (&curtime);
+ if (!(npth_timercmp (&curtime, &abstime, <)))
+ {
+ /* Timeout. */
+ handle_tick ();
+ npth_clock_gettime (&abstime);
+ abstime.tv_sec += TIMERTICK_INTERVAL;
+ }
+ npth_timersub (&abstime, &curtime, &timeout);
+
+#ifndef HAVE_W32_SYSTEM
+ ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
+ npth_sigev_sigmask ());
+ saved_errno = errno;
+
+ {
+ int signo;
+ while (npth_sigev_get_pending (&signo))
+ handle_signal (signo);
+ }
+#else
+ ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
+ events, &events_set);
+ saved_errno = errno;
+
+ /* This is valid even if npth_eselect returns an error. */
+ if ((events_set & 1))
+ kbxd_sigusr2_action ();
+#endif
+
+ if (ret == -1 && saved_errno != EINTR)
+ {
+ log_error (_("npth_pselect failed: %s - waiting 1s\n"),
+ strerror (saved_errno));
+ npth_sleep (1);
+ continue;
+ }
+ if (ret <= 0)
+ {
+ /* Interrupt or timeout. Will be handled when calculating the
+ * next timeout. */
+ continue;
+ }
+
+ /* The inotify fds are set even when a shutdown is pending (see
+ * above). So we must handle them in any case. To avoid that
+ * they trigger a second time we close them immediately. */
+ if (sock_inotify_fd != -1
+ && FD_ISSET (sock_inotify_fd, &read_fdset)
+ && gnupg_inotify_has_name (sock_inotify_fd, KEYBOXD_SOCK_NAME))
+ {
+ shutdown_pending = 1;
+ close (sock_inotify_fd);
+ sock_inotify_fd = -1;
+ log_info ("socket file has been removed - shutting down\n");
+ }
+
+ if (home_inotify_fd != -1
+ && FD_ISSET (home_inotify_fd, &read_fdset))
+ {
+ shutdown_pending = 1;
+ close (home_inotify_fd);
+ home_inotify_fd = -1;
+ log_info ("homedir has been removed - shutting down\n");
+ }
+
+ if (!shutdown_pending)
+ {
+ int idx;
+ ctrl_t ctrl;
+ npth_t thread;
+
+ for (idx=0; idx < DIM(listentbl); idx++)
+ {
+ if (listentbl[idx].l_fd == GNUPG_INVALID_FD)
+ continue;
+ if (!FD_ISSET (FD2INT (listentbl[idx].l_fd), &read_fdset))
+ continue;
+
+ plen = sizeof paddr;
+ fd = INT2FD (npth_accept (FD2INT(listentbl[idx].l_fd),
+ (struct sockaddr *)&paddr, &plen));
+ if (fd == GNUPG_INVALID_FD)
+ {
+ log_error ("accept failed for %s: %s\n",
+ listentbl[idx].name, strerror (errno));
+ }
+ else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)))
+ {
+ log_error ("error allocating connection data for %s: %s\n",
+ listentbl[idx].name, strerror (errno) );
+ assuan_sock_close (fd);
+ }
+ else
+ {
+ ctrl->thread_startup.fd = fd;
+ ret = npth_create (&thread, &tattr,
+ listentbl[idx].func, ctrl);
+ if (ret)
+ {
+ log_error ("error spawning connection handler for %s:"
+ " %s\n", listentbl[idx].name, strerror (ret));
+ assuan_sock_close (fd);
+ xfree (ctrl);
+ }
+ }
+ }
+ }
+ }
+
+ if (sock_inotify_fd != -1)
+ close (sock_inotify_fd);
+ if (home_inotify_fd != -1)
+ close (home_inotify_fd);
+ cleanup ();
+ log_info (_("%s %s stopped\n"), strusage(11), strusage(13));
+ npth_attr_destroy (&tattr);
+}
+
+
+
+/* Helper for check_own_socket. */
+static gpg_error_t
+check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length)
+{
+ membuf_t *mb = opaque;
+ put_membuf (mb, buffer, length);
+ return 0;
+}
+
+
+/* The thread running the actual check. We need to run this in a
+ * separate thread so that check_own_thread can be called from the
+ * timer tick. */
+static void *
+check_own_socket_thread (void *arg)
+{
+ int rc;
+ char *sockname = arg;
+ assuan_context_t ctx = NULL;
+ membuf_t mb;
+ char *buffer;
+
+ check_own_socket_running++;
+
+ rc = assuan_new (&ctx);
+ if (rc)
+ {
+ log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ assuan_set_flag (ctx, ASSUAN_NO_LOGGING, 1);
+
+ rc = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
+ if (rc)
+ {
+ log_error ("can't connect my own socket: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ init_membuf (&mb, 100);
+ rc = assuan_transact (ctx, "GETINFO pid", check_own_socket_pid_cb, &mb,
+ NULL, NULL, NULL, NULL);
+ put_membuf (&mb, "", 1);
+ buffer = get_membuf (&mb, NULL);
+ if (rc || !buffer)
+ {
+ log_error ("sending command \"%s\" to my own socket failed: %s\n",
+ "GETINFO pid", gpg_strerror (rc));
+ rc = 1;
+ }
+ else if ( (pid_t)strtoul (buffer, NULL, 10) != getpid ())
+ {
+ log_error ("socket is now serviced by another server\n");
+ rc = 1;
+ }
+ else if (opt.verbose > 1)
+ log_error ("socket is still served by this server\n");
+
+ xfree (buffer);
+
+ leave:
+ xfree (sockname);
+ if (ctx)
+ assuan_release (ctx);
+ if (rc)
+ {
+ /* We may not remove the socket as it is now in use by another
+ * server. */
+ inhibit_socket_removal = 1;
+ shutdown_pending = 2;
+ log_info ("this process is useless - shutting down\n");
+ }
+ check_own_socket_running--;
+ return NULL;
+}
+
+
+/* Check whether we are still listening on our own socket. In case
+ * another keyboxd process started after us has taken ownership of our
+ * socket, we would linger around without any real task. Thus we
+ * better check once in a while whether we are really needed. */
+static void
+check_own_socket (void)
+{
+ char *sockname;
+ npth_t thread;
+ npth_attr_t tattr;
+ int err;
+
+ if (disable_check_own_socket)
+ return;
+
+ if (check_own_socket_running || shutdown_pending)
+ return; /* Still running or already shutting down. */
+
+ sockname = make_filename_try (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL);
+ if (!sockname)
+ return; /* Out of memory. */
+
+ err = npth_attr_init (&tattr);
+ if (err)
+ return;
+ npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
+ err = npth_create (&thread, &tattr, check_own_socket_thread, sockname);
+ if (err)
+ log_error ("error spawning check_own_socket_thread: %s\n", strerror (err));
+ npth_attr_destroy (&tattr);
+}
+
+
+
+/* Figure out whether a keyboxd is available and running. Prints an
+ * error if not. If SILENT is true, no messages are printed. Returns
+ * 0 if the agent is running. */
+static int
+check_for_running_kbxd (int silent)
+{
+ gpg_error_t err;
+ char *sockname;
+ assuan_context_t ctx = NULL;
+
+ sockname = make_filename_try (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL);
+ if (!sockname)
+ return gpg_error_from_syserror ();
+
+ err = assuan_new (&ctx);
+ if (!err)
+ err = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
+ xfree (sockname);
+ if (err)
+ {
+ if (!silent)
+ log_error (_("no keyboxd running in this session\n"));
+
+ if (ctx)
+ assuan_release (ctx);
+ return -1;
+ }
+
+ if (!opt.quiet && !silent)
+ log_info ("keyboxd running and available\n");
+
+ assuan_release (ctx);
+ return 0;
+}
diff --git a/kbx/keyboxd.h b/kbx/keyboxd.h
new file mode 100644
index 000000000..edef8975c
--- /dev/null
+++ b/kbx/keyboxd.h
@@ -0,0 +1,156 @@
+/* keyboxd.h - Global definitions for keyboxd
+ * Copyright (C) 2018 Werner Koch
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef KEYBOXD_H
+#define KEYBOXD_H
+
+#ifdef GPG_ERR_SOURCE_DEFAULT
+#error GPG_ERR_SOURCE_DEFAULT already defined
+#endif
+#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_KEYBOX
+#include <gpg-error.h>
+
+#include <gcrypt.h>
+#include "../common/util.h"
+#include "../common/membuf.h"
+#include "../common/sysutils.h" /* (gnupg_fd_t) */
+
+
+/* A large struct name "opt" to keep global flags */
+struct
+{
+ unsigned int debug; /* Debug flags (DBG_foo_VALUE) */
+ int verbose; /* Verbosity level */
+ int quiet; /* Be as quiet as possible */
+ int dry_run; /* Don't change any persistent data */
+ int batch; /* Batch mode */
+
+ /* True if we are running detached from the tty. */
+ int running_detached;
+
+} opt;
+
+
+/* Bit values for the --debug option. */
+#define DBG_MPI_VALUE 2 /* debug mpi details */
+#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */
+#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */
+#define DBG_CACHE_VALUE 64 /* debug the caching */
+#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */
+#define DBG_HASHING_VALUE 512 /* debug hashing operations */
+#define DBG_IPC_VALUE 1024 /* Enable Assuan debugging. */
+#define DBG_CLOCK_VALUE 4096 /* debug timings (required build option). */
+#define DBG_LOOKUP_VALUE 8192 /* debug the key lookup */
+
+/* Test macros for the debug option. */
+#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
+#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE)
+#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE)
+#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE)
+#define DBG_IPC (opt.debug & DBG_IPC_VALUE)
+#define DBG_CLOCK (opt.debug & DBG_CLOCK_VALUE)
+#define DBG_LOOKUP (opt.debug & DBG_LOOKUP_VALUE)
+
+
+/* Declaration of a database request object. This is used for all
+ * database operation (search, insert, update, delete). */
+struct db_request_s;
+typedef struct db_request_s *db_request_t;
+
+/* Forward reference for local definitions in command.c. */
+struct server_local_s;
+
+#if SIZEOF_UNSIGNED_LONG == 8
+# define SERVER_CONTROL_MAGIC 0x6b6579626f786420
+#else
+# define SERVER_CONTROL_MAGIC 0x6b627864
+#endif
+
+/* Collection of data per session (aka connection). */
+struct server_control_s
+{
+ unsigned long magic;/* Always has SERVER_CONTROL_MAGIC. */
+ int refcount; /* Count additional references to this object. */
+
+ /* Private data used to fire up the connection thread. We use this
+ * structure do avoid an extra allocation for only a few bytes while
+ * spawning a new connection thread. */
+ struct {
+ gnupg_fd_t fd;
+ } thread_startup;
+
+ /* Private data of the server (kbxserver.c). */
+ struct server_local_s *server_local;
+
+ /* Environment settings for the connection. */
+ char *lc_messages;
+
+ /* Miscellaneous info on the connection. */
+ unsigned long client_pid;
+ int client_uid;
+
+ /* Two database request objects used with a connection. They are
+ * auto-created as needed. */
+ db_request_t opgp_req;
+ db_request_t x509_req;
+
+ /* Flags for the current request. */
+ unsigned int no_data_return : 1; /* Used by SEARCH and NEXT. */
+};
+
+
+/* This is a special version of the usual _() gettext macro. It
+ * assumes a server connection control variable with the name "ctrl"
+ * and uses that to translate a string according to the locale set for
+ * the connection. The macro LunderscoreIMPL is used by i18n to
+ * actually define the inline function when needed. */
+#if defined (ENABLE_NLS) || defined (USE_SIMPLE_GETTEXT)
+#define L_(a) keyboxd_Lunderscore (ctrl, (a))
+#define LunderscorePROTO \
+ static inline const char *keyboxd_Lunderscore (ctrl_t ctrl, \
+ const char *string) \
+ GNUPG_GCC_ATTR_FORMAT_ARG(2);
+#define LunderscoreIMPL \
+ static inline const char * \
+ keyboxd_Lunderscore (ctrl_t ctrl, const char *string) \
+ { \
+ return ctrl? i18n_localegettext (ctrl->lc_messages, string) \
+ /* */: gettext (string); \
+ }
+#else
+#define L_(a) (a)
+#endif
+
+
+/*-- keyboxd.c --*/
+void kbxd_exit (int rc) GPGRT_ATTR_NORETURN;
+void kbxd_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what,
+ int printchar, int current, int total),
+ ctrl_t ctrl);
+const char *get_kbxd_socket_name (void);
+int get_kbxd_active_connection_count (void);
+void kbxd_sighup_action (void);
+
+
+/*-- kbxserver.c --*/
+gpg_error_t kbxd_write_data_line (ctrl_t ctrl,
+ const void *buffer_arg, size_t size);
+void kbxd_start_command_handler (ctrl_t, gnupg_fd_t, unsigned int);
+
+#endif /*KEYBOXD_H*/
diff --git a/po/Makevars b/po/Makevars
index 07778e055..d5318a9b0 100644
--- a/po/Makevars
+++ b/po/Makevars
@@ -53,6 +53,7 @@ XGETTEXT_OPTIONS = \
--flag=xasprintf:1:c-format \
--flag=xtryasprintf:1:c-format \
--flag=log_debug_with_string:2:c-format \
+ --flag=status_printf:3:c-format \
--flag=print_assuan_status:3:c-format \
--flag=vprint_assuan_status:3:c-format \
--flag=agent_print_status:3:c-format \
diff --git a/sm/keydb.c b/sm/keydb.c
index 16ed85be5..5c7ff6fce 100644
--- a/sm/keydb.c
+++ b/sm/keydb.c
@@ -224,7 +224,7 @@ maybe_create_keybox (char *filename, int force, int *r_created)
/* 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);
+ rc = _keybox_write_header_blob (fp, NULL, 0);
if (rc)
{
fclose (fp);
diff --git a/tools/gpg-connect-agent.c b/tools/gpg-connect-agent.c
index 7eb7ffa3a..0a128b31a 100644
--- a/tools/gpg-connect-agent.c
+++ b/tools/gpg-connect-agent.c
@@ -60,12 +60,14 @@ enum cmd_and_opt_values
oHomedir,
oAgentProgram,
oDirmngrProgram,
+ oKeyboxdProgram,
oHex,
oDecode,
oNoExtConnect,
oDirmngr,
+ oKeyboxd,
oUIServer,
- oNoAutostart,
+ oNoAutostart
};
@@ -79,6 +81,7 @@ static ARGPARSE_OPTS opts[] = {
ARGPARSE_s_n (oHex, "hex", N_("print data out hex encoded")),
ARGPARSE_s_n (oDecode,"decode", N_("decode received data lines")),
ARGPARSE_s_n (oDirmngr,"dirmngr", N_("connect to the dirmngr")),
+ ARGPARSE_s_n (oKeyboxd,"keyboxd", N_("connect to the keyboxd")),
ARGPARSE_s_n (oUIServer, "uiserver", "@"),
ARGPARSE_s_s (oRawSocket, "raw-socket",
N_("|NAME|connect to Assuan socket NAME")),
@@ -97,6 +100,7 @@ static ARGPARSE_OPTS opts[] = {
ARGPARSE_s_s (oHomedir, "homedir", "@" ),
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"),
+ ARGPARSE_s_s (oKeyboxdProgram, "keyboxd-program", "@"),
ARGPARSE_end ()
};
@@ -109,11 +113,13 @@ struct
int quiet; /* Be extra quiet. */
int autostart; /* Start the server if not running. */
const char *homedir; /* Configuration directory name */
- const char *agent_program; /* Value of --agent-program. */
+ const char *agent_program; /* Value of --agent-program. */
const char *dirmngr_program; /* Value of --dirmngr-program. */
+ const char *keyboxd_program; /* Value of --keyboxd-program. */
int hex; /* Print data lines in hex format. */
int decode; /* Decode received data lines. */
int use_dirmngr; /* Use the dirmngr and not gpg-agent. */
+ int use_keyboxd; /* Use the keyboxd and not gpg-agent. */
int use_uiserver; /* Use the standard UI server. */
const char *raw_socket; /* Name of socket to connect in raw mode. */
const char *tcp_socket; /* Name of server to connect in tcp mode. */
@@ -1200,10 +1206,12 @@ main (int argc, char **argv)
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oAgentProgram: opt.agent_program = pargs.r.ret_str; break;
case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break;
+ case oKeyboxdProgram: opt.keyboxd_program = pargs.r.ret_str; break;
case oNoAutostart: opt.autostart = 0; break;
case oHex: opt.hex = 1; break;
case oDecode: opt.decode = 1; break;
case oDirmngr: opt.use_dirmngr = 1; break;
+ case oKeyboxd: opt.use_keyboxd = 1; break;
case oUIServer: opt.use_uiserver = 1; break;
case oRawSocket: opt.raw_socket = pargs.r.ret_str; break;
case oTcpSocket: opt.tcp_socket = pargs.r.ret_str; break;
@@ -1879,7 +1887,10 @@ main (int argc, char **argv)
}
if (opt.verbose)
- log_info ("closing connection to agent\n");
+ log_info ("closing connection to %s\n",
+ opt.use_dirmngr? "dirmngr" :
+ opt.use_keyboxd? "keyboxd" :
+ "agent");
/* XXX: We would like to release the context here, but libassuan
nicely says good bye to the server, which results in a SIGPIPE if
@@ -2224,6 +2235,13 @@ start_agent (void)
opt.autostart,
!opt.quiet, 0,
NULL, NULL);
+ else if (opt.use_keyboxd)
+ err = start_new_keyboxd (&ctx,
+ GPG_ERR_SOURCE_DEFAULT,
+ opt.keyboxd_program,
+ opt.autostart,
+ !opt.quiet, 0,
+ NULL, NULL);
else
err = start_new_gpg_agent (&ctx,
GPG_ERR_SOURCE_DEFAULT,
@@ -2239,12 +2257,15 @@ start_agent (void)
{
if (!opt.autostart
&& (gpg_err_code (err)
- == (opt.use_dirmngr? GPG_ERR_NO_DIRMNGR : GPG_ERR_NO_AGENT)))
+ == (opt.use_dirmngr? GPG_ERR_NO_DIRMNGR :
+ opt.use_keyboxd? GPG_ERR_NO_KEYBOXD : GPG_ERR_NO_AGENT)))
{
/* In the no-autostart case we don't make gpg-connect-agent
fail on a missing server. */
log_info (opt.use_dirmngr?
_("no dirmngr running in this session\n"):
+ opt.use_keyboxd?
+ _("no keybox daemon running in this session\n"):
_("no gpg-agent running in this session\n"));
exit (0);
}