From 4bac7e0e95712a4c7bfd03471c973f491ad81df4 Mon Sep 17 00:00:00 2001 From: Nikos Mavrogiannopoulos Date: Wed, 24 Aug 2016 11:40:21 +0200 Subject: p11-kit: Add 'p11-kit server' command This adds a new tool to the p11-kit command called 'server', which allows us to access a PKCS#11 module over a Unix domain socket. Internally, it is implemented as a wrapper around 'p11-kit remote'. Upon connection it executes 'p11-kit remote' in a forked process. --- p11-kit/Makefile.am | 14 ++ p11-kit/server.c | 578 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 592 insertions(+) create mode 100644 p11-kit/server.c (limited to 'p11-kit') diff --git a/p11-kit/Makefile.am b/p11-kit/Makefile.am index d51c052..d53f51c 100644 --- a/p11-kit/Makefile.am +++ b/p11-kit/Makefile.am @@ -149,6 +149,20 @@ p11_kit_remote_LDADD = \ libp11-kit.la \ $(NULL) +if !OS_WIN32 +private_PROGRAMS += p11-kit-server + +p11_kit_server_SOURCES = \ + p11-kit/server.c \ + $(NULL) + +p11_kit_server_LDADD = \ + libp11-tool.la \ + libp11-common.la \ + libp11-kit.la \ + $(NULL) +endif + # Tests ---------------------------------------------------------------- p11_kit_LIBS = \ diff --git a/p11-kit/server.c b/p11-kit/server.c new file mode 100644 index 0000000..c96382c --- /dev/null +++ b/p11-kit/server.c @@ -0,0 +1,578 @@ +/* + * Copyright (C) 2014 Red Hat Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Author: Stef Walter + */ + +#include "config.h" + +#include "compat.h" +#include "debug.h" +#include "message.h" +#include "path.h" +#include "p11-kit.h" +#include "remote.h" +#include "unix-peer.h" +#include "tool.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SIGHANDLER_T +#define SIGHANDLER_T sighandler_t +#elif HAVE_SIG_T +#define SIGHANDLER_T sig_t +#elif HAVE___SIGHANDLER_T +#define SIGHANDLER_T __sighandler_t +#else +typedef void (*sighandler_t)(int); +#define SIGHANDLER_T sighandler_t +#endif + +static bool need_children_cleanup = false; +static bool terminate = false; +static unsigned children_avail = 0; +static bool quiet = false; + +typedef struct { + char *module_name; + + uid_t uid; + gid_t gid; + + char *pkcs11_address; + int pkcs11_socket; +} Server; + +static SIGHANDLER_T +ocsignal (int signum, SIGHANDLER_T handler) +{ + struct sigaction new_action, old_action; + + new_action.sa_handler = handler; + sigemptyset (&new_action.sa_mask); + new_action.sa_flags = 0; + + sigaction (signum, &new_action, &old_action); + return old_action.sa_handler; +} + +static void +cleanup_children (void) +{ + int status; + pid_t pid; + + while ((pid = waitpid (-1, &status, WNOHANG)) > 0) { + if (children_avail > 0) + children_avail--; + if (WIFSIGNALED (status)) { + if (WTERMSIG (status) == SIGSEGV) + p11_message ("child %u died with sigsegv", (unsigned)pid); + else + p11_message ("child %u died with signal %d", (unsigned)pid, (int)WTERMSIG (status)); + } + } + need_children_cleanup = false; +} + +static void +handle_children (int signo) +{ + need_children_cleanup = true; +} + +static void +handle_term (int signo) +{ + terminate = true; +} + +static int +set_cloexec_on_fd (void *data, + int fd) +{ + int *max_fd = data; + if (fd >= *max_fd) + fcntl (fd, F_SETFD, FD_CLOEXEC); + return 0; +} + +static int +exec_external (int argc, + char *argv[]) +{ + const char *private_dir; + char *path; + int rc; + + return_val_if_fail (argc >= 1, -1); + + private_dir = secure_getenv ("P11_KIT_PRIVATEDIR"); + if (!private_dir || !private_dir[0]) + private_dir = PRIVATEDIR; + + /* Add our libexec directory to the path */ + path = p11_path_build (private_dir, argv[0], NULL); + return_val_if_fail (path != NULL, -1); + + argv[argc] = NULL; + rc = execv (path, argv); + + free (path); + return rc; +} + +static int +create_socket (const char *address, + uid_t uid, + gid_t gid) +{ + int rc, sd; + struct sockaddr_un sa; + const char *socket_file; + + memset (&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + + return_val_if_fail (strlen (address) < sizeof (sa.sun_path), -1); + strncpy (sa.sun_path, address, sizeof (sa.sun_path)); + socket_file = sa.sun_path; + + remove (sa.sun_path); + + sd = socket (AF_UNIX, SOCK_STREAM, 0); + if (sd == -1) { + p11_message_err (errno, "could not create socket %s", socket_file); + return -1; + } + + umask (066); + rc = bind (sd, (struct sockaddr *)&sa, SUN_LEN (&sa)); + if (rc == -1) { + p11_message_err (errno, "could not create socket %s", socket_file); + return -1; + } + + if (uid != -1 && gid != -1) { + rc = chown (socket_file, uid, gid); + if (rc == -1) { + p11_message_err (errno, "could not chown socket %s", socket_file); + return -1; + } + } + + return sd; +} + +static bool +check_credentials (int fd, + uid_t uid, + gid_t gid) +{ + int rc; + uid_t tuid; + gid_t tgid; + + rc = p11_get_upeer_id (fd, &tuid, &tgid, NULL); + if (rc == -1) { + p11_message_err (errno, "could not check uid from socket"); + close (fd); + return false; + } + + if (uid != -1 && uid != tuid) { + p11_message ("connecting uid (%u) doesn't match expected (%u)", + (unsigned)tuid, (unsigned)uid); + close (fd); + return false; + } + + if (gid != -1 && gid != tgid) { + p11_message ("connecting gid (%u) doesn't match expected (%u)", + (unsigned)tgid, (unsigned)gid); + close (fd); + return false; + } + + return true; +} + +static Server * +server_new (const char *module_name, const char *socket_base, const char *socket_name, uid_t uid, gid_t gid) +{ + Server *server = calloc (1, sizeof (Server)); + char *name; + + server->module_name = strdup (module_name); + return_val_if_fail (server->module_name != NULL, NULL); + + server->uid = uid; + server->gid = gid; + + if (socket_name != NULL) + name = strdup (socket_name); + else if (asprintf (&name, "pkcs11-%d", getpid ()) < 0) + return_val_if_reached (NULL); + + server->pkcs11_address = p11_path_build (socket_base, name, NULL); + free (name); + + return_val_if_fail (server->pkcs11_address != NULL, NULL); + server->pkcs11_socket = -1; + + return server; +} + +static void +server_free (Server *server) +{ + free (server->module_name); + free (server->pkcs11_address); + if (server->pkcs11_socket >= 0) + close (server->pkcs11_socket); + free (server); +} + +static int +server_loop (Server *server, + bool foreground, + struct timespec *timeout) +{ + int ret = 1, rc; + int cfd; + pid_t pid; + socklen_t sa_len; + struct sockaddr_un sa; + fd_set rd_set; + sigset_t emptyset, blockset; + char *args[] = { "p11-kit-remote", NULL, NULL }; + int max_fd; + int errn; + + sigemptyset (&blockset); + sigemptyset (&emptyset); + sigaddset (&blockset, SIGCHLD); + sigaddset (&blockset, SIGTERM); + sigaddset (&blockset, SIGINT); + ocsignal (SIGCHLD, handle_children); + ocsignal (SIGTERM, handle_term); + ocsignal (SIGINT, handle_term); + + server->pkcs11_socket = create_socket (server->pkcs11_address, server->uid, server->gid); + if (server->pkcs11_socket == -1) + return 1; + + /* run as daemon */ + if (!foreground) { + pid = fork (); + switch (pid) { + case -1: + p11_message_err (errno, "could not fork() to daemonize"); + return 1; + case 0: + break; + default: + _exit (0); + } + if (setsid () == -1) { + p11_message_err (errno, "could not create a new session"); + return 1; + } + } + + rc = listen (server->pkcs11_socket, 1024); + if (rc == -1) { + p11_message_err (errno, "could not listen to socket %s", server->pkcs11_address); + return 1; + } + + sigprocmask (SIG_BLOCK, &blockset, NULL); + + if (!quiet) { + char *path; + + path = p11_path_encode (server->pkcs11_address); + printf ("P11_KIT_SERVER_ADDRESS=unix:path=%s\n", path); + free (path); + printf ("P11_KIT_SERVER_PID=%d\n", getpid ()); + } + + /* accept connections */ + ret = 0; + for (;;) { + if (need_children_cleanup) + cleanup_children (); + + if (terminate) + break; + + FD_ZERO (&rd_set); + FD_SET (server->pkcs11_socket, &rd_set); + + ret = pselect (server->pkcs11_socket + 1, &rd_set, NULL, NULL, timeout, &emptyset); + if (ret == -1 && errno == EINTR) + continue; + + if (ret == 0 && children_avail == 0) { /* timeout */ + p11_message ("no connections to %s for %lu secs, exiting", server->pkcs11_address, timeout->tv_sec); + break; + } + + if (FD_ISSET (server->pkcs11_socket, &rd_set)) { + sa_len = sizeof (sa); + cfd = accept (server->pkcs11_socket, (struct sockaddr *)&sa, &sa_len); + if (cfd == -1) { + if (errno != EINTR) + p11_message_err (errno, "could not accept from socket %s", server->pkcs11_address); + continue; + } + + if (!check_credentials (cfd, server->uid, server->gid)) + continue; + + pid = fork (); + switch (pid) { + case -1: + p11_message_err (errno, "failed to fork for accept"); + continue; + /* Child */ + case 0: + sigprocmask (SIG_UNBLOCK, &blockset, NULL); + if (dup2 (cfd, STDIN_FILENO) < 0 || + dup2 (cfd, STDOUT_FILENO) < 0) { + errn = errno; + p11_message_err (errn, "couldn't dup file descriptors in remote child"); + _exit (errn); + } + + /* Close file descriptors, except for above on exec */ + max_fd = STDERR_FILENO + 1; + fdwalk (set_cloexec_on_fd, &max_fd); + + /* Execute 'p11-kit remote'; this shouldn't return */ + args[1] = (char *) server->module_name; + exec_external (2, args); + + errn = errno; + p11_message_err (errn, "couldn't execute 'p11-kit remote' for module '%s'", server->module_name); + _exit (errn); + default: + children_avail++; + break; + } + close (cfd); + } + } + + remove (server->pkcs11_address); + + return ret; +} + +int +main (int argc, + char *argv[]) +{ + char *module_name; + char *socket_base; + uid_t uid = -1, run_as_uid = -1; + gid_t gid = -1, run_as_gid = -1; + int opt; + const struct passwd *pwd; + const struct group *grp; + bool foreground = false; + struct timespec *timeout = NULL, ts; + const char *name = NULL; + Server *server; + int ret; + + enum { + opt_verbose = 'v', + opt_quiet = 'q', + opt_help = 'h', + opt_user = 'u', + opt_group = 'g', + opt_run_as_user = 'a', + opt_run_as_group = 'z', + opt_foreground = 'f', + opt_timeout = 't', + opt_name = 'n', + }; + + struct option options[] = { + { "verbose", no_argument, NULL, opt_verbose }, + { "quiet", no_argument, NULL, opt_quiet }, + { "help", no_argument, NULL, opt_help }, + { "foreground", no_argument, NULL, opt_foreground }, + { "user", required_argument, NULL, opt_user }, + { "group", required_argument, NULL, opt_group }, + { "run-as-user", required_argument, NULL, opt_run_as_user }, + { "run-as-group", required_argument, NULL, opt_run_as_group }, + { "timeout", required_argument, NULL, opt_timeout }, + { "name", required_argument, NULL, opt_name }, + { 0 }, + }; + + p11_tool_desc usages[] = { + { 0, "usage: p11-kit server []" }, + { opt_foreground, "run the server in foreground" }, + { opt_user, "specify user who can connect to the socket" }, + { opt_group, "specify group who can connect to the socket" }, + { opt_run_as_user, "specify user who runs the server" }, + { opt_run_as_group, "specify group who runs the server" }, + { opt_timeout, "exit if no connection until the given timeout" }, + { opt_name, "specify name of the socket (default: pkcs11-" }, + { 0 }, + }; + + while ((opt = p11_tool_getopt (argc, argv, options)) != -1) { + switch (opt) { + case opt_verbose: + p11_kit_be_loud (); + break; + case opt_quiet: + quiet = true; + break; + case opt_timeout: + ts.tv_sec = atoi (optarg); + ts.tv_nsec = 0; + timeout = &ts; + break; + case opt_name: + name = optarg; + break; + case opt_group: + grp = getgrnam (optarg); + if (grp == NULL) { + p11_message ("unknown group: %s", optarg); + return 2; + } + gid = grp->gr_gid; + break; + case opt_user: + pwd = getpwnam (optarg); + if (pwd == NULL) { + p11_message ("unknown user: %s", optarg); + return 2; + } + uid = pwd->pw_uid; + break; + case opt_run_as_group: + grp = getgrnam (optarg); + if (grp == NULL) { + p11_message ("unknown group: %s", optarg); + return 2; + } + run_as_gid = grp->gr_gid; + break; + case opt_run_as_user: + pwd = getpwnam (optarg); + if (pwd == NULL) { + p11_message ("unknown user: %s", optarg); + return 2; + } + run_as_uid = pwd->pw_uid; + break; + case opt_foreground: + foreground = true; + break; + case opt_help: + case '?': + p11_tool_usage (usages, options); + return 0; + default: + assert_not_reached (); + break; + } + } + + argc -= optind; + argv += optind; + + if (!(argc >= 1 && argc <= 2)) { + p11_tool_usage (usages, options); + return 2; + } + + module_name = argv[0]; + + if (argc == 2) + socket_base = argv[1]; + else { + const char *runtime_dir = secure_getenv ("XDG_RUNTIME_DIR"); + if (!runtime_dir || !runtime_dir[0]) { + p11_message_err (errno, "cannot determine runtime directory"); + return 1; + } + socket_base = p11_path_build (runtime_dir, "p11-kit", NULL); + return_val_if_fail (socket_base != NULL, 1); + mkdir (socket_base, 0700); + } + + if (run_as_gid != -1) { + if (setgid (run_as_gid) == -1) { + p11_message_err (errno, "cannot set gid to %u", (unsigned)run_as_gid); + return 1; + } + + if (setgroups (1, &run_as_gid) == -1) { + p11_message_err (errno, "cannot setgroups to %u", (unsigned)run_as_gid); + return 1; + } + } + + if (run_as_uid != -1) { + if (setuid (run_as_uid) == -1) { + p11_message_err (errno, "cannot set uid to %u", (unsigned)run_as_uid); + return 1; + } + } + + server = server_new (module_name, socket_base, name, uid, gid); + ret = server_loop (server, foreground, timeout); + server_free (server); + remove (socket_base); + + return ret; +} -- cgit v1.1