From d4489be467e7229e17fb17a0489bf711d6ce66d6 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 5 Sep 2018 16:57:30 +0200 Subject: [PATCH 01/19] common: New function status_printf. * common/asshelp2.c (set_assuan_context_func): New. (status_printf): New. * po/Makevars (XGETTEXT_OPTIONS): Add status_printf -- This is a first patch to unify the status printing functions. Signed-off-by: Werner Koch --- common/asshelp.h | 6 ++++++ common/asshelp2.c | 37 +++++++++++++++++++++++++++++++++++++ po/Makevars | 1 + 3 files changed, 44 insertions(+) diff --git a/common/asshelp.h b/common/asshelp.h index bf1bd1705..c2c87e3c4 100644 --- a/common/asshelp.h +++ b/common/asshelp.h @@ -82,8 +82,14 @@ 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, 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..32e60dfb0 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,22 @@ 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, 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/po/Makevars b/po/Makevars index 90b0c5b83..60a6fb36e 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 \ From 512be1d04b98a9d6a9067bd34c16513089a0db9f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 5 Sep 2018 17:00:17 +0200 Subject: [PATCH 02/19] kbx: Add framework for a public key daemon. * kbx/keyboxd.c: New. * kbx/keyboxd.h: New. * kbx/kbxserver.c: New. * kbx/keyboxd-w32info.rc: New. * kbx/Makefile.am (EXTRA_DIST): Add new rc file. (resource_objs): Ditto. (libexec_PROGRAMS): New. (common_libs, commonpth_libs): New. (kbxutil_LDADD): Use here. (keyboxd_SOURCES): New. (keyboxd_CFLAGS): New. (keyboxd_LDADD): New. (keyboxd_LDFLAGS): New. (keyboxd_DEPENDENCIES): new. ($(PROGRAMS)): Extend. Signed-off-by: Werner Koch --- configure.ac | 5 +- kbx/Makefile.am | 30 +- kbx/kbxserver.c | 447 ++++++++++ kbx/keyboxd-w32info.rc | 50 ++ kbx/keyboxd.c | 1817 ++++++++++++++++++++++++++++++++++++++++ kbx/keyboxd.h | 137 +++ 6 files changed, 2482 insertions(+), 4 deletions(-) create mode 100644 kbx/kbxserver.c create mode 100644 kbx/keyboxd-w32info.rc create mode 100644 kbx/keyboxd.c create mode 100644 kbx/keyboxd.h diff --git a/configure.ac b/configure.ac index 78a03c420..9f78259e8 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 @@ -505,6 +505,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_DEF_COPYRIGHT_LINE \ @@ -1882,6 +1883,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, diff --git a/kbx/Makefile.am b/kbx/Makefile.am index 8fca24afb..51cabbfb3 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,26 @@ 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 + +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/kbxserver.c b/kbx/kbxserver.c new file mode 100644 index 000000000..59fcb64c7 --- /dev/null +++ b/kbx/kbxserver.c @@ -0,0 +1,447 @@ +/* kbxserver.c - Handle Assuan commands send to the keyboxd + * Copyright (C) 2018 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0+ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "keyboxd.h" +#include + +#include "../common/i18n.h" +#include "../common/server-help.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; + + /* Dummy option. */ + int foo; +}; + + + + +/* 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) + { + 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, "foo")) + { + ctrl->server_local->foo = 1; + } + else 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_foo[] = + "FOO \n" + "\n" + "Bla bla\n" + "more bla."; +static gpg_error_t +cmd_foo (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + + (void)ctrl; + (void)line; + + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + return leave_cmd (ctx, err); +} + + + +static const char hlp_getinfo[] = + "GETINFO \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; +} + + + +/* 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[] = { + { "FOO", cmd_foo, hlp_foo }, + { "GETINFO", cmd_getinfo, hlp_getinfo }, + { "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; +} + + +/* 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; +} + + +/* 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); + } + + 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; + } + } + + + 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); + ctrl->server_local = NULL; + } +} 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..28232b3ea --- /dev/null +++ b/kbx/keyboxd.c @@ -0,0 +1,1817 @@ +/* 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 . + * SPDX-License-Identifier: GPL-3.0+ + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_W32_SYSTEM +# ifndef WINVER +# define WINVER 0x0500 /* Same as in common/sysutils.c */ +# endif +# include +#else /*!HAVE_W32_SYSTEM*/ +# include +# include +#endif /*!HAVE_W32_SYSTEM*/ +#include +#ifdef HAVE_SIGNAL_H +# include +#endif +#include + +#define GNUPG_COMMON_NEED_AFLOCAL +#include "keyboxd.h" +#include /* 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" + + +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" }, + { 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, NULL); + + 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_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); + } + + 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; + 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..7719061f4 --- /dev/null +++ b/kbx/keyboxd.h @@ -0,0 +1,137 @@ +/* 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 . + */ + +#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 + +#include +#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. */ + +/* 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) + +/* 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; + +}; + + +/* 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 --*/ +void kbxd_start_command_handler (ctrl_t, gnupg_fd_t, unsigned int); + +#endif /*KEYBOXD_H*/ From d8a84594abe4be933756db07b987dc8bcd79c8b9 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 2 Aug 2019 13:10:29 +0200 Subject: [PATCH 03/19] common: Change yet unused status_printf function. * common/asshelp2.c (status_printf): Rename to status_no_printf. (status_printf): New. -- Using the the status identifier from status.h is a good idea for the external API but for some inter-component status line a keyword is a better way. Signed-off-by: Werner Koch --- common/asshelp.h | 4 +++- common/asshelp2.c | 21 ++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/common/asshelp.h b/common/asshelp.h index c2c87e3c4..a04a0775c 100644 --- a/common/asshelp.h +++ b/common/asshelp.h @@ -87,7 +87,9 @@ 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, int no, const char *format, +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, diff --git a/common/asshelp2.c b/common/asshelp2.c index 32e60dfb0..8410808e3 100644 --- a/common/asshelp2.c +++ b/common/asshelp2.c @@ -157,7 +157,26 @@ print_assuan_status_strings (assuan_context_t ctx, const char *keyword, ...) /* 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, int no, const char *format, ...) +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; From e22ebf357050f98558b428502a565fc3dc256932 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 5 Aug 2019 09:49:09 +0200 Subject: [PATCH 04/19] common: Remove code duplication for service starting. * common/homedir.c (gpg_agent_socket_name): New. * common/asshelp.c (start_new_service): New. Based on start_new_gpg_agent. (start_new_gpg_agent): Divert to start_new_service. (start_new_dirmngr): Ditto. -- This prepares for adding yet another service starting function. Signed-off-by: Werner Koch --- common/asshelp.c | 250 +++++++++++++++++++---------------------------- common/homedir.c | 11 +++ 2 files changed, 113 insertions(+), 148 deletions(-) diff --git a/common/asshelp.c b/common/asshelp.c index 5209ea6cf..84bc546e2 100644 --- a/common/asshelp.c +++ b/common/asshelp.c @@ -372,25 +372,34 @@ 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; const char *argv[6]; *r_ctx = NULL; @@ -402,10 +411,26 @@ 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; + default: + err = gpg_error (GPG_ERR_INV_ARG); assuan_release (ctx); return err; } @@ -422,12 +447,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 +467,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 +492,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,32 +500,31 @@ 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)) + if (!(err = lock_spawning (&lock, gnupg_homedir (), lock_name, verbose)) && assuan_socket_connect (ctx, sockname, 0, 0)) { - err = gnupg_spawn_process_detached (program? program : agent_program, + err = gnupg_spawn_process_detached (program? program : program_name, argv, NULL); 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, + err = wait_for_sock (seconds_to_wait, 0, sockname, verbose, ctx, &did_success_msg); } - unlock_spawning (&lock, "agent"); + unlock_spawning (&lock, lock_name); xfree (abs_homedir); xfree (program); } @@ -510,17 +532,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); - err = assuan_transact (ctx, "RESET", - NULL, NULL, NULL, NULL, NULL, NULL); - if (!err) + if (module_name_id == GNUPG_MODULE_NAME_AGENT) + err = assuan_transact (ctx, "RESET", + NULL, NULL, NULL, NULL, NULL, NULL); + + if (!err + && module_name_id == GNUPG_MODULE_NAME_AGENT) { err = send_pinentry_environment (ctx, errsource, opt_lc_ctype, opt_lc_messages, @@ -528,7 +554,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)) { @@ -549,6 +575,26 @@ 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. */ @@ -556,110 +602,18 @@ 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, + 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; - } - - 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)) - { - err = gnupg_spawn_process_detached (dirmngr_program, argv, NULL); - 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; + autostart = 0; +#endif + 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/homedir.c b/common/homedir.c index e9e75d01e..9c680a57f 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) From 0611f548bcd3c772084d6c3111dc88a09a67f65a Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 6 Aug 2019 14:28:08 +0200 Subject: [PATCH 05/19] tools: New option --keyboxd for gpg-connect-agent. * configure.ac: New option --keyboxd-pgm. (KEYBOXD_NAME, KEYBOXD_DISP_NAME): New ac_defines. * common/util.h: Add substitutes for new error codes. (GNUPG_MODULE_NAME_KEYBOXD): New. * common/homedir.c (gnupg_module_name): Support GNUPG_MODULE_NAME_KEYBOXD. * common/asshelp.c (SECS_TO_WAIT_FOR_KEYBOXD): New. (wait_for_sock): Support keyboxd. (start_new_service): Ditto. (start_new_keyboxd): New. * tools/gpg-connect-agent.c: New options --keyboxd and --keyboxd-program. (start_agent): Implement new option. -- This change allows us to test the new keyboxd using our standard helper. It also provides the necessary code to start keyboxd on the fly. Signed-off-by: Werner Koch --- common/asshelp.c | 48 +++++++++++++++++++++++++++++++-------- common/asshelp.h | 10 ++++++++ common/homedir.c | 7 ++++++ common/util.h | 6 +++++ configure.ac | 13 +++++++++++ doc/tools.texi | 11 +++++++++ tools/gpg-connect-agent.c | 29 +++++++++++++++++++---- 7 files changed, 111 insertions(+), 13 deletions(-) diff --git a/common/asshelp.c b/common/asshelp.c index 84bc546e2..a5724fad5 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,14 @@ 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 +/* 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. 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, int verbose, assuan_context_t ctx, int *did_success_msg) { gpg_error_t err = 0; @@ -343,8 +342,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; @@ -357,8 +358,10 @@ wait_for_sock (int secs, int which, const char *sockname, { 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; } @@ -429,6 +432,14 @@ start_new_service (assuan_context_t *r_ctx, 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; + break; default: err = gpg_error (GPG_ERR_INV_ARG); assuan_release (ctx); @@ -520,7 +531,7 @@ start_new_service (assuan_context_t *r_ctx, printed_name, program? program : program_name, gpg_strerror (err)); else - err = wait_for_sock (seconds_to_wait, 0, + err = wait_for_sock (seconds_to_wait, module_name_id, sockname, verbose, ctx, &did_success_msg); } @@ -595,6 +606,25 @@ start_new_gpg_agent (assuan_context_t *r_ctx, } +/* 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_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) +{ + return start_new_service (r_ctx, GNUPG_MODULE_NAME_KEYBOXD, + errsource, keyboxd_program, + NULL, NULL, NULL, + 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. */ diff --git a/common/asshelp.h b/common/asshelp.h index a04a0775c..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 diff --git a/common/homedir.c b/common/homedir.c index 9c680a57f..4425d7811 100644 --- a/common/homedir.c +++ b/common/homedir.c @@ -1115,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/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 df5ca1238..f18e7a26e 100644 --- a/configure.ac +++ b/configure.ac @@ -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="" ) @@ -1870,6 +1878,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]) @@ -2090,6 +2102,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/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/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); } From 1f980d23af8b818ed8246ec6bf13b9c61b963ec0 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 6 Aug 2019 14:59:37 +0200 Subject: [PATCH 06/19] kbx: Allow writing using a estream. * kbx/keybox-file.c (_keybox_write_header_blob): New optional arg stream. Change callers. Signed-off-by: Werner Koch --- g10/keydb.c | 2 +- kbx/keybox-file.c | 18 +++++++++++++----- kbx/keybox-update.c | 4 ++-- kbx/keybox.h | 3 ++- sm/keydb.c | 2 +- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/g10/keydb.c b/g10/keydb.c index a7691bbe2..d3a2709bc 100644 --- a/g10/keydb.c +++ b/g10/keydb.c @@ -448,7 +448,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) 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-update.c b/kbx/keybox-update.c index e09fefc41..d22da1bf8 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..d614c32d1 100644 --- a/kbx/keybox.h +++ b/kbx/keybox.h @@ -81,7 +81,8 @@ 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_keyblock (KEYBOX_HANDLE hd, iobuf_t *r_iobuf, diff --git a/sm/keydb.c b/sm/keydb.c index 53e3cf887..cf643abcd 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); From 5ea6250cc5761612d17ca4fb34eed096f26e2826 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 6 Aug 2019 16:07:33 +0200 Subject: [PATCH 07/19] kbx: Add framework for the SEARCH command * kbx/backend-kbx.c: New. * kbx/backend-support.c: New. * kbx/backend.h: New. * kbx/frontend.c: New. * kbx/frontend.h: New. * kbx/kbxserver.c: Implement SEARCH and NEXT command. * kbx/keybox-search-desc.h (enum pubkey_types): New. * kbx/keybox-search.c (keybox_get_data): New. * kbx/keyboxd.c (main): Add a standard resource. Signed-off-by: Werner Koch --- doc/DETAILS | 11 ++ kbx/Makefile.am | 8 +- kbx/backend-kbx.c | 292 +++++++++++++++++++++++++++++++++++ kbx/backend-support.c | 128 ++++++++++++++++ kbx/backend.h | 95 ++++++++++++ kbx/frontend.c | 320 +++++++++++++++++++++++++++++++++++++++ kbx/frontend.h | 36 +++++ kbx/kbxserver.c | 263 ++++++++++++++++++++++++++++---- kbx/keybox-search-desc.h | 9 ++ kbx/keybox-search.c | 63 +++++++- kbx/keybox.h | 3 + kbx/keyboxd.c | 24 ++- kbx/keyboxd.h | 19 +++ 13 files changed, 1237 insertions(+), 34 deletions(-) create mode 100644 kbx/backend-kbx.c create mode 100644 kbx/backend-support.c create mode 100644 kbx/backend.h create mode 100644 kbx/frontend.c create mode 100644 kbx/frontend.h diff --git a/doc/DETAILS b/doc/DETAILS index 3046523da..3fa651e12 100644 --- a/doc/DETAILS +++ b/doc/DETAILS @@ -1137,6 +1137,17 @@ 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_TYPE + The type of the public key in the following D-lines or communicated + via a pipe. is the value of =enum pubkey_types=. + + + + * Format of the --attribute-fd output diff --git a/kbx/Makefile.am b/kbx/Makefile.am index 51cabbfb3..48ed31740 100644 --- a/kbx/Makefile.am +++ b/kbx/Makefile.am @@ -73,8 +73,12 @@ kbxutil_LDADD = $(common_libs) \ keyboxd_SOURCES = \ - keyboxd.c keyboxd.h \ - kbxserver.c + keyboxd.c keyboxd.h \ + kbxserver.c \ + frontend.c frontend.h \ + backend.h backend-support.c \ + backend-kbx.c \ + $(common_sources) keyboxd_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) \ $(INCICONV) diff --git a/kbx/backend-kbx.c b/kbx/backend-kbx.c new file mode 100644 index 000000000..7f9ef358b --- /dev/null +++ b/kbx/backend-kbx.c @@ -0,0 +1,292 @@ +/* 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 . + * SPDX-License-Identifier: GPL-3.0+ + */ + +#include +#include +#include +#include +#include + +#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; /* Id of this 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 0; +} + + +/* 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); +} + + +/* 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. */ + 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) + { + err = gpg_error_from_syserror (); + goto leave; + } + part->backend_id = backend_hd->backend_id; + part->kbx_hd = keybox_new_openpgp (backend_hd->token, 0); + if (!part->kbx_hd) + { + err = gpg_error_from_syserror (); + xfree (part); + goto leave; + } + part->next = request->part; + request->part = part; + } + + 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; + + err = keybox_get_data (part->kbx_hd, &buffer, &buflen, &pubkey_type); + if (err) + goto leave; + /* Note: be_return_pubkey always takes ownership of BUFFER. */ + err = be_return_pubkey (ctrl, buffer, buflen, pubkey_type); + } + + leave: + return err; +} diff --git a/kbx/backend-support.c b/kbx/backend-support.c new file mode 100644 index 000000000..28b51875c --- /dev/null +++ b/kbx/backend-support.c @@ -0,0 +1,128 @@ +/* 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 . + * SPDX-License-Identifier: GPL-3.0+ + */ + +#include +#include +#include +#include +#include + +#include "keyboxd.h" +#include "../common/i18n.h" +#include "../common/asshelp.h" +#include "backend.h" + + +/* Common definition part of all backend handle. */ +struct backend_handle_s +{ + enum database_types db_type; +}; + + + +/* 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_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_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); + } +} + + +/* Return the public key (BUFFER,BUFLEN) which has the type + * PUBVKEY_TYPE to the caller. Owenership of BUFFER is taken by thgis + * function even in the error case. */ +gpg_error_t +be_return_pubkey (ctrl_t ctrl, void *buffer, size_t buflen, + enum pubkey_types pubkey_type) +{ + gpg_error_t err; + + err = status_printf (ctrl, "PUBKEY_TYPE", "%d", pubkey_type); + if (err) + goto leave; + + if (ctrl->no_data_return) + err = 0; + else + err = kbxd_write_data_line(ctrl, buffer, buflen); + + leave: + xfree (buffer); + return err; +} diff --git a/kbx/backend.h b/kbx/backend.h new file mode 100644 index 000000000..e96f5023c --- /dev/null +++ b/kbx/backend.h @@ -0,0 +1,95 @@ +/* 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 . + */ + +#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_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 databsde 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; +}; +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. */ + + db_request_part_t part; + + /* Counter to track the next to be searched database index. */ + unsigned int next_dbidx; +}; + + + +/*-- 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_return_pubkey (ctrl_t ctrl, void *buffer, size_t buflen, + enum pubkey_types pubkey_type); + + +/*-- 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_search (ctrl_t ctrl, backend_handle_t hd, + db_request_t request, + KEYDB_SEARCH_DESC *desc, unsigned int ndesc); + + +#endif /*KBX_BACKEND_H*/ diff --git a/kbx/frontend.c b/kbx/frontend.c new file mode 100644 index 000000000..233cc1e0b --- /dev/null +++ b/kbx/frontend.c @@ -0,0 +1,320 @@ +/* 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 . + * SPDX-License-Identifier: GPL-3.0+ + */ + +#include +#include +#include +#include +#include + +#include "keyboxd.h" +#include +#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 which 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; + backend_handle_t handle = NULL; + unsigned int n, dbidx; + + /* Do tilde expansion etc. */ + 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); + + n = strlen (filename); + 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_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 data bases. Note that the 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) + 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; + + 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_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. */ + 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_KBX: + err = be_kbx_search (ctrl, db->backend_handle, request, + desc, ndesc); + 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; + if (!err) + request->any_found = 1; + else if (gpg_err_code (err) == GPG_ERR_EOF) + { + 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 . + */ + +#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 index 59fcb64c7..1f70ef779 100644 --- a/kbx/kbxserver.c +++ b/kbx/kbxserver.c @@ -1,5 +1,5 @@ /* kbxserver.c - Handle Assuan commands send to the keyboxd - * Copyright (C) 2018 g10 Code GmbH + * Copyright (C) 2019 g10 Code GmbH * * This file is part of GnuPG. * @@ -31,10 +31,11 @@ #include "keyboxd.h" #include - #include "../common/i18n.h" #include "../common/server-help.h" - +#include "../common/userids.h" +#include "../common/asshelp.h" +#include "frontend.h" @@ -65,12 +66,99 @@ struct server_local_s unsigned int inhibit_data_logging : 1; unsigned int inhibit_data_logging_now : 1; - /* Dummy option. */ - int foo; + /* 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; }; + +/* 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; +} + + +/* 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); + + /* 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 (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_now = 0; + ctrl->server_local->inhibit_data_logging_count += size; + } + + return err; +} + + /* Helper to print a message while leaving a command. */ static gpg_error_t @@ -100,11 +188,7 @@ 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, "foo")) - { - ctrl->server_local->foo = 1; - } - else if (!strcmp (key, "lc-messages")) + if (!strcmp (key, "lc-messages")) { if (ctrl->lc_messages) xfree (ctrl->lc_messages); @@ -120,22 +204,151 @@ option_handler (assuan_context_t ctx, const char *key, const char *value) -static const char hlp_foo[] = - "FOO \n" +static const char hlp_search[] = + "SEARCH [--no-data] [[--more] PATTERN]\n" "\n" - "Bla bla\n" - "more bla."; + "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_foo (assuan_context_t ctx, char *line) +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 && 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; + } + + 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->no_data_return = opt_no_data; + 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; + 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; - (void)ctrl; - (void)line; + opt_no_data = has_option (line, "--no-data"); + line = skip_options (line); - err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + 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->no_data_return = opt_no_data; + 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, 0); + else + err = kbxd_search (ctrl, &ctrl->server_local->search_desc, 1, 0); + if (err) + goto leave; + + leave: + ctrl->no_data_return = 0; return leave_cmd (ctx, err); } @@ -250,7 +463,8 @@ register_commands (assuan_context_t ctx) assuan_handler_t handler; const char * const help; } table[] = { - { "FOO", cmd_foo, hlp_foo }, + { "SEARCH", cmd_search, hlp_search }, + { "NEXT", cmd_next, hlp_next }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "KILLKEYBOXD",cmd_killkeyboxd,hlp_killkeyboxd }, { "RELOADKEYBOXD",cmd_reloadkeyboxd,hlp_reloadkeyboxd }, @@ -306,16 +520,6 @@ kbxd_assuan_log_monitor (assuan_context_t ctx, unsigned int cat, } -/* 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; -} - - /* 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. */ @@ -441,6 +645,7 @@ kbxd_start_command_handler (ctrl_t ctrl, gnupg_fd_t fd, unsigned int session_id) ctrl->refcount); else { + xfree (ctrl->server_local->multi_search_desc); xfree (ctrl->server_local); ctrl->server_local = NULL; } diff --git a/kbx/keybox-search-desc.h b/kbx/keybox-search-desc.h index 7286d2ae3..fdd0bcbf9 100644 --- a/kbx/keybox-search-desc.h +++ b/kbx/keybox-search-desc.h @@ -47,6 +47,15 @@ typedef enum { } 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; diff --git a/kbx/keybox-search.c b/kbx/keybox-search.c index 77469a24c..971f93745 100644 --- a/kbx/keybox-search.c +++ b/kbx/keybox-search.c @@ -1180,11 +1180,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.h b/kbx/keybox.h index d614c32d1..8a22580dd 100644 --- a/kbx/keybox.h +++ b/kbx/keybox.h @@ -85,6 +85,9 @@ 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.c b/kbx/keyboxd.c index 28232b3ea..5a34f237f 100644 --- a/kbx/keyboxd.c +++ b/kbx/keyboxd.c @@ -56,7 +56,7 @@ #include "../common/init.h" #include "../common/gc-opt-flags.h" #include "../common/exechelp.h" - +#include "frontend.h" enum cmd_and_opt_values { @@ -127,6 +127,8 @@ static struct debug_flags_s debug_flags [] = { 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 "?". */ }; @@ -727,6 +729,9 @@ main (int argc, char **argv ) 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); @@ -848,6 +853,22 @@ main (int argc, char **argv ) 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); @@ -974,6 +995,7 @@ 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); diff --git a/kbx/keyboxd.h b/kbx/keyboxd.h index 7719061f4..edef8975c 100644 --- a/kbx/keyboxd.h +++ b/kbx/keyboxd.h @@ -55,6 +55,8 @@ struct #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) @@ -62,6 +64,14 @@ struct #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; @@ -95,6 +105,13 @@ struct server_control_s 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. */ }; @@ -132,6 +149,8 @@ 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*/ From 1545b948e1c8e8fa4873d434fb790a88ed96091c Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 9 Sep 2019 09:01:28 +0200 Subject: [PATCH 08/19] kbx: Allow searching from start. * kbx/kbxserver.c (cmd_search): Detect empty pattern. -- Signed-off-by: Werner Koch --- kbx/backend-kbx.c | 2 +- kbx/kbxserver.c | 34 +++++++++++++++++++++++----------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/kbx/backend-kbx.c b/kbx/backend-kbx.c index 7f9ef358b..b8d39c2ed 100644 --- a/kbx/backend-kbx.c +++ b/kbx/backend-kbx.c @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, see . - * SPDX-License-Identifier: GPL-3.0+ + * SPDX-License-Identifier: GPL-3.0-or-later */ #include diff --git a/kbx/kbxserver.c b/kbx/kbxserver.c index 1f70ef779..a54444178 100644 --- a/kbx/kbxserver.c +++ b/kbx/kbxserver.c @@ -225,22 +225,34 @@ cmd_search (assuan_context_t ctx, char *line) ctrl->server_local->search_any_found = 0; - if (!*line && opt_more) + if (!*line) { - err = set_error (GPG_ERR_INV_ARG, "--more but no pattern"); - goto leave; + 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 if (!*line && ctrl->server_local->search_expecting_more) + else { - /* 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; + err = classify_user_id (line, &ctrl->server_local->search_desc, 0); + if (err) + goto leave; } - 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 From 5e00c1773d8fd44ba95b39a48e12b0ec94ac8cbe Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 9 Sep 2019 14:25:02 +0200 Subject: [PATCH 09/19] kbx: Fix keyboxd search first. * kbx/kbxserver.c (cmd_next): Switch to mode next if needed. -- Signed-off-by: Werner Koch --- kbx/kbxserver.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/kbx/kbxserver.c b/kbx/kbxserver.c index a54444178..df110b301 100644 --- a/kbx/kbxserver.c +++ b/kbx/kbxserver.c @@ -352,10 +352,24 @@ cmd_next (assuan_context_t ctx, char *line) ctrl->no_data_return = opt_no_data; 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, 0); + { + /* 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 - err = kbxd_search (ctrl, &ctrl->server_local->search_desc, 1, 0); + { + /* 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; From aba82684fe14289cf62b4694bc398f3a274b4762 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 9 Sep 2019 14:34:09 +0200 Subject: [PATCH 10/19] gpg: New option --use-keyboxd. * g10/gpg.c (oUseKeyboxd,oKeyboxdProgram): New consts. (opts): New options --use-keyboxd and --keyboxd-program. (main): Implement them. * g10/keydb.c: Move some defs out to ... * g10/keydb-private.h: new file. * g10/keydb.c: prefix function names with "internal" and move original functions to ... * g10/call-keyboxd.c: new file. Divert to the internal fucntion if --use-keyboxd is used. Add a CTRL arg to most fucntions and change all callers. * g10/Makefile.am (common_source): Add new files. (noinst_PROGRAMS): Do bot build gpgcompose. -- Note that this is just the framework with only a basic implementation of searching via keyboxd. Signed-off-by: Werner Koch --- g10/Makefile.am | 32 +- g10/call-keyboxd.c | 842 +++++++++++++++++++++++++++++++++++++ g10/delkey.c | 6 +- g10/export.c | 2 +- g10/getkey.c | 34 +- g10/gpg.c | 21 +- g10/gpg.h | 12 +- g10/gpgcompose.c | 4 +- g10/import.c | 15 +- g10/keydb-private.h | 176 ++++++++ g10/keydb.c | 240 +++-------- g10/keydb.h | 71 ++-- g10/keyedit.c | 6 +- g10/keygen.c | 4 +- g10/keylist.c | 2 +- g10/keyserver.c | 2 +- g10/main.h | 2 +- g10/misc.c | 4 +- g10/options.h | 3 + g10/photoid.c | 2 +- g10/revoke.c | 4 +- g10/sign.c | 12 +- g10/t-keydb-get-keyblock.c | 6 +- g10/t-keydb.c | 7 +- g10/tofu.c | 2 +- g10/trustdb.c | 2 +- 26 files changed, 1221 insertions(+), 292 deletions(-) create mode 100644 g10/call-keyboxd.c create mode 100644 g10/keydb-private.h diff --git a/g10/Makefile.am b/g10/Makefile.am index 884b4749b..1404014e9 100644 --- a/g10/Makefile.am +++ b/g10/Makefile.am @@ -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 = \ @@ -99,7 +99,10 @@ common_source = \ filter.h \ free-packet.c \ getkey.c \ - keydb.c keydb.h \ + keydb.h \ + keydb-private.h \ + call-keyboxd.c \ + keydb.c \ keyring.c keyring.h \ seskey.c \ kbnode.c \ @@ -160,7 +163,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 @@ -179,29 +182,32 @@ gpg_LDADD = $(LDADD) $(SQLITE3_LIBS) $(LIBGCRYPT_LIBS) $(LIBREADLINE) \ $(LIBICONV) $(resource_objs) $(extra_sys_libs) gpg_LDFLAGS = $(extra_bin_ldflags) gpgv_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) \ - $(GPG_ERROR_LIBS) \ + $(LIBASSUAN_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) $(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) $(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) $(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..f05a9e85f --- /dev/null +++ b/g10/call-keyboxd.c @@ -0,0 +1,842 @@ +/* call-keyboxd.c - Access to the keyboxd storage server + * Copyright (C) 2019 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LOCALE_H +# include +#endif + +#include "gpg.h" +#include +#include "../common/util.h" +#include "../common/membuf.h" +#include "options.h" +#include "../common/i18n.h" +#include "../common/asshelp.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 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; +}; + + + + +/* 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 + assuan_release (kbl->ctx); + 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; +} + + +/* Get a context for accessing keyboxd. If no context is available a + * new one is created and if necessary keyboxd is started. On success + * an assuan context is stored at R_CTX. This context may only be + * released by means of close_context. Note that NULL is stored at + * R_CTX on error. */ +static gpg_error_t +open_context (ctrl_t ctrl, assuan_context_t *r_ctx) +{ + gpg_error_t err; + keyboxd_local_t kbl; + + *r_ctx = 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) + { + kbl->per_session_init_done = 1; + } + + kbl->is_active = 1; + + *r_ctx = kbl->ctx; + return 0; + } + + /* None found. Create a new session and retry. */ + kbl = xtrycalloc (1, sizeof *kbl); + if (!kbl) + return gpg_error_from_syserror (); + err = create_new_context (ctrl, &kbl->ctx); + if (err) + { + xfree (kbl); + return err; + } + + /* Although we are not yet using nPth in gpg we better prepare + * to be nPth thread safe. Thus 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; + } +} + + +/* Close the assuan context CTX and return it to a pool of unused + * contexts. If CTX is NULL, the function does nothing. */ +static void +close_context (ctrl_t ctrl, assuan_context_t ctx) +{ + keyboxd_local_t kbl; + + if (!ctx) + return; + + for (kbl = ctrl->keyboxd_local; kbl; kbl = kbl->next) + { + if (kbl->ctx == ctx) + { + if (!kbl->is_active) + log_fatal ("closing inactive keyboxd context %p\n", ctx); + kbl->is_active = 0; + return; + } + } + log_fatal ("closing unknown keyboxd ctx %p\n", ctx); +} + + + +/* 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; + hd->need_search_reset = 1; + + err = open_context (ctrl, &hd->assuan_context); + if (err) + goto leave; + + 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) +{ + if (!hd) + return; + + if (!hd->use_keyboxd) + internal_keydb_deinit (hd); + else + { + close_context (hd->ctrl, hd->assuan_context); + } + 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; +} + + +/* 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->search_result) + { + err = gpg_error (GPG_ERR_VALUE_NOT_FOUND); + goto leave; + } + + pk_no = uid_no = 0; /*FIXME: Get this from the keyboxd. */ + err = keydb_get_keyblock_do_parse (hd->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->search_result); + hd->search_result = NULL; + + + 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->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; + } + + + if (hd->search_result) + { + iobuf_close (hd->search_result); + hd->search_result = NULL; + } + + if (!hd->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->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_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: + { + membuf_t data; + void *buffer; + size_t len; + + init_membuf (&data, 8192); + err = assuan_transact (hd->assuan_context, 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; + } + + /* Fixme: Avoid double allocation. */ + hd->search_result = iobuf_temp_with_content (buffer, len); + } + + + leave: + if (DBG_CLOCK) + log_clock ("%s leave (%sfound)", __func__, err? "not ":""); + return err; +} 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 e8bf14cf5..15861cc0d 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 57617a0a9..2cc56cdbd 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; @@ -793,7 +793,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 (); @@ -1428,7 +1428,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 (); @@ -1574,7 +1574,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 (); @@ -1612,13 +1612,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) @@ -1638,7 +1639,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; @@ -1655,7 +1657,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 (); @@ -1737,7 +1739,7 @@ parse_def_secret_key (ctrl_t ctrl) if (! hd) { - hd = keydb_new (); + hd = keydb_new (ctrl); if (!hd) return NULL; } @@ -2712,7 +2714,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 @@ -4078,7 +4080,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; @@ -4087,7 +4089,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 0bbe72394..145796cbc 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -361,6 +361,7 @@ enum cmd_and_opt_values oUseAgent, oNoUseAgent, oGpgAgentInfo, + oUseKeyboxd, oMergeOnly, oTryAllSecrets, oTrustedKey, @@ -378,6 +379,7 @@ enum cmd_and_opt_values oPersonalDigestPreferences, oPersonalCompressPreferences, oAgentProgram, + oKeyboxdProgram, oDirmngrProgram, oDisableDirmngr, oDisplay, @@ -849,6 +851,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", "@"), @@ -895,6 +898,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", "@"), /* Dummy options with warnings. */ ARGPARSE_s_n (oUseAgent, "use-agent", "@"), @@ -2734,6 +2738,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; @@ -3491,6 +3500,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: @@ -4105,8 +4115,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))) { @@ -4118,9 +4130,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; } @@ -5064,7 +5075,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 c32dbf059..cab36e0b2 100644 --- a/g10/import.c +++ b/g10/import.c @@ -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..c5315b991 --- /dev/null +++ b/g10/keydb-private.h @@ -0,0 +1,176 @@ +/* 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 . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef G10_KEYDB_PRIVATE_H +#define G10_KEYDB_PRIVATE_H + +#include +#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; + + /* The context used to communicate with the keyboxd. */ + assuan_context_t assuan_context; + + /* I/O buffer with the last search result or NULL. */ + iobuf_t search_result; + + /* Flag indicating that a search reset is required. */ + unsigned int need_search_reset : 1; + + /* 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 d3a2709bc..6ca239475 100644 --- a/g10/keydb.c +++ b/g10/keydb.c @@ -37,25 +37,10 @@ #include "keydb.h" #include "../common/i18n.h" +#include "keydb-private.h" /* For struct keydb_handle_s */ + static int active_handles; -typedef enum - { - KEYDB_RESOURCE_TYPE_NONE = 0, - KEYDB_RESOURCE_TYPE_KEYRING, - KEYDB_RESOURCE_TYPE_KEYBOX - } KeydbResourceType; -#define MAX_KEYDB_RESOURCES 40 - -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); @@ -875,26 +792,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; @@ -936,28 +844,21 @@ keydb_new (void) keydb_stats.handles++; if (die) - { - keydb_release (hd); - gpg_err_set_errno (reterrno); - hd = NULL; - } + err = gpg_error_from_errno (reterrno); - leave: - if (!hd) - log_error (_("error opening key DB: %s\n"), - gpg_strerror (gpg_error_from_syserror())); - - 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--; @@ -979,19 +880,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) @@ -1007,7 +906,7 @@ keydb_lock (KEYDB_HANDLE hd) void keydb_disable_caching (KEYDB_HANDLE hd) { - if (hd) + if (hd && !hd->use_keyboxd) hd->no_caching = 1; } @@ -1029,6 +928,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) @@ -1152,6 +1054,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) { @@ -1183,6 +1087,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) { @@ -1347,6 +1253,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 @@ -1356,17 +1263,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) { @@ -1385,8 +1286,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; } } @@ -1434,9 +1334,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; } @@ -1485,6 +1382,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. * @@ -1499,20 +1397,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); @@ -1575,6 +1469,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 @@ -1585,13 +1480,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); @@ -1650,12 +1544,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); @@ -1708,6 +1601,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; @@ -1759,6 +1655,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)) @@ -1781,38 +1680,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; @@ -1840,6 +1734,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 @@ -1857,21 +1752,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) { @@ -1879,26 +1769,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); } @@ -1926,7 +1801,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. @@ -2016,9 +1891,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 6ad8dce4c..25304ed25 100644 --- a/g10/keydb.h +++ b/g10/keydb.h @@ -163,6 +163,38 @@ is_in_klist (struct key_item *k, PKT_signature *sig) } +/*-- call-keyboxd.c --*/ + +/* 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 --*/ @@ -181,17 +213,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. */ @@ -206,18 +227,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); @@ -228,13 +237,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); @@ -323,7 +325,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. */ @@ -383,13 +385,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); @@ -397,7 +400,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 1bf5de9b2..9238d5d53 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 d9037d29d..5aeda36ce 100644 --- a/g10/keygen.c +++ b/g10/keygen.c @@ -4332,7 +4332,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; @@ -5184,7 +5184,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 1274775d2..5f5fe9d34 100644 --- a/g10/keylist.c +++ b/g10/keylist.c @@ -527,7 +527,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 b07afb128..030bc9297 100644 --- a/g10/keyserver.c +++ b/g10/keyserver.c @@ -1196,7 +1196,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/options.h b/g10/options.h index 234929b15..bfa2cc31e 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; @@ -284,6 +285,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 f9720d329..9bb4ab58f 100644 --- a/g10/photoid.c +++ b/g10/photoid.c @@ -338,7 +338,7 @@ show_photos (ctrl_t ctrl, const struct user_attribute *attrs, int count, #endif /* make command grow */ - command=pct_expando(opt.photo_viewer,&args); + command=pct_expando (ctrl, opt.photo_viewer,&args); if(!command) goto fail; 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 d71580639..8c70b0a2e 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; @@ -97,7 +97,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")); @@ -122,7 +122,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 " @@ -145,7 +145,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" @@ -834,7 +834,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); @@ -1660,7 +1660,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 a230a6c03..352aecede 100644 --- a/g10/trustdb.c +++ b/g10/trustdb.c @@ -2015,7 +2015,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 (); From 2f0fdab8aabdf408495163ef99b2d4d111f74692 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 10 Sep 2019 15:45:58 +0200 Subject: [PATCH 11/19] common: Allow a readlimit for iobuf_esopen. * common/iobuf.c (file_es_filter_ctx_t): Add fields use_readlimit and readlimit. (file_es_filter): Implement them. (iobuf_esopen): Add new arg readlimit. * g10/decrypt-data.c (decrypt_data): Adjust for change. * g10/import.c (import_keys_es_stream): Ditto. -- This comes handy for (length,datablob) style streams. Signed-off-by: Werner Koch --- common/iobuf.c | 37 +++++++++++++++++++++++++++++++++++-- common/iobuf.h | 6 ++++-- g10/decrypt-data.c | 2 +- g10/import.c | 2 +- 4 files changed, 41 insertions(+), 6 deletions(-) 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/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/import.c b/g10/import.c index cab36e0b2..17afaa6f6 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 (); From 6c327b4dd6d8041b84f856ffc2a7d82b352d273f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 10 Sep 2019 15:52:12 +0200 Subject: [PATCH 12/19] kbx: Allow fd-passing for the keyboxd. * kbx/kbxserver.c: Include host2net.h (struct server_local_s): Add field outstream. (prepare_outstream): New. (kbxd_writen): New. (kbxd_write_data_line): Write to file descrptor. Disable the slow human reader friendly data line formatting. (cmd_search, cmd_next): Disable data logging. (kbxd_start_command_handler): Add OUTPUT command. * kbx/keyboxd.c (main): Enable log monitor. -- Signed-off-by: Werner Koch --- kbx/kbxserver.c | 107 +++++++++++++++++++++++++++++++++++++++++++++--- kbx/keyboxd.c | 8 +++- 2 files changed, 109 insertions(+), 6 deletions(-) diff --git a/kbx/kbxserver.c b/kbx/kbxserver.c index df110b301..c693c7ce2 100644 --- a/kbx/kbxserver.c +++ b/kbx/kbxserver.c @@ -35,6 +35,7 @@ #include "../common/server-help.h" #include "../common/userids.h" #include "../common/asshelp.h" +#include "../common/host2net.h" #include "frontend.h" @@ -82,6 +83,9 @@ struct server_local_s 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; }; @@ -97,6 +101,56 @@ get_assuan_ctx_from_ctrl (ctrl_t ctrl) } +/* 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 @@ -109,11 +163,29 @@ kbxd_write_data_line (ctrl_t ctrl, const void *buffer_arg, size_t size) 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 (opt.verbose && buffer && size) + if (0 && opt.verbose && buffer && size) { /* Ease reading of output by limiting the line length. */ size_t n, nbytes; @@ -151,8 +223,8 @@ kbxd_write_data_line (ctrl_t ctrl, const void *buffer_arg, size_t size) leave: if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging) { - ctrl->server_local->inhibit_data_logging_now = 0; ctrl->server_local->inhibit_data_logging_count += size; + ctrl->server_local->inhibit_data_logging_now = 0; } return err; @@ -304,8 +376,14 @@ cmd_search (assuan_context_t ctx, char *line) 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; - if (ctrl->server_local->multi_search_desc_len) + 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 @@ -320,6 +398,7 @@ cmd_search (assuan_context_t ctx, char *line) 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); } @@ -350,8 +429,14 @@ cmd_next (assuan_context_t ctx, char *line) 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; - if (ctrl->server_local->multi_search_desc_len) + 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. */ @@ -375,6 +460,7 @@ cmd_next (assuan_context_t ctx, char *line) leave: ctrl->no_data_return = 0; + ctrl->server_local->inhibit_data_logging = 0; return leave_cmd (ctx, err); } @@ -479,6 +565,13 @@ cmd_reloadkeyboxd (assuan_context_t ctx, char *line) } +static const char hlp_output[] = + "OUTPUT FD[=]\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 @@ -492,6 +585,7 @@ register_commands (assuan_context_t ctx) { "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 } @@ -584,7 +678,9 @@ kbxd_start_command_handler (ctrl_t ctrl, gnupg_fd_t fd, unsigned int session_id) } else { - rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED); + rc = assuan_init_socket_server (ctx, fd, + (ASSUAN_SOCKET_SERVER_ACCEPTED + |ASSUAN_SOCKET_SERVER_FDPASSING)); } if (rc) @@ -658,6 +754,7 @@ kbxd_start_command_handler (ctrl_t ctrl, gnupg_fd_t fd, unsigned int session_id) } } + assuan_close_output_fd (ctx); set_assuan_context_func (NULL); ctrl->server_local->assuan_ctx = NULL; diff --git a/kbx/keyboxd.c b/kbx/keyboxd.c index 5a34f237f..d39b35749 100644 --- a/kbx/keyboxd.c +++ b/kbx/keyboxd.c @@ -58,6 +58,12 @@ #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, @@ -488,7 +494,7 @@ main (int argc, char **argv ) 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, NULL); + setup_libassuan_logging (&opt.debug, kbxd_assuan_log_monitor); setup_libgcrypt_logging (); gcry_set_progress_handler (kbxd_libgcrypt_progress_cb, NULL); From ce9906b008c94c2aa4ac770a981d1e1e0b8aea47 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 10 Sep 2019 16:05:54 +0200 Subject: [PATCH 13/19] gpg: First rough implementation of keyboxd access for key lookup. * g10/Makefile.am: Add nPth flags. * g10/gpg.c: Include npth.h. (gpg_deinit_default_ctrl): Deinit call-keyboxd local data. (main): Init nPth. * g10/keydb-private.h (struct keydb_handle_s): Add field 'kbl' and remove the search result and the assuan context. * g10/call-keyboxd.c (struct keyboxd_local_s): Add more fields. (lock_datastream, unlock_datastream): New. (gpg_keyboxd_deinit_session_data): Adjust for changed data structures. (prepare_data_pipe): New. (open_context): Return kbl instead of an Assuan context. Init mutexes etc. (close_context): Merge into ... (keydb_release): here. Adjust for changed data structures. (datastream_thread): New. (keydb_get_keyblock): Implement datastream stuff. (keydb_search): Ditto. * common/asshelp.c (wait_for_sock): Add arg connect_flags. (start_new_service): Set FDPASSING flag for the keyboxd. -- This code as a lot of rough edges, in particular it relies on a well behaving keyboxd. We need to add code to shutdown the datastream reader thread in case of errors and to properly get it up again. We also need to make really sure that both threads run in lockstep so that the datastream thread is only active while we are sending a command to the keyboxd. We should also see whether we can depend nPth initialization on the --use-keyboxd option to avoid any problems with nPth. And we need to test on Windows. Signed-off-by: Werner Koch --- common/asshelp.c | 22 ++- g10/Makefile.am | 14 +- g10/call-keyboxd.c | 424 +++++++++++++++++++++++++++++++++++--------- g10/gpg.c | 14 +- g10/keydb-private.h | 11 +- g10/keydb.h | 3 + 6 files changed, 382 insertions(+), 106 deletions(-) diff --git a/common/asshelp.c b/common/asshelp.c index a5724fad5..174933a83 100644 --- a/common/asshelp.c +++ b/common/asshelp.c @@ -310,14 +310,15 @@ unlock_spawning (lock_spawn_t *lock, const char *name) } -/* 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. 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 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; @@ -353,7 +354,7 @@ wait_for_sock (int secs, int module_name_id, 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) @@ -403,6 +404,7 @@ start_new_service (assuan_context_t *r_ctx, 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; @@ -439,6 +441,7 @@ start_new_service (assuan_context_t *r_ctx, 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); @@ -446,7 +449,7 @@ start_new_service (assuan_context_t *r_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; @@ -522,7 +525,7 @@ start_new_service (assuan_context_t *r_ctx, argv[i++] = NULL; if (!(err = lock_spawning (&lock, gnupg_homedir (), lock_name, verbose)) - && assuan_socket_connect (ctx, sockname, 0, 0)) + && assuan_socket_connect (ctx, sockname, 0, connect_flags)) { err = gnupg_spawn_process_detached (program? program : program_name, argv, NULL); @@ -532,7 +535,8 @@ start_new_service (assuan_context_t *r_ctx, gpg_strerror (err)); else err = wait_for_sock (seconds_to_wait, module_name_id, - sockname, verbose, ctx, &did_success_msg); + sockname, connect_flags, + verbose, ctx, &did_success_msg); } unlock_spawning (&lock, lock_name); diff --git a/g10/Makefile.am b/g10/Makefile.am index 1404014e9..fd2cd21b9 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. @@ -178,11 +178,11 @@ 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) \ - $(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBASSUAN_LIBS) $(NPTH_LIBS) $(GPG_ERROR_LIBS) \ $(LIBICONV) $(resource_objs) $(extra_sys_libs) gpgv_LDFLAGS = $(extra_bin_ldflags) @@ -197,17 +197,17 @@ 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) \ - $(LIBASSUAN_LIBS) $(GPG_ERROR_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) \ - $(LIBASSUAN_LIBS) $(GPG_ERROR_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) \ - $(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBASSUAN_LIBS) $(NPTH_LIBS) $(GPG_ERROR_LIBS) \ $(LIBICONV) $(t_common_ldadd) diff --git a/g10/call-keyboxd.c b/g10/call-keyboxd.c index f05a9e85f..97f84c03d 100644 --- a/g10/call-keyboxd.c +++ b/g10/call-keyboxd.c @@ -28,6 +28,7 @@ #ifdef HAVE_LOCALE_H # include #endif +#include #include "gpg.h" #include @@ -36,6 +37,8 @@ #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" @@ -54,17 +57,63 @@ struct keyboxd_local_s /* The active Assuan context. */ assuan_context_t ctx; + /* This object is used if fd-passing is used to convey the + * keyblocks. */ + struct { + /* NULL or a stream used to receive data. */ + estream_t fp; + + /* Condition variable to sync the datastream with the command. */ + npth_mutex_t mutex; + npth_cond_t cond; + + /* The found keyblock or the parsing error. */ + kbnode_t found_keyblock; + gpg_error_t found_err; + } datastream; + + /* 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) @@ -77,7 +126,12 @@ gpg_keyboxd_deinit_session_data (ctrl_t ctrl) if (kbl->is_active) log_error ("oops: trying to cleanup an active keyboxd context\n"); else - assuan_release (kbl->ctx); + { + es_fclose (kbl->datastream.fp); + kbl->datastream.fp = NULL; + assuan_release (kbl->ctx); + kbl->ctx = NULL; + } xfree (kbl); } } @@ -165,18 +219,86 @@ create_new_context (ctrl_t ctrl, assuan_context_t *r_ctx) } -/* Get a context for accessing keyboxd. If no context is available a - * new one is created and if necessary keyboxd is started. On success - * an assuan context is stored at R_CTX. This context may only be - * released by means of close_context. Note that NULL is stored at - * R_CTX on error. */ + +/* Setup the pipe used for receiving data from the keyboxd. Store the + * info on KBL. */ static gpg_error_t -open_context (ctrl_t ctrl, assuan_context_t *r_ctx) +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_ctx = NULL; + *r_kbl = NULL; for (;;) { for (kbl = ctrl->keyboxd_local; kbl && kbl->is_active; kbl = kbl->next) @@ -189,12 +311,16 @@ open_context (ctrl_t ctrl, assuan_context_t *r_ctx) /* 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_ctx = kbl->ctx; + *r_kbl = kbl; return 0; } @@ -202,43 +328,40 @@ open_context (ctrl_t ctrl, assuan_context_t *r_ctx) kbl = xtrycalloc (1, sizeof *kbl); if (!kbl) return gpg_error_from_syserror (); - err = create_new_context (ctrl, &kbl->ctx); - if (err) + + 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; } - /* Although we are not yet using nPth in gpg we better prepare - * to be nPth thread safe. Thus we add it to the list and - * retry; this is easier than to employ a lock. */ + 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; } -} - - -/* Close the assuan context CTX and return it to a pool of unused - * contexts. If CTX is NULL, the function does nothing. */ -static void -close_context (ctrl_t ctrl, assuan_context_t ctx) -{ - keyboxd_local_t kbl; - - if (!ctx) - return; - - for (kbl = ctrl->keyboxd_local; kbl; kbl = kbl->next) - { - if (kbl->ctx == ctx) - { - if (!kbl->is_active) - log_fatal ("closing inactive keyboxd context %p\n", ctx); - kbl->is_active = 0; - return; - } - } - log_fatal ("closing unknown keyboxd ctx %p\n", ctx); + /*NOTREACHED*/ } @@ -275,11 +398,8 @@ keydb_new (ctrl_t ctrl) } hd->use_keyboxd = 1; hd->ctrl = ctrl; - hd->need_search_reset = 1; - err = open_context (ctrl, &hd->assuan_context); - if (err) - goto leave; + err = open_context (ctrl, &hd->kbl); leave: if (err) @@ -300,14 +420,25 @@ keydb_new (ctrl_t ctrl) 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 { - close_context (hd->ctrl, hd->assuan_context); + 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); } @@ -465,6 +596,86 @@ keydb_get_keyblock_do_parse (iobuf_t iobuf, int pk_no, int uid_no, } +/* 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 @@ -494,20 +705,28 @@ keydb_get_keyblock (KEYDB_HANDLE hd, kbnode_t *ret_kb) goto leave; } - if (!hd->search_result) + 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; } - pk_no = uid_no = 0; /*FIXME: Get this from the keyboxd. */ - err = keydb_get_keyblock_do_parse (hd->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->search_result); - hd->search_result = NULL; - - leave: if (DBG_CLOCK) log_clock ("%s leave%s", __func__, err? " (failed)":""); @@ -636,7 +855,7 @@ keydb_search_reset (KEYDB_HANDLE hd) /* All we need todo is to tell search that a reset is pending. Noet * that keydb_new sets this flag as well. */ - hd->need_search_reset = 1; + hd->kbl->need_search_reset = 1; err = 0; leave: @@ -697,14 +916,20 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, goto leave; } - - if (hd->search_result) + /* Clear the result objects. */ + if (hd->kbl->search_result) { - iobuf_close (hd->search_result); - hd->search_result = NULL; + 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; } - if (!hd->need_search_reset) + /* 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 @@ -717,7 +942,7 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, goto do_search; } - hd->need_search_reset = 0; + hd->kbl->need_search_reset = 0; if (!ndesc) { @@ -807,31 +1032,68 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, } do_search: - { - membuf_t data; - void *buffer; - size_t len; + 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; - init_membuf (&data, 8192); - err = assuan_transact (hd->assuan_context, line, - put_membuf_cb, &data, - NULL, NULL, - NULL, NULL); - if (err) - { - xfree (get_membuf (&data, &len)); - goto leave; - } + /* 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; - buffer = get_membuf (&data, &len); - if (!buffer) - { - err = gpg_error_from_syserror (); - goto leave; - } + 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; + } - /* Fixme: Avoid double allocation. */ - hd->search_result = iobuf_temp_with_content (buffer, len); + 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); } diff --git a/g10/gpg.c b/g10/gpg.c index 145796cbc..821d3959b 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -36,6 +36,7 @@ # endif # include #endif +#include #define INCLUDED_BY_MAIN_MODULE 1 #include "gpg.h" @@ -979,6 +980,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*)) @@ -2246,6 +2250,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); } @@ -3736,6 +3741,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 @@ -3743,7 +3753,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) diff --git a/g10/keydb-private.h b/g10/keydb-private.h index c5315b991..efef82289 100644 --- a/g10/keydb-private.h +++ b/g10/keydb-private.h @@ -92,14 +92,9 @@ struct keydb_handle_s /* A shallow pointer with the CTRL used to create this handle. */ ctrl_t ctrl; - /* The context used to communicate with the keyboxd. */ - assuan_context_t assuan_context; - - /* I/O buffer with the last search result or NULL. */ - iobuf_t search_result; - - /* Flag indicating that a search reset is required. */ - unsigned int need_search_reset : 1; + /* Connection info which also keep the local state. (This is points + * into the CTRL->keybox_local list.) */ + keyboxd_local_t kbl; /* END USE_KEYBOXD */ diff --git a/g10/keydb.h b/g10/keydb.h index 25304ed25..cc6f05956 100644 --- a/g10/keydb.h +++ b/g10/keydb.h @@ -165,6 +165,9 @@ 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); From 1f987516f6b1cded0a5743142ef50c559f08bcdf Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 17 Sep 2019 16:19:28 +0200 Subject: [PATCH 14/19] tests: Add two user-id parsing test cases. -- Signed-off-by: Werner Koch --- common/t-mbox-util.c | 6 ++++++ 1 file changed, 6 insertions(+) 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()o@example.org" }, { "fo()o@example.org", NULL}, { "Mr. Foo ", "foo@example.org"}, + { "Surname, Forename | company ", "foo@example.org"}, + /* 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). */ + { "\"\" ", "foo@example.org"}, { NULL, NULL } }; int idx; From d38f877bd88c348644742f79bfb4909dd3af493b Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 25 Sep 2019 16:21:30 +0200 Subject: [PATCH 15/19] doc: Minor doc updates and a typo fix. -- --- doc/DETAILS | 12 ++++++------ g10/objcache.c | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/DETAILS b/doc/DETAILS index 3fa651e12..ed5cadec6 100644 --- a/doc/DETAILS +++ b/doc/DETAILS @@ -1141,12 +1141,12 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: Status codes are also used between the components of the GnuPG system via the Assuan S lines. Some of them are documented here: -*** PUBKEY_TYPE - The type of the public key in the following D-lines or communicated - via a pipe. is the value of =enum pubkey_types=. - - - +*** PUBKEY_INFO + The type of the public key in the following D-lines or + communicated via a pipe. is the value of =enum pubkey_types= + and the Unique Blob ID 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 of up to 64 characters. * Format of the --attribute-fd output 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 From 280e9c9cfac31ae5ac874c928eee063cc922e27e Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 27 Sep 2019 09:24:58 +0200 Subject: [PATCH 16/19] kbx: First take on a cache for the keyboxd. * kbx/backend.h (enum database_types): Add DB_TYPE_CACHE. (struct db_request_part_s): Add seqno fields. (struct db_request_s): Add infos for the cache backend. * kbx/backend-support.c (struct backend_handle_s): Add 'backend_id'. (strdbtype): Support DB_TYPE_CACHE. (be_generic_release_backend): Ditto. (be_find_request_part): New. (be_return_pubkey): New arg UBID and chnage status name. * kbx/backend-cache.c: New. * kbx/backend-kbx.c (be_kbx_init_request_part): New. (be_kbx_search): Factor some code out to a support function. (be_kbx_seek): New. * kbx/frontend.c (kbxd_add_resource): Support DB_TYPE_CACHE. (kbxd_search): Support the NEXR operation with the cache. * kbx/keybox-search-desc.h (KEYDB_SEARCH_MODE_UBID): New. (struct keydb_search_desc): Add field u.ubid. * kbx/keybox-search.c (has_ubid): New. (keybox_search): Support the UBID search. -- This adds a caching backend to the keyboxd. This tries to accommodate for duplicate use of fingerprints and thus be correct in case a fingerprint is used in several keys. It also turned out that we need to have a unique identifier (UBID) to identify a keyblock or X.509 certificate. In particular with an OpenPGP keyblob we can't easily use the primary fingerprint as an identifier because that fingerprint may also be used as subkey in another key. Thus using a hash of the entire keyblock is a better identifier to be used to address a keyblock for restarting a search or for identifying the keyblock to be updated. Note that this new UBID is not a permanent identifier because it changes with all keyblock update; it should be viewed as a handle to the keyblock or X509 cert. --- kbx/Makefile.am | 1 + kbx/backend-cache.c | 1167 ++++++++++++++++++++++++++++++++++++++ kbx/backend-kbx.c | 84 ++- kbx/backend-support.c | 59 +- kbx/backend.h | 50 +- kbx/frontend.c | 77 ++- kbx/kbxserver.c | 2 +- kbx/keybox-search-desc.h | 2 + kbx/keybox-search.c | 24 + 9 files changed, 1421 insertions(+), 45 deletions(-) create mode 100644 kbx/backend-cache.c diff --git a/kbx/Makefile.am b/kbx/Makefile.am index 48ed31740..42c3c4be8 100644 --- a/kbx/Makefile.am +++ b/kbx/Makefile.am @@ -77,6 +77,7 @@ keyboxd_SOURCES = \ kbxserver.c \ frontend.c frontend.h \ backend.h backend-support.c \ + backend-cache.c \ backend-kbx.c \ $(common_sources) diff --git a/kbx/backend-cache.c b/kbx/backend-cache.c new file mode 100644 index 000000000..d5b46b50a --- /dev/null +++ b/kbx/backend-cache.c @@ -0,0 +1,1167 @@ +/* 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 . + * 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 +#include +#include +#include +#include + +#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, + 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->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; + 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; */ + + 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, 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 index b8d39c2ed..e58b74a3b 100644 --- a/kbx/backend-kbx.c +++ b/kbx/backend-kbx.c @@ -34,7 +34,7 @@ struct backend_handle_s { enum database_types db_type; /* Always DB_TYPE_KBX. */ - unsigned int backend_id; /* Id of this backend. */ + unsigned int backend_id; /* Always the id of the backend. */ void *token; /* Used to create a new KEYBOX_HANDLE. */ char filename[1]; @@ -226,6 +226,17 @@ be_kbx_release_kbx_hd (KEYBOX_HANDLE 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. */ @@ -242,28 +253,9 @@ be_kbx_search (ctrl_t ctrl, backend_handle_t backend_hd, db_request_t request, log_assert (request); /* Find the specific request part or allocate it. */ - 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) - { - err = gpg_error_from_syserror (); - goto leave; - } - part->backend_id = backend_hd->backend_id; - part->kbx_hd = keybox_new_openpgp (backend_hd->token, 0); - if (!part->kbx_hd) - { - err = gpg_error_from_syserror (); - xfree (part); - goto leave; - } - part->next = request->part; - request->part = part; - } + err = be_find_request_part (backend_hd, request, &part); + if (err) + goto leave; if (!desc) err = keybox_search_reset (part->kbx_hd); @@ -279,14 +271,56 @@ be_kbx_search (ctrl_t ctrl, backend_handle_t backend_hd, db_request_t request, 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; - /* Note: be_return_pubkey always takes ownership of BUFFER. */ - err = be_return_pubkey (ctrl, buffer, buflen, pubkey_type); + 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; + + 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 index 28b51875c..62551cafa 100644 --- a/kbx/backend-support.c +++ b/kbx/backend-support.c @@ -28,12 +28,15 @@ #include "../common/i18n.h" #include "../common/asshelp.h" #include "backend.h" +#include "keybox.h" -/* Common definition part of all backend handle. */ +/* 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; }; @@ -45,6 +48,7 @@ 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 "?"; @@ -76,6 +80,9 @@ be_generic_release_backend (ctrl_t ctrl, backend_handle_t hd) 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; @@ -104,16 +111,53 @@ be_release_request (db_request_t req) } -/* Return the public key (BUFFER,BUFLEN) which has the type - * PUBVKEY_TYPE to the caller. Owenership of BUFFER is taken by thgis - * function even in the error case. */ +/* 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_return_pubkey (ctrl_t ctrl, void *buffer, size_t buflen, - enum pubkey_types pubkey_type) +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; - err = status_printf (ctrl, "PUBKEY_TYPE", "%d", pubkey_type); + 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; @@ -123,6 +167,5 @@ be_return_pubkey (ctrl_t ctrl, void *buffer, size_t buflen, err = kbxd_write_data_line(ctrl, buffer, buflen); leave: - xfree (buffer); return err; } diff --git a/kbx/backend.h b/kbx/backend.h index e96f5023c..8b389d35c 100644 --- a/kbx/backend.h +++ b/kbx/backend.h @@ -31,6 +31,7 @@ typedef struct keybox_handle *KEYBOX_HANDLE; 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). */ }; @@ -42,7 +43,7 @@ struct backend_handle_s; typedef struct backend_handle_s *backend_handle_t; -/* Object to store backend specific databsde information per database +/* Object to store backend specific database information per database * handle. */ struct db_request_part_s { @@ -53,6 +54,14 @@ struct db_request_part_s /* 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; + } cache_seqno; }; typedef struct db_request_part_s *db_request_part_t; @@ -63,11 +72,24 @@ 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]; }; @@ -77,8 +99,26 @@ 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_return_pubkey (ctrl_t ctrl, void *buffer, size_t buflen, - enum pubkey_types pubkey_type); +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 --*/ @@ -87,9 +127,13 @@ gpg_error_t be_kbx_add_resource (ctrl_t ctrl, backend_handle_t *r_hd, 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 index 233cc1e0b..806ff27db 100644 --- a/kbx/frontend.c +++ b/kbx/frontend.c @@ -67,7 +67,7 @@ take_read_lock (ctrl_t ctrl) /* Release a lock. It is valid to call this even if no lock has been - * taken which which case this is a nop. */ + * taken in which case this is a nop. */ static void release_lock (ctrl_t ctrl) { @@ -87,12 +87,17 @@ kbxd_add_resource (ctrl_t ctrl, const char *filename_arg, int readonly) { gpg_error_t err; char *filename; - enum database_types db_type; + enum database_types db_type = 0; backend_handle_t handle = NULL; unsigned int n, dbidx; /* Do tilde expansion etc. */ - if (strchr (filename_arg, DIRSEP_C) + 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 @@ -102,8 +107,20 @@ kbxd_add_resource (ctrl_t ctrl, const char *filename_arg, int readonly) 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 (n > 4 && !strcmp (filename + n - 4, ".kbx")) + if (db_type) + ; /* We already know it. */ + else if (n > 4 && !strcmp (filename + n - 4, ".kbx")) db_type = DB_TYPE_KBX; else { @@ -118,6 +135,10 @@ kbxd_add_resource (ctrl_t ctrl, const char *filename_arg, int readonly) 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; @@ -134,8 +155,8 @@ kbxd_add_resource (ctrl_t ctrl, const char *filename_arg, int readonly) /* No table yet or table is full. */ if (!databases) { - /* Create first set of data bases. Note that the type for all - * entries is DB_TYPE_NONE. */ + /* 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) @@ -204,6 +225,7 @@ kbxd_search (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, unsigned int ndesc, unsigned int dbidx; db_desc_t db; db_request_t request; + int start_at_ubid = 0; if (DBG_CLOCK) log_clock ("%s: enter", __func__); @@ -248,6 +270,10 @@ kbxd_search (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, unsigned int ndesc, 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; @@ -278,7 +304,10 @@ kbxd_search (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, unsigned int ndesc, request->next_dbidx = dbidx; if (!(dbidx < no_of_databases)) { - /* All databases have been searched. */ + /* 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; } @@ -292,9 +321,32 @@ kbxd_search (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, unsigned int ndesc, 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; } @@ -303,10 +355,19 @@ kbxd_search (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, unsigned int ndesc, __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; + { + 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; } diff --git a/kbx/kbxserver.c b/kbx/kbxserver.c index c693c7ce2..929ee6116 100644 --- a/kbx/kbxserver.c +++ b/kbx/kbxserver.c @@ -236,7 +236,7 @@ kbxd_write_data_line (ctrl_t ctrl, const void *buffer_arg, size_t size) static gpg_error_t leave_cmd (assuan_context_t ctx, gpg_error_t err) { - if (err) + if (err && opt.verbose) { const char *name = assuan_get_command_name (ctx); if (!name) diff --git a/kbx/keybox-search-desc.h b/kbx/keybox-search-desc.h index fdd0bcbf9..7fa97e97b 100644 --- a/kbx/keybox-search-desc.h +++ b/kbx/keybox-search-desc.h @@ -42,6 +42,7 @@ 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; @@ -79,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 971f93745..cb763cf5f 100644 --- a/kbx/keybox-search.c +++ b/kbx/keybox-search.c @@ -696,6 +696,26 @@ 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*/ + 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 +1139,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; From 4be79b5abeae82b9840e6aa93874f743e13c6df7 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 27 Sep 2019 10:05:07 +0200 Subject: [PATCH 17/19] kbx,gpg: Allow lookup using a UBID. * common/userids.c (classify_user_id): Detect UBIDs. * kbx/backend-cache.c (blob_table_put): Store the public key type. (be_cache_search): Add search mode for UBIDs. * kbx/backend.h (struct db_request_part_s): Add cache.seqno_ubid. * g10/keydb.c (keydb_search_desc_dump): Fix printing of keygrip. Add ubid printing. * g10/call-keyboxd.c (keydb_search): Support search by UBID. Signed-off-by: Werner Koch --- common/userids.c | 14 ++++++++++++++ doc/DETAILS | 9 +++++---- g10/call-keyboxd.c | 8 ++++++++ g10/keydb.c | 10 +++++++++- kbx/backend-cache.c | 27 +++++++++++++++++++++++++-- kbx/backend-kbx.c | 2 ++ kbx/backend.h | 1 + 7 files changed, 64 insertions(+), 7 deletions(-) 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/doc/DETAILS b/doc/DETAILS index ed5cadec6..0610108f4 100644 --- a/doc/DETAILS +++ b/doc/DETAILS @@ -1144,10 +1144,11 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: *** PUBKEY_INFO The type of the public key in the following D-lines or communicated via a pipe. is the value of =enum pubkey_types= - and the Unique Blob ID 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 of up to 64 characters. - + and 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 of up to 64 characters. + Note that the keyboxd SEARCH command can be used to lookup the + public key using the prefixed with a caret (^). * Format of the --attribute-fd output diff --git a/g10/call-keyboxd.c b/g10/call-keyboxd.c index 97f84c03d..88ad07817 100644 --- a/g10/call-keyboxd.c +++ b/g10/call-keyboxd.c @@ -1017,6 +1017,14 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, } 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; diff --git a/g10/keydb.c b/g10/keydb.c index 6ca239475..39ec5442e 100644 --- a/g10/keydb.c +++ b/g10/keydb.c @@ -456,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: @@ -495,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: diff --git a/kbx/backend-cache.c b/kbx/backend-cache.c index d5b46b50a..10a6f6bd9 100644 --- a/kbx/backend-cache.c +++ b/kbx/backend-cache.c @@ -207,7 +207,7 @@ compare_blobs (const void *arg_a, const void *arg_b) /* 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, +blob_table_put (const unsigned char *ubid, enum pubkey_types pktype, const void *blobdata, unsigned int blobdatalen) { unsigned int hash; @@ -335,6 +335,7 @@ blob_table_put (const unsigned char *ubid, b = blob_attic; blob_attic = b->next; b->next = NULL; + b->pktype = pktype; b->data = blobdatacopy; b->datalen = blobdatalen; memcpy (b->ubid, ubid, 20); @@ -932,6 +933,7 @@ be_cache_search (ctrl_t ctrl, backend_handle_t backend_hd, db_request_t request, reqpart->cache_seqno.fpr = 0; reqpart->cache_seqno.kid = 0; reqpart->cache_seqno.grip = 0; + reqpart->cache_seqno.ubid = 0; err = 0; goto leave; } @@ -992,6 +994,27 @@ be_cache_search (ctrl_t ctrl, backend_handle_t backend_hd, db_request_t request, /* 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; @@ -1123,7 +1146,7 @@ be_cache_pubkey (ctrl_t ctrl, const unsigned char *ubid, return; } - blob_table_put (ubid, blob, bloblen); + blob_table_put (ubid, pubkey_type, blob, bloblen); kinfo = &info.primary; key_table_put (kinfo->fpr, kinfo->fprlen, ubid, 0); diff --git a/kbx/backend-kbx.c b/kbx/backend-kbx.c index e58b74a3b..851f2dadf 100644 --- a/kbx/backend-kbx.c +++ b/kbx/backend-kbx.c @@ -302,6 +302,8 @@ be_kbx_seek (ctrl_t ctrl, backend_handle_t backend_hd, 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); diff --git a/kbx/backend.h b/kbx/backend.h index 8b389d35c..675ec213d 100644 --- a/kbx/backend.h +++ b/kbx/backend.h @@ -61,6 +61,7 @@ struct db_request_part_s unsigned int fpr; unsigned int kid; unsigned int grip; + unsigned int ubid; } cache_seqno; }; typedef struct db_request_part_s *db_request_part_t; From 0af1c6447dc0f981ab7306e3bef520f37aded167 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 27 Sep 2019 13:51:52 +0200 Subject: [PATCH 18/19] kbx: Store the UBIB in the blob. * kbx/keybox-blob.c (create_blob_header): New blob flag UBIB. (create_blob_finish): Write the UBIB. * kbx/keybox-dump.c (print_ubib): New. (_keybox_dump_blob): Print UBIB flag. * kbx/keybox-search.c (has_ubid): Compare the stored UBIB if available. -- This make scanning the keybox for a given UBIB much faster once it has been stored. Signed-off-by: Werner Koch --- kbx/keybox-blob.c | 21 ++++++++++++++++----- kbx/keybox-dump.c | 46 +++++++++++++++++++++++++++++++++++++++++++++ kbx/keybox-search.c | 21 +++++++++++++++------ 3 files changed, 77 insertions(+), 11 deletions(-) diff --git a/kbx/keybox-blob.c b/kbx/keybox-blob.c index 8ac67afbe..ac49ac79c 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 48c3f63c5..37646832e 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-search.c b/kbx/keybox-search.c index cb763cf5f..95ad07251 100644 --- a/kbx/keybox-search.c +++ b/kbx/keybox-search.c @@ -707,14 +707,23 @@ has_ubid (KEYBOXBLOB blob, const unsigned char *ubid) buffer = _keybox_get_blob_image (blob, &length); if (length < 40) return 0; /*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 0; /*GPG_ERR_TOO_SHORT*/ - gcry_md_hash_buffer (GCRY_MD_SHA1, ubid_blob, buffer + image_off, image_len); + 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*/ - return !memcmp (ubid, ubid_blob, 20); + gcry_md_hash_buffer (GCRY_MD_SHA1, ubid_blob, buffer+image_off,image_len); + return !memcmp (ubid, ubid_blob, 20); + } } static inline int From 8e574130482167dc7d2e2888cc80ad6584345e3d Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 27 Sep 2019 14:28:36 +0200 Subject: [PATCH 19/19] kbx: Fix error code return in keyboxd. * kbx/frontend.c (kbxd_add_resource): Print a diagnostic on error. * kbx/backend-kbx.c (be_kbx_add_resource): Acttually returh the error code. Signed-off-by: Werner Koch --- kbx/backend-kbx.c | 2 +- kbx/frontend.c | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/kbx/backend-kbx.c b/kbx/backend-kbx.c index 851f2dadf..438d300b0 100644 --- a/kbx/backend-kbx.c +++ b/kbx/backend-kbx.c @@ -200,7 +200,7 @@ be_kbx_add_resource (ctrl_t ctrl, backend_handle_t *r_hd, leave: xfree (hd); - return 0; + return err; } diff --git a/kbx/frontend.c b/kbx/frontend.c index 806ff27db..6e0cbcb11 100644 --- a/kbx/frontend.c +++ b/kbx/frontend.c @@ -194,7 +194,11 @@ kbxd_add_resource (ctrl_t ctrl, const char *filename_arg, int readonly) leave: if (err) - be_generic_release_backend (ctrl, handle); + { + log_error ("error adding resource '%s': %s\n", + filename, gpg_strerror (err)); + be_generic_release_backend (ctrl, handle); + } xfree (filename); return err; }