diff options
Diffstat (limited to 'tkd/command.c')
-rw-r--r-- | tkd/command.c | 682 |
1 files changed, 682 insertions, 0 deletions
diff --git a/tkd/command.c b/tkd/command.c new file mode 100644 index 000000000..a78ede3b7 --- /dev/null +++ b/tkd/command.c @@ -0,0 +1,682 @@ +/* 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 <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <signal.h> +#ifdef USE_NPTH +# include <npth.h> +#endif + +#include "tkdaemon.h" +#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; + +gpg_error_t +initialize_module_command (void) +{ + return 0; +} + +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 + +static const char hlp_getinfo[] = + "GETINFO <what>\n" + "\n" + "Multi purpose command to return certain information. \n" + "Supported values of WHAT are:\n" + "\n" + " version - Return the version of the program.\n" + " pid - Return the process id of the server.\n" + " socket_name - Return the name of the socket.\n" + " connections - Return number of active connections."; +static gpg_error_t +cmd_getinfo (assuan_context_t ctx, char *line) +{ + int rc = 0; + const char *s; + + if (!strcmp (line, "version")) + { + s = VERSION; + rc = assuan_send_data (ctx, s, strlen (s)); + } + else if (!strcmp (line, "pid")) + { + char numbuf[50]; + + snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); + rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); + } + else if (!strcmp (line, "socket_name")) + { + s = tkd_get_socket_name (); + if (s) + rc = assuan_send_data (ctx, s, strlen (s)); + else + rc = gpg_error (GPG_ERR_NO_DATA); + } + else if (!strcmp (line, "connections")) + { + char numbuf[20]; + + snprintf (numbuf, sizeof numbuf, "%d", get_active_connection_count ()); + rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); + } + else + rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); + return rc; +} + + +static const char hlp_restart[] = + "RESTART\n" + "\n" + "Restart the current connection.\n" + "\n" + "This is used by gpg-agent to reuse a primary pipe connection."; +/* + * TKDeamon does not have a context for a connection (for now). + * So, this command does nothing. + */ +static gpg_error_t +cmd_restart (assuan_context_t ctx, char *line) +{ + (void)line; + (void)ctx; + return 0; +} + + +/* 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; + + line = skip_options (line); + (void)line; + + err = tkd_init (ctrl, ctx, 1); + return err; +} + +static const char hlp_readkey[] = + "READKEY [--info[-only]] <keygrip>\n" + "\n" + "Return the public key for the given KEYGRIP, as a standard\n" + "S-expression."; +static gpg_error_t +cmd_readkey (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + const char *keygrip; + + line = xtrystrdup (line); /* Need a copy of the line. */ + if (!line) + return gpg_error_from_syserror (); + + keygrip = skip_options (line); + if (strlen (keygrip) != 40) + err = gpg_error (GPG_ERR_INV_ID); + + err = tkd_readkey (ctrl, ctx, keygrip); + + xfree (line); + return err; +} + +static const char hlp_pksign[] = + "PKSIGN [--hash=[sha{256,384,512}|none]] <keygrip>\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; + 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"); + + line = xtrystrdup (line); /* Need a copy of the line. */ + if (!line) + return gpg_error_from_syserror (); + + keygrip = skip_options (line); + + if (strlen (keygrip) != 40) + err = gpg_error (GPG_ERR_INV_ID); + + err = tkd_sign (ctrl, ctx, keygrip, hash_algo, &outdata, &outdatalen); + if (err) + { + log_error ("tkd_sign failed: %s\n", gpg_strerror (err)); + } + else + { + err = assuan_send_data (ctx, outdata, outdatalen); + xfree (outdata); + } + + xfree (line); + 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] <keygrip>\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 <keygrip> T <serialno> <idstr> <usage>\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) +{ + gpg_error_t err; + int cap; + int opt_data; + const char *keygrip = NULL; + ctrl_t ctrl = assuan_get_pointer (ctx); + + opt_data = has_option (line, "--data"); + + line = xtrystrdup (line); /* Need a copy of the line. */ + if (!line) + return gpg_error_from_syserror (); + + 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); + + err = tkd_keyinfo (ctrl, ctx, keygrip, opt_data, cap); + + xfree (line); + return err; +} + + +/* 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 }, + { "GETINFO", cmd_getinfo, hlp_getinfo }, + { "RESTART", cmd_restart, hlp_restart }, + { 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; +} |