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