On Mon, 2014-11-10 at 11:41 +0100, Stef Walter wrote: > On 03.11.2014 13:09, Nikos Mavrogiannopoulos wrote: > > The attached patch allows to use p11-kit to run and use an isolated > > PKCS #11 module. The performance cost seems to be quite limited. > > I've tested it with softhsm (isolated) + lighttpd2 and a > > pseudo-benchmark (run in the same pc) shows: > > This is great! Nice work. I'd like to get this in. Some review below > that would need to be fixed first. Happy to have discussion about any > points that aren't clear or where I've misunderstood things.
Attached is an update to the original patch. What is handled is discussed in the comments inline. > I think than using '/' as the prefix here is appropriate. Note of the > other conceived address forms start with '/'. Done. > I have some more general purpose code for this, which I've relicensed > for this purpose. This makes it build on other Unix variants. Should > we merge this first or would you like to incorporate it into your > patch set? Incorporated. > Because things like like SELinux and AppArmor would want to treat the > server differently, we should make it run in a separate process. You > can see how this was done for 'p11-kit remote'. Done. For some reason the server now defaults in debugging mode and prints a lot of output. No idea how that happened. > +#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 unsigned need_children_cleanup = 0; > +static unsigned terminate = 0; > +static unsigned children_avail = 0; > We shouldn't have unprotected globals like this in a library. They're > also not thread safe. As an alternative, you could choose to move as > much of the code out of the library and into p11_kit_server() if you want. Moved everything of the kind to the server. > +static void fix_info(const char *id, CK_INFO *info) > +{ > + unsigned len; > + unsigned i; > + > + /* replace description */ > + snprintf((char*)info->manufacturerID, sizeof(info->manufacturerID), > "V:%s", id); > + len = strlen((char*)info->manufacturerID); > + > + for (i=len;i<sizeof(info->manufacturerID);i++) > + info->manufacturerID[i] = ' '; > +} > The reason for this code isn't commented or documented anywhere. Would > prefer if it was a separate commit with its own commit message, test, etc. I've added a special test case. > case P11_RPC_CALL_##name: \ > ret = rpc_##name (self, &msg); \ > break; > + #define CASE_CALL_ID(id, name) \ > + case P11_RPC_CALL_##name: \ > + ret = rpc_##name (id, self, &msg); \ > + break; > > Since this is only called once, it probably make sense just to use the > code directly without a macro. Done. > + if (!p11_rpc_server_handle (name, &virt->funcs, buffer, > buffer)) { > + p11_message ("unexpected error handling rpc message"); > + goto out; > + } > > This means we cannot handle multi-threading in the PKCS#11 client. Is > this expected? Is it a limitation of your first round implementation? Handled through a thread lock which serializes the requests. Once we have a multi-threaded server a more advanced client will be needed that can issue multiple requests and wake up the correct thread on a response. > + if (daemon(0,0) == -1) { > daemon() is unfortunately not portable. We'll get complains about that > in short order. But here's an implementation which can go into > compat.[ch]: If a system doesn't have daemon(), server is not compiled. > + bool is_socket; /* in that case sfile should be used */ > + char sfile[_POSIX_PATH_MAX]; > } rpc_exec; > rpc_exec is a struct that is used for the exec style rpc peer. We > should define another rpc_connect for a peer which we connect() to. Done. regards, Nikos
>From 08ff6a17e93fa499700257e35ed6bb97fb38e762 Mon Sep 17 00:00:00 2001 From: Nikos Mavrogiannopoulos <n...@redhat.com> Date: Mon, 1 Dec 2014 15:21:00 +0100 Subject: [PATCH] Added the ability to use a PKCS #11 server process via a socket When the remote option is specified with a file starting with '/' a unix socket will be used for PKCS #11 operations. That way one can connect to a p11-kit isolated module (run as different user in a different process). The isolated module can be started using the "p11-kit server" command. --- common/Makefile.am | 1 + common/compat.h | 4 - common/unix-credentials.c | 259 +++++++++++++++++ common/unix-credentials.h | 50 ++++ configure.ac | 6 + p11-kit/Makefile.am | 21 +- p11-kit/p11-kit.c | 3 + p11-kit/rpc-server.c | 54 +++- p11-kit/rpc-transport-cli.c | 475 +++++++++++++++++++++++++++++++ p11-kit/rpc-transport.c | 312 ++------------------- p11-kit/rpc-transport.h | 88 ++++++ p11-kit/rpc.h | 3 +- p11-kit/server.c | 577 ++++++++++++++++++++++++++++++++++++++ p11-kit/test-log.c | 2 +- p11-kit/test-managed.c | 2 +- p11-kit/test-mock.c | 30 +- p11-kit/test-proxy.c | 2 +- p11-kit/test-rpc.c | 4 +- p11-kit/test-transport-isolated.c | 370 ++++++++++++++++++++++++ p11-kit/test-transport.c | 2 +- 20 files changed, 1944 insertions(+), 321 deletions(-) create mode 100644 common/unix-credentials.c create mode 100644 common/unix-credentials.h create mode 100644 p11-kit/rpc-transport-cli.c create mode 100644 p11-kit/rpc-transport.h create mode 100644 p11-kit/server.c create mode 100644 p11-kit/test-transport-isolated.c diff --git a/common/Makefile.am b/common/Makefile.am index b053ec0..335039d 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -26,6 +26,7 @@ libp11_common_la_SOURCES = \ common/path.c common/path.h \ common/pkcs11.h common/pkcs11x.h common/pkcs11i.h \ common/url.c common/url.h \ + common/unix-credentials.c common/unix-credentials.h \ $(NULL) libp11_library_la_SOURCES = \ diff --git a/common/compat.h b/common/compat.h index acbccf9..15b3106 100644 --- a/common/compat.h +++ b/common/compat.h @@ -40,10 +40,6 @@ #include <sys/types.h> #include <sys/stat.h> -#ifdef _GNU_SOURCE -#error Make the crap stop. _GNU_SOURCE is completely unportable and breaks all sorts of behavior -#endif - #if !defined(__cplusplus) && (__GNUC__ > 2) #define GNUC_PRINTF(x, y) __attribute__((__format__(__printf__, x, y))) #else diff --git a/common/unix-credentials.c b/common/unix-credentials.c new file mode 100644 index 0000000..2043ddf --- /dev/null +++ b/common/unix-credentials.c @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2008 Stefan Walter + * 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 <st...@thewalter.net> + */ + +#include "config.h" + +/* for ucred */ +#ifdef __linux__ +# define _GNU_SOURCE +#endif + +#include "unix-credentials.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <sys/un.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#if defined(HAVE_GETPEERUCRED) +# include <ucred.h> +#endif + +int +_p11_unix_credentials_read (int sock, + pid_t *pid /* optional and may be zero on some systems */, + uid_t *uid, + gid_t *gid) +{ + struct msghdr msg; + struct iovec iov; + char buf; + int ret; + +#if defined(HAVE_CMSGCRED) || defined(LOCAL_CREDS) + /* Prefer CMSGCRED over LOCAL_CREDS because the former provides the + * remote PID. */ +#if defined(HAVE_CMSGCRED) + struct cmsgcred *cred; +#else /* defined(LOCAL_CREDS) */ + struct sockcred *cred; +#endif + union { + struct cmsghdr hdr; + char cred[CMSG_SPACE (sizeof *cred)]; + } cmsg; +#endif + + if (pid) + *pid = 0; + *uid = 0; + *gid = 0; + + /* If LOCAL_CREDS are used in this platform, they have already been + * initialized by init_connection prior to sending of the credentials + * byte we receive below. */ + + iov.iov_base = &buf; + iov.iov_len = 1; + + memset (&msg, 0, sizeof (msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + +#if defined(HAVE_CMSGCRED) || defined(LOCAL_CREDS) + memset (&cmsg, 0, sizeof (cmsg)); + msg.msg_control = (caddr_t) &cmsg; + msg.msg_controllen = CMSG_SPACE(sizeof *cred); +#endif + + again: + ret = recvmsg (sock, &msg, 0); + + if (ret < 0) { + if (errno == EINTR) + goto again; + return -1; + + } else if (ret == 0) { + /* Disconnected */ + return -1; + } + + if (buf != '\0') { + fprintf (stderr, "credentials byte was not nul\n"); + return -1; + } + +#if defined(HAVE_CMSGCRED) || defined(LOCAL_CREDS) + if (cmsg.hdr.cmsg_len < CMSG_LEN (sizeof *cred) || + cmsg.hdr.cmsg_type != SCM_CREDS) { + fprintf (stderr, "message from recvmsg() was not SCM_CREDS\n"); + return -1; + } +#endif + + { +#ifdef SO_PEERCRED +#ifndef __OpenBSD__ + struct ucred cr; +#else + struct sockpeercred cr; +#endif + socklen_t cr_len = sizeof (cr); + + if (getsockopt (sock, SOL_SOCKET, SO_PEERCRED, &cr, &cr_len) == 0 && + cr_len == sizeof (cr)) { + if (pid) + *pid = cr.pid; + *uid = cr.uid; + *gid = cr.gid; + } else { + fprintf (stderr, "failed to getsockopt() credentials, returned len %d/%d\n", + cr_len, (int) sizeof (cr)); + return -1; + } +#elif defined(HAVE_CMSGCRED) + cred = (struct cmsgcred *) CMSG_DATA (&cmsg.hdr); + if (pid) + *pid = cred->cmcred_pid; + *uid = cred->cmcred_euid; + *gid = cred->cmcred_egid; +#elif defined(LOCAL_CREDS) + cred = (struct sockcred *) CMSG_DATA (&cmsg.hdr); + if (pid) + *pid = 0; + *uid = cred->sc_euid; + *gid = cred->sc_egid; + set_local_creds(sock, 0); +#elif defined(HAVE_GETPEEREID) /* OpenBSD */ + uid_t euid; + gid_t egid; + if (pid) + *pid = 0; + + if (getpeereid (sock, &euid, &egid) == 0) { + *uid = euid; + *uid = egid; + } else { + fprintf (stderr, "getpeereid() failed: %s\n", strerror (errno)); + return -1; + } +#elif defined(HAVE_GETPEERUCRED) + ucred_t *uc = NULL; + + if (getpeerucred (sock, &uc) == 0) { + if (pid) + *pid = ucred_getpid (uc); + *uid = ucred_geteuid (uc); + *uid = ucred_getegid (uc); + ucred_free (uc); + } else { + fprintf (stderr, "getpeerucred() failed: %s\n", strerror (errno)); + return -1; + } +#else /* !SO_PEERCRED && !HAVE_CMSGCRED */ + fprintf (stderr, "socket credentials not supported on this OS\n"); + return -1; +#endif + } + + return 0; +} + +int +_p11_unix_credentials_write (int socket) +{ + char buf; + int bytes_written; +#if defined(HAVE_CMSGCRED) && (!defined(LOCAL_CREDS) || defined(__FreeBSD__)) + union { + struct cmsghdr hdr; + char cred[CMSG_SPACE (sizeof (struct cmsgcred))]; + } cmsg; + struct iovec iov; + struct msghdr msg; +#endif + + buf = 0; + +#if defined(HAVE_CMSGCRED) && (!defined(LOCAL_CREDS) || defined(__FreeBSD__)) + iov.iov_base = &buf; + iov.iov_len = 1; + + memset (&msg, 0, sizeof (msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + msg.msg_control = (caddr_t) &cmsg; + msg.msg_controllen = CMSG_SPACE (sizeof (struct cmsgcred)); + memset (&cmsg, 0, sizeof (cmsg)); + cmsg.hdr.cmsg_len = CMSG_LEN (sizeof (struct cmsgcred)); + cmsg.hdr.cmsg_level = SOL_SOCKET; + cmsg.hdr.cmsg_type = SCM_CREDS; +#endif + +again: + +#if defined(HAVE_CMSGCRED) && (!defined(LOCAL_CREDS) || defined(__FreeBSD__)) + bytes_written = sendmsg (socket, &msg, 0); +#else + bytes_written = write (socket, &buf, 1); +#endif + + if (bytes_written < 0 && errno == EINTR) + goto again; + + if (bytes_written <= 0) + return -1; + + return 0; +} + +int +_p11_unix_credentials_setup (int sock) +{ + int retval = 0; +#if defined(LOCAL_CREDS) && !defined(HAVE_CMSGCRED) + int val = 1; + if (setsockopt (sock, 0, LOCAL_CREDS, &val, sizeof (val)) < 0) { + fprintf (stderr, "unable to set LOCAL_CREDS socket option on fd %d\n", fd); + retval = -1; + } +#endif + return retval; +} diff --git a/common/unix-credentials.h b/common/unix-credentials.h new file mode 100644 index 0000000..fd21a01 --- /dev/null +++ b/common/unix-credentials.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2008 Stefan Walter + * + * 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 <st...@thewalter.net> + */ + +#ifndef _UNIX_CREDENTIALS_H_ +#define _UNIX_CREDENTIALS_H_ + +#include <unistd.h> +#include <grp.h> + +int _p11_unix_credentials_read (int sock, + pid_t *pid, + uid_t *uid, + gid_t *gid); + +int _p11_unix_credentials_write (int sock); + +int _p11_unix_credentials_setup (int sock); + +#endif /* _UNIX_CREDENTIALS_H_*/ diff --git a/configure.ac b/configure.ac index 02ea526..99368d0 100644 --- a/configure.ac +++ b/configure.ac @@ -68,6 +68,11 @@ AC_C_BIGENDIAN AC_HEADER_STDBOOL if test "$os_unix" = "yes"; then + AC_CHECK_TYPES([sighandler_t, sig_t, __sighandler_t],,, + [#include <sys/types.h> + #include <signal.h> + ]) + AC_CHECK_FUNC([pthread_create], , [ AC_CHECK_LIB(pthread, pthread_create, , [ AC_MSG_ERROR([could not find pthread_create]) @@ -94,6 +99,7 @@ if test "$os_unix" = "yes"; then AC_CHECK_FUNCS([timegm]) AC_CHECK_FUNCS([fdwalk]) AC_CHECK_FUNCS([setenv]) + AC_CHECK_FUNCS([daemon]) AC_CHECK_DECLS([asprintf, vasprintf], [], [], [[#include <stdio.h>]]) diff --git a/p11-kit/Makefile.am b/p11-kit/Makefile.am index 3ef70f9..c66a31f 100644 --- a/p11-kit/Makefile.am +++ b/p11-kit/Makefile.am @@ -21,6 +21,7 @@ MODULE_SRCS = \ p11-kit/proxy.c p11-kit/proxy.h \ p11-kit/messages.c \ p11-kit/rpc-transport.c p11-kit/rpc.h \ + p11-kit/rpc-transport-cli.c \ p11-kit/rpc-message.c p11-kit/rpc-message.h \ p11-kit/rpc-client.c p11-kit/rpc-server.c \ p11-kit/uri.c \ @@ -118,7 +119,7 @@ p11_kit_p11_kit_LDADD = \ $(LTLIBINTL) \ $(NULL) -private_PROGRAMS += p11-kit-remote +private_PROGRAMS += p11-kit-remote p11-kit-server p11_kit_remote_SOURCES = \ p11-kit/remote.c \ @@ -130,6 +131,20 @@ p11_kit_remote_LDADD = \ libp11-kit.la \ $(NULL) +p11_kit_server_SOURCES = \ + p11-kit/server.c \ + p11-kit/rpc-transport.c p11-kit/rpc.h \ + p11-kit/rpc-server.c \ + p11-kit/rpc-message.c p11-kit/rpc-message.h \ + p11-kit/virtual.c p11-kit/virtual.h \ + $(NULL) + +p11_kit_server_LDADD = \ + libp11-tool.la \ + libp11-common.la \ + libp11-kit.la \ + $(NULL) + # Tests ---------------------------------------------------------------- p11_kit_LIBS = \ @@ -202,6 +217,7 @@ CHECK_PROGS += \ test-managed \ test-log \ test-transport \ + test-transport-isolated \ $(NULL) test_log_SOURCES = p11-kit/test-log.c @@ -213,6 +229,9 @@ test_managed_LDADD = $(p11_kit_LIBS) test_transport_SOURCES = p11-kit/test-transport.c test_transport_LDADD = $(p11_kit_LIBS) +test_transport_isolated_SOURCES = p11-kit/test-transport-isolated.c +test_transport_isolated_LDADD = $(p11_kit_LIBS) + test_virtual_SOURCES = p11-kit/test-virtual.c test_virtual_LDADD = $(p11_kit_LIBS) diff --git a/p11-kit/p11-kit.c b/p11-kit/p11-kit.c index a7b9212..2faa820 100644 --- a/p11-kit/p11-kit.c +++ b/p11-kit/p11-kit.c @@ -48,7 +48,9 @@ #include <stdio.h> #include <stdlib.h> #include <unistd.h> +#include <sys/types.h> +#include "remote.h" #include "tool.h" int p11_kit_list_modules (int argc, @@ -63,6 +65,7 @@ int p11_kit_external (int argc, static const p11_tool_command commands[] = { { "list-modules", p11_kit_list_modules, "List modules and tokens" }, { "remote", p11_kit_external, "Run a specific PKCS#11 module remotely" }, + { "server", p11_kit_external, "Run a specific PKCS#11 module as an isolated server" }, { P11_TOOL_FALLBACK, p11_kit_external, NULL }, { 0, } }; diff --git a/p11-kit/rpc-server.c b/p11-kit/rpc-server.c index a2562e9..8558a72 100644 --- a/p11-kit/rpc-server.c +++ b/p11-kit/rpc-server.c @@ -50,6 +50,7 @@ #include <assert.h> #include <errno.h> #include <stdlib.h> +#include <stdio.h> #include <string.h> #include <unistd.h> @@ -708,15 +709,37 @@ rpc_C_Finalize (CK_X_FUNCTION_LIST *self, END_CALL; } +/* This function modifies the manufacturer ID of a remote + * server to V:provided@id, which typically is the socket + * file of the remote module. That way a remote module will + * be distinct from a local one. + */ +static void +fix_info(const char *id, CK_INFO *info) +{ + unsigned idlen = strlen(id); + if (idlen > 30) + idlen = 30; + + memset(info->manufacturerID, ' ', 32); + /* replace description */ + memcpy((char*)info->manufacturerID, "V:", 2); + memcpy((char*)&info->manufacturerID[2], id, idlen); +} + static CK_RV -rpc_C_GetInfo (CK_X_FUNCTION_LIST *self, +rpc_C_GetInfo (const char *id, + CK_X_FUNCTION_LIST *self, p11_rpc_message *msg) { CK_INFO info; BEGIN_CALL (GetInfo); PROCESS_CALL ((self, &info)); - OUT_INFO (info); + if (id) + fix_info (id, &info); + + OUT_INFO (info); END_CALL; } @@ -1764,7 +1787,8 @@ rpc_C_GenerateRandom (CK_X_FUNCTION_LIST *self, } bool -p11_rpc_server_handle (CK_X_FUNCTION_LIST *self, +p11_rpc_server_handle (const char *desc, + CK_X_FUNCTION_LIST *self, p11_buffer *request, p11_buffer *response) { @@ -1792,13 +1816,15 @@ p11_rpc_server_handle (CK_X_FUNCTION_LIST *self, req_id = msg.call_id; switch(req_id) { - #define CASE_CALL(name) \ - case P11_RPC_CALL_##name: \ - ret = rpc_##name (self, &msg); \ + #define CASE_CALL(desc) \ + case P11_RPC_CALL_##desc: \ + ret = rpc_##desc (self, &msg); \ + break; + case P11_RPC_CALL_C_GetInfo: + ret = rpc_C_GetInfo (desc, self, &msg); break; CASE_CALL (C_Initialize) CASE_CALL (C_Finalize) - CASE_CALL (C_GetInfo) CASE_CALL (C_GetSlotList) CASE_CALL (C_GetSlotInfo) CASE_CALL (C_GetTokenInfo) @@ -1914,7 +1940,7 @@ p11_kit_remote_serve_module (CK_FUNCTION_LIST *module, p11_buffer options; p11_buffer buffer; size_t state; - int ret = 1; + int ret = 1, rc; int code; return_val_if_fail (module != NULL, 1); @@ -1929,7 +1955,7 @@ p11_kit_remote_serve_module (CK_FUNCTION_LIST *module, goto out; case 1: if (version != 0) { - p11_message ("unspported version received: %d", (int)version); + p11_message ("unsupported version received: %d", (int)version); goto out; } break; @@ -1939,11 +1965,15 @@ p11_kit_remote_serve_module (CK_FUNCTION_LIST *module, } version = 0; - switch (write (out_fd, &version, out_fd)) { + do { + rc = write (out_fd, &version, 1); + } while(rc == -1 && (errno == EAGAIN || errno == EINTR)); + + switch (rc) { case 1: break; default: - p11_message_err (errno, "couldn't write credential byte"); + p11_message_err (errno, "couldn't write header byte"); goto out; } @@ -1970,7 +2000,7 @@ p11_kit_remote_serve_module (CK_FUNCTION_LIST *module, goto out; } - if (!p11_rpc_server_handle (&virt.funcs, &buffer, &buffer)) { + if (!p11_rpc_server_handle (NULL, &virt.funcs, &buffer, &buffer)) { p11_message ("unexpected error handling rpc message"); goto out; } diff --git a/p11-kit/rpc-transport-cli.c b/p11-kit/rpc-transport-cli.c new file mode 100644 index 0000000..75c86c9 --- /dev/null +++ b/p11-kit/rpc-transport-cli.c @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2012 Stefan Walter + * Copyright (C) 2013, 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 <st...@gnome.org> + */ + +#include "config.h" + +#include "argv.h" +#include "compat.h" +#define P11_DEBUG_FLAG P11_DEBUG_RPC +#include "debug.h" +#include "message.h" +#include "pkcs11.h" +#include "private.h" +#include "rpc.h" +#include "rpc-message.h" +#include "rpc-transport.h" +#include "unix-credentials.h" + +#include <sys/types.h> + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <limits.h> + +#ifdef OS_UNIX +#include <sys/socket.h> +#include <sys/wait.h> +#include <sys/un.h> +#include <signal.h> +#include <unistd.h> +#endif + +#ifdef OS_WIN32 +#include <winsock2.h> +#endif + +#ifndef EPROTO +#define EPROTO EIO +#endif + +#ifdef OS_UNIX + +typedef struct { + p11_rpc_transport base; + p11_array *argv; + pid_t pid; +} rpc_exec; + +typedef struct { + p11_rpc_transport base; + char sfile[_POSIX_PATH_MAX]; +} rpc_isolated; + +static rpc_socket * +rpc_socket_new (int fd) +{ + rpc_socket *sock; + + sock = calloc (1, sizeof (rpc_socket)); + return_val_if_fail (sock != NULL, NULL); + + sock->fd = fd; + sock->last_code = 0x10; + sock->read_creds = false; + sock->sent_creds = false; + sock->refs = 1; + + p11_mutex_init (&sock->write_lock); + + return sock; +} + +static rpc_socket * +rpc_socket_file_new (const char *file) +{ + rpc_socket *sock; + struct sockaddr_un sa; + int ret, e; + + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + + snprintf(sa.sun_path, sizeof(sa.sun_path), "%s", file); + + sock = calloc (1, sizeof (rpc_socket)); + return_val_if_fail (sock != NULL, NULL); + + sock->fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock->fd == -1) { + free(sock); + p11_message ("could not open socket"); + return NULL; + } + + p11_debug ("connecting to: %s", file); + + /* try to connect to child */ + ret = connect(sock->fd, (struct sockaddr *)&sa, sizeof(sa)); + if (ret == -1) { + e = errno; + close(sock->fd); + free(sock); + p11_message ("could not connect to socket: '%s': %s", file, strerror(e)); + return NULL; + } + + ret = _p11_unix_credentials_setup(sock->fd); + if (ret == -1) { + e = errno; + p11_message ("could not establish credentials for socket %s: %s", file, strerror(e)); + close(sock->fd); + free(sock); + return NULL; + } + + /* check the uid of the peer */ + ret = _p11_unix_credentials_write(sock->fd); + if (ret == -1) { + e = errno; + p11_message ("could not send credentials to socket %s: %s", file, strerror(e)); + close(sock->fd); + free(sock); + return NULL; + } + + p11_debug ("connected to: %s", file); + + sock->last_code = 0x10; + sock->read_creds = false; + sock->sent_creds = false; + sock->refs = 1; + + p11_mutex_init (&sock->write_lock); + + return sock; +} + + +static void +rpc_exec_wait_or_terminate (pid_t pid) +{ + bool terminated = false; + int status; + int sig; + int ret; + int i; + + + for (i = 0; i < 3 * 1000; i += 100) { + ret = waitpid (pid, &status, WNOHANG); + if (ret != 0) + break; + p11_sleep_ms (100); + } + + if (ret == 0) { + p11_message ("process %d did not exit, terminating", (int)pid); + kill (pid, SIGTERM); + terminated = true; + ret = waitpid (pid, &status, 0); + } + + if (ret < 0) { + p11_message_err (errno, "failed to wait for executed child: %d", (int)pid); + status = 0; + } else if (WIFEXITED (status)) { + status = WEXITSTATUS (status); + if (status == 0) + p11_debug ("process %d exited with status 0", (int)pid); + else + p11_message ("process %d exited with status %d", (int)pid, status); + } else if (WIFSIGNALED (status)) { + sig = WTERMSIG (status); + if (!terminated || sig != SIGTERM) + p11_message ("process %d was terminated with signal %d", (int)pid, sig); + } +} + +static void +rpc_exec_disconnect (p11_rpc_client_vtable *vtable, + void *fini_reserved) +{ + rpc_exec *rex = (rpc_exec *)vtable; + + if (rex->base.socket) + rpc_socket_close (rex->base.socket); + + if (rex->pid) + rpc_exec_wait_or_terminate (rex->pid); + rex->pid = 0; + + /* Do the common disconnect stuff */ + rpc_transport_disconnect (vtable, fini_reserved); +} + +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 CK_RV +rpc_exec_connect (p11_rpc_client_vtable *vtable, + void *init_reserved) +{ + rpc_exec *rex = (rpc_exec *)vtable; + pid_t pid; + int max_fd; + int fds[2]; + int errn; + + p11_debug ("executing rpc transport: %s", (char *)rex->argv->elem[0]); + + if (socketpair (AF_UNIX, SOCK_STREAM, 0, fds) < 0) { + p11_message_err (errno, "failed to create pipe for remote"); + return CKR_DEVICE_ERROR; + } + + pid = fork (); + switch (pid) { + + /* Failure */ + case -1: + close (fds[0]); + close (fds[1]); + p11_message_err (errno, "failed to fork for remote"); + return CKR_DEVICE_ERROR; + + /* Child */ + case 0: + if (dup2 (fds[1], STDIN_FILENO) < 0 || + dup2 (fds[1], 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); + execvp (rex->argv->elem[0], (char **)rex->argv->elem); + + errn = errno; + p11_message_err (errn, "couldn't execute program for rpc: %s", + (char *)rex->argv->elem[0]); + _exit (errn); + + /* The parent */ + default: + break; + } + + close (fds[1]); + rex->pid = pid; + rex->base.socket = rpc_socket_new (fds[0]); + return_val_if_fail (rex->base.socket != NULL, CKR_GENERAL_ERROR); + + return CKR_OK; +} + + +static void +rpc_exec_free (void *data) +{ + rpc_exec *rex = data; + rpc_exec_disconnect (data, NULL); + rpc_transport_uninit (&rex->base); + p11_array_free (rex->argv); + free (rex); +} + +static void +on_argv_parsed (char *argument, + void *data) +{ + p11_array *argv = data; + + if (!p11_array_push (argv, strdup (argument))) + return_if_reached (); +} + +static p11_rpc_transport * +rpc_exec_init (const char *remote, + const char *name) +{ + p11_array *argv; + rpc_exec *rex; + + argv = p11_array_new (free); + if (!p11_argv_parse (remote, on_argv_parsed, argv) || argv->num < 1) { + p11_message ("invalid remote command line: %s", remote); + p11_array_free (argv); + return NULL; + } + + rex = calloc (1, sizeof (rpc_exec)); + return_val_if_fail (rex != NULL, NULL); + + p11_array_push (argv, NULL); + rex->argv = argv; + + rex->base.vtable.connect = rpc_exec_connect; + rex->base.vtable.disconnect = rpc_exec_disconnect; + rex->base.vtable.transport = rpc_transport_buffer; + rpc_transport_init (&rex->base, name, rpc_exec_free); + + p11_debug ("initialized rpc exec: %s", remote); + return &rex->base; +} + +static void +rpc_isolated_disconnect (p11_rpc_client_vtable *vtable, + void *fini_reserved) +{ + rpc_isolated *rex = (rpc_isolated *)vtable; + + if (rex->base.socket) + rpc_socket_close (rex->base.socket); + + /* Do the common disconnect stuff */ + rpc_transport_disconnect (vtable, fini_reserved); +} + +static CK_RV +rpc_isolated_connect (p11_rpc_client_vtable *vtable, + void *init_reserved) +{ + rpc_isolated *rex = (rpc_isolated *)vtable; + int errn; + unsigned char dummy = 1; + + p11_debug ("connecting to socket: %s", (char *)rex->sfile); + + rex->base.socket = rpc_socket_file_new (rex->sfile); + if (rex->base.socket == NULL) { + p11_message_err (errno, "couldn't connect to socket: %s", rex->sfile); + return CKR_DEVICE_ERROR; + } + + /* this is read as version from the peer --nmav */ + if (rpc_transport_write_all (rex->base.socket->fd, &dummy, 1) != 1) { + p11_message_err (errno, "couldn't send version"); + return CKR_DEVICE_ERROR; + } + rex->base.socket->sent_creds = true; + + do { + errn = read(rex->base.socket->fd, &dummy, 1); + } while (errn == -1 && (errno == EAGAIN || errno == EINTR)); + + if (errn != 1) { + p11_message_err (errno, "couldn't read version: %d", errn); + return CKR_DEVICE_ERROR; + } + rex->base.socket->read_creds = true; + + return CKR_OK; +} + +static void +rpc_isolated_free (void *data) +{ + rpc_isolated *rex = data; + rpc_isolated_disconnect (data, NULL); + rpc_transport_uninit (&rex->base); + remove(rex->sfile); + free (rex); +} + + +static p11_rpc_transport * +rpc_isolated_init (const char *remote, + const char *name) +{ + rpc_isolated *rex; + + rex = calloc (1, sizeof (rpc_isolated)); + return_val_if_fail (rex != NULL, NULL); + + snprintf(rex->sfile, sizeof(rex->sfile), "%s", remote); + + rex->base.vtable.connect = rpc_isolated_connect; + rex->base.vtable.disconnect = rpc_isolated_disconnect; + rex->base.vtable.transport = rpc_transport_buffer; + rpc_transport_init (&rex->base, name, rpc_isolated_free); + + p11_debug ("initialized rpc socket: %s", rex->sfile); + return &rex->base; +} + +#endif /* OS_UNIX */ + + +p11_rpc_transport * +p11_rpc_transport_new (p11_virtual *virt, + const char *remote, + const char *name) +{ + p11_rpc_transport *rpc = NULL; + + return_val_if_fail (virt != NULL, NULL); + return_val_if_fail (remote != NULL, NULL); + return_val_if_fail (name != NULL, NULL); + +#ifdef OS_WIN32 + p11_message ("Windows not yet supported for remote"); + return NULL; +#endif + + /* This is a command we can execute */ + if (remote[0] == '|') { + rpc = rpc_exec_init (remote + 1, name); + } else if (remote[0] == '/') { + rpc = rpc_isolated_init (remote, name); + } else { + p11_message ("remote option not supported: %s", remote); + return NULL; + } + + if (!p11_rpc_client_init (virt, &rpc->vtable)) + return_val_if_reached (NULL); + + return rpc; +} + +void +p11_rpc_transport_free (void *data) +{ + p11_rpc_transport *rpc = data; + + if (rpc != NULL) { + assert (rpc->destroyer); + (rpc->destroyer) (data); + } +} diff --git a/p11-kit/rpc-transport.c b/p11-kit/rpc-transport.c index b3651ad..481e3c3 100644 --- a/p11-kit/rpc-transport.c +++ b/p11-kit/rpc-transport.c @@ -44,6 +44,8 @@ #include "private.h" #include "rpc.h" #include "rpc-message.h" +#include "unix-credentials.h" +#include "rpc-transport.h" #include <sys/types.h> @@ -52,7 +54,9 @@ #include <fcntl.h> #include <stdint.h> #include <stdlib.h> +#include <stdio.h> #include <string.h> +#include <limits.h> #ifdef OS_UNIX #include <sys/socket.h> @@ -70,44 +74,6 @@ #define EPROTO EIO #endif -typedef struct { - /* Never changes */ - int fd; - - /* Protected by the lock */ - p11_mutex_t write_lock; - int refs; - int last_code; - bool sent_creds; - - /* This data is protected by read mutex */ - p11_mutex_t read_lock; - bool read_creds; - uint32_t read_code; - uint32_t read_olen; - uint32_t read_dlen; -} rpc_socket; - -static rpc_socket * -rpc_socket_new (int fd) -{ - rpc_socket *sock; - - sock = calloc (1, sizeof (rpc_socket)); - return_val_if_fail (sock != NULL, NULL); - - sock->fd = fd; - sock->last_code = 0x10; - sock->read_creds = false; - sock->sent_creds = false; - sock->refs = 1; - - p11_mutex_init (&sock->write_lock); - p11_mutex_init (&sock->read_lock); - - return sock; -} - #if 0 static rpc_socket * rpc_socket_ref (rpc_socket *sock) @@ -129,7 +95,7 @@ rpc_socket_is_open (rpc_socket *sock) } #endif -static void +void rpc_socket_close (rpc_socket *sock) { assert (sock != NULL); @@ -158,11 +124,10 @@ rpc_socket_unref (rpc_socket *sock) rpc_socket_close (sock); p11_mutex_uninit (&sock->write_lock); - p11_mutex_uninit (&sock->read_lock); } -static bool -write_all (int fd, +bool +rpc_transport_write_all (int fd, unsigned char* data, size_t len) { @@ -229,7 +194,7 @@ rpc_socket_write_inlock (rpc_socket *sock, /* Place holder byte, will later carry unix credentials (on some systems) */ if (!sock->sent_creds) { - if (write_all (sock->fd, &dummy, 1) != 1) { + if (rpc_transport_write_all (sock->fd, &dummy, 1) != 1) { p11_message_err (errno, "couldn't send socket credentials"); return CKR_DEVICE_ERROR; } @@ -240,9 +205,9 @@ rpc_socket_write_inlock (rpc_socket *sock, p11_rpc_buffer_encode_uint32 (header + 4, options->len); p11_rpc_buffer_encode_uint32 (header + 8, buffer->len); - if (!write_all (sock->fd, header, 12) || - !write_all (sock->fd, options->data, options->len) || - !write_all (sock->fd, buffer->data, buffer->len)) + if (!rpc_transport_write_all (sock->fd, header, 12) || + !rpc_transport_write_all (sock->fd, options->data, options->len) || + !rpc_transport_write_all (sock->fd, buffer->data, buffer->len)) return CKR_DEVICE_ERROR; return CKR_OK; @@ -340,7 +305,7 @@ p11_rpc_transport_write (int fd, } static int -rpc_socket_read (rpc_socket *sock, +rpc_socket_read_inlock (rpc_socket *sock, int *code, p11_buffer *buffer) { @@ -357,8 +322,6 @@ rpc_socket_read (rpc_socket *sock, * is referenced, and won't go away */ - p11_mutex_lock (&sock->read_lock); - if (!sock->read_creds) { if (read_all (sock->fd, &dummy, 1) != 1) return CKR_DEVICE_ERROR; @@ -410,19 +373,15 @@ rpc_socket_read (rpc_socket *sock, /* Give another thread the chance to read data for this header */ if (sock->read_code != 0) { p11_debug ("received header in wrong thread"); - p11_mutex_unlock (&sock->read_lock); /* Used as a simple wait */ FD_ZERO (&rfds); FD_SET (sock->fd, &rfds); if (select (sock->fd + 1, &rfds, NULL, NULL, NULL) < 0) p11_message ("couldn't use select to wait on rpc socket"); - - p11_mutex_lock (&sock->read_lock); } } - p11_mutex_unlock (&sock->read_lock); return ret; } @@ -539,14 +498,8 @@ p11_rpc_transport_read (int fd, return status; } -struct _p11_rpc_transport { - p11_rpc_client_vtable vtable; - p11_destroyer destroyer; - rpc_socket *socket; - p11_buffer options; -}; -static void +void rpc_transport_disconnect (p11_rpc_client_vtable *vtable, void *init_reserved) { @@ -559,7 +512,7 @@ rpc_transport_disconnect (p11_rpc_client_vtable *vtable, } } -static bool +bool rpc_transport_init (p11_rpc_transport *rpc, const char *module_name, p11_destroyer destroyer) @@ -573,13 +526,13 @@ rpc_transport_init (p11_rpc_transport *rpc, return true; } -static void +void rpc_transport_uninit (p11_rpc_transport *rpc) { p11_buffer_uninit (&rpc->options); } -static CK_RV +CK_RV rpc_transport_buffer (p11_rpc_client_vtable *vtable, p11_buffer *request, p11_buffer *response) @@ -610,11 +563,7 @@ rpc_transport_buffer (p11_rpc_client_vtable *vtable, /* We unlock the socket mutex while reading a response */ if (rv == CKR_OK) { - p11_mutex_unlock (&sock->write_lock); - - rv = rpc_socket_read (sock, &call_code, response); - - p11_mutex_lock (&sock->write_lock); + rv = rpc_socket_read_inlock (sock, &call_code, response); } if (rv != CKR_OK && sock->fd != -1) { @@ -629,230 +578,3 @@ rpc_transport_buffer (p11_rpc_client_vtable *vtable, return rv; } - -#ifdef OS_UNIX - -typedef struct { - p11_rpc_transport base; - p11_array *argv; - pid_t pid; -} rpc_exec; - -static void -rpc_exec_wait_or_terminate (pid_t pid) -{ - bool terminated = false; - int status; - int sig; - int ret; - int i; - - - for (i = 0; i < 3 * 1000; i += 100) { - ret = waitpid (pid, &status, WNOHANG); - if (ret != 0) - break; - p11_sleep_ms (100); - } - - if (ret == 0) { - p11_message ("process %d did not exit, terminating", (int)pid); - kill (pid, SIGTERM); - terminated = true; - ret = waitpid (pid, &status, 0); - } - - if (ret < 0) { - p11_message_err (errno, "failed to wait for executed child: %d", (int)pid); - status = 0; - } else if (WIFEXITED (status)) { - status = WEXITSTATUS (status); - if (status == 0) - p11_debug ("process %d exited with status 0", (int)pid); - else - p11_message ("process %d exited with status %d", (int)pid, status); - } else if (WIFSIGNALED (status)) { - sig = WTERMSIG (status); - if (!terminated || sig != SIGTERM) - p11_message ("process %d was terminated with signal %d", (int)pid, sig); - } -} - -static void -rpc_exec_disconnect (p11_rpc_client_vtable *vtable, - void *fini_reserved) -{ - rpc_exec *rex = (rpc_exec *)vtable; - - if (rex->base.socket) - rpc_socket_close (rex->base.socket); - - if (rex->pid) - rpc_exec_wait_or_terminate (rex->pid); - rex->pid = 0; - - /* Do the common disconnect stuff */ - rpc_transport_disconnect (vtable, fini_reserved); -} - -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 CK_RV -rpc_exec_connect (p11_rpc_client_vtable *vtable, - void *init_reserved) -{ - rpc_exec *rex = (rpc_exec *)vtable; - pid_t pid; - int max_fd; - int fds[2]; - int errn; - - p11_debug ("executing rpc transport: %s", (char *)rex->argv->elem[0]); - - if (socketpair (AF_UNIX, SOCK_STREAM, 0, fds) < 0) { - p11_message_err (errno, "failed to create pipe for remote"); - return CKR_DEVICE_ERROR; - } - - pid = fork (); - switch (pid) { - - /* Failure */ - case -1: - close (fds[0]); - close (fds[1]); - p11_message_err (errno, "failed to fork for remote"); - return CKR_DEVICE_ERROR; - - /* Child */ - case 0: - if (dup2 (fds[1], STDIN_FILENO) < 0 || - dup2 (fds[1], 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); - execvp (rex->argv->elem[0], (char **)rex->argv->elem); - - errn = errno; - p11_message_err (errn, "couldn't execute program for rpc: %s", - (char *)rex->argv->elem[0]); - _exit (errn); - - /* The parent */ - default: - break; - } - - close (fds[1]); - rex->pid = pid; - rex->base.socket = rpc_socket_new (fds[0]); - return_val_if_fail (rex->base.socket != NULL, CKR_GENERAL_ERROR); - - return CKR_OK; -} - -static void -rpc_exec_free (void *data) -{ - rpc_exec *rex = data; - rpc_exec_disconnect (data, NULL); - rpc_transport_uninit (&rex->base); - p11_array_free (rex->argv); - free (rex); -} - -static void -on_argv_parsed (char *argument, - void *data) -{ - p11_array *argv = data; - - if (!p11_array_push (argv, strdup (argument))) - return_if_reached (); -} - -static p11_rpc_transport * -rpc_exec_init (const char *remote, - const char *name) -{ - p11_array *argv; - rpc_exec *rex; - - argv = p11_array_new (free); - if (!p11_argv_parse (remote, on_argv_parsed, argv) || argv->num < 1) { - p11_message ("invalid remote command line: %s", remote); - p11_array_free (argv); - return NULL; - } - - rex = calloc (1, sizeof (rpc_exec)); - return_val_if_fail (rex != NULL, NULL); - - p11_array_push (argv, NULL); - rex->argv = argv; - - rex->base.vtable.connect = rpc_exec_connect; - rex->base.vtable.disconnect = rpc_exec_disconnect; - rex->base.vtable.transport = rpc_transport_buffer; - rpc_transport_init (&rex->base, name, rpc_exec_free); - - p11_debug ("initialized rpc exec: %s", remote); - return &rex->base; -} - -#endif /* OS_UNIX */ - -p11_rpc_transport * -p11_rpc_transport_new (p11_virtual *virt, - const char *remote, - const char *name) -{ - p11_rpc_transport *rpc = NULL; - - return_val_if_fail (virt != NULL, NULL); - return_val_if_fail (remote != NULL, NULL); - return_val_if_fail (name != NULL, NULL); - -#ifdef OS_WIN32 - p11_message ("Windows not yet supported for remote"); - return NULL; -#endif - - /* This is a command we can execute */ - if (remote[0] == '|') { - rpc = rpc_exec_init (remote + 1, name); - - } else { - p11_message ("remote not supported: %s", remote); - return NULL; - } - - if (!p11_rpc_client_init (virt, &rpc->vtable)) - return_val_if_reached (NULL); - - return rpc; -} - -void -p11_rpc_transport_free (void *data) -{ - p11_rpc_transport *rpc = data; - - if (rpc != NULL) { - assert (rpc->destroyer); - (rpc->destroyer) (data); - } -} diff --git a/p11-kit/rpc-transport.h b/p11-kit/rpc-transport.h new file mode 100644 index 0000000..d331a48 --- /dev/null +++ b/p11-kit/rpc-transport.h @@ -0,0 +1,88 @@ +/* + * 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 <st...@gnome.org> + */ + +#ifndef _RPC_TRANSPORT_H +#define _RPC_TRANSPORT_H + + +typedef struct { + /* Never changes */ + int fd; + + /* Protected by the lock */ + p11_mutex_t write_lock; + int refs; + int last_code; + bool sent_creds; + + /* This data is protected by read mutex */ + bool read_creds; + uint32_t read_code; + uint32_t read_olen; + uint32_t read_dlen; +} rpc_socket; + + +struct _p11_rpc_transport { + p11_rpc_client_vtable vtable; + p11_destroyer destroyer; + rpc_socket *socket; + p11_buffer options; +}; + +void +rpc_socket_close (rpc_socket *sock); + +bool +rpc_transport_init (p11_rpc_transport *rpc, + const char *module_name, + p11_destroyer destroyer); + +void +rpc_transport_disconnect (p11_rpc_client_vtable *vtable, + void *init_reserved); + +bool +rpc_transport_write_all (int fd, + unsigned char* data, + size_t len); + +void +rpc_transport_uninit (p11_rpc_transport *rpc); + +CK_RV +rpc_transport_buffer (p11_rpc_client_vtable *vtable, + p11_buffer *request, + p11_buffer *response); +#endif diff --git a/p11-kit/rpc.h b/p11-kit/rpc.h index b129e61..070f4a2 100644 --- a/p11-kit/rpc.h +++ b/p11-kit/rpc.h @@ -59,7 +59,8 @@ struct _p11_rpc_client_vtable { bool p11_rpc_client_init (p11_virtual *virt, p11_rpc_client_vtable *vtable); -bool p11_rpc_server_handle (CK_X_FUNCTION_LIST *funcs, +bool p11_rpc_server_handle (const char *desc, + CK_X_FUNCTION_LIST *funcs, p11_buffer *request, p11_buffer *response); diff --git a/p11-kit/server.c b/p11-kit/server.c new file mode 100644 index 0000000..43fe4b3 --- /dev/null +++ b/p11-kit/server.c @@ -0,0 +1,577 @@ +/* + * 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: Nikos Mavrogiannopoulos <n...@redhat.com> + */ + +#include "config.h" + +#include "compat.h" +#include "debug.h" +#include "message.h" +#include "p11-kit.h" +#include "remote.h" +#include "tool.h" +#include "rpc.h" +#include "rpc-message.h" + +#if defined(HAVE_DAEMON) && !defined(OS_WIN32) + +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <grp.h> +#include <pwd.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <sys/un.h> +#include <grp.h> + +#include "unix-credentials.h" + +#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 unsigned need_children_cleanup = 0; +static unsigned terminate = 0; +static unsigned children_avail = 0; + +static +SIGHANDLER_T p11_signal(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 int +serve_isolated_module (const char *name, + CK_FUNCTION_LIST *module, + p11_buffer *options, p11_buffer *buffer, + p11_virtual *virt, + int fd) +{ + p11_rpc_status status; + unsigned char version; + size_t state; + int ret = 1, rc; + int code; + + switch (read (fd, &version, 1)) { + case 0: + status = P11_RPC_EOF; + goto out; + case 1: + if (version != 1) { + p11_message ("unsupported version received: %d", (int)version); + goto out; + } + break; + default: + p11_message_err (errno, "couldn't read version byte"); + goto out; + } + + version = 0; + + do { + rc = write (fd, &version, 1); + } while(rc == -1 && (errno == EINTR || errno == EAGAIN)); + + switch(rc) { + case 1: + break; + default: + p11_message_err (errno, "couldn't write header bytes"); + goto out; + } + + status = P11_RPC_OK; + while (status == P11_RPC_OK) { + state = 0; + code = 0; + + do { + status = p11_rpc_transport_read (fd, &state, &code, + options, buffer); + } while (status == P11_RPC_AGAIN); + + switch (status) { + case P11_RPC_OK: + break; + case P11_RPC_EOF: + ret = 0; + continue; + case P11_RPC_AGAIN: + assert_not_reached (); + case P11_RPC_ERROR: + p11_message_err (errno, "failed to read rpc message"); + goto out; + } + + if (!p11_rpc_server_handle (name, &virt->funcs, buffer, buffer)) { + p11_message ("unexpected error handling rpc message"); + goto out; + } + + state = 0; + options->len = 0; + do { + status = p11_rpc_transport_write (fd, &state, code, + options, buffer); + } while (status == P11_RPC_AGAIN); + + switch (status) { + case P11_RPC_OK: + break; + case P11_RPC_EOF: + case P11_RPC_AGAIN: + assert_not_reached (); + case P11_RPC_ERROR: + p11_message_err (errno, "failed to write rpc message"); + goto out; + } + } + +out: + return ret; +} + +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\n", (unsigned)pid); + else + p11_message("child %u died with signal %d\n", (unsigned)pid, (int)WTERMSIG(status)); + } + } + need_children_cleanup = 0; +} + +static void handle_children(int signo) +{ + need_children_cleanup = 1; +} + +static void handle_term(int signo) +{ + terminate = 1; +} + + +static int +p11_kit_remote_isolate_module (CK_FUNCTION_LIST *module, + const char *socket_file, + mode_t mode, + uid_t uid, + gid_t gid, + uid_t run_as_uid, + gid_t run_as_gid, + unsigned foreground, + unsigned timeout) +{ + p11_virtual virt; + p11_buffer options; + p11_buffer buffer; + int ret = 1, rc, sd; + int e, cfd; + pid_t pid; + socklen_t sa_len; + struct sockaddr_un sa; + fd_set rd_set; + sigset_t emptyset, blockset; + uid_t tuid; + gid_t tgid; + struct timespec ts; + + sigemptyset(&blockset); + sigemptyset(&emptyset); + sigaddset(&blockset, SIGCHLD); + sigaddset(&blockset, SIGTERM); + sigaddset(&blockset, SIGINT); + p11_signal(SIGCHLD, handle_children); + p11_signal(SIGTERM, handle_term); + p11_signal(SIGINT, handle_term); + + return_val_if_fail (module != NULL, 1); + + /* listen to unix socket */ + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + snprintf(sa.sun_path, sizeof(sa.sun_path), "%s", socket_file); + + remove(socket_file); + + sd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sd == -1) { + e = errno; + p11_message ("could not create socket %s: %s", socket_file, strerror(e)); + return 1; + } + + umask(066); + rc = bind(sd, (struct sockaddr *)&sa, SUN_LEN(&sa)); + if (rc == -1) { + e = errno; + p11_message ("could not create socket %s: %s", socket_file, strerror(e)); + return 1; + } + + if (uid != -1 && gid != -1) { + if (uid == getuid()) { + p11_message ("server is running using the same uid as client, this may not provide the required isolation"); + } + + rc = chown(socket_file, uid, gid); + if (rc == -1) { + e = errno; + p11_message ("could not chown socket %s: %s", socket_file, strerror(e)); + return 1; + } + } + + if (mode != 0) { + rc = chmod(socket_file, mode); + if (rc == -1) { + e = errno; + p11_message ("could not chmod socket %s to %o: %s", socket_file, mode, strerror(e)); + } + } + + if (run_as_gid != -1) { + if (setgid(run_as_gid) == -1) { + e = errno; + p11_message("cannot set gid to %u: %s\n", (unsigned)run_as_gid, strerror(e)); + return 1; + } + + if (setgroups(1, &run_as_gid) == -1) { + e = errno; + p11_message("cannot setgroups to %u: %s\n", (unsigned)run_as_gid, strerror(e)); + return 1; + } + } + + if (run_as_uid != -1) { + if (setuid(run_as_uid) == -1) { + e = errno; + p11_message("cannot set uid to %u: %s\n", (unsigned)run_as_uid, strerror(e)); + return 1; + } + } + + /* run as daemon */ + if (foreground == 0) { + if (daemon(0,0) == -1) { + e = errno; + p11_message ("could not daemonize: %s", strerror(e)); + } + } + + rc = listen(sd, 1024); + if (rc == -1) { + e = errno; + p11_message ("could not listen to socket %s: %s", socket_file, strerror(e)); + return 1; + } + + p11_buffer_init (&options, 0); + p11_buffer_init (&buffer, 0); + + p11_virtual_init (&virt, &p11_virtual_base, module, NULL); + + sigprocmask(SIG_BLOCK, &blockset, NULL); + /* accept connections */ + for (;;) { + if (need_children_cleanup) + cleanup_children(); + + if (terminate != 0) + goto exit; + + FD_ZERO(&rd_set); + FD_SET(sd, &rd_set); + + ts.tv_sec = timeout; + ts.tv_nsec = 0; + if (timeout > 0) + ret = pselect(sd + 1, &rd_set, NULL, NULL, &ts, &emptyset); + else + ret = pselect(sd + 1, &rd_set, NULL, NULL, NULL, &emptyset); + if (ret == -1 && errno == EINTR) + continue; + + if (ret == 0 && children_avail == 0) { /* timeout */ + p11_message ("no connections to %s for %u secs, exiting", socket_file, timeout); + goto exit; + } + + if (FD_ISSET(sd, &rd_set)) { + sa_len = sizeof(sa); + cfd = accept(sd, (struct sockaddr *)&sa, &sa_len); + if (cfd == -1) { + e = errno; + if (e != EINTR) { + p11_message ("could not accept from socket %s: %s", socket_file, strerror(e)); + } + continue; + } + + rc = _p11_unix_credentials_setup(cfd); + if (rc == -1) { + e = errno; + p11_message ("could not establish credentials for socket %s: %s", socket_file, strerror(e)); + goto cont; + } + + /* check the uid of the peer */ + rc = _p11_unix_credentials_read(cfd, NULL, &tuid, &tgid); + if (rc == -1) { + e = errno; + p11_message ("could not check uid from socket %s: %s", socket_file, strerror(e)); + goto cont; + } + + if (uid != -1) { + if (uid != tuid) { + p11_message ("connecting uid (%u) doesn't match expected (%u)", + (unsigned)tuid, (unsigned)uid); + goto cont; + } + } + + if (gid != -1) { + if (gid != tgid) { + p11_message ("connecting gid (%u) doesn't match expected (%u)", + (unsigned)tgid, (unsigned)gid); + goto cont; + } + } + + pid = fork(); + switch(pid) { + case -1: + p11_message_err (errno, "failed to fork for accept"); + continue; + case 0: + /* child */ + sigprocmask(SIG_UNBLOCK, &blockset, NULL); + serve_isolated_module (socket_file, module, &options, &buffer, &virt, cfd); + _exit(0); + default: + children_avail++; + break; + } + cont: + close(cfd); + } + } + + p11_buffer_uninit (&buffer); + p11_buffer_uninit (&options); + + p11_virtual_uninit (&virt); + return ret; + exit: + remove(socket_file); + exit(0); +} + +int +main (int argc, + char *argv[]) +{ + char *socket_file = NULL; + CK_FUNCTION_LIST *module; + uid_t uid = -1, run_as_uid = -1; + gid_t gid = -1, run_as_gid = -1; + int opt; + int ret; + const struct passwd* pwd; + const struct group* grp; + unsigned foreground = 0; + unsigned timeout = 0; + mode_t mode = 0; + + enum { + opt_verbose = 'v', + opt_help = 'h', + opt_socket = 's', + opt_mode = 'm', + opt_user = 'u', + opt_group = 'g', + opt_run_as_user = 'a', + opt_run_as_group = 'z', + opt_foreground = 'f', + opt_timeout = 't', + }; + + struct option options[] = { + { "verbose", no_argument, NULL, opt_verbose }, + { "help", no_argument, NULL, opt_help }, + { "foreground", no_argument, NULL, opt_foreground }, + { "socket", required_argument, NULL, opt_socket }, + { "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 }, + { "mode", required_argument, NULL, opt_mode }, + { 0 }, + }; + + p11_tool_desc usages[] = { + { 0, "usage: p11-kit server --help" }, + { 0, "usage: p11-kit server <module> -s <socket-file>" }, + { 0, "usage: p11-kit server <module> -s <socket-file> -f" }, + { 0, "usage: p11-kit server <module> -s <socket-file> -u <allowed-user> -g <allowed-group> --run-as-user <user> --run-as-group <group> -m <mode>" }, + { 0 }, + }; + + while ((opt = p11_tool_getopt (argc, argv, options)) != -1) { + switch (opt) { + case opt_verbose: + p11_kit_be_loud (); + break; + case opt_socket: + socket_file = strdup (optarg); + break; + case opt_timeout: + timeout = atoi (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_mode: + mode = strtol (optarg, NULL, 8); + 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 = 1; + break; + case opt_help: + case '?': + p11_tool_usage (usages, options); + return 0; + default: + assert_not_reached (); + break; + } + } + + argc -= optind; + argv += optind; + + if (socket_file == NULL) { + p11_tool_usage (usages, options); + return 2; + } + + if (argc != 1) { + p11_message ("specify the module to run as isolated server"); + return 2; + } + + module = p11_kit_module_load (argv[0], 0); + if (module == NULL) + return 1; + + ret = p11_kit_remote_isolate_module (module, socket_file, mode, + uid, gid, + run_as_uid, run_as_gid, + foreground, timeout); + p11_kit_module_release (module); + + return ret; +} + +#else +int +main (int argc, + char *argv[]) +{ + fprintf(stderr, "This program is unsupported on this platform"); + return 1; +} +#endif diff --git a/p11-kit/test-log.c b/p11-kit/test-log.c index e7dab70..8af5ef6 100644 --- a/p11-kit/test-log.c +++ b/p11-kit/test-log.c @@ -103,7 +103,7 @@ main (int argc, p11_library_init (); mock_module_init (); - test_mock_add_tests ("/log"); + test_mock_add_tests ("/log", 0); p11_kit_be_quiet (); p11_log_output = false; diff --git a/p11-kit/test-managed.c b/p11-kit/test-managed.c index fc673ea..616aff1 100644 --- a/p11-kit/test-managed.c +++ b/p11-kit/test-managed.c @@ -263,7 +263,7 @@ main (int argc, p11_test (test_fork_and_reinitialize, "/managed/fork-and-reinitialize"); #endif - test_mock_add_tests ("/managed"); + test_mock_add_tests ("/managed", 0); p11_kit_be_quiet (); diff --git a/p11-kit/test-mock.c b/p11-kit/test-mock.c index 8454f1f..c1d8015 100644 --- a/p11-kit/test-mock.c +++ b/p11-kit/test-mock.c @@ -67,6 +67,27 @@ test_get_info (void) } static void +test_get_info_no_mid (void) +{ + CK_FUNCTION_LIST_PTR module; + CK_INFO info; + CK_RV rv; + + module = setup_mock_module (NULL); + + rv = (module->C_GetInfo) (&info); + assert_num_eq (rv, CKR_OK); + assert_num_eq (MOCK_INFO.cryptokiVersion.major, info.cryptokiVersion.major); + assert_num_eq (MOCK_INFO.cryptokiVersion.minor, info.cryptokiVersion.minor); + assert_num_eq (MOCK_INFO.flags, info.flags); + assert (memcmp (MOCK_INFO.libraryDescription, info.libraryDescription, sizeof (info.libraryDescription)) == 0); + assert_num_eq (MOCK_INFO.libraryVersion.major, info.libraryVersion.major); + assert_num_eq (MOCK_INFO.libraryVersion.minor, info.libraryVersion.minor); + + teardown_mock_module (module); +} + +static void test_get_slot_list (void) { CK_FUNCTION_LIST_PTR module; @@ -1637,11 +1658,16 @@ test_random (void) teardown_mock_module (module); } +#define MOCK_NO_CHECK_FOR_MANUFACTURER_ID 1 + static void -test_mock_add_tests (const char *prefix) +test_mock_add_tests (const char *prefix, unsigned flags) { p11_fixture (NULL, NULL); - p11_test (test_get_info, "%s/test_get_info", prefix); + if (flags & MOCK_NO_CHECK_FOR_MANUFACTURER_ID) + p11_test (test_get_info_no_mid, "%s/test_get_info", prefix); + else + p11_test (test_get_info, "%s/test_get_info", prefix); p11_test (test_get_slot_list, "%s/test_get_slot_list", prefix); p11_test (test_get_slot_info, "%s/test_get_slot_info", prefix); p11_test (test_get_token_info, "%s/test_get_token_info", prefix); diff --git a/p11-kit/test-proxy.c b/p11-kit/test-proxy.c index e4998be..63ffa4f 100644 --- a/p11-kit/test-proxy.c +++ b/p11-kit/test-proxy.c @@ -189,7 +189,7 @@ main (int argc, p11_test (test_initialize_finalize, "/proxy/initialize-finalize"); p11_test (test_initialize_multiple, "/proxy/initialize-multiple"); - test_mock_add_tests ("/proxy"); + test_mock_add_tests ("/proxy", 0); return p11_test_run (argc, argv); } diff --git a/p11-kit/test-rpc.c b/p11-kit/test-rpc.c index c9f8333..b238a6a 100644 --- a/p11-kit/test-rpc.c +++ b/p11-kit/test-rpc.c @@ -394,7 +394,7 @@ rpc_transport (p11_rpc_client_vtable *vtable, assert_str_eq (vtable->data, "vtable-data"); /* Just pass directly to the server code */ - ret = p11_rpc_server_handle (&base.funcs, request, response); + ret = p11_rpc_server_handle (NULL, &base.funcs, request, response); assert (ret == true); return CKR_OK; @@ -1055,7 +1055,7 @@ main (int argc, p11_test (test_fork_and_reinitialize, "/rpc/fork-and-reinitialize"); #endif - test_mock_add_tests ("/rpc"); + test_mock_add_tests ("/rpc", 0); return p11_test_run (argc, argv); } diff --git a/p11-kit/test-transport-isolated.c b/p11-kit/test-transport-isolated.c new file mode 100644 index 0000000..a4b27fd --- /dev/null +++ b/p11-kit/test-transport-isolated.c @@ -0,0 +1,370 @@ +/* + * 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: Nikos Mavrogiannopoulos <n...@redhat.com> + */ + +#include "config.h" +#include "test.h" + +#include "library.h" +#include "mock.h" +#include "path.h" +#include "private.h" +#include "p11-kit.h" +#include "rpc.h" + +#include <sys/types.h> +#ifdef OS_UNIX +#include <sys/wait.h> +#endif +#include <stdlib.h> +#include <stdio.h> +#include <sys/time.h> + +static int initialized = 0; + +struct { + char *directory; + char *user_config; + char *socket_name; + char *user_modules; + pid_t pid; +} test; + +static void +setup_remote (void *unused) +{ + char *data; + char name[64+sizeof(BUILDDIR)]; + pid_t pid; + int ret; + int retries = 15; + struct timeval tv; + + test.pid = -1; + + test.directory = p11_test_directory ("p11-test-config"); + test.user_modules = p11_path_build (test.directory, "modules", NULL); +#ifdef OS_UNIX + if (mkdir (test.user_modules, 0700) < 0) +#else + if (mkdir (test.user_modules) < 0) +#endif + assert_not_reached (); + + data = "user-config: only\n"; + test.user_config = p11_path_build (test.directory, "pkcs11.conf", NULL); + p11_test_file_write (NULL, test.user_config, data, strlen (data)); + + setenv ("P11_KIT_PRIVATEDIR", BUILDDIR, 1); + if (initialized == 0) { + gettimeofday(&tv, NULL); + snprintf(name, sizeof(name), BUILDDIR"/sock.%u.%u", + (unsigned)tv.tv_sec, (unsigned)tv.tv_usec); + data = p11_path_build (test.directory, name, NULL); + + pid = fork(); + switch(pid) { + case -1: + assert_not_reached (); + case 0: + if (execl(BUILDDIR"/p11-kit/p11-kit", BUILDDIR"/p11-kit/p11-kit", "server", "-f", + BUILDDIR"/.libs/mock-two.so", "-s", name, "-t", "30", NULL) == -1) + assert_not_reached (); + exit(0); + default: + break; + } + + test.pid = pid; + test.socket_name = strdup(name); + if (asprintf(&data, "remote: %s", name) == -1) + assert_not_reached(); + p11_test_file_write (test.user_modules, "remote.module", data, strlen (data)); + + initialized = 1; + + /* wait for the server to start */ + do { + ret = access(data, F_OK); + usleep(100000); + } while(ret < 0 && (--retries) > 0); + } + + p11_config_user_modules = test.user_modules; + p11_config_user_file = test.user_config; +} + +static void +teardown_remote (void *unused) +{ + p11_test_directory_delete (test.user_modules); + p11_test_directory_delete (test.directory); + + free (test.directory); + free (test.user_config); + free (test.user_modules); +} + +static CK_FUNCTION_LIST * +setup_mock_module (CK_SESSION_HANDLE *session) +{ + CK_FUNCTION_LIST **modules; + CK_FUNCTION_LIST *module; + CK_RV rv; + int i; + + setup_remote (NULL); + + modules = p11_kit_modules_load (NULL, 0); + + module = p11_kit_module_for_name (modules, "remote"); + assert (module != NULL); + + rv = p11_kit_module_initialize (module); + assert_num_eq (rv, CKR_OK); + + if (session) { + rv = (module->C_OpenSession) (MOCK_SLOT_ONE_ID, CKF_RW_SESSION | CKF_SERIAL_SESSION, + NULL, NULL, session); + assert (rv == CKR_OK); + } + + /* Release all the other modules */ + for (i = 0; modules[i] != NULL; i++) { + if (modules[i] != module) + p11_kit_module_release (modules[i]); + } + + free (modules); + return module; +} + +static void +teardown_mock_module (CK_FUNCTION_LIST *module) +{ + p11_kit_module_finalize (module); + teardown_remote (NULL); +} + +static void +test_basic_exec (void) +{ + CK_FUNCTION_LIST **modules; + CK_FUNCTION_LIST *module; + CK_RV rv; + + modules = p11_kit_modules_load (NULL, 0); + + module = p11_kit_module_for_name (modules, "remote"); + assert (module != NULL); + + rv = p11_kit_module_initialize (module); + assert_num_eq (rv, CKR_OK); + + rv = p11_kit_module_finalize (module); + assert_num_eq (rv, CKR_OK); + + p11_kit_modules_release (modules); + initialized = 0; +} + +static void * +invoke_in_thread (void *arg) +{ + CK_FUNCTION_LIST *rpc_module = arg; + CK_INFO info; + CK_RV rv; + + rv = (rpc_module->C_GetInfo) (&info); + assert_num_eq (rv, CKR_OK); + + assert (memcmp (info.libraryDescription, MOCK_INFO.libraryDescription, + sizeof (info.libraryDescription)) == 0); + + return NULL; +} + +static void +test_get_manufacturer_id (void) +{ + CK_FUNCTION_LIST **modules; + CK_FUNCTION_LIST_PTR module; + CK_INFO info; + CK_RV rv; + char exp_name[33]; + + snprintf(exp_name, sizeof(exp_name), "V:%s", test.socket_name); + + modules = p11_kit_modules_load (NULL, 0); + + module = p11_kit_module_for_name (modules, "remote"); + assert (module != NULL); + + rv = p11_kit_module_initialize (module); + assert_num_eq (rv, CKR_OK); + + rv = (module->C_GetInfo) (&info); + assert_num_eq (rv, CKR_OK); + assert (memcmp (exp_name, info.manufacturerID, strlen(exp_name)) == 0); + + rv = p11_kit_module_finalize (module); + assert_num_eq (rv, CKR_OK); + + p11_kit_modules_release (modules); + initialized = 0; +} + +static void +test_simultaneous_functions (void) +{ + CK_FUNCTION_LIST **modules; + CK_FUNCTION_LIST *module; + const int num_threads = 128; + p11_thread_t threads[num_threads]; + int i, ret; + CK_RV rv; + + modules = p11_kit_modules_load (NULL, 0); + + module = p11_kit_module_for_name (modules, "remote"); + assert (module != NULL); + + rv = p11_kit_module_initialize (module); + assert_num_eq (rv, CKR_OK); + + for (i = 0; i < num_threads; i++) { + ret = p11_thread_create (threads + i, invoke_in_thread, module); + assert_num_eq (0, ret); + } + + for (i = 0; i < num_threads; i++) + p11_thread_join (threads[i]); + + rv = p11_kit_module_finalize (module); + assert_num_eq (rv, CKR_OK); + + p11_kit_modules_release (modules); + initialized = 0; +} + +#ifdef OS_UNIX + +static void +test_fork_and_reinitialize (void) +{ + CK_FUNCTION_LIST **modules; + CK_FUNCTION_LIST *module; + CK_INFO info; + int status; + CK_RV rv; + pid_t pid; + int i; + + modules = p11_kit_modules_load (NULL, 0); + + module = p11_kit_module_for_name (modules, "remote"); + assert (module != NULL); + + rv = p11_kit_module_initialize (module); + assert_num_eq (rv, CKR_OK); + + pid = fork (); + assert_num_cmp (pid, >=, 0); + + /* The child */ + if (pid == 0) { + rv = (module->C_Initialize) (NULL); + assert_num_eq (CKR_OK, rv); + + for (i = 0; i < 32; i++) { + rv = (module->C_GetInfo) (&info); + assert_num_eq (CKR_OK, rv); + } + + rv = (module->C_Finalize) (NULL); + assert_num_eq (CKR_OK, rv); + + _exit (66); + } + + for (i = 0; i < 128; i++) { + rv = (module->C_GetInfo) (&info); + assert_num_eq (CKR_OK, rv); + } + + assert_num_eq (waitpid (pid, &status, 0), pid); + assert_num_eq (WEXITSTATUS (status), 66); + + rv = p11_kit_module_finalize (module); + assert_num_eq (rv, CKR_OK); + + p11_kit_modules_release (modules); + initialized = 0; +} + +#endif /* OS_UNIX */ + +#include "test-mock.c" + +int +main (int argc, + char *argv[]) +{ + CK_MECHANISM_TYPE mechanisms[] = { + CKM_MOCK_CAPITALIZE, + CKM_MOCK_PREFIX, + CKM_MOCK_GENERATE, + CKM_MOCK_WRAP, + CKM_MOCK_DERIVE, + CKM_MOCK_COUNT, + 0, + }; + + p11_library_init (); + + /* Override the mechanisms that the RPC mechanism will handle */ + p11_rpc_mechanisms_override_supported = mechanisms; + + p11_fixture (setup_remote, teardown_remote); + p11_test (test_basic_exec, "/transport/basic"); + p11_test (test_simultaneous_functions, "/transport/simultaneous-functions"); + p11_test (test_get_manufacturer_id, "/transport/get-manufactured-id"); + +#ifdef OS_UNIX + p11_test (test_fork_and_reinitialize, "/transport/fork-and-reinitialize"); +#endif + + test_mock_add_tests ("/transport", MOCK_NO_CHECK_FOR_MANUFACTURER_ID); + + return p11_test_run (argc, argv); +} diff --git a/p11-kit/test-transport.c b/p11-kit/test-transport.c index 397a65a..5fc7c47 100644 --- a/p11-kit/test-transport.c +++ b/p11-kit/test-transport.c @@ -288,7 +288,7 @@ main (int argc, p11_test (test_fork_and_reinitialize, "/transport/fork-and-reinitialize"); #endif - test_mock_add_tests ("/transport"); + test_mock_add_tests ("/transport", 0); return p11_test_run (argc, argv); } -- 1.9.3
_______________________________________________ p11-glue mailing list p11-glue@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/p11-glue