From 512be1d04b98a9d6a9067bd34c16513089a0db9f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 5 Sep 2018 17:00:17 +0200 Subject: [PATCH] kbx: Add framework for a public key daemon. * kbx/keyboxd.c: New. * kbx/keyboxd.h: New. * kbx/kbxserver.c: New. * kbx/keyboxd-w32info.rc: New. * kbx/Makefile.am (EXTRA_DIST): Add new rc file. (resource_objs): Ditto. (libexec_PROGRAMS): New. (common_libs, commonpth_libs): New. (kbxutil_LDADD): Use here. (keyboxd_SOURCES): New. (keyboxd_CFLAGS): New. (keyboxd_LDADD): New. (keyboxd_LDFLAGS): New. (keyboxd_DEPENDENCIES): new. ($(PROGRAMS)): Extend. Signed-off-by: Werner Koch --- configure.ac | 5 +- kbx/Makefile.am | 30 +- kbx/kbxserver.c | 447 ++++++++++ kbx/keyboxd-w32info.rc | 50 ++ kbx/keyboxd.c | 1817 ++++++++++++++++++++++++++++++++++++++++ kbx/keyboxd.h | 137 +++ 6 files changed, 2482 insertions(+), 4 deletions(-) create mode 100644 kbx/kbxserver.c create mode 100644 kbx/keyboxd-w32info.rc create mode 100644 kbx/keyboxd.c create mode 100644 kbx/keyboxd.h diff --git a/configure.ac b/configure.ac index 78a03c420..9f78259e8 100644 --- a/configure.ac +++ b/configure.ac @@ -56,7 +56,7 @@ AC_DEFINE_UNQUOTED(GNUPG_SWDB_TAG, "gnupg24", [swdb tag for this branch]) NEED_GPG_ERROR_VERSION=1.29 NEED_LIBGCRYPT_API=1 -NEED_LIBGCRYPT_VERSION=1.7.0 +NEED_LIBGCRYPT_VERSION=1.8.0 NEED_LIBASSUAN_API=2 NEED_LIBASSUAN_VERSION=2.5.0 @@ -505,6 +505,7 @@ AH_BOTTOM([ #define GNUPG_DEFAULT_HOMEDIR "~/.gnupg" #endif #define GNUPG_PRIVATE_KEYS_DIR "private-keys-v1.d" +#define GNUPG_PUBLIC_KEYS_DIR "public-keys.d" #define GNUPG_OPENPGP_REVOC_DIR "openpgp-revocs.d" #define GNUPG_DEF_COPYRIGHT_LINE \ @@ -1882,6 +1883,8 @@ AC_DEFINE_UNQUOTED(DIRMNGR_INFO_NAME, "DIRMNGR_INFO", [The name of the dirmngr info envvar]) AC_DEFINE_UNQUOTED(SCDAEMON_SOCK_NAME, "S.scdaemon", [The name of the SCdaemon socket]) +AC_DEFINE_UNQUOTED(KEYBOXD_SOCK_NAME, "S.keyboxd", + [The name of the keyboxd socket]) AC_DEFINE_UNQUOTED(DIRMNGR_SOCK_NAME, "S.dirmngr", [The name of the dirmngr socket]) AC_DEFINE_UNQUOTED(DIRMNGR_DEFAULT_KEYSERVER, diff --git a/kbx/Makefile.am b/kbx/Makefile.am index 8fca24afb..51cabbfb3 100644 --- a/kbx/Makefile.am +++ b/kbx/Makefile.am @@ -18,16 +18,20 @@ ## Process this file with automake to produce Makefile.in -EXTRA_DIST = mkerrors +EXTRA_DIST = mkerrors keyboxd-w32info.rc AM_CPPFLAGS = include $(top_srcdir)/am/cmacros.am +if HAVE_W32_SYSTEM +resource_objs += keyboxd-w32info.o +endif AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS) noinst_LIBRARIES = libkeybox.a libkeybox509.a bin_PROGRAMS = kbxutil +libexec_PROGRAMS = keyboxd if HAVE_W32CE_SYSTEM extra_libs = $(LIBASSUAN_LIBS) @@ -35,6 +39,9 @@ else extra_libs = endif +common_libs = $(libcommon) +commonpth_libs = $(libcommonpth) + common_sources = \ keybox.h keybox-defs.h keybox-search-desc.h \ keybox-util.c \ @@ -59,9 +66,26 @@ libkeybox509_a_CFLAGS = $(AM_CFLAGS) -DKEYBOX_WITH_X509=1 # to do it this way. kbxutil_SOURCES = kbxutil.c $(common_sources) kbxutil_CFLAGS = $(AM_CFLAGS) -DKEYBOX_WITH_X509=1 -kbxutil_LDADD = ../common/libcommon.a \ +kbxutil_LDADD = $(common_libs) \ $(KSBA_LIBS) $(LIBGCRYPT_LIBS) $(extra_libs) \ $(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV) $(W32SOCKLIBS) \ $(NETLIBS) -$(PROGRAMS) : ../common/libcommon.a + +keyboxd_SOURCES = \ + keyboxd.c keyboxd.h \ + kbxserver.c + +keyboxd_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) \ + $(INCICONV) +keyboxd_LDADD = $(commonpth_libs) \ + $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \ + $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ + $(resource_objs) +keyboxd_LDFLAGS = $(extra_bin_ldflags) +keyboxd_DEPENDENCIES = $(resource_objs) + + +# Make sure that all libs are build before we use them. This is +# important for things like make -j2. +$(PROGRAMS): $(common_libs) $(commonpth_libs) diff --git a/kbx/kbxserver.c b/kbx/kbxserver.c new file mode 100644 index 000000000..59fcb64c7 --- /dev/null +++ b/kbx/kbxserver.c @@ -0,0 +1,447 @@ +/* kbxserver.c - Handle Assuan commands send to the keyboxd + * Copyright (C) 2018 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0+ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "keyboxd.h" +#include + +#include "../common/i18n.h" +#include "../common/server-help.h" + + + + +#define PARM_ERROR(t) assuan_set_error (ctx, \ + gpg_error (GPG_ERR_ASS_PARAMETER), (t)) +#define set_error(e,t) (ctx ? assuan_set_error (ctx, gpg_error (e), (t)) \ + /**/: gpg_error (e)) + + + +/* Control structure per connection. */ +struct server_local_s +{ + /* Data used to associate an Assuan context with local server data */ + assuan_context_t assuan_ctx; + + /* The session id (a counter). */ + unsigned int session_id; + + /* If this flag is set to true this process will be terminated after + * the end of this session. */ + int stopme; + + /* If the first both flags are set the assuan logging of data lines + * is suppressed. The count variable is used to show the number of + * non-logged bytes. */ + size_t inhibit_data_logging_count; + unsigned int inhibit_data_logging : 1; + unsigned int inhibit_data_logging_now : 1; + + /* Dummy option. */ + int foo; +}; + + + + +/* Helper to print a message while leaving a command. */ +static gpg_error_t +leave_cmd (assuan_context_t ctx, gpg_error_t err) +{ + if (err) + { + const char *name = assuan_get_command_name (ctx); + if (!name) + name = "?"; + if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT) + log_error ("command '%s' failed: %s\n", name, + gpg_strerror (err)); + else + log_error ("command '%s' failed: %s <%s>\n", name, + gpg_strerror (err), gpg_strsource (err)); + } + return err; +} + + + +/* Handle OPTION commands. */ +static gpg_error_t +option_handler (assuan_context_t ctx, const char *key, const char *value) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err = 0; + + if (!strcmp (key, "foo")) + { + ctrl->server_local->foo = 1; + } + else if (!strcmp (key, "lc-messages")) + { + if (ctrl->lc_messages) + xfree (ctrl->lc_messages); + ctrl->lc_messages = xtrystrdup (value); + if (!ctrl->lc_messages) + return out_of_core (); + } + else + err = gpg_error (GPG_ERR_UNKNOWN_OPTION); + + return err; +} + + + +static const char hlp_foo[] = + "FOO \n" + "\n" + "Bla bla\n" + "more bla."; +static gpg_error_t +cmd_foo (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + + (void)ctrl; + (void)line; + + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + return leave_cmd (ctx, err); +} + + + +static const char hlp_getinfo[] = + "GETINFO \n" + "\n" + "Multi purpose command to return certain information. \n" + "Supported values of WHAT are:\n" + "\n" + "version - Return the version of the program.\n" + "pid - Return the process id of the server.\n" + "socket_name - Return the name of the socket.\n" + "session_id - Return the current session_id.\n" + "getenv NAME - Return value of envvar NAME\n"; +static gpg_error_t +cmd_getinfo (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + char numbuf[50]; + + if (!strcmp (line, "version")) + { + const char *s = VERSION; + err = assuan_send_data (ctx, s, strlen (s)); + } + else if (!strcmp (line, "pid")) + { + snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); + err = assuan_send_data (ctx, numbuf, strlen (numbuf)); + } + else if (!strcmp (line, "socket_name")) + { + const char *s = get_kbxd_socket_name (); + if (!s) + s = "[none]"; + err = assuan_send_data (ctx, s, strlen (s)); + } + else if (!strcmp (line, "session_id")) + { + snprintf (numbuf, sizeof numbuf, "%u", ctrl->server_local->session_id); + err = assuan_send_data (ctx, numbuf, strlen (numbuf)); + } + else if (!strncmp (line, "getenv", 6) + && (line[6] == ' ' || line[6] == '\t' || !line[6])) + { + line += 6; + while (*line == ' ' || *line == '\t') + line++; + if (!*line) + err = gpg_error (GPG_ERR_MISSING_VALUE); + else + { + const char *s = getenv (line); + if (!s) + err = set_error (GPG_ERR_NOT_FOUND, "No such envvar"); + else + err = assuan_send_data (ctx, s, strlen (s)); + } + } + else + err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); + + return leave_cmd (ctx, err); +} + + + +static const char hlp_killkeyboxd[] = + "KILLKEYBOXD\n" + "\n" + "This command allows a user - given sufficient permissions -\n" + "to kill this keyboxd process.\n"; +static gpg_error_t +cmd_killkeyboxd (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + + (void)line; + + ctrl->server_local->stopme = 1; + assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); + return gpg_error (GPG_ERR_EOF); +} + + +static const char hlp_reloadkeyboxd[] = + "RELOADKEYBOXD\n" + "\n" + "This command is an alternative to SIGHUP\n" + "to reload the configuration."; +static gpg_error_t +cmd_reloadkeyboxd (assuan_context_t ctx, char *line) +{ + (void)ctx; + (void)line; + + kbxd_sighup_action (); + return 0; +} + + + +/* Tell the assuan library about our commands. */ +static int +register_commands (assuan_context_t ctx) +{ + static struct { + const char *name; + assuan_handler_t handler; + const char * const help; + } table[] = { + { "FOO", cmd_foo, hlp_foo }, + { "GETINFO", cmd_getinfo, hlp_getinfo }, + { "KILLKEYBOXD",cmd_killkeyboxd,hlp_killkeyboxd }, + { "RELOADKEYBOXD",cmd_reloadkeyboxd,hlp_reloadkeyboxd }, + { NULL, NULL } + }; + int i, j, rc; + + for (i=j=0; table[i].name; i++) + { + rc = assuan_register_command (ctx, table[i].name, table[i].handler, + table[i].help); + if (rc) + return rc; + } + return 0; +} + + +/* Note that we do not reset the list of configured keyservers. */ +static gpg_error_t +reset_notify (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + + (void)line; + (void)ctrl; + + return 0; +} + + +/* This function is called by our assuan log handler to test whether a + * log message shall really be printed. The function must return + * false to inhibit the logging of MSG. CAT gives the requested log + * category. MSG might be NULL. */ +int +kbxd_assuan_log_monitor (assuan_context_t ctx, unsigned int cat, + const char *msg) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + + (void)cat; + (void)msg; + + if (!ctrl || !ctrl->server_local) + return 1; /* Can't decide - allow logging. */ + + if (!ctrl->server_local->inhibit_data_logging) + return 1; /* Not requested - allow logging. */ + + /* Disallow logging if *_now is true. */ + return !ctrl->server_local->inhibit_data_logging_now; +} + + +/* Return the assuan contxt from the local server info in CTRL. */ +static assuan_context_t +get_assuan_ctx_from_ctrl (ctrl_t ctrl) +{ + if (!ctrl || !ctrl->server_local) + return NULL; + return ctrl->server_local->assuan_ctx; +} + + +/* Startup the server and run the main command loop. With FD = -1, + * use stdin/stdout. SESSION_ID is either 0 or a unique number + * identifying a session. */ +void +kbxd_start_command_handler (ctrl_t ctrl, gnupg_fd_t fd, unsigned int session_id) +{ + static const char hello[] = "Keyboxd " VERSION " at your service"; + static char *hello_line; + int rc; + assuan_context_t ctx; + + ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local); + if (!ctrl->server_local) + { + log_error (_("can't allocate control structure: %s\n"), + gpg_strerror (gpg_error_from_syserror ())); + xfree (ctrl); + return; + } + + rc = assuan_new (&ctx); + if (rc) + { + log_error (_("failed to allocate assuan context: %s\n"), + gpg_strerror (rc)); + kbxd_exit (2); + } + + if (fd == GNUPG_INVALID_FD) + { + assuan_fd_t filedes[2]; + + filedes[0] = assuan_fdopen (0); + filedes[1] = assuan_fdopen (1); + rc = assuan_init_pipe_server (ctx, filedes); + } + else + { + rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED); + } + + if (rc) + { + assuan_release (ctx); + log_error (_("failed to initialize the server: %s\n"), + gpg_strerror (rc)); + kbxd_exit (2); + } + + rc = register_commands (ctx); + if (rc) + { + log_error (_("failed to the register commands with Assuan: %s\n"), + gpg_strerror(rc)); + kbxd_exit (2); + } + + + if (!hello_line) + { + hello_line = xtryasprintf + ("Home: %s\n" + "Config: %s\n" + "%s", + gnupg_homedir (), + /*opt.config_filename? opt.config_filename :*/ "[none]", + hello); + } + + ctrl->server_local->assuan_ctx = ctx; + assuan_set_pointer (ctx, ctrl); + + assuan_set_hello_line (ctx, hello_line); + assuan_register_option_handler (ctx, option_handler); + assuan_register_reset_notify (ctx, reset_notify); + + ctrl->server_local->session_id = session_id; + + /* The next call enable the use of status_printf. */ + set_assuan_context_func (get_assuan_ctx_from_ctrl); + + for (;;) + { + rc = assuan_accept (ctx); + if (rc == -1) + break; + if (rc) + { + log_info (_("Assuan accept problem: %s\n"), gpg_strerror (rc)); + break; + } + +#ifndef HAVE_W32_SYSTEM + if (opt.verbose) + { + assuan_peercred_t peercred; + + if (!assuan_get_peercred (ctx, &peercred)) + log_info ("connection from process %ld (%ld:%ld)\n", + (long)peercred->pid, (long)peercred->uid, + (long)peercred->gid); + } +#endif + + rc = assuan_process (ctx); + if (rc) + { + log_info (_("Assuan processing failed: %s\n"), gpg_strerror (rc)); + continue; + } + } + + + set_assuan_context_func (NULL); + ctrl->server_local->assuan_ctx = NULL; + assuan_release (ctx); + + if (ctrl->server_local->stopme) + kbxd_exit (0); + + if (ctrl->refcount) + log_error ("oops: connection control structure still referenced (%d)\n", + ctrl->refcount); + else + { + xfree (ctrl->server_local); + ctrl->server_local = NULL; + } +} diff --git a/kbx/keyboxd-w32info.rc b/kbx/keyboxd-w32info.rc new file mode 100644 index 000000000..421747499 --- /dev/null +++ b/kbx/keyboxd-w32info.rc @@ -0,0 +1,50 @@ +/* keyboxd-w32info.rc -*- c -*- + * Copyright (C) 2018 g10 Code GmbH + * + * This file is free software; as a special exception the author gives + * unlimited permission to copy and/or distribute it, with or without + * modifications, as long as this notice is preserved. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "afxres.h" +#include "../common/w32info-rc.h" + +1 ICON "../common/gnupg.ico" + +1 VERSIONINFO + FILEVERSION W32INFO_VI_FILEVERSION + PRODUCTVERSION W32INFO_VI_PRODUCTVERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/ +#else + FILEFLAGS 0x00L +#endif + FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */ + FILETYPE 0x1L /* VFT_APP (0x1) */ + FILESUBTYPE 0x0L /* VFT2_UNKNOWN */ + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" /* US English (0409), Unicode (04b0) */ + BEGIN + VALUE "FileDescription", L"GnuPG\x2019s public key daemon\0" + VALUE "InternalName", "keyboxd\0" + VALUE "OriginalFilename", "keyboxd.exe\0" + VALUE "ProductName", W32INFO_PRODUCTNAME + VALUE "ProductVersion", W32INFO_PRODUCTVERSION + VALUE "CompanyName", W32INFO_COMPANYNAME + VALUE "FileVersion", W32INFO_FILEVERSION + VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT + VALUE "Comments", W32INFO_COMMENTS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0x4b0 + END + END diff --git a/kbx/keyboxd.c b/kbx/keyboxd.c new file mode 100644 index 000000000..28232b3ea --- /dev/null +++ b/kbx/keyboxd.c @@ -0,0 +1,1817 @@ +/* keyboxd.c - The GnuPG Keybox Daemon + * Copyright (C) 2000-2007, 2009-2010 Free Software Foundation, Inc. + * Copyright (C) 2000-2018 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0+ + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_W32_SYSTEM +# ifndef WINVER +# define WINVER 0x0500 /* Same as in common/sysutils.c */ +# endif +# include +#else /*!HAVE_W32_SYSTEM*/ +# include +# include +#endif /*!HAVE_W32_SYSTEM*/ +#include +#ifdef HAVE_SIGNAL_H +# include +#endif +#include + +#define GNUPG_COMMON_NEED_AFLOCAL +#include "keyboxd.h" +#include /* Malloc hooks and socket wrappers. */ + +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "../common/asshelp.h" +#include "../common/init.h" +#include "../common/gc-opt-flags.h" +#include "../common/exechelp.h" + + +enum cmd_and_opt_values + { + aNull = 0, + oQuiet = 'q', + oVerbose = 'v', + + oNoVerbose = 500, + aGPGConfList, + aGPGConfTest, + oOptions, + oDebug, + oDebugAll, + oDebugWait, + oNoGreeting, + oNoOptions, + oHomedir, + oNoDetach, + oLogFile, + oServer, + oDaemon, + oBatch, + oFakedSystemTime, + oListenBacklog, + oDisableCheckOwnSocket, + + oDummy + }; + + +static ARGPARSE_OPTS opts[] = { + ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"), + ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"), + + ARGPARSE_group (301, N_("@Options:\n ")), + + ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")), + ARGPARSE_s_n (oServer, "server", N_("run in server mode (foreground)")), + ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), + ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), + ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")), + + ARGPARSE_s_s (oDebug, "debug", "@"), + ARGPARSE_s_n (oDebugAll, "debug-all", "@"), + ARGPARSE_s_i (oDebugWait, "debug-wait", "@"), + + ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"), + ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")), + ARGPARSE_s_s (oLogFile, "log-file", N_("use a log file for the server")), + ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), + + ARGPARSE_s_n (oBatch, "batch", "@"), + ARGPARSE_s_s (oHomedir, "homedir", "@"), + + ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"), + + ARGPARSE_end () /* End of list */ +}; + + +/* The list of supported debug flags. */ +static struct debug_flags_s debug_flags [] = + { + { DBG_MPI_VALUE , "mpi" }, + { DBG_CRYPTO_VALUE , "crypto" }, + { DBG_MEMORY_VALUE , "memory" }, + { DBG_CACHE_VALUE , "cache" }, + { DBG_MEMSTAT_VALUE, "memstat" }, + { DBG_HASHING_VALUE, "hashing" }, + { DBG_IPC_VALUE , "ipc" }, + { 77, NULL } /* 77 := Do not exit on "help" or "?". */ + }; + +/* The timer tick used for housekeeping stuff. Note that on Windows + * we use a SetWaitableTimer seems to signal earlier than about 2 + * seconds. Thus we use 4 seconds on all platforms except for + * Windowsce. CHECK_OWN_SOCKET_INTERVAL defines how often we check + * our own socket in standard socket mode. If that value is 0 we + * don't check at all. All values are in seconds. */ +#if defined(HAVE_W32CE_SYSTEM) +# define TIMERTICK_INTERVAL (60) +# define CHECK_OWN_SOCKET_INTERVAL (0) /* Never */ +#else +# define TIMERTICK_INTERVAL (4) +# define CHECK_OWN_SOCKET_INTERVAL (60) +#endif + +/* The list of open file descriptors at startup. Note that this list + * has been allocated using the standard malloc. */ +#ifndef HAVE_W32_SYSTEM +static int *startup_fd_list; +#endif + +/* The signal mask at startup and a flag telling whether it is valid. */ +#ifdef HAVE_SIGPROCMASK +static sigset_t startup_signal_mask; +static int startup_signal_mask_valid; +#endif + +/* Flag to indicate that a shutdown was requested. */ +static int shutdown_pending; + +/* Counter for the currently running own socket checks. */ +static int check_own_socket_running; + +/* Flag to indicate that we shall not watch our own socket. */ +static int disable_check_own_socket; + +/* Flag to inhibit socket removal in cleanup. */ +static int inhibit_socket_removal; + +/* Name of the communication socket used for client requests. */ +static char *socket_name; + +/* We need to keep track of the server's nonces (these are dummies for + * POSIX systems). */ +static assuan_sock_nonce_t socket_nonce; + +/* Value for the listen() backlog argument. We use the same value for + * all sockets - 64 is on current Linux half of the default maximum. + * Let's try this as default. Change at runtime with --listen-backlog. */ +static int listen_backlog = 64; + +/* Name of a config file, which will be reread on a HUP if it is not NULL. */ +static char *config_filename; + +/* Keep track of the current log file so that we can avoid updating + * the log file after a SIGHUP if it didn't changed. Malloced. */ +static char *current_logfile; + +/* This flag is true if the inotify mechanism for detecting the + * removal of the homedir is active. This flag is used to disable the + * alternative but portable stat based check. */ +static int have_homedir_inotify; + +/* Depending on how keyboxd was started, the homedir inotify watch may + * not be reliable. This flag is set if we assume that inotify works + * reliable. */ +static int reliable_homedir_inotify; + +/* Number of active connections. */ +static int active_connections; + +/* This object is used to dispatch progress messages from Libgcrypt to + * the right thread. Given that we will have at max only a few dozen + * connections at a time, using a linked list is the easiest way to + * handle this. */ +struct progress_dispatch_s +{ + struct progress_dispatch_s *next; + /* The control object of the connection. If this is NULL no + * connection is associated with this item and it is free for reuse + * by new connections. */ + ctrl_t ctrl; + + /* The thread id of (npth_self) of the connection. */ + npth_t tid; + + /* The callback set by the connection. This is similar to the + * Libgcrypt callback but with the control object passed as the + * first argument. */ + void (*cb)(ctrl_t ctrl, + const char *what, int printchar, + int current, int total); +}; +struct progress_dispatch_s *progress_dispatch_list; + + + + +/* + * Local prototypes. + */ + +static char *create_socket_name (char *standard_name, int with_homedir); +static gnupg_fd_t create_server_socket (char *name, int cygwin, + assuan_sock_nonce_t *nonce); +static void create_directories (void); + +static void kbxd_libgcrypt_progress_cb (void *data, const char *what, + int printchar, + int current, int total); +static void kbxd_init_default_ctrl (ctrl_t ctrl); +static void kbxd_deinit_default_ctrl (ctrl_t ctrl); + +static void handle_connections (gnupg_fd_t listen_fd); +static void check_own_socket (void); +static int check_for_running_kbxd (int silent); + +/* Pth wrapper function definitions. */ +ASSUAN_SYSTEM_NPTH_IMPL; + + +/* + * Functions. + */ + +/* Allocate a string describing a library version by calling a GETFNC. + * This function is expected to be called only once. GETFNC is + * expected to have a semantic like gcry_check_version (). */ +static char * +make_libversion (const char *libname, const char *(*getfnc)(const char*)) +{ + return xstrconcat (libname, " ", getfnc (NULL), NULL); +} + + +/* Return strings describing this program. The case values are + * described in common/argparse.c:strusage. The values here override + * the default values given by strusage. */ +static const char * +my_strusage (int level) +{ + static char *ver_gcry; + const char *p; + + switch (level) + { + case 11: p = "keyboxd (@GNUPG@)"; + break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + /* TRANSLATORS: @EMAIL@ will get replaced by the actual bug + reporting address. This is so that we can change the + reporting address without breaking the translations. */ + case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; + + case 20: + if (!ver_gcry) + ver_gcry = make_libversion ("libgcrypt", gcry_check_version); + p = ver_gcry; + break; + + case 1: + case 40: p = _("Usage: keyboxd [options] (-h for help)"); + break; + case 41: p = _("Syntax: keyboxd [options] [command [args]]\n" + "Public key management for @GNUPG@\n"); + break; + + default: p = NULL; + } + return p; +} + + + +/* Setup the debugging. Note that we don't fail here, because it is + * important to keep keyboxd running even after re-reading the options + * due to a SIGHUP. */ +static void +set_debug (void) +{ + if (opt.debug && !opt.verbose) + opt.verbose = 1; + if (opt.debug && opt.quiet) + opt.quiet = 0; + + if (opt.debug & DBG_MPI_VALUE) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2); + if (opt.debug & DBG_CRYPTO_VALUE ) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); + gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); + + if (opt.debug) + parse_debug_flag (NULL, &opt.debug, debug_flags); +} + + +/* Helper for cleanup to remove one socket with NAME. */ +static void +remove_socket (char *name) +{ + if (name && *name) + { + gnupg_remove (name); + *name = 0; + } +} + + +/* Cleanup code for this program. This is either called has an atexit + handler or directly. */ +static void +cleanup (void) +{ + static int done; + + if (done) + return; + done = 1; + if (!inhibit_socket_removal) + remove_socket (socket_name); +} + + +/* Handle options which are allowed to be reset after program start. + * Return true when the current option in PARGS could be handled and + * false if not. As a special feature, passing a value of NULL for + * PARGS, resets the options to the default. REREAD should be set + * true if it is not the initial option parsing. */ +static int +parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) +{ + if (!pargs) + { /* reset mode */ + opt.quiet = 0; + opt.verbose = 0; + opt.debug = 0; + disable_check_own_socket = 0; + return 1; + } + + switch (pargs->r_opt) + { + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + + case oDebug: + parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags); + break; + case oDebugAll: opt.debug = ~0; break; + + case oLogFile: + if (!reread) + return 0; /* not handeld */ + if (!current_logfile || !pargs->r.ret_str + || strcmp (current_logfile, pargs->r.ret_str)) + { + log_set_file (pargs->r.ret_str); + xfree (current_logfile); + current_logfile = xtrystrdup (pargs->r.ret_str); + } + break; + + case oDisableCheckOwnSocket: disable_check_own_socket = 1; break; + + default: + return 0; /* not handled */ + } + + return 1; /* handled */ +} + + +/* Fixup some options after all have been processed. */ +static void +finalize_rereadable_options (void) +{ +} + + +static void +thread_init_once (void) +{ + static int npth_initialized = 0; + + if (!npth_initialized) + { + npth_initialized++; + npth_init (); + } + gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); + /* Now that we have set the syscall clamp we need to tell Libgcrypt + * that it should get them from libgpg-error. Note that Libgcrypt + * has already been initialized but at that point nPth was not + * initialized and thus Libgcrypt could not set its system call + * clamp. */ + gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0); +} + + +static void +initialize_modules (void) +{ + thread_init_once (); + assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); +} + + +/* The main entry point. */ +int +main (int argc, char **argv ) +{ + ARGPARSE_ARGS pargs; + int orig_argc; + char **orig_argv; + FILE *configfp = NULL; + char *configname = NULL; + unsigned configlineno; + int parse_debug = 0; + int default_config =1; + int pipe_server = 0; + int is_daemon = 0; + int nodetach = 0; + char *logfile = NULL; + int gpgconf_list = 0; + int debug_wait = 0; + struct assuan_malloc_hooks malloc_hooks; + + early_system_init (); + + /* Before we do anything else we save the list of currently open + * file descriptors and the signal mask. This info is required to + * do the exec call properly. We don't need it on Windows. */ +#ifndef HAVE_W32_SYSTEM + startup_fd_list = get_all_open_fds (); +#endif /*!HAVE_W32_SYSTEM*/ +#ifdef HAVE_SIGPROCMASK + if (!sigprocmask (SIG_UNBLOCK, NULL, &startup_signal_mask)) + startup_signal_mask_valid = 1; +#endif /*HAVE_SIGPROCMASK*/ + + /* Set program name etc. */ + set_strusage (my_strusage); + log_set_prefix ("keyboxd", GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_WITH_PID); + + /* Make sure that our subsystems are ready. */ + i18n_init (); + init_common_subsystems (&argc, &argv); + gcry_control (GCRYCTL_DISABLE_SECMEM, 0); + + malloc_hooks.malloc = gcry_malloc; + malloc_hooks.realloc = gcry_realloc; + malloc_hooks.free = gcry_free; + assuan_set_malloc_hooks (&malloc_hooks); + assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); + assuan_sock_init (); + assuan_sock_set_system_hooks (ASSUAN_SYSTEM_NPTH); + setup_libassuan_logging (&opt.debug, NULL); + + setup_libgcrypt_logging (); + gcry_set_progress_handler (kbxd_libgcrypt_progress_cb, NULL); + + /* Set default options. */ + parse_rereadable_options (NULL, 0); /* Reset them to default values. */ + + /* Check whether we have a config file on the commandline */ + orig_argc = argc; + orig_argv = argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */ + while (arg_parse( &pargs, opts)) + { + if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll) + parse_debug++; + else if (pargs.r_opt == oOptions) + { /* Yes, a config file was given so we do not try the default + * one. Instead we read the config file when it is + * encountered during main parsing of the command line. */ + default_config = 0; + } + else if (pargs.r_opt == oNoOptions) + default_config = 0; /* --no-options */ + else if (pargs.r_opt == oHomedir) + gnupg_set_homedir (pargs.r.ret_str); + } + + if (default_config) + configname = make_filename (gnupg_homedir (), + "keyboxd" EXTSEP_S "conf", NULL); + + argc = orig_argc; + argv = orig_argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1; /* do not remove the args */ + next_pass: + if (configname) + { + configlineno = 0; + configfp = fopen (configname, "r"); + if (!configfp) + { + if (default_config) + { + if (parse_debug) + log_info (_("Note: no default option file '%s'\n"), configname); + /* Save the default confif file name so that + * reread_configuration is able to test whether the + * config file has been created in the meantime. */ + xfree (config_filename); + config_filename = configname; + configname = NULL; + } + else + { + log_error (_("option file '%s': %s\n"), + configname, strerror (errno)); + exit (2); + } + xfree (configname); + configname = NULL; + } + if (parse_debug && configname ) + log_info (_("reading options from '%s'\n"), configname); + default_config = 0; + } + + while (optfile_parse (configfp, configname, &configlineno, &pargs, opts)) + { + if (parse_rereadable_options (&pargs, 0)) + continue; /* Already handled */ + switch (pargs.r_opt) + { + case aGPGConfList: gpgconf_list = 1; break; + case aGPGConfTest: gpgconf_list = 2; break; + case oBatch: opt.batch=1; break; + case oDebugWait: debug_wait = pargs.r.ret_int; break; + case oOptions: + /* Config files may not be nested (silently ignore them). */ + if (!configfp) + { + xfree (configname); + configname = xstrdup (pargs.r.ret_str); + goto next_pass; + } + break; + case oNoGreeting: /* Dummy option. */ break; + case oNoVerbose: opt.verbose = 0; break; + case oNoOptions: break; /* no-options */ + case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; + case oNoDetach: nodetach = 1; break; + case oLogFile: logfile = pargs.r.ret_str; break; + case oServer: pipe_server = 1; break; + case oDaemon: is_daemon = 1; break; + case oFakedSystemTime: + { + time_t faked_time = isotime2epoch (pargs.r.ret_str); + if (faked_time == (time_t)(-1)) + faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10); + gnupg_set_time (faked_time, 0); + } + break; + + case oListenBacklog: + listen_backlog = pargs.r.ret_int; + break; + + default : pargs.err = configfp? 1:2; break; + } + } + if (configfp) + { + fclose (configfp); + configfp = NULL; + /* Keep a copy of the name so that it can be read on SIGHUP. */ + if (config_filename != configname) + { + xfree (config_filename); + config_filename = configname; + } + configname = NULL; + goto next_pass; + } + + xfree (configname); + configname = NULL; + if (log_get_errorcount(0)) + exit (2); + + finalize_rereadable_options (); + + /* Print a warning if an argument looks like an option. */ + if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) + { + int i; + + for (i=0; i < argc; i++) + if (argv[i][0] == '-' && argv[i][1] == '-') + log_info (_("Note: '%s' is not considered an option\n"), argv[i]); + } + +#ifdef ENABLE_NLS + /* keyboxd usually does not output any messages because it runs in + * the background. For log files it is acceptable to have messages + * always encoded in utf-8. We switch here to utf-8, so that + * commands like --help still give native messages. It is far + * easier to switch only once instead of for every message and it + * actually helps when more then one thread is active (avoids an + * extra copy step). */ + bind_textdomain_codeset (PACKAGE_GT, "UTF-8"); +#endif + + if (!pipe_server && !is_daemon && !gpgconf_list) + { + /* We have been called without any command and thus we merely + * check whether an instance of us is already running. We do + * this right here so that we don't clobber a logfile with this + * check but print the status directly to stderr. */ + opt.debug = 0; + set_debug (); + check_for_running_kbxd (0); + kbxd_exit (0); + } + + set_debug (); + + if (atexit (cleanup)) + { + log_error ("atexit failed\n"); + cleanup (); + exit (1); + } + + /* Try to create missing directories. */ + create_directories (); + + if (debug_wait && pipe_server) + { + thread_init_once (); + log_debug ("waiting for debugger - my pid is %u .....\n", + (unsigned int)getpid()); + gnupg_sleep (debug_wait); + log_debug ("... okay\n"); + } + + if (gpgconf_list == 2) + kbxd_exit (0); + else if (gpgconf_list) + { + char *filename; + char *filename_esc; + + /* List options and default values in the gpgconf format. */ + filename = make_filename (gnupg_homedir (), + "keyboxd" EXTSEP_S "conf", NULL); + filename_esc = percent_escape (filename, NULL); + + es_printf ("%s-%s.conf:%lu:\"%s\n", + GPGCONF_NAME, "keyboxd", GC_OPT_FLAG_DEFAULT, filename_esc); + xfree (filename); + xfree (filename_esc); + + es_printf ("verbose:%lu:\n" + "quiet:%lu:\n" + "log-file:%lu:\n", + GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, + GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, + GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME ); + + kbxd_exit (0); + } + + /* Now start with logging to a file if this is desired. */ + if (logfile) + { + log_set_file (logfile); + log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX + | GPGRT_LOG_WITH_TIME + | GPGRT_LOG_WITH_PID)); + current_logfile = xstrdup (logfile); + } + + if (pipe_server) + { + /* This is the simple pipe based server */ + ctrl_t ctrl; + + initialize_modules (); + + ctrl = xtrycalloc (1, sizeof *ctrl); + if (!ctrl) + { + log_error ("error allocating connection control data: %s\n", + strerror (errno) ); + kbxd_exit (1); + } + kbxd_init_default_ctrl (ctrl); + kbxd_start_command_handler (ctrl, GNUPG_INVALID_FD, 0); + kbxd_deinit_default_ctrl (ctrl); + xfree (ctrl); + } + else if (!is_daemon) + ; /* NOTREACHED */ + else + { /* Regular daemon mode. */ + gnupg_fd_t fd; +#ifndef HAVE_W32_SYSTEM + pid_t pid; +#endif + + /* Create the sockets. */ + socket_name = create_socket_name (KEYBOXD_SOCK_NAME, 1); + fd = create_server_socket (socket_name, 0, &socket_nonce); + + fflush (NULL); + +#ifdef HAVE_W32_SYSTEM + + (void)nodetach; + initialize_modules (); + +#else /*!HAVE_W32_SYSTEM*/ + + pid = fork (); + if (pid == (pid_t)-1) + { + log_fatal ("fork failed: %s\n", strerror (errno) ); + exit (1); + } + else if (pid) + { /* We are the parent */ + + /* Close the socket FD. */ + close (fd); + + /* The signal mask might not be correct right now and thus + * we restore it. That is not strictly necessary but some + * programs falsely assume a cleared signal mask. */ + +#ifdef HAVE_SIGPROCMASK + if (startup_signal_mask_valid) + { + if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL)) + log_error ("error restoring signal mask: %s\n", + strerror (errno)); + } + else + log_info ("no saved signal mask\n"); +#endif /*HAVE_SIGPROCMASK*/ + + *socket_name = 0; /* Don't let cleanup() remove the socket - + the child should do this from now on */ + + exit (0); + /*NOTREACHED*/ + } /* End parent */ + + /* + * This is the child + */ + + initialize_modules (); + + /* Detach from tty and put process into a new session */ + if (!nodetach) + { + int i; + unsigned int oldflags; + + /* Close stdin, stdout and stderr unless it is the log stream */ + for (i=0; i <= 2; i++) + { + if (!log_test_fd (i) && i != fd ) + { + if ( ! close (i) + && open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1) + { + log_error ("failed to open '%s': %s\n", + "/dev/null", strerror (errno)); + cleanup (); + exit (1); + } + } + } + if (setsid() == -1) + { + log_error ("setsid() failed: %s\n", strerror(errno) ); + cleanup (); + exit (1); + } + + log_get_prefix (&oldflags); + log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED); + opt.running_detached = 1; + + /* Because we don't support running a program on the command + * line we can assume that the inotify things works and thus + * we can avoid the regular stat calls. */ + reliable_homedir_inotify = 1; + } + + { + struct sigaction sa; + + sa.sa_handler = SIG_IGN; + sigemptyset (&sa.sa_mask); + sa.sa_flags = 0; + sigaction (SIGPIPE, &sa, NULL); + } +#endif /*!HAVE_W32_SYSTEM*/ + + if (gnupg_chdir (gnupg_daemon_rootdir ())) + { + log_error ("chdir to '%s' failed: %s\n", + gnupg_daemon_rootdir (), strerror (errno)); + exit (1); + } + + log_info ("%s %s started\n", strusage(11), strusage(13) ); + handle_connections (fd); + assuan_sock_close (fd); + } + + return 0; +} + + +/* Exit entry point. This function should be called instead of a + plain exit. */ +void +kbxd_exit (int rc) +{ + /* As usual we run our cleanup handler. */ + cleanup (); + + /* at this time a bit annoying */ + if ((opt.debug & DBG_MEMSTAT_VALUE)) + gcry_control (GCRYCTL_DUMP_MEMORY_STATS ); + rc = rc? rc : log_get_errorcount(0)? 2 : 0; + exit (rc); +} + + +/* This is our callback function for gcrypt progress messages. It is + * set once at startup and dispatches progress messages to the + * corresponding threads of ours. */ +static void +kbxd_libgcrypt_progress_cb (void *data, const char *what, int printchar, + int current, int total) +{ + struct progress_dispatch_s *dispatch; + npth_t mytid = npth_self (); + + (void)data; + + for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next) + if (dispatch->ctrl && dispatch->tid == mytid) + break; + if (dispatch && dispatch->cb) + dispatch->cb (dispatch->ctrl, what, printchar, current, total); +} + + +/* If a progress dispatcher callback has been associated with the + * current connection unregister it. */ +static void +unregister_progress_cb (void) +{ + struct progress_dispatch_s *dispatch; + npth_t mytid = npth_self (); + + for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next) + if (dispatch->ctrl && dispatch->tid == mytid) + break; + if (dispatch) + { + dispatch->ctrl = NULL; + dispatch->cb = NULL; + } +} + + +/* Setup a progress callback CB for the current connection. Using a + * CB of NULL disables the callback. */ +void +kbxd_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what, + int printchar, int current, int total), + ctrl_t ctrl) +{ + struct progress_dispatch_s *dispatch, *firstfree; + npth_t mytid = npth_self (); + + firstfree = NULL; + for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next) + { + if (dispatch->ctrl && dispatch->tid == mytid) + break; + if (!dispatch->ctrl && !firstfree) + firstfree = dispatch; + } + if (!dispatch) /* None allocated: Reuse or allocate a new one. */ + { + if (firstfree) + { + dispatch = firstfree; + } + else if ((dispatch = xtrycalloc (1, sizeof *dispatch))) + { + dispatch->next = progress_dispatch_list; + progress_dispatch_list = dispatch; + } + else + { + log_error ("error allocating new progress dispatcher slot: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + return; + } + dispatch->ctrl = ctrl; + dispatch->tid = mytid; + } + + dispatch->cb = cb; +} + + +/* Each thread has its own local variables conveyed by a control + * structure usually identified by an argument named CTRL. This + * function is called immediately after allocating the control + * structure. Its purpose is to setup the default values for that + * structure. Note that some values may have already been set. */ +static void +kbxd_init_default_ctrl (ctrl_t ctrl) +{ + ctrl->magic = SERVER_CONTROL_MAGIC; +} + + +/* Release all resources allocated by default in the control + structure. This is the counterpart to kbxd_init_default_ctrl. */ +static void +kbxd_deinit_default_ctrl (ctrl_t ctrl) +{ + if (!ctrl) + return; + ctrl->magic = 0xdeadbeef; + unregister_progress_cb (); + xfree (ctrl->lc_messages); +} + + +/* Reread parts of the configuration. Note, that this function is + * obviously not thread-safe and should only be called from the PTH + * signal handler. + * + * Fixme: Due to the way the argument parsing works, we create a + * memory leak here for all string type arguments. There is currently + * no clean way to tell whether the memory for the argument has been + * allocated or points into the process' original arguments. Unless + * we have a mechanism to tell this, we need to live on with this. */ +static void +reread_configuration (void) +{ + ARGPARSE_ARGS pargs; + FILE *fp; + unsigned int configlineno = 0; + int dummy; + + if (!config_filename) + return; /* No config file. */ + + fp = fopen (config_filename, "r"); + if (!fp) + { + log_info (_("option file '%s': %s\n"), + config_filename, strerror(errno) ); + return; + } + + parse_rereadable_options (NULL, 1); /* Start from the default values. */ + + memset (&pargs, 0, sizeof pargs); + dummy = 0; + pargs.argc = &dummy; + pargs.flags = 1; /* do not remove the args */ + while (optfile_parse (fp, config_filename, &configlineno, &pargs, opts) ) + { + if (pargs.r_opt < -1) + pargs.err = 1; /* Print a warning. */ + else /* Try to parse this option - ignore unchangeable ones. */ + parse_rereadable_options (&pargs, 1); + } + fclose (fp); + finalize_rereadable_options (); + set_debug (); +} + + +/* Return the file name of the socket we are using for requests. */ +const char * +get_kbxd_socket_name (void) +{ + const char *s = socket_name; + + return (s && *s)? s : NULL; +} + + +/* Return the number of active connections. */ +int +get_kbxd_active_connection_count (void) +{ + return active_connections; +} + + +/* Create a name for the socket in the home directory as using + * STANDARD_NAME. We also check for valid characters as well as + * against a maximum allowed length for a Unix domain socket is done. + * The function terminates the process in case of an error. The + * function returns a pointer to an allocated string with the absolute + * name of the socket used. */ +static char * +create_socket_name (char *standard_name, int with_homedir) +{ + char *name; + + if (with_homedir) + name = make_filename (gnupg_socketdir (), standard_name, NULL); + else + name = make_filename (standard_name, NULL); + if (strchr (name, PATHSEP_C)) + { + log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S); + kbxd_exit (2); + } + return name; +} + + + +/* Create a Unix domain socket with NAME. Returns the file descriptor + * or terminates the process in case of an error. If CYGWIN is set a + * Cygwin compatible socket is created (Windows only). */ +static gnupg_fd_t +create_server_socket (char *name, int cygwin, assuan_sock_nonce_t *nonce) +{ + struct sockaddr *addr; + struct sockaddr_un *unaddr; + socklen_t len; + gnupg_fd_t fd; + int rc; + + fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0); + if (fd == ASSUAN_INVALID_FD) + { + log_error (_("can't create socket: %s\n"), strerror (errno)); + *name = 0; /* Inhibit removal of the socket by cleanup(). */ + kbxd_exit (2); + } + + if (cygwin) + assuan_sock_set_flag (fd, "cygwin", 1); + + unaddr = xmalloc (sizeof *unaddr); + addr = (struct sockaddr*)unaddr; + + if (assuan_sock_set_sockaddr_un (name, addr, NULL)) + { + if (errno == ENAMETOOLONG) + log_error (_("socket name '%s' is too long\n"), name); + else + log_error ("error preparing socket '%s': %s\n", + name, gpg_strerror (gpg_error_from_syserror ())); + *name = 0; /* Inhibit removal of the socket by cleanup(). */ + xfree (unaddr); + kbxd_exit (2); + } + + len = SUN_LEN (unaddr); + rc = assuan_sock_bind (fd, addr, len); + + /* Our error code mapping on W32CE returns EEXIST thus we also test + for this. */ + if (rc == -1 + && (errno == EADDRINUSE +#ifdef HAVE_W32_SYSTEM + || errno == EEXIST +#endif + )) + { + /* Check whether a keyboxd is already running. */ + if (!check_for_running_kbxd (1)) + { + log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX); + log_set_file (NULL); + log_error (_("a keyboxd is already running - " + "not starting a new one\n")); + *name = 0; /* Inhibit removal of the socket by cleanup(). */ + assuan_sock_close (fd); + xfree (unaddr); + kbxd_exit (2); + } + gnupg_remove (unaddr->sun_path); + rc = assuan_sock_bind (fd, addr, len); + } + if (rc != -1 && (rc=assuan_sock_get_nonce (addr, len, nonce))) + log_error (_("error getting nonce for the socket\n")); + if (rc == -1) + { + /* We use gpg_strerror here because it allows us to get strings + for some W32 socket error codes. */ + log_error (_("error binding socket to '%s': %s\n"), + unaddr->sun_path, gpg_strerror (gpg_error_from_syserror ())); + + assuan_sock_close (fd); + *name = 0; /* Inhibit removal of the socket by cleanup(). */ + xfree (unaddr); + kbxd_exit (2); + } + + if (gnupg_chmod (unaddr->sun_path, "-rwx")) + log_error (_("can't set permissions of '%s': %s\n"), + unaddr->sun_path, strerror (errno)); + + if (listen (FD2INT(fd), listen_backlog ) == -1) + { + log_error ("listen(fd,%d) failed: %s\n", listen_backlog, strerror (errno)); + *name = 0; /* Inhibit removal of the socket by cleanup(). */ + assuan_sock_close (fd); + xfree (unaddr); + kbxd_exit (2); + } + + if (opt.verbose) + log_info (_("listening on socket '%s'\n"), unaddr->sun_path); + + xfree (unaddr); + return fd; +} + + +/* Check that the directory for storing the public keys exists and + * create it if not. This function won't fail as it is only a + * convenience function and not strictly necessary. */ +static void +create_public_keys_directory (const char *home) +{ + char *fname; + struct stat statbuf; + + fname = make_filename (home, GNUPG_PUBLIC_KEYS_DIR, NULL); + if (stat (fname, &statbuf) && errno == ENOENT) + { + if (gnupg_mkdir (fname, "-rwxr-x")) + log_error (_("can't create directory '%s': %s\n"), + fname, strerror (errno) ); + else if (!opt.quiet) + log_info (_("directory '%s' created\n"), fname); + } + if (gnupg_chmod (fname, "-rwxr-x")) + log_error (_("can't set permissions of '%s': %s\n"), + fname, strerror (errno)); + xfree (fname); +} + + +/* Create the directory only if the supplied directory name is the + * same as the default one. This way we avoid to create arbitrary + * directories when a non-default home directory is used. To cope + * with HOME, we compare only the suffix if we see that the default + * homedir does start with a tilde. We don't stop here in case of + * problems because other functions will throw an error anyway.*/ +static void +create_directories (void) +{ + struct stat statbuf; + const char *defhome = standard_homedir (); + char *home; + + home = make_filename (gnupg_homedir (), NULL); + if (stat (home, &statbuf)) + { + if (errno == ENOENT) + { + if ( +#ifdef HAVE_W32_SYSTEM + ( !compare_filenames (home, defhome) ) +#else + (*defhome == '~' + && (strlen (home) >= strlen (defhome+1) + && !strcmp (home + strlen(home) + - strlen (defhome+1), defhome+1))) + || (*defhome != '~' && !strcmp (home, defhome) ) +#endif + ) + { + if (gnupg_mkdir (home, "-rwx")) + log_error (_("can't create directory '%s': %s\n"), + home, strerror (errno) ); + else + { + if (!opt.quiet) + log_info (_("directory '%s' created\n"), home); + } + } + } + else + log_error (_("stat() failed for '%s': %s\n"), home, strerror (errno)); + } + else if ( !S_ISDIR(statbuf.st_mode)) + { + log_error (_("can't use '%s' as home directory\n"), home); + } + else /* exists and is a directory. */ + { + create_public_keys_directory (home); + } + xfree (home); +} + + + +/* This is the worker for the ticker. It is called every few seconds + * and may only do fast operations. */ +static void +handle_tick (void) +{ + static time_t last_minute; + struct stat statbuf; + + if (!last_minute) + last_minute = time (NULL); + + /* Code to be run from time to time. */ +#if CHECK_OWN_SOCKET_INTERVAL > 0 + if (last_minute + CHECK_OWN_SOCKET_INTERVAL <= time (NULL)) + { + check_own_socket (); + last_minute = time (NULL); + } +#endif + + + /* Check whether the homedir is still available. */ + if (!shutdown_pending + && (!have_homedir_inotify || !reliable_homedir_inotify) + && stat (gnupg_homedir (), &statbuf) && errno == ENOENT) + { + shutdown_pending = 1; + log_info ("homedir has been removed - shutting down\n"); + } +} + + +/* A global function which allows us to call the reload stuff from + * other places too. This is only used when build for W32. */ +void +kbxd_sighup_action (void) +{ + log_info ("SIGHUP received - " + "re-reading configuration and flushing cache\n"); + + reread_configuration (); +} + + +/* A helper function to handle SIGUSR2. */ +static void +kbxd_sigusr2_action (void) +{ + if (opt.verbose) + log_info ("SIGUSR2 received - no action\n"); + /* Nothing to do right now. */ +} + + +#ifndef HAVE_W32_SYSTEM +/* The signal handler for this program. It is expected to be run in + * its own thread and not in the context of a signal handler. */ +static void +handle_signal (int signo) +{ + switch (signo) + { + case SIGHUP: + kbxd_sighup_action (); + break; + + case SIGUSR1: + log_info ("SIGUSR1 received - printing internal information:\n"); + /* Fixme: We need to see how to integrate pth dumping into our + logging system. */ + /* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */ + break; + + case SIGUSR2: + kbxd_sigusr2_action (); + break; + + case SIGTERM: + if (!shutdown_pending) + log_info ("SIGTERM received - shutting down ...\n"); + else + log_info ("SIGTERM received - still %i open connections\n", + active_connections); + shutdown_pending++; + if (shutdown_pending > 2) + { + log_info ("shutdown forced\n"); + log_info ("%s %s stopped\n", strusage(11), strusage(13) ); + cleanup (); + kbxd_exit (0); + } + break; + + case SIGINT: + log_info ("SIGINT received - immediate shutdown\n"); + log_info( "%s %s stopped\n", strusage(11), strusage(13)); + cleanup (); + kbxd_exit (0); + break; + + default: + log_info ("signal %d received - no action defined\n", signo); + } +} +#endif + +/* Check the nonce on a new connection. This is a NOP unless we + are using our Unix domain socket emulation under Windows. */ +static int +check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce) +{ + if (assuan_sock_check_nonce (ctrl->thread_startup.fd, nonce)) + { + log_info (_("error reading nonce on fd %d: %s\n"), + FD2INT(ctrl->thread_startup.fd), strerror (errno)); + assuan_sock_close (ctrl->thread_startup.fd); + xfree (ctrl); + return -1; + } + else + return 0; +} + + +static void * +do_start_connection_thread (ctrl_t ctrl) +{ + static unsigned int last_session_id; + unsigned int session_id; + + active_connections++; + kbxd_init_default_ctrl (ctrl); + if (opt.verbose && !DBG_IPC) + log_info (_("handler 0x%lx for fd %d started\n"), + (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); + + session_id = ++last_session_id; + if (!session_id) + session_id = ++last_session_id; + kbxd_start_command_handler (ctrl, ctrl->thread_startup.fd, session_id); + if (opt.verbose && !DBG_IPC) + log_info (_("handler 0x%lx for fd %d terminated\n"), + (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); + + kbxd_deinit_default_ctrl (ctrl); + xfree (ctrl); + active_connections--; + return NULL; +} + + +/* This is the standard connection thread's main function. */ +static void * +start_connection_thread (void *arg) +{ + ctrl_t ctrl = arg; + + if (check_nonce (ctrl, &socket_nonce)) + { + log_error ("handler 0x%lx nonce check FAILED\n", + (unsigned long) npth_self()); + return NULL; + } + + return do_start_connection_thread (ctrl); +} + + +/* Connection handler loop. Wait for connection requests and spawn a + * thread after accepting a connection. */ +static void +handle_connections (gnupg_fd_t listen_fd) +{ + gpg_error_t err; + npth_attr_t tattr; + struct sockaddr_un paddr; + socklen_t plen; + fd_set fdset, read_fdset; + int ret; + gnupg_fd_t fd; + int nfd; + int saved_errno; + struct timespec abstime; + struct timespec curtime; + struct timespec timeout; +#ifdef HAVE_W32_SYSTEM + HANDLE events[2]; + unsigned int events_set; +#endif + int sock_inotify_fd = -1; + int home_inotify_fd = -1; + struct { + const char *name; + void *(*func) (void *arg); + gnupg_fd_t l_fd; + } listentbl[] = { + { "std", start_connection_thread }, + }; + + + ret = npth_attr_init(&tattr); + if (ret) + log_fatal ("error allocating thread attributes: %s\n", strerror (ret)); + npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); + +#ifndef HAVE_W32_SYSTEM + npth_sigev_init (); + npth_sigev_add (SIGHUP); + npth_sigev_add (SIGUSR1); + npth_sigev_add (SIGUSR2); + npth_sigev_add (SIGINT); + npth_sigev_add (SIGTERM); + npth_sigev_fini (); +#else +# ifdef HAVE_W32CE_SYSTEM + /* Use a dummy event. */ + sigs = 0; + ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); +# else + events[0] = INVALID_HANDLE_VALUE; +# endif +#endif + + if (disable_check_own_socket) + sock_inotify_fd = -1; + else if ((err = gnupg_inotify_watch_socket (&sock_inotify_fd, socket_name))) + { + if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED) + log_info ("error enabling daemon termination by socket removal: %s\n", + gpg_strerror (err)); + } + + if (disable_check_own_socket) + home_inotify_fd = -1; + else if ((err = gnupg_inotify_watch_delete_self (&home_inotify_fd, + gnupg_homedir ()))) + { + if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED) + log_info ("error enabling daemon termination by homedir removal: %s\n", + gpg_strerror (err)); + } + else + have_homedir_inotify = 1; + + FD_ZERO (&fdset); + FD_SET (FD2INT (listen_fd), &fdset); + nfd = FD2INT (listen_fd); + if (sock_inotify_fd != -1) + { + FD_SET (sock_inotify_fd, &fdset); + if (sock_inotify_fd > nfd) + nfd = sock_inotify_fd; + } + if (home_inotify_fd != -1) + { + FD_SET (home_inotify_fd, &fdset); + if (home_inotify_fd > nfd) + nfd = home_inotify_fd; + } + + listentbl[0].l_fd = listen_fd; + + npth_clock_gettime (&abstime); + abstime.tv_sec += TIMERTICK_INTERVAL; + + for (;;) + { + /* Shutdown test. */ + if (shutdown_pending) + { + if (!active_connections) + break; /* ready */ + + /* Do not accept new connections but keep on running the + * loop to cope with the timer events. + * + * Note that we do not close the listening socket because a + * client trying to connect to that socket would instead + * restart a new keyboxd instance - which is unlikely the + * intention of a shutdown. */ + FD_ZERO (&fdset); + nfd = -1; + if (sock_inotify_fd != -1) + { + FD_SET (sock_inotify_fd, &fdset); + nfd = sock_inotify_fd; + } + if (home_inotify_fd != -1) + { + FD_SET (home_inotify_fd, &fdset); + if (home_inotify_fd > nfd) + nfd = home_inotify_fd; + } + } + + read_fdset = fdset; + + npth_clock_gettime (&curtime); + if (!(npth_timercmp (&curtime, &abstime, <))) + { + /* Timeout. */ + handle_tick (); + npth_clock_gettime (&abstime); + abstime.tv_sec += TIMERTICK_INTERVAL; + } + npth_timersub (&abstime, &curtime, &timeout); + +#ifndef HAVE_W32_SYSTEM + ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout, + npth_sigev_sigmask ()); + saved_errno = errno; + + { + int signo; + while (npth_sigev_get_pending (&signo)) + handle_signal (signo); + } +#else + ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout, + events, &events_set); + saved_errno = errno; + + /* This is valid even if npth_eselect returns an error. */ + if ((events_set & 1)) + kbxd_sigusr2_action (); +#endif + + if (ret == -1 && saved_errno != EINTR) + { + log_error (_("npth_pselect failed: %s - waiting 1s\n"), + strerror (saved_errno)); + npth_sleep (1); + continue; + } + if (ret <= 0) + { + /* Interrupt or timeout. Will be handled when calculating the + * next timeout. */ + continue; + } + + /* The inotify fds are set even when a shutdown is pending (see + * above). So we must handle them in any case. To avoid that + * they trigger a second time we close them immediately. */ + if (sock_inotify_fd != -1 + && FD_ISSET (sock_inotify_fd, &read_fdset) + && gnupg_inotify_has_name (sock_inotify_fd, KEYBOXD_SOCK_NAME)) + { + shutdown_pending = 1; + close (sock_inotify_fd); + sock_inotify_fd = -1; + log_info ("socket file has been removed - shutting down\n"); + } + + if (home_inotify_fd != -1 + && FD_ISSET (home_inotify_fd, &read_fdset)) + { + shutdown_pending = 1; + close (home_inotify_fd); + home_inotify_fd = -1; + log_info ("homedir has been removed - shutting down\n"); + } + + if (!shutdown_pending) + { + int idx; + ctrl_t ctrl; + npth_t thread; + + for (idx=0; idx < DIM(listentbl); idx++) + { + if (listentbl[idx].l_fd == GNUPG_INVALID_FD) + continue; + if (!FD_ISSET (FD2INT (listentbl[idx].l_fd), &read_fdset)) + continue; + + plen = sizeof paddr; + fd = INT2FD (npth_accept (FD2INT(listentbl[idx].l_fd), + (struct sockaddr *)&paddr, &plen)); + if (fd == GNUPG_INVALID_FD) + { + log_error ("accept failed for %s: %s\n", + listentbl[idx].name, strerror (errno)); + } + else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl))) + { + log_error ("error allocating connection data for %s: %s\n", + listentbl[idx].name, strerror (errno) ); + assuan_sock_close (fd); + } + else + { + ctrl->thread_startup.fd = fd; + ret = npth_create (&thread, &tattr, + listentbl[idx].func, ctrl); + if (ret) + { + log_error ("error spawning connection handler for %s:" + " %s\n", listentbl[idx].name, strerror (ret)); + assuan_sock_close (fd); + xfree (ctrl); + } + } + } + } + } + + if (sock_inotify_fd != -1) + close (sock_inotify_fd); + if (home_inotify_fd != -1) + close (home_inotify_fd); + cleanup (); + log_info (_("%s %s stopped\n"), strusage(11), strusage(13)); + npth_attr_destroy (&tattr); +} + + + +/* Helper for check_own_socket. */ +static gpg_error_t +check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length) +{ + membuf_t *mb = opaque; + put_membuf (mb, buffer, length); + return 0; +} + + +/* The thread running the actual check. We need to run this in a + * separate thread so that check_own_thread can be called from the + * timer tick. */ +static void * +check_own_socket_thread (void *arg) +{ + int rc; + char *sockname = arg; + assuan_context_t ctx = NULL; + membuf_t mb; + char *buffer; + + check_own_socket_running++; + + rc = assuan_new (&ctx); + if (rc) + { + log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); + goto leave; + } + assuan_set_flag (ctx, ASSUAN_NO_LOGGING, 1); + + rc = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0); + if (rc) + { + log_error ("can't connect my own socket: %s\n", gpg_strerror (rc)); + goto leave; + } + + init_membuf (&mb, 100); + rc = assuan_transact (ctx, "GETINFO pid", check_own_socket_pid_cb, &mb, + NULL, NULL, NULL, NULL); + put_membuf (&mb, "", 1); + buffer = get_membuf (&mb, NULL); + if (rc || !buffer) + { + log_error ("sending command \"%s\" to my own socket failed: %s\n", + "GETINFO pid", gpg_strerror (rc)); + rc = 1; + } + else if ( (pid_t)strtoul (buffer, NULL, 10) != getpid ()) + { + log_error ("socket is now serviced by another server\n"); + rc = 1; + } + else if (opt.verbose > 1) + log_error ("socket is still served by this server\n"); + + xfree (buffer); + + leave: + xfree (sockname); + if (ctx) + assuan_release (ctx); + if (rc) + { + /* We may not remove the socket as it is now in use by another + * server. */ + inhibit_socket_removal = 1; + shutdown_pending = 2; + log_info ("this process is useless - shutting down\n"); + } + check_own_socket_running--; + return NULL; +} + + +/* Check whether we are still listening on our own socket. In case + * another keyboxd process started after us has taken ownership of our + * socket, we would linger around without any real task. Thus we + * better check once in a while whether we are really needed. */ +static void +check_own_socket (void) +{ + char *sockname; + npth_t thread; + npth_attr_t tattr; + int err; + + if (disable_check_own_socket) + return; + + if (check_own_socket_running || shutdown_pending) + return; /* Still running or already shutting down. */ + + sockname = make_filename_try (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL); + if (!sockname) + return; /* Out of memory. */ + + err = npth_attr_init (&tattr); + if (err) + return; + npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); + err = npth_create (&thread, &tattr, check_own_socket_thread, sockname); + if (err) + log_error ("error spawning check_own_socket_thread: %s\n", strerror (err)); + npth_attr_destroy (&tattr); +} + + + +/* Figure out whether a keyboxd is available and running. Prints an + * error if not. If SILENT is true, no messages are printed. Returns + * 0 if the agent is running. */ +static int +check_for_running_kbxd (int silent) +{ + gpg_error_t err; + char *sockname; + assuan_context_t ctx = NULL; + + sockname = make_filename_try (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL); + if (!sockname) + return gpg_error_from_syserror (); + + err = assuan_new (&ctx); + if (!err) + err = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0); + xfree (sockname); + if (err) + { + if (!silent) + log_error (_("no keyboxd running in this session\n")); + + if (ctx) + assuan_release (ctx); + return -1; + } + + if (!opt.quiet && !silent) + log_info ("keyboxd running and available\n"); + + assuan_release (ctx); + return 0; +} diff --git a/kbx/keyboxd.h b/kbx/keyboxd.h new file mode 100644 index 000000000..7719061f4 --- /dev/null +++ b/kbx/keyboxd.h @@ -0,0 +1,137 @@ +/* keyboxd.h - Global definitions for keyboxd + * Copyright (C) 2018 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef KEYBOXD_H +#define KEYBOXD_H + +#ifdef GPG_ERR_SOURCE_DEFAULT +#error GPG_ERR_SOURCE_DEFAULT already defined +#endif +#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_KEYBOX +#include + +#include +#include "../common/util.h" +#include "../common/membuf.h" +#include "../common/sysutils.h" /* (gnupg_fd_t) */ + + +/* A large struct name "opt" to keep global flags */ +struct +{ + unsigned int debug; /* Debug flags (DBG_foo_VALUE) */ + int verbose; /* Verbosity level */ + int quiet; /* Be as quiet as possible */ + int dry_run; /* Don't change any persistent data */ + int batch; /* Batch mode */ + + /* True if we are running detached from the tty. */ + int running_detached; + +} opt; + + +/* Bit values for the --debug option. */ +#define DBG_MPI_VALUE 2 /* debug mpi details */ +#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */ +#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */ +#define DBG_CACHE_VALUE 64 /* debug the caching */ +#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */ +#define DBG_HASHING_VALUE 512 /* debug hashing operations */ +#define DBG_IPC_VALUE 1024 /* Enable Assuan debugging. */ + +/* Test macros for the debug option. */ +#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) +#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE) +#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE) +#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE) +#define DBG_IPC (opt.debug & DBG_IPC_VALUE) + +/* Forward reference for local definitions in command.c. */ +struct server_local_s; + +#if SIZEOF_UNSIGNED_LONG == 8 +# define SERVER_CONTROL_MAGIC 0x6b6579626f786420 +#else +# define SERVER_CONTROL_MAGIC 0x6b627864 +#endif + +/* Collection of data per session (aka connection). */ +struct server_control_s +{ + unsigned long magic;/* Always has SERVER_CONTROL_MAGIC. */ + int refcount; /* Count additional references to this object. */ + + /* Private data used to fire up the connection thread. We use this + * structure do avoid an extra allocation for only a few bytes while + * spawning a new connection thread. */ + struct { + gnupg_fd_t fd; + } thread_startup; + + /* Private data of the server (kbxserver.c). */ + struct server_local_s *server_local; + + /* Environment settings for the connection. */ + char *lc_messages; + + /* Miscellaneous info on the connection. */ + unsigned long client_pid; + int client_uid; + +}; + + +/* This is a special version of the usual _() gettext macro. It + * assumes a server connection control variable with the name "ctrl" + * and uses that to translate a string according to the locale set for + * the connection. The macro LunderscoreIMPL is used by i18n to + * actually define the inline function when needed. */ +#if defined (ENABLE_NLS) || defined (USE_SIMPLE_GETTEXT) +#define L_(a) keyboxd_Lunderscore (ctrl, (a)) +#define LunderscorePROTO \ + static inline const char *keyboxd_Lunderscore (ctrl_t ctrl, \ + const char *string) \ + GNUPG_GCC_ATTR_FORMAT_ARG(2); +#define LunderscoreIMPL \ + static inline const char * \ + keyboxd_Lunderscore (ctrl_t ctrl, const char *string) \ + { \ + return ctrl? i18n_localegettext (ctrl->lc_messages, string) \ + /* */: gettext (string); \ + } +#else +#define L_(a) (a) +#endif + + +/*-- keyboxd.c --*/ +void kbxd_exit (int rc) GPGRT_ATTR_NORETURN; +void kbxd_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what, + int printchar, int current, int total), + ctrl_t ctrl); +const char *get_kbxd_socket_name (void); +int get_kbxd_active_connection_count (void); +void kbxd_sighup_action (void); + + +/*-- kbxserver.c --*/ +void kbxd_start_command_handler (ctrl_t, gnupg_fd_t, unsigned int); + +#endif /*KEYBOXD_H*/