diff --git a/am/cmacros.am b/am/cmacros.am index d6d466fb6..0841efd86 100644 --- a/am/cmacros.am +++ b/am/cmacros.am @@ -44,6 +44,9 @@ endif if GNUPG_SCDAEMON_PGM AM_CPPFLAGS += -DGNUPG_DEFAULT_SCDAEMON="\"@GNUPG_SCDAEMON_PGM@\"" endif +if GNUPG_TKDAEMON_PGM +AM_CPPFLAGS += -DGNUPG_DEFAULT_TKDAEMON="\"@GNUPG_TKDAEMON_PGM@\"" +endif if GNUPG_TPM2DAEMON_PGM AM_CPPFLAGS += -DGNUPG_DEFAULT_TPM2DAEMON="\"@GNUPG_TPM2DAEMON_PGM@\"" endif diff --git a/common/mapstrings.c b/common/mapstrings.c index 9efe95c0d..f95dd8ad9 100644 --- a/common/mapstrings.c +++ b/common/mapstrings.c @@ -50,6 +50,7 @@ static struct { { "GPGSM", GPGSM_NAME }, { "GPG_AGENT", GPG_AGENT_NAME }, { "SCDAEMON", SCDAEMON_NAME }, + { "TKDAEMON", TKDAEMON_NAME }, { "TPM2DAEMON",TPM2DAEMON_NAME}, { "DIRMNGR", DIRMNGR_NAME }, { "G13", G13_NAME }, diff --git a/common/util.h b/common/util.h index d80e4fb25..899b775ea 100644 --- a/common/util.h +++ b/common/util.h @@ -298,6 +298,7 @@ char *_gnupg_socketdir_internal (int skip_checks, unsigned *r_info); #define GNUPG_MODULE_NAME_KEYBOXD 13 #define GNUPG_MODULE_NAME_TPM2DAEMON 14 #define GNUPG_MODULE_NAME_CARD 15 +#define GNUPG_MODULE_NAME_TKDAEMON 16 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 a9509b2e5..cc12667ea 100644 --- a/configure.ac +++ b/configure.ac @@ -187,6 +187,13 @@ AM_CONDITIONAL(GNUPG_SCDAEMON_PGM, test -n "$GNUPG_SCDAEMON_PGM") show_gnupg_scdaemon_pgm="(default)" test -n "$GNUPG_SCDAEMON_PGM" && show_gnupg_scdaemon_pgm="$GNUPG_SCDAEMON_PGM" +AC_ARG_WITH(tkdaemon-pgm, + [ --with-tkdaemon-pgm=PATH Use PATH as the default for the tkdaemon)], + GNUPG_TKDAEMON_PGM="$withval", GNUPG_TKDAEMON_PGM="" ) +AC_SUBST(GNUPG_TKDAEMON_PGM) +AM_CONDITIONAL(GNUPG_TKDAEMON_PGM, test -n "$GNUPG_TKDAEMON_PGM") +show_gnupg_tkdaemon_pgm="(default)" +test -n "$GNUPG_TKDAEMON_PGM" && show_gnupg_tkdaemon_pgm="$GNUPG_TKDAEMON_PGM" AC_ARG_WITH(tpm2daemon-pgm, [ --with-tpm2daemon-pgm=PATH Use PATH as the default for the tpm2daemon)], @@ -1870,6 +1877,9 @@ fi if test "$build_scdaemon" = yes ; then AC_DEFINE(BUILD_WITH_SCDAEMON,1,[Defined if SCDAEMON is to be build]) fi +if test "$build_tkdaemon" = yes ; then + AC_DEFINE(BUILD_WITH_TKDAEMON,1,[Defined if TKDAEMON is to be build]) +fi if test "$build_dirmngr" = yes ; then AC_DEFINE(BUILD_WITH_DIRMNGR,1,[Defined if DIRMNGR is to be build]) fi @@ -1907,6 +1917,10 @@ AC_DEFINE_UNQUOTED(SCDAEMON_NAME, "scdaemon", [The name of the scdaemon]) AC_DEFINE_UNQUOTED(SCDAEMON_DISP_NAME, "SCDaemon", [The displayed name of scdaemon]) +AC_DEFINE_UNQUOTED(TKDAEMON_NAME, "tkdaemon", [The name of the tkdaemon]) +AC_DEFINE_UNQUOTED(TKDAEMON_DISP_NAME, "TKDaemon", + [The displayed name of tkdaemon]) + AC_DEFINE_UNQUOTED(DIRMNGR_NAME, "dirmngr", [The name of the dirmngr]) AC_DEFINE_UNQUOTED(DIRMNGR_DISP_NAME, "DirMngr", [The displayed name of dirmngr]) @@ -1936,6 +1950,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(TKDAEMON_SOCK_NAME, "S.tkdaemon", + [The name of the TKDaemon socket]) AC_DEFINE_UNQUOTED(KEYBOXD_SOCK_NAME, "S.keyboxd", [The name of the keyboxd socket]) AC_DEFINE_UNQUOTED(TPM2DAEMON_SOCK_NAME, "S.tpm2daemon", @@ -2131,6 +2147,7 @@ echo " S/MIME: $build_gpgsm Agent: $build_agent Smartcard: $build_scdaemon $build_scdaemon_extra + token: $build_tkdaemon $build_tkdaemon_extra TPM: $build_tpm2d $show_tss_type G13: $build_g13 Dirmngr: $build_dirmngr @@ -2143,6 +2160,7 @@ echo " Default agent: $show_gnupg_agent_pgm Default pinentry: $show_gnupg_pinentry_pgm Default scdaemon: $show_gnupg_scdaemon_pgm + Default tkdaemon: $show_gnupg_tkdaemon_pgm Default keyboxd: $show_gnupg_keyboxd_pgm Default tpm2daemon: $show_gnupg_tpm2daemon_pgm Default dirmngr: $show_gnupg_dirmngr_pgm diff --git a/tkd/Makefile.am b/tkd/Makefile.am index b6c30eccb..4f381f249 100644 --- a/tkd/Makefile.am +++ b/tkd/Makefile.am @@ -18,9 +18,7 @@ EXTRA_DIST = -# libexec_PROGRAMS = tkdaemon - -noinst_PROGRAMS = pksign +libexec_PROGRAMS = tkdaemon AM_CPPFLAGS = @@ -31,7 +29,7 @@ include $(top_srcdir)/am/cmacros.am tkdaemon_SOURCES = \ tkdaemon.c tkdaemon.h \ - command.c + command.c pkcs11.c tkdaemon_LDADD = $(libcommonpth) \ $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \ diff --git a/tkd/command.c b/tkd/command.c new file mode 100644 index 000000000..682d71064 --- /dev/null +++ b/tkd/command.c @@ -0,0 +1,695 @@ +/* command.c - TKdaemon command handler + * Copyright (C) 2001, 2002, 2003, 2004, 2005, + * 2007, 2008, 2009, 2011 Free Software Foundation, Inc. + * + * 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 +#include +#ifdef USE_NPTH +# include +#endif + +#include "tkdaemon.h" +#include +#include "../common/asshelp.h" +#include "../common/server-help.h" +#include "../common/ssh-utils.h" + +/* Maximum length allowed as a PIN; used for INQUIRE NEEDPIN. That + * length needs to small compared to the maximum Assuan line length. */ +#define MAXLEN_PIN 100 + +#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) + + +/* Data used to associate an Assuan context with local server data. + This object describes the local properties of one session. */ +struct server_local_s +{ + /* We keep a list of all active sessions with the anchor at + SESSION_LIST (see below). This field is used for linking. */ + struct server_local_s *next_session; + + /* This object is usually assigned to a CTRL object (which is + globally visible). While enumerating all sessions we sometimes + need to access data of the CTRL object; thus we keep a + backpointer here. */ + ctrl_t ctrl_backlink; + + /* The Assuan context used by this session/server. */ + assuan_context_t assuan_ctx; + +#ifdef HAVE_W32_SYSTEM + void *event_signal; /* Or NULL if not used. */ +#else + int event_signal; /* Or 0 if not used. */ +#endif + + /* If set to true we will be terminate ourself at the end of the + this session. */ + unsigned int stopme:1; +}; + + +struct token_ctx_s +{ +}; + +/* To keep track of all running sessions, we link all active server + contexts and the anchor in this variable. */ +static struct server_local_s *session_list; + +static void +finalize (ctrl_t ctrl) +{ + (void)ctrl; +} + +static gpg_error_t +option_handler (assuan_context_t ctx, const char *key, const char *value) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + + if (!strcmp (key, "event-signal")) + { + /* A value of 0 is allowed to reset the event signal. */ +#ifdef HAVE_W32_SYSTEM + if (!*value) + return gpg_error (GPG_ERR_ASS_PARAMETER); +#ifdef _WIN64 + ctrl->server_local->event_signal = (void *)strtoull (value, NULL, 16); +#else + ctrl->server_local->event_signal = (void *)strtoul (value, NULL, 16); +#endif +#else + int i = *value? atoi (value) : -1; + if (i < 0) + return gpg_error (GPG_ERR_ASS_PARAMETER); + ctrl->server_local->event_signal = i; +#endif + } + + return 0; +} + +#if 0 +static gpg_error_t +pin_cb (void *opaque, const char *info, char **retstr) +{ + assuan_context_t ctx = opaque; + char *command; + int rc; + unsigned char *value; + size_t valuelen; + + if (!retstr) + { + /* We prompt for pinpad entry. To make sure that the popup has + been show we use an inquire and not just a status message. + We ignore any value returned. */ + if (info) + { + log_debug ("prompting for pinpad entry '%s'\n", info); + rc = gpgrt_asprintf (&command, "POPUPPINPADPROMPT %s", info); + if (rc < 0) + return gpg_error (gpg_err_code_from_errno (errno)); + rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN); + xfree (command); + } + else + { + log_debug ("dismiss pinpad entry prompt\n"); + rc = assuan_inquire (ctx, "DISMISSPINPADPROMPT", + &value, &valuelen, MAXLEN_PIN); + } + if (!rc) + xfree (value); + return rc; + } + + *retstr = NULL; + log_debug ("asking for PIN '%s'\n", info); + + rc = gpgrt_asprintf (&command, "NEEDPIN %s", info); + if (rc < 0) + return gpg_error (gpg_err_code_from_errno (errno)); + + /* Fixme: Write an inquire function which returns the result in + secure memory and check all further handling of the PIN. */ + assuan_begin_confidential (ctx); + rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN); + assuan_end_confidential (ctx); + xfree (command); + if (rc) + return rc; + + if (!valuelen || value[valuelen-1]) + { + /* We require that the returned value is an UTF-8 string */ + xfree (value); + return gpg_error (GPG_ERR_INV_RESPONSE); + } + *retstr = (char*)value; + return 0; +} +#endif + +/* SLOTLIST command + * A command to (re)scan for available keys, something like SERIALNO + * command of scdaemon. + */ +static const char hlp_slotlist[] = + "SLOTLIST\n" + "\n" + "Return the status of each token using a status response. This\n" + "function should be used to check for the presence of tokens."; +static gpg_error_t +cmd_slotlist (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err = 0; + + line = skip_options (line); + + (void)ctrl; + return err; +} + +static const char hlp_readkey[] = + "READKEY [--format=advanced|ssh] [--info[-only]] \n" + "\n" + "Return the public key for the given cert or key ID as a standard\n" + "S-expression. With --format option, it may be returned in advanced\n" + "S-expression format, or SSH format. With --info a KEYPAIRINFO\n" + "status line is also emitted; with --info-only the regular output is\n" + "suppressed."; +static gpg_error_t +cmd_readkey (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + int advanced = 0; + int ssh = 0; + int opt_info = 0; + int opt_nokey = 0; + unsigned char *pk = NULL; + size_t pklen; + token_t token; + const char *keygrip; + + if (has_option (line, "--format=advanced")) + advanced = 1; + if (has_option (line, "--format=ssh")) + ssh = 1; + if (has_option (line, "--info")) + opt_info = 1; + if (has_option (line, "--info-only")) + opt_info = opt_nokey = 1; + + keygrip = skip_options (line); + if (strlen (keygrip) != 40) + err = gpg_error (GPG_ERR_INV_ID); + + token = token_get (ctrl, keygrip); + if (token) + { + err = token_readkey (token, opt_info, &pk, &pklen); + token_put (token); + } + else + err = gpg_error (GPG_ERR_NO_SECKEY); + + if (err) + goto leave; + + if (opt_nokey) + ; + else if (ssh) + { + estream_t stream = NULL; + gcry_sexp_t s_key; + void *buf = NULL; + size_t buflen; + + stream = es_fopenmem (0, "r+b"); + if (!stream) + { + err = gpg_error_from_syserror (); + goto leave; + } + + err = gcry_sexp_new (&s_key, pk, pklen, 0); + if (err) + { + es_fclose (stream); + goto leave; + } + + err = ssh_public_key_in_base64 (s_key, stream, "(none)"); + if (err) + { + gcry_sexp_release (s_key); + es_fclose (stream); + goto leave; + } + + err = es_fclose_snatch (stream, &buf, &buflen); + gcry_sexp_release (s_key); + if (!err) + err = assuan_send_data (ctx, buf, buflen); + } + else if (advanced) + { + gcry_sexp_t s_key; + unsigned char *pkadv; + size_t pkadvlen; + + err = gcry_sexp_new (&s_key, pk, pklen, 0); + if (err) + goto leave; + + pkadvlen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, NULL, 0); + pkadv = xtrymalloc (pkadvlen); + if (!pkadv) + { + err = gpg_error_from_syserror (); + gcry_sexp_release (s_key); + goto leave; + } + log_assert (pkadvlen); + + gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, pkadv, pkadvlen); + gcry_sexp_release (s_key); + /* (One less to adjust for the trailing '\0') */ + err = assuan_send_data (ctx, pkadv, pkadvlen-1); + xfree (pkadv); + } + else + err = assuan_send_data (ctx, pk, pklen); + + leave: + xfree (pk); + return err; +} + +static const char hlp_pksign[] = + "PKSIGN [--hash=[sha{256,384,512}|none]] \n" + "\n" + "The --hash option is optional; the default is none."; +static gpg_error_t +cmd_pksign (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + int hash_algo; + const char *keygrip; + token_t token; + unsigned char *outdata; + size_t outdatalen; + + if (has_option (line, "--hash=sha256")) + hash_algo = GCRY_MD_SHA256; + else if (has_option (line, "--hash=sha384")) + hash_algo = GCRY_MD_SHA384; + else if (has_option (line, "--hash=sha512")) + hash_algo = GCRY_MD_SHA512; + else if (has_option (line, "--hash=none")) + hash_algo = 0; + else if (!strstr (line, "--")) + hash_algo = 0; + else + return set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm"); + + keygrip = skip_options (line); + + if (strlen (keygrip) != 40) + err = gpg_error (GPG_ERR_INV_ID); + + token = token_get (ctrl, keygrip); + if (token) + { + err = token_sign (token, keygrip, hash_algo, &outdata, &outdatalen); + token_put (token); + } + else + err = gpg_error (GPG_ERR_NO_SECKEY); + + if (err) + { + log_error ("token_sign failed: %s\n", gpg_strerror (err)); + } + else + { + err = assuan_send_data (ctx, outdata, outdatalen); + xfree (outdata); + } + + return err; +} + +static const char hlp_killtkd[] = + "KILLTKD\n" + "\n" + "Commit suicide."; +static gpg_error_t +cmd_killtkd (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 0; +} + + +static const char hlp_keyinfo[] = + "KEYINFO [--list[=auth|encr|sign]] [--data] \n" + "\n" + "Return information about the key specified by the KEYGRIP. If the\n" + "key is not available GPG_ERR_NOT_FOUND is returned. If the option\n" + "--list is given the keygrip is ignored and information about all\n" + "available keys are returned. Capability may limit the listing.\n" + "Unless --data is given, the\n" + "information is returned as a status line using the format:\n" + "\n" + " KEYINFO T \n" + "\n" + "KEYGRIP is the keygrip.\n" + "\n" + "SERIALNO is an ASCII string with the serial number of the\n" + " smartcard. If the serial number is not known a single\n" + " dash '-' is used instead.\n" + "\n" + "IDSTR is a string used to distinguish keys on a smartcard. If it\n" + " is not known a dash is used instead.\n" + "\n" + "USAGE is a string of capabilities of the key, 's' for sign, \n" + "'e' for encryption, 'a' for auth, and 'c' for cert. If it is not\n" + "known a dash is used instead.\n" + "\n" + "More information may be added in the future."; +static gpg_error_t +cmd_keyinfo (assuan_context_t ctx, char *line) +{ + int cap; + int opt_data; + const char *keygrip = NULL; + ctrl_t ctrl = assuan_get_pointer (ctx); + + opt_data = has_option (line, "--data"); + + cap = 0; + if (has_option (line, "--list")) + cap = 0; + else if (has_option (line, "--list=sign")) + cap = GCRY_PK_USAGE_SIGN; + else if (has_option (line, "--list=encr")) + cap = GCRY_PK_USAGE_ENCR; + else if (has_option (line, "--list=auth")) + cap = GCRY_PK_USAGE_AUTH; + else + keygrip = skip_options (line); + + token_keyinfo (ctrl, keygrip, opt_data, cap); + + return 0; +} + + +/* Send a keyinfo string as used by the KEYGRIP_ACTION_SEND_DATA. If + * DATA is true the string is emitted as a data line, else as a status + * line. */ +void +send_keyinfo (ctrl_t ctrl, int data, const char *keygrip_str, + const char *serialno, const char *idstr, const char *usage) +{ + char *string; + assuan_context_t ctx = ctrl->server_local->assuan_ctx; + + string = xtryasprintf ("%s T %s %s %s%s", keygrip_str, + serialno? serialno : "-", + idstr? idstr : "-", + usage? usage : "-", + data? "\n" : ""); + + if (!string) + return; + + if (!data) + assuan_write_status (ctx, "KEYINFO", string); + else + assuan_send_data (ctx, string, strlen (string)); + + xfree (string); + return; +} + +/* 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[] = { + { "INPUT", NULL }, + { "OUTPUT", NULL }, + { "SLOTLIST", cmd_slotlist, hlp_slotlist }, + { "READKEY", cmd_readkey, hlp_readkey }, + { "PKSIGN", cmd_pksign, hlp_pksign }, + { "KILLTKD", cmd_killtkd, hlp_killtkd }, + { "KEYINFO", cmd_keyinfo, hlp_keyinfo }, + { NULL } + }; + int i, rc; + + for (i=0; table[i].name; i++) + { + rc = assuan_register_command (ctx, table[i].name, table[i].handler, + table[i].help); + if (rc) + return rc; + } + assuan_set_hello_line (ctx, "GNU Privacy Guard's token daemon ready"); + + assuan_register_option_handler (ctx, option_handler); + return 0; +} + + +/* Startup the server. If FD is given as -1 this is simple pipe + server, otherwise it is a regular server. Returns true if there + are no more active asessions. */ +int +tkd_command_handler (ctrl_t ctrl, gnupg_fd_t fd) +{ + int rc; + assuan_context_t ctx = NULL; + int stopme; + + rc = assuan_new (&ctx); + if (rc) + { + log_error ("failed to allocate assuan context: %s\n", + gpg_strerror (rc)); + tkd_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) + { + log_error ("failed to initialize the server: %s\n", + gpg_strerror(rc)); + tkd_exit (2); + } + rc = register_commands (ctx); + if (rc) + { + log_error ("failed to register commands with Assuan: %s\n", + gpg_strerror(rc)); + tkd_exit (2); + } + assuan_set_pointer (ctx, ctrl); + + /* Allocate and initialize the server object. Put it into the list + of active sessions. */ + ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local); + ctrl->server_local->next_session = session_list; + session_list = ctrl->server_local; + ctrl->server_local->ctrl_backlink = ctrl; + ctrl->server_local->assuan_ctx = ctx; + + /* Command processing loop. */ + for (;;) + { + rc = assuan_accept (ctx); + if (rc == -1) + { + break; + } + else if (rc) + { + log_info ("Assuan accept problem: %s\n", gpg_strerror (rc)); + break; + } + + rc = assuan_process (ctx); + if (rc) + { + log_info ("Assuan processing failed: %s\n", gpg_strerror (rc)); + continue; + } + } + + /* Cleanup. */ + finalize (ctrl); + + /* Release the server object. */ + if (session_list == ctrl->server_local) + session_list = ctrl->server_local->next_session; + else + { + struct server_local_s *sl; + + for (sl=session_list; sl->next_session; sl = sl->next_session) + if (sl->next_session == ctrl->server_local) + break; + if (!sl->next_session) + BUG (); + sl->next_session = ctrl->server_local->next_session; + } + stopme = ctrl->server_local->stopme; + xfree (ctrl->server_local); + ctrl->server_local = NULL; + + /* Release the Assuan context. */ + assuan_release (ctx); + + if (stopme) + tkd_exit (0); + + /* If there are no more sessions return true. */ + return !session_list; +} + + +/* Send a line with status information via assuan and escape all given + buffers. The variable elements are pairs of (char *, size_t), + terminated with a (NULL, 0). */ +void +send_status_info (ctrl_t ctrl, const char *keyword, ...) +{ + va_list arg_ptr; + const unsigned char *value; + size_t valuelen; + char buf[950], *p; + size_t n; + assuan_context_t ctx = ctrl->server_local->assuan_ctx; + + va_start (arg_ptr, keyword); + + p = buf; + n = 0; + while ( (value = va_arg (arg_ptr, const unsigned char *)) + && n < DIM (buf)-2 ) + { + valuelen = va_arg (arg_ptr, size_t); + if (!valuelen) + continue; /* empty buffer */ + if (n) + { + *p++ = ' '; + n++; + } + for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, value++) + { + if (*value == '+' || *value == '\"' || *value == '%' + || *value < ' ') + { + sprintf (p, "%%%02X", *value); + p += 3; + n += 2; + } + else if (*value == ' ') + *p++ = '+'; + else + *p++ = *value; + } + } + *p = 0; + assuan_write_status (ctx, keyword, buf); + + va_end (arg_ptr); +} + + +/* Send a ready formatted status line via assuan. */ +gpg_error_t +send_status_direct (ctrl_t ctrl, const char *keyword, const char *args) +{ + assuan_context_t ctx = ctrl->server_local->assuan_ctx; + + if (strchr (args, '\n')) + { + log_error ("error: LF detected in status line - not sending\n"); + return gpg_error (GPG_ERR_INTERNAL); + } + return assuan_write_status (ctx, keyword, args); +} + + +/* This status functions expects a printf style format string. No + * filtering of the data is done instead the printf formatted data is + * send using assuan_send_status. */ +gpg_error_t +send_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 || !ctrl->server_local || !(ctx = ctrl->server_local->assuan_ctx)) + return 0; + + va_start (arg_ptr, format); + err = vprint_assuan_status (ctx, keyword, format, arg_ptr); + va_end (arg_ptr); + return err; +} diff --git a/tkd/pksign.c b/tkd/pkcs11.c similarity index 96% rename from tkd/pksign.c rename to tkd/pkcs11.c index cf6d41583..a0a21ed62 100644 --- a/tkd/pksign.c +++ b/tkd/pkcs11.c @@ -213,6 +213,9 @@ notify_cb (ck_session_handle_t session, struct p11dev *priv = application; (void)priv; + (void)session; + (void)event; + (void)application; return 0; } @@ -224,7 +227,6 @@ open_session (struct token *token) ck_slot_id_t slot_id = token->slot_id; ck_session_handle_t session_handle; ck_flags_t session_flags; - int option = 0; session_flags = CKU_USER; // session_flags = session_flags | CKF_RW_SESSION; @@ -234,7 +236,7 @@ open_session (struct token *token) (void *)&p11_priv, notify_cb, &session_handle); if (err) { - printf ("open_session: %d\n", err); + log_debug ("open_session: %ld\n", err); return -1; } @@ -308,7 +310,6 @@ examine_public_key (struct token *token, struct key *k, unsigned long keytype, unsigned char ecpoint[256]; struct ck_attribute templ[3]; unsigned long mechanisms[3]; - int i; if (keytype == CKK_RSA) { @@ -340,9 +341,9 @@ examine_public_key (struct token *token, struct key *k, unsigned long keytype, } /* Found a RSA key. */ - printf ("RSA: %d %d\n", - templ[0].ulValueLen, - templ[1].ulValueLen); + log_debug ("RSA: %ld %ld\n", + templ[0].ulValueLen, + templ[1].ulValueLen); puts ("Public key:"); compute_keygrip_rsa (k->keygrip, modulus, templ[0].ulValueLen, @@ -377,9 +378,9 @@ examine_public_key (struct token *token, struct key *k, unsigned long keytype, } /* Found an ECC key. */ - printf ("ECC: %d %d\n", - templ[0].ulValueLen, - templ[1].ulValueLen); + log_debug ("ECC: %ld %ld\n", + templ[0].ulValueLen, + templ[1].ulValueLen); curve_oid = openpgp_oidbuf_to_str (ecparams+1, templ[0].ulValueLen-1); curve = openpgp_oid_to_curve (curve_oid, 1); @@ -400,7 +401,7 @@ examine_public_key (struct token *token, struct key *k, unsigned long keytype, if (templ[0].ulValueLen) { /* Scute works well. */ - printf ("mechanism: %x %d\n", mechanisms[0], templ[0].ulValueLen); + log_debug ("mechanism: %lx %ld\n", mechanisms[0], templ[0].ulValueLen); k->mechanism = mechanisms[0]; } else @@ -434,7 +435,6 @@ detect_private_keys (struct token *token) unsigned long cnt = 0; ck_object_handle_t obj; - int i; class = CKO_PRIVATE_KEY; templ[0].type = CKA_CLASS; @@ -484,8 +484,8 @@ detect_private_keys (struct token *token) k->id_len = templ[2].ulValueLen; k->id[k->id_len] = 0; - printf ("slot: %x handle: %ld label: %s key_type: %d id: %s\n", - token->slot_id, obj, k->label, keytype, k->id); + log_debug ("slot: %lx handle: %ld label: %s key_type: %ld id: %s\n", + token->slot_id, obj, k->label, keytype, k->id); if (examine_public_key (token, k, keytype, 1, obj)) continue; @@ -573,7 +573,7 @@ check_public_keys (struct token *token) if (i == token->num_keys) continue; - printf ("pub: slot: %x handle: %ld label: %s key_type: %d id: %s\n", + log_debug ("pub: slot: %lx handle: %ld label: %s key_type: %ld id: %s\n", token->slot_id, obj, label, keytype, id); if (examine_public_key (token, k, keytype, 0, obj)) @@ -588,6 +588,7 @@ check_public_keys (struct token *token) } } +#if 0 static long get_certificate (struct token *token) { @@ -652,11 +653,11 @@ get_certificate (struct token *token) return 0; } +#endif static long learn_keys (struct token *token) { - unsigned long err = 0; int i; /* Detect private keys on the token. @@ -773,7 +774,7 @@ do_pksign (struct key *key, key->p11_keyid); if (err) { - printf ("C_SignInit error: %d", err); + log_error ("C_SignInit error: %ld", err); return err; } @@ -786,7 +787,7 @@ do_pksign (struct key *key, return 0; } - +#ifdef TESTING int main (int argc, const char *argv[]) { @@ -896,9 +897,4 @@ main (int argc, const char *argv[]) ck->f->C_Finalize (NULL); return 0; } - -/* -cc -g -o test_tk pksign.c -lgcrypt -./test_tk /usr/lib/softhsm/libsofthsm2.so 5678 -./test_tk /usr/local/lib/x86_64-linux-gnu/scute.so - */ +#endif diff --git a/tkd/tkdaemon.c b/tkd/tkdaemon.c new file mode 100644 index 000000000..d16f512a0 --- /dev/null +++ b/tkd/tkdaemon.c @@ -0,0 +1,1295 @@ +/* TKdaemon.c - The GnuPG TK Daemon + * Copyright (C) 2001-2002, 2004-2005, 2007-2020 Free Software Foundation, Inc. + * Copyright (C) 2001-2002, 2004-2005, 2007-2019 Werner Koch + * Copyright (C) 2020 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef HAVE_W32_SYSTEM +#include +#include +#endif /*HAVE_W32_SYSTEM*/ +#include +#include +#include + +#define INCLUDED_BY_MAIN_MODULE 1 +#define GNUPG_COMMON_NEED_AFLOCAL +#include "tkdaemon.h" +#include + +#include /* malloc hooks */ + +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "../common/gc-opt-flags.h" +#include "../common/asshelp.h" +#include "../common/exechelp.h" +#include "../common/comopt.h" +#include "../common/init.h" + +#ifndef ENAMETOOLONG +# define ENAMETOOLONG EINVAL +#endif + +enum cmd_and_opt_values +{ aNull = 0, + oCsh = 'c', + oQuiet = 'q', + oSh = 's', + oVerbose = 'v', + + oNoVerbose = 500, + aGPGConfList, + aGPGConfTest, + oOptions, + oDebug, + oDebugAll, + oDebugLevel, + oDebugWait, + oDebugAllowCoreDump, + oDebugLogTid, + oDebugAssuanLogCats, + oNoGreeting, + oNoOptions, + oHomedir, + oNoDetach, + oNoGrab, + oLogFile, + oServer, + oMultiServer, + oDaemon, + oBatch, + oPkcs11Driver, + oListenBacklog +}; + + + +static gpgrt_opt_t opts[] = { + ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"), + ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"), + + ARGPARSE_header (NULL, N_("Options used for startup")), + + ARGPARSE_s_n (oServer,"server", N_("run in server mode (foreground)")), + ARGPARSE_s_n (oMultiServer, "multi-server", + N_("run in multi server mode (foreground)")), + ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")), + ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")), + ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")), + ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")), + ARGPARSE_s_s (oHomedir, "homedir", "@"), + ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")), + ARGPARSE_noconffile (oNoOptions, "no-options", "@"), + + + ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")), + + ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), + ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), + ARGPARSE_s_s (oDebug, "debug", "@"), + ARGPARSE_s_n (oDebugAll, "debug-all", "@"), + ARGPARSE_s_s (oDebugLevel, "debug-level" , + N_("|LEVEL|set the debugging level to LEVEL")), + ARGPARSE_s_i (oDebugWait, "debug-wait", "@"), + ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"), + ARGPARSE_s_n (oDebugLogTid, "debug-log-tid", "@"), + ARGPARSE_p_u (oDebugAssuanLogCats, "debug-assuan-log-cats", "@"), + ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write a log to FILE")), + + + ARGPARSE_header ("Configuration", + N_("Options controlling the configuration")), + + ARGPARSE_s_s (oPkcs11Driver, "pkcs11-driver", + N_("|NAME|use NAME as PKCS#11 driver")), + + ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"), + + + ARGPARSE_header("Security", N_("Options controlling the security")), + + ARGPARSE_end () +}; + + +/* 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_TOKEN_VALUE , "token" }, + { DBG_TOKEN_IO_VALUE, "tokenio" }, + { 0, NULL } + }; + + +/* Flag to indicate that a shutdown was requested. */ +static int shutdown_pending; + +/* It is possible that we are currently running under setuid permissions */ +static int maybe_setuid = 1; + +/* Flag telling whether we are running as a pipe server. */ +static int pipe_server; + +/* Name of the communication socket */ +static char *socket_name; +/* Name of the redirected socket or NULL. */ +static char *redir_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. Change at runtime with + * --listen-backlog. */ +static int listen_backlog = 64; + +#ifdef HAVE_W32_SYSTEM +static HANDLE the_event; +#else +/* PID to notify update of usb devices. */ +static pid_t main_thread_pid; +#endif +#ifdef HAVE_PSELECT_NO_EINTR +/* FD to notify changes. */ +static int notify_fd; +#endif + +static char *create_socket_name (char *standard_name); +static gnupg_fd_t create_server_socket (const char *name, + char **r_redir_name, + assuan_sock_nonce_t *nonce); + +static void *start_connection_thread (void *arg); +static void handle_connections (gnupg_fd_t listen_fd); + +/* Pth wrapper function definitions. */ +ASSUAN_SYSTEM_NPTH_IMPL; + +static int active_connections; + + +static char * +make_libversion (const char *libname, const char *(*getfnc)(const char*)) +{ + const char *s; + char *result; + + if (maybe_setuid) + { + gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */ + maybe_setuid = 0; + } + s = getfnc (NULL); + result = xmalloc (strlen (libname) + 1 + strlen (s) + 1); + strcpy (stpcpy (stpcpy (result, libname), " "), s); + return result; +} + + +static const char * +my_strusage (int level) +{ + static char *ver_gcry; + const char *p; + + switch (level) + { + case 9: p = "GPL-3.0-or-later"; break; + case 11: p = "@TKDAEMON@ (@GNUPG@)"; + break; + case 13: p = VERSION; break; + case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; + case 17: p = PRINTABLE_OS_NAME; break; + 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: @TKDAEMON@ [options] (-h for help)"); + break; + case 41: p = _("Syntax: tkdaemon [options] [command [args]]\n" + "Token daemon for @GNUPG@\n"); + break; + + default: p = NULL; + } + return p; +} + + +static int +tid_log_callback (unsigned long *rvalue) +{ + int len = sizeof (*rvalue); + npth_t thread; + + thread = npth_self (); + if (sizeof (thread) < len) + len = sizeof (thread); + memcpy (rvalue, &thread, len); + + return 2; /* Use use hex representation. */ +} + + +/* Setup the debugging. With a LEVEL of NULL only the active debug + flags are propagated to the subsystems. With LEVEL set, a specific + set of debug flags is set; thus overriding all flags already + set. */ +static void +set_debug (const char *level) +{ + int numok = (level && digitp (level)); + int numlvl = numok? atoi (level) : 0; + + if (!level) + ; + else if (!strcmp (level, "none") || (numok && numlvl < 1)) + opt.debug = 0; + else if (!strcmp (level, "basic") || (numok && numlvl <= 2)) + opt.debug = DBG_IPC_VALUE; + else if (!strcmp (level, "advanced") || (numok && numlvl <= 5)) + opt.debug = DBG_IPC_VALUE; + else if (!strcmp (level, "expert") || (numok && numlvl <= 8)) + opt.debug = (DBG_IPC_VALUE|DBG_CACHE_VALUE|DBG_TOKEN_IO_VALUE); + else if (!strcmp (level, "guru") || numok) + { + opt.debug = ~0; + /* Unless the "guru" string has been used we don't want to allow + hashing debugging. The rationale is that people tend to + select the highest debug value and would then clutter their + disk with debug files which may reveal confidential data. */ + if (numok) + opt.debug &= ~(DBG_HASHING_VALUE); + } + else + { + log_error (_("invalid debug-level '%s' given\n"), level); + tkd_exit(2); + } + + + 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); +} + + + +static void +cleanup (void) +{ + if (socket_name && *socket_name) + { + char *name; + + name = redir_socket_name? redir_socket_name : socket_name; + + gnupg_remove (name); + *socket_name = 0; + } +} + +static void +setup_signal_mask (void) +{ +#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 (SIGCONT); + npth_sigev_add (SIGTERM); + npth_sigev_fini (); + main_thread_pid = getpid (); +#endif +} + +int +main (int argc, char **argv ) +{ + gpgrt_argparse_t pargs; + int orig_argc; + char **orig_argv; + char *last_configname = NULL; + const char *configname = NULL; + int debug_argparser = 0; + const char *debug_level = NULL; + int greeting = 0; + int nogreeting = 0; + int multi_server = 0; + int is_daemon = 0; + int nodetach = 0; + char *logfile = NULL; + int debug_wait = 0; + int gpgconf_list = 0; + char *config_filename = NULL; + int allow_coredump = 0; + struct assuan_malloc_hooks malloc_hooks; + int res; + npth_t pipecon_handler; + + early_system_init (); + gpgrt_set_strusage (my_strusage); + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + /* Please note that we may running SUID(ROOT), so be very CAREFUL + when adding any stuff between here and the call to INIT_SECMEM() + somewhere after the option parsing */ + log_set_prefix ("tkdaemon", GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID); + + /* Make sure that our subsystems are ready. */ + i18n_init (); + init_common_subsystems (&argc, &argv); + + 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_set_system_hooks (ASSUAN_SYSTEM_NPTH); + assuan_sock_init (); + setup_libassuan_logging (&opt.debug, NULL); + + setup_libgcrypt_logging (); + gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); + + disable_core_dumps (); + + /* Set default options. */ + + /* Check whether we have a config file on the commandline */ + orig_argc = argc; + orig_argv = argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); + while (gpgrt_argparse (NULL, &pargs, opts)) + { + switch (pargs.r_opt) + { + case oDebug: + case oDebugAll: + debug_argparser++; + break; + case oHomedir: + gnupg_set_homedir (pargs.r.ret_str); + break; + } + } + /* Reset the flags. */ + pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); + + /* initialize the secure memory. */ + gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); + maybe_setuid = 0; + + /* + Now we are working under our real uid + */ + + /* The configuraton directories for use by gpgrt_argparser. */ + gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); + gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); + + /* We are re-using the struct, thus the reset flag. We OR the + * flags so that the internal intialized flag won't be cleared. */ + argc = orig_argc; + argv = orig_argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags |= (ARGPARSE_FLAG_RESET + | ARGPARSE_FLAG_KEEP + | ARGPARSE_FLAG_SYS + | ARGPARSE_FLAG_USER); + while (gpgrt_argparser (&pargs, opts, TKDAEMON_NAME EXTSEP_S "conf")) + { + switch (pargs.r_opt) + { + case ARGPARSE_CONFFILE: + if (debug_argparser) + log_info (_("reading options from '%s'\n"), + pargs.r_type? pargs.r.ret_str: "[cmdline]"); + if (pargs.r_type) + { + xfree (last_configname); + last_configname = xstrdup (pargs.r.ret_str); + configname = last_configname; + } + else + configname = NULL; + break; + + case aGPGConfList: gpgconf_list = 1; break; + case aGPGConfTest: gpgconf_list = 2; break; + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + case oBatch: opt.batch=1; break; + + case oDebug: + if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags)) + { + pargs.r_opt = ARGPARSE_INVALID_ARG; + pargs.err = ARGPARSE_PRINT_ERROR; + } + break; + case oDebugAll: opt.debug = ~0; break; + case oDebugLevel: debug_level = pargs.r.ret_str; break; + case oDebugWait: debug_wait = pargs.r.ret_int; break; + case oDebugAllowCoreDump: + enable_core_dumps (); + allow_coredump = 1; + break; + case oDebugLogTid: + log_set_pid_suffix_cb (tid_log_callback); + break; + case oDebugAssuanLogCats: + set_libassuan_log_cats (pargs.r.ret_ulong); + break; + + case oNoGreeting: nogreeting = 1; break; + case oNoVerbose: opt.verbose = 0; break; + 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 oMultiServer: pipe_server = 1; multi_server = 1; break; + case oDaemon: is_daemon = 1; break; + + case oPkcs11Driver: opt.pkcs11_driver = pargs.r.ret_str; break; + + case oListenBacklog: + listen_backlog = pargs.r.ret_int; + break; + + default: + if (configname) + pargs.err = ARGPARSE_PRINT_WARNING; + else + pargs.err = ARGPARSE_PRINT_ERROR; + break; + } + } + + gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ + + if (!last_configname) + config_filename = gpgrt_fnameconcat (gnupg_homedir (), + TKDAEMON_NAME EXTSEP_S "conf", + NULL); + else + { + config_filename = last_configname; + last_configname = NULL; + } + + if (log_get_errorcount(0)) + exit(2); + + /* Process common component options. */ + if (parse_comopt (GNUPG_MODULE_NAME_TKDAEMON, debug_argparser)) + exit(2); + + if (!logfile) + { + logfile = comopt.logfile; + comopt.logfile = NULL; + } + + if (nogreeting ) + greeting = 0; + + if (greeting) + { + es_fprintf (es_stderr, "%s %s; %s\n", + gpgrt_strusage (11),gpgrt_strusage (13),gpgrt_strusage (14)); + es_fprintf (es_stderr, "%s\n", gpgrt_strusage (15)); + } +#ifdef IS_DEVELOPMENT_VERSION + log_info ("NOTE: this is a development version!\n"); +#endif + + /* 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]); + } + + if (atexit (cleanup)) + { + log_error ("atexit failed\n"); + cleanup (); + exit (1); + } + + set_debug (debug_level); + + if (initialize_module_command ()) + { + log_error ("initialization failed\n"); + cleanup (); + exit (1); + } + + if (gpgconf_list == 2) + tkd_exit (0); + if (gpgconf_list) + { + /* List options and default values in the GPG Conf format. */ + es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT); + + tkd_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)); + } + + if (debug_wait && pipe_server) + { + log_debug ("waiting for debugger - my pid is %u .....\n", + (unsigned int)getpid()); + gnupg_sleep (debug_wait); + log_debug ("... okay\n"); + } + + if (pipe_server) + { + /* This is the simple pipe based server */ + ctrl_t ctrl; + npth_attr_t tattr; + gnupg_fd_t fd = GNUPG_INVALID_FD; + +#ifndef HAVE_W32_SYSTEM + { + struct sigaction sa; + + sa.sa_handler = SIG_IGN; + sigemptyset (&sa.sa_mask); + sa.sa_flags = 0; + sigaction (SIGPIPE, &sa, NULL); + } +#endif + + npth_init (); + setup_signal_mask (); + gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); + + /* If --debug-allow-core-dump has been given we also need to + switch the working directory to a place where we can actually + write. */ + if (allow_coredump) + { + if (chdir("/tmp")) + log_debug ("chdir to '/tmp' failed: %s\n", strerror (errno)); + else + log_debug ("changed working directory to '/tmp'\n"); + } + + /* In multi server mode we need to listen on an additional + socket. Create that socket now before starting the handler + for the pipe connection. This allows that handler to send + back the name of that socket. */ + if (multi_server) + { + socket_name = create_socket_name (TKDAEMON_SOCK_NAME); + fd = create_server_socket (socket_name, + &redir_socket_name, &socket_nonce); + } + + res = npth_attr_init (&tattr); + if (res) + { + log_error ("error allocating thread attributes: %s\n", + strerror (res)); + tkd_exit (2); + } + npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); + + ctrl = xtrycalloc (1, sizeof *ctrl); + if ( !ctrl ) + { + log_error ("error allocating connection control data: %s\n", + strerror (errno) ); + tkd_exit (2); + } + ctrl->thread_startup.fd = GNUPG_INVALID_FD; + res = npth_create (&pipecon_handler, &tattr, start_connection_thread, ctrl); + if (res) + { + log_error ("error spawning pipe connection handler: %s\n", + strerror (res) ); + xfree (ctrl); + tkd_exit (2); + } + npth_setname_np (pipecon_handler, "pipe-connection"); + npth_attr_destroy (&tattr); + + /* We run handle_connection to wait for the shutdown signal and + to run the ticker stuff. */ + handle_connections (fd); + if (fd != GNUPG_INVALID_FD) + assuan_sock_close (fd); + } + else if (!is_daemon) + { + log_info (_("please use the option '--daemon'" + " to run the program in the background\n")); + } + else + { /* Regular server mode */ + gnupg_fd_t fd; +#ifndef HAVE_W32_SYSTEM + pid_t pid; + int i; +#endif + + /* Create the socket. */ + socket_name = create_socket_name (TKDAEMON_SOCK_NAME); + fd = create_server_socket (socket_name, + &redir_socket_name, &socket_nonce); + + + fflush (NULL); +#ifdef HAVE_W32_SYSTEM + (void)nodetach; +#else + pid = fork (); + if (pid == (pid_t)-1) + { + log_fatal ("fork failed: %s\n", strerror (errno) ); + exit (1); + } + else if (pid) + { /* we are the parent */ + char *infostr; + + close (fd); + + /* create the info string: :: */ + if (gpgrt_asprintf (&infostr, "TKDAEMON_INFO=%s:%lu:1", + socket_name, (ulong) pid) < 0) + { + log_error ("out of core\n"); + kill (pid, SIGTERM); + exit (1); + } + *socket_name = 0; /* don't let cleanup() remove the socket - + the child should do this from now on */ + if (argc) + { /* run the program given on the commandline */ + if (putenv (infostr)) + { + log_error ("failed to set environment: %s\n", + strerror (errno) ); + kill (pid, SIGTERM ); + exit (1); + } + execvp (argv[0], argv); + log_error ("failed to run the command: %s\n", strerror (errno)); + kill (pid, SIGTERM); + exit (1); + } + else + { + xfree (infostr); + exit (0); + } + /* NOTREACHED */ + } /* end parent */ + + /* This is the child. */ + + npth_init (); + setup_signal_mask (); + gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); + + /* Detach from tty and put process into a new session. */ + if (!nodetach ) + { + /* 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); + } + } + + { + 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); + } + + handle_connections (fd); + + assuan_sock_close (fd); + } + + xfree (config_filename); + return 0; +} + +void +tkd_exit (int rc) +{ + gcry_control (GCRYCTL_TERM_SECMEM ); + rc = rc? rc : log_get_errorcount(0)? 2 : 0; + exit (rc); +} + + +static void +tkd_init_default_ctrl (ctrl_t ctrl) +{ + (void)ctrl; +} + +static void +tkd_deinit_default_ctrl (ctrl_t ctrl) +{ + if (!ctrl) + return; + xfree (ctrl->in_data.value); + ctrl->in_data.value = NULL; + ctrl->in_data.valuelen = 0; +} + + +/* Return the name of the socket to be used to connect to this + process. If no socket is available, return NULL. */ +const char * +tkd_get_socket_name (void) +{ + if (socket_name && *socket_name) + return socket_name; + return NULL; +} + + +#ifndef HAVE_W32_SYSTEM +static void +handle_signal (int signo) +{ + switch (signo) + { + case SIGHUP: + log_info ("SIGHUP received - " + "re-reading configuration and resetting tokens\n"); +/* reread_configuration (); */ + break; + + case SIGUSR1: + log_info ("SIGUSR1 received - printing internal information:\n"); + break; + + case SIGUSR2: + log_info ("SIGUSR2 received - no action defined\n"); + break; + + case SIGCONT: + /* Nothing. */ + log_debug ("SIGCONT received - breaking select\n"); + break; + + case SIGTERM: + if (!shutdown_pending) + log_info ("SIGTERM received - shutting down ...\n"); + else + log_info ("SIGTERM received - still %i running threads\n", + active_connections); + shutdown_pending++; + if (shutdown_pending > 2) + { + log_info ("shutdown forced\n"); + log_info ("%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13)); + cleanup (); + tkd_exit (0); + } + break; + + case SIGINT: + log_info ("SIGINT received - immediate shutdown\n"); + log_info( "%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13)); + cleanup (); + tkd_exit (0); + break; + + default: + log_info ("signal %d received - no action defined\n", signo); + } +} +#endif /*!HAVE_W32_SYSTEM*/ + + +/* Create a name for the socket. We 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. + Returns: Pointer to an allocated string with the absolute name of + the socket used. */ +static char * +create_socket_name (char *standard_name) +{ + char *name; + + name = make_filename (gnupg_socketdir (), standard_name, NULL); + if (strchr (name, PATHSEP_C)) + { + log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S); + tkd_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 the socket has + been redirected the name of the real socket is stored as a malloced + string at R_REDIR_NAME. */ +static gnupg_fd_t +create_server_socket (const char *name, char **r_redir_name, + assuan_sock_nonce_t *nonce) +{ + struct sockaddr *addr; + struct sockaddr_un *unaddr; + socklen_t len; + gnupg_fd_t fd; + int rc; + + xfree (*r_redir_name); + *r_redir_name = NULL; + + fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0); + if (fd == GNUPG_INVALID_FD) + { + log_error (_("can't create socket: %s\n"), strerror (errno)); + tkd_exit (2); + } + + unaddr = xmalloc (sizeof (*unaddr)); + addr = (struct sockaddr*)unaddr; + + { + int redirected; + + if (assuan_sock_set_sockaddr_un (name, addr, &redirected)) + { + 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 ())); + tkd_exit (2); + } + if (redirected) + { + *r_redir_name = xstrdup (unaddr->sun_path); + if (opt.verbose) + log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name); + } + } + + len = SUN_LEN (unaddr); + + rc = assuan_sock_bind (fd, addr, len); + if (rc == -1 && errno == EADDRINUSE) + { + 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) + { + log_error (_("error binding socket to '%s': %s\n"), + unaddr->sun_path, + gpg_strerror (gpg_error_from_syserror ())); + assuan_sock_close (fd); + tkd_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, gpg_strerror (gpg_error_from_syserror ())); + assuan_sock_close (fd); + tkd_exit (2); + } + + if (opt.verbose) + log_info (_("listening on socket '%s'\n"), unaddr->sun_path); + + return fd; +} + + + +/* This is the standard connection thread's main function. */ +static void * +start_connection_thread (void *arg) +{ + ctrl_t ctrl = arg; + + if (ctrl->thread_startup.fd != GNUPG_INVALID_FD + && assuan_sock_check_nonce (ctrl->thread_startup.fd, &socket_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 NULL; + } + + active_connections++; + + tkd_init_default_ctrl (ctrl); + if (opt.verbose) + log_info (_("handler for fd %d started\n"), + FD2INT(ctrl->thread_startup.fd)); + + /* If this is a pipe server, we request a shutdown if the command + handler asked for it. With the next ticker event and given that + no other connections are running the shutdown will then + happen. */ + if (tkd_command_handler (ctrl, ctrl->thread_startup.fd) + && pipe_server) + shutdown_pending = 1; + + if (opt.verbose) + log_info (_("handler for fd %d terminated\n"), + FD2INT (ctrl->thread_startup.fd)); + + tkd_deinit_default_ctrl (ctrl); + xfree (ctrl); + + if (--active_connections == 0) + tkd_kick_the_loop (); + + return NULL; +} + + +void +tkd_kick_the_loop (void) +{ + /* Kick the select loop. */ +#ifdef HAVE_W32_SYSTEM + int ret = SetEvent (the_event); + if (ret == 0) + log_error ("SetEvent for tkd_kick_the_loop failed: %s\n", + w32_strerror (-1)); +#elif defined(HAVE_PSELECT_NO_EINTR) + write (notify_fd, "", 1); +#else + int ret = kill (main_thread_pid, SIGCONT); + if (ret < 0) + log_error ("sending signal for tkd_kick_the_loop failed: %s\n", + gpg_strerror (gpg_error_from_syserror ())); +#endif +} + +#define TIMERTICK_INTERVAL_SEC (0) +#define TIMERTICK_INTERVAL_USEC (500000) + +/* Connection handler loop. Wait for connection requests and spawn a + thread after accepting a connection. LISTEN_FD is allowed to be -1 + in which case this code will only do regular timeouts and handle + signals. */ +static void +handle_connections (gnupg_fd_t listen_fd) +{ + npth_attr_t tattr; + struct sockaddr_un paddr; + socklen_t plen; + fd_set fdset, read_fdset; + int nfd; + int ret; + struct timespec timeout; + struct timespec *t; + int saved_errno; +#ifdef HAVE_W32_SYSTEM + HANDLE events[2]; + unsigned int events_set; +#else + int signo; +#endif +#ifdef HAVE_PSELECT_NO_EINTR + int pipe_fd[2]; + + ret = gnupg_create_pipe (pipe_fd); + if (ret) + { + log_error ("pipe creation failed: %s\n", gpg_strerror (ret)); + return; + } + notify_fd = pipe_fd[1]; +#endif + + ret = npth_attr_init(&tattr); + if (ret) + { + log_error ("npth_attr_init failed: %s\n", strerror (ret)); + return; + } + + npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); + +#ifdef HAVE_W32_SYSTEM + { + HANDLE h, h2; + SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE}; + + events[0] = the_event = INVALID_HANDLE_VALUE; + events[1] = INVALID_HANDLE_VALUE; + /* Create event for manual reset, initially non-signaled. Make it + * waitable and inheritable. */ + h = CreateEvent (&sa, TRUE, FALSE, NULL); + if (!h) + log_error ("can't create tkd event: %s\n", w32_strerror (-1) ); + else if (!DuplicateHandle (GetCurrentProcess(), h, + GetCurrentProcess(), &h2, + EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0)) + { + log_error ("setting synchronize for tkd_kick_the_loop failed: %s\n", + w32_strerror (-1) ); + CloseHandle (h); + } + else + { + CloseHandle (h); + events[0] = the_event = h2; + } + } +#endif + + FD_ZERO (&fdset); + nfd = 0; + if (listen_fd != GNUPG_INVALID_FD) + { + FD_SET (FD2INT (listen_fd), &fdset); + nfd = FD2INT (listen_fd); + } + + for (;;) + { + int max_fd = nfd; + + if (shutdown_pending) + { + if (active_connections == 0) + break; /* ready */ + + /* Do not accept anymore connections but wait for existing + connections to terminate. We do this by clearing out all + file descriptors to wait for, so that the select will be + used to just wait on a signal or timeout event. */ + FD_ZERO (&fdset); + listen_fd = GNUPG_INVALID_FD; + } + + timeout.tv_sec = TIMERTICK_INTERVAL_SEC; + timeout.tv_nsec = TIMERTICK_INTERVAL_USEC * 1000; + + if (shutdown_pending) + t = &timeout; + else + t = NULL; + + /* POSIX says that fd_set should be implemented as a structure, + thus a simple assignment is fine to copy the entire set. */ + read_fdset = fdset; + +#ifdef HAVE_PSELECT_NO_EINTR + FD_SET (pipe_fd[0], &read_fdset); + if (max_fd < pipe_fd[0]) + max_fd = pipe_fd[0]; +#else + (void)max_fd; +#endif + +#ifndef HAVE_W32_SYSTEM + ret = npth_pselect (max_fd+1, &read_fdset, NULL, NULL, t, + npth_sigev_sigmask ()); + saved_errno = errno; + + while (npth_sigev_get_pending(&signo)) + handle_signal (signo); +#else + ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, t, + events, &events_set); + saved_errno = errno; + if (events_set & 1) + continue; +#endif + + if (ret == -1 && saved_errno != EINTR) + { + log_error (_("npth_pselect failed: %s - waiting 1s\n"), + strerror (saved_errno)); + gnupg_sleep (1); + continue; + } + + if (ret <= 0) + /* Timeout. Will be handled when calculating the next timeout. */ + continue; + +#ifdef HAVE_PSELECT_NO_EINTR + if (FD_ISSET (pipe_fd[0], &read_fdset)) + { + char buf[256]; + + read (pipe_fd[0], buf, sizeof buf); + } +#endif + + if (listen_fd != GNUPG_INVALID_FD + && FD_ISSET (FD2INT (listen_fd), &read_fdset)) + { + ctrl_t ctrl; + gnupg_fd_t fd; + + plen = sizeof paddr; + fd = INT2FD (npth_accept (FD2INT (listen_fd), + (struct sockaddr *)&paddr, &plen)); + if (fd == GNUPG_INVALID_FD) + { + log_error ("accept failed: %s\n", strerror (errno)); + } + else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)) ) + { + log_error ("error allocating connection control data: %s\n", + strerror (errno) ); + assuan_sock_close (fd); + } + else + { + char threadname[50]; + npth_t thread; + + snprintf (threadname, sizeof threadname, "conn fd=%d", + FD2INT (fd)); + ctrl->thread_startup.fd = fd; + ret = npth_create (&thread, &tattr, start_connection_thread, ctrl); + if (ret) + { + log_error ("error spawning connection handler: %s\n", + strerror (ret)); + xfree (ctrl); + assuan_sock_close (fd); + } + else + npth_setname_np (thread, threadname); + } + } + } + +#ifdef HAVE_W32_SYSTEM + if (the_event != INVALID_HANDLE_VALUE) + CloseHandle (the_event); +#endif +#ifdef HAVE_PSELECT_NO_EINTR + close (pipe_fd[0]); + close (pipe_fd[1]); +#endif + cleanup (); + log_info (_("%s %s stopped\n"), gpgrt_strusage(11), gpgrt_strusage(13)); + npth_attr_destroy (&tattr); +} + +/* Return the number of active connections. */ +int +get_active_connection_count (void) +{ + return active_connections; +} diff --git a/tkd/tkdaemon.h b/tkd/tkdaemon.h new file mode 100644 index 000000000..bfa99e484 --- /dev/null +++ b/tkd/tkdaemon.h @@ -0,0 +1,110 @@ +/* tkdaemon.h - Global definitions for the TKdaemon + * Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc. + * + * 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 TKDAEMON_H +#define TKDAEMON_H + +#ifdef GPG_ERR_SOURCE_DEFAULT +#error GPG_ERR_SOURCE_DEFAULT already defined +#endif +#define GPG_ERR_SOURCE_DEFAULT 18 // GPG_ERR_SOURCE_TKD +#include + +#include +#include +#include "../common/util.h" +#include "../common/sysutils.h" + +typedef struct token_ctx_s *token_t; + +/* A large struct name "opt" to keep global flags. */ +EXTERN_UNLESS_MAIN_MODULE +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. */ + const char *pkcs11_driver; /* Library to access the PKCS#1 module. */ +} opt; + + +#define DBG_APP_VALUE 1 /* Debug app specific stuff. */ +#define DBG_MPI_VALUE 2 /* debug mpi details */ +#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */ +#define DBG_TOKEN_VALUE 16 /* debug token info */ +#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 +#define DBG_TOKEN_IO_VALUE 2048 /* debug token I/O. */ + +#define DBG_APP (opt.debug & DBG_APP_VALUE) +#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_TOKEN (opt.debug & DBG_TOKEN_VALUE) +#define DBG_TOKEN_IO (opt.debug & DBG_TOKEN_IO_VALUE) + +struct server_local_s; + +struct server_control_s +{ + /* Private data used to fire up the connection thread. We use this + structure do avoid an extra allocation for just a few bytes. */ + struct { + gnupg_fd_t fd; + } thread_startup; + + /* Local data of the server; used only in command.c. */ + struct server_local_s *server_local; + + /* Helper to store the value we are going to sign */ + struct + { + unsigned char *value; + int valuelen; + } in_data; +}; + + +/*-- tkdaemon.c --*/ +void tkd_exit (int rc); +void tkd_kick_the_loop (void); +const char *tkd_get_socket_name (void); + +/*-- command.c --*/ +gpg_error_t initialize_module_command (void); +int tkd_command_handler (ctrl_t, gnupg_fd_t); +void send_status_info (ctrl_t ctrl, const char *keyword, ...) + GPGRT_ATTR_SENTINEL(1); +gpg_error_t send_status_direct (ctrl_t ctrl, + const char *keyword, const char *args); +gpg_error_t send_status_printf (ctrl_t ctrl, const char *keyword, + const char *format, ...) GPGRT_ATTR_PRINTF(3,4); +void send_keyinfo (ctrl_t ctrl, int data, const char *keygrip_str, + const char *serialno, const char *idstr, + const char *usage); + +#endif /*TKDAEMON_H*/