/*
 * Copyright (C) 2012 Stefan Walter
 * Copyright (C) 2013 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 <stefw@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 "path.h"

#include <sys/types.h>

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef OS_UNIX
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <signal.h>
#include <unistd.h>
#include <limits.h>
#endif

#ifdef OS_WIN32
#include <process.h>
#include <signal.h>
#include <winsock2.h>
#ifndef EWOULDBLOCK
#define EWOULDBLOCK WSAEWOULDBLOCK
#endif
#endif

#ifndef EPROTO
#define EPROTO EIO
#endif

#ifdef ENABLE_NLS
#include <libintl.h>
#define _(x) dgettext(PACKAGE_NAME, x)
#else
#define _(x) (x)
#endif

typedef struct {
	/* Never changes.  On Unix, these are identical, as it is
	 * backed by a socket.  On Windows, it is another file
	 * descriptor, as they are backed by two pipes */
	int read_fd;
	int write_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;
#ifdef OS_UNIX
        /* Signalled when read_code changes */
        p11_cond_t read_code_cond;
#endif
	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->read_fd = fd;
	sock->write_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);

#ifdef OS_UNIX
        p11_cond_init (&sock->read_code_cond);
#endif

	return sock;
}

#if 0
static rpc_socket *
rpc_socket_ref (rpc_socket *sock)
{
	assert (sock != NULL);

	p11_mutex_lock (&sock->write_lock);
	sock->refs++;
	p11_mutex_unlock (&sock->write_lock);

	return sock;
}

static bool
rpc_socket_is_open (rpc_socket *sock)
{
	assert (sock != NULL);
	return sock->read_fd >= 0;
}
#endif

static void
rpc_socket_close (rpc_socket *sock)
{
	assert (sock != NULL);
	if (sock->read_fd != -1)
		close (sock->read_fd);
	sock->read_fd = -1;
#ifdef OS_WIN32
	if (sock->write_fd != -1)
		close (sock->write_fd);
	sock->write_fd = -1;
#endif
}

static void
rpc_socket_unref (rpc_socket *sock)
{
	int release = 0;

	assert (sock != NULL);

	p11_mutex_lock (&sock->write_lock);
	if (--sock->refs == 0)
		release = 1;
	p11_mutex_unlock (&sock->write_lock);

	if (!release)
		return;

	assert (sock != NULL);
	assert (sock->refs == 0);

	rpc_socket_close (sock);
	p11_mutex_uninit (&sock->write_lock);
	p11_mutex_uninit (&sock->read_lock);
#ifdef OS_UNIX
        p11_cond_uninit (&sock->read_code_cond);
#endif
	free (sock);
}

static bool
write_all (int fd,
           unsigned char* data,
           size_t len)
{
	int r;

	while (len > 0) {
		r = write (fd, data, len);
		if (r == -1) {
			if (errno == EPIPE) {
				p11_message (_("couldn't send data: closed connection"));
				return false;
			} else if (errno != EAGAIN && errno != EINTR) {
				p11_message_err (errno, _("couldn't send data"));
				return false;
			}
		} else {
			p11_debug ("wrote %d bytes", r);
			data += r;
			len -= r;
		}
	}

	return true;
}

static bool
read_all (int fd,
          unsigned char* data,
          size_t len)
{
	int r;

	while (len > 0) {
		r = read (fd, data, len);
		if (r == 0) {
			p11_message (_("couldn't receive data: closed connection"));
			return false;
		} else if (r == -1) {
			if (errno != EAGAIN && errno != EINTR) {
				p11_message_err (errno, _("couldn't receive data"));
				return false;
			}
		} else {
			p11_debug ("read %d bytes", r);
			data += r;
			len -= r;
		}
	}

	return true;
}

static CK_RV
rpc_socket_write_inlock (rpc_socket *sock,
                         int code,
                         p11_buffer *options,
                         p11_buffer *buffer)
{
	unsigned char header[12];
	unsigned char dummy = '\0';

	/* The socket is locked and referenced at this point */
	assert (buffer != NULL);

	/* Place holder byte, will later carry unix credentials (on some systems) */
	if (!sock->sent_creds) {
		if (write_all (sock->write_fd, &dummy, 1) != 1) {
			p11_message_err (errno, _("couldn't send socket credentials"));
			return CKR_DEVICE_ERROR;
		}
		sock->sent_creds = true;
	}

	p11_rpc_buffer_encode_uint32 (header, code);
	p11_rpc_buffer_encode_uint32 (header + 4, options->len);
	p11_rpc_buffer_encode_uint32 (header + 8, buffer->len);

	if (!write_all (sock->write_fd, header, 12) ||
	    !write_all (sock->write_fd, options->data, options->len) ||
	    !write_all (sock->write_fd, buffer->data, buffer->len))
		return CKR_DEVICE_ERROR;

	return CKR_OK;
}

static p11_rpc_status
write_at (int fd,
          unsigned char *data,
          size_t len,
          size_t offset,
          size_t *at)
{
	p11_rpc_status status;
	ssize_t num;
	size_t from;
	int errn;

	assert (*at >= offset);

	if (*at >= offset + len)
		return P11_RPC_OK;

	from = *at - offset;
	assert (from < len);

	num = write (fd, data + from, len - from);
	errn = errno;

	/* Update state */
	if (num > 0)
		*at += num;

	/* Completely written out this block */
	if (num == len - from) {
		p11_debug ("ok: wrote block of %d", (int)num);
		status = P11_RPC_OK;

	/* Partially written out this block */
	} else if (num >= 0) {
		p11_debug ("again: partial read of %d", (int)num);
		status = P11_RPC_AGAIN;

	/* Didn't write out block due to transient issue */
	} else if (errn == EINTR || errn == EAGAIN || errn == EWOULDBLOCK) {
		p11_debug ("again: due to %d", errn);
		status = P11_RPC_AGAIN;

	/* Failure */
	} else {
		p11_debug ("error: due to %d", errn);
		status = P11_RPC_ERROR;
	}

	errno = errn;
	return status;
}

p11_rpc_status
p11_rpc_transport_write (int fd,
                         size_t *state,
                         int call_code,
                         p11_buffer *options,
                         p11_buffer *buffer)
{
	unsigned char header[12] = { 0, };
	p11_rpc_status status;

	assert (state != NULL);
	assert (options != NULL);
	assert (buffer != NULL);

	if (*state < 12) {
		p11_rpc_buffer_encode_uint32 (header, call_code);
		p11_rpc_buffer_encode_uint32 (header + 4, options->len);
		p11_rpc_buffer_encode_uint32 (header + 8, buffer->len);
	}

	status = write_at (fd, header, 12, 0, state);

	if (status == P11_RPC_OK) {
		status = write_at (fd, options->data, options->len,
		                   12, state);
	}

	if (status == P11_RPC_OK) {
		status = write_at (fd, buffer->data, buffer->len,
		                   12 + options->len, state);
	}

	/* All done */
	if (status == P11_RPC_OK)
		*state = 0;

	return status;
}

static void
rpc_socket_set_read_code_inlock (rpc_socket *sock,
                                 int code)
{
        sock->read_code = code;
#ifdef OS_UNIX
        p11_cond_broadcast (&sock->read_code_cond);
#endif
}

#ifdef OS_UNIX
static void
rpc_socket_wait_for_read_code_change_inlock (rpc_socket *sock)
{
        p11_cond_wait (&sock->read_code_cond, &sock->read_lock);
}
#endif

static int
rpc_socket_read (rpc_socket *sock,
                 int *code,
                 p11_buffer *buffer)
{
	CK_RV ret = CKR_DEVICE_ERROR;
	unsigned char header[12];
	unsigned char dummy;
#ifdef OS_WIN32
	HANDLE handle;
	DWORD mode;
#endif

	assert (code != NULL);
	assert (buffer != NULL);

	/*
	 * We are not in the main socket lock here, but the socket
	 * is referenced, and won't go away
	 */

	p11_mutex_lock (&sock->read_lock);

	if (!sock->read_creds) {
		if (read_all (sock->read_fd, &dummy, 1) != 1) {
			p11_mutex_unlock (&sock->read_lock);
			return CKR_DEVICE_ERROR;
		}
		sock->read_creds = true;
	}

	for (;;) {
		/* No message header has been read yet? ... read one in */
		if (sock->read_code == 0) {
			if (!read_all (sock->read_fd, header, 12))
				break;

			/* Decode and check the message header */
			rpc_socket_set_read_code_inlock (sock, p11_rpc_buffer_decode_uint32 (header));
			sock->read_olen = p11_rpc_buffer_decode_uint32 (header + 4);
			sock->read_dlen = p11_rpc_buffer_decode_uint32 (header + 8);
			if (sock->read_code == 0) {
				p11_message (_("received invalid rpc header values: perhaps wrong protocol"));
				break;
			}
		}

		/* If it's our header (or caller doesn't care), then yay! */
		if (*code == -1 || sock->read_code == *code) {

			/* We ignore the options, so read into the same as buffer */
			if (!p11_buffer_reset (buffer, sock->read_olen) ||
			    !p11_buffer_reset (buffer, sock->read_dlen)) {
				warn_if_reached ();
				break;
			}

			/* Read in the the options first, and then data */
			if (!read_all (sock->read_fd, buffer->data, sock->read_olen) ||
			    !read_all (sock->read_fd, buffer->data, sock->read_dlen))
				break;

			buffer->len = sock->read_dlen;
			*code = sock->read_code;

			/* Yay, we got our data, off we go */
                        rpc_socket_set_read_code_inlock (sock, 0);
			sock->read_olen = 0;
			sock->read_dlen = 0;
			ret = CKR_OK;
			break;
		}

		/* Give another thread the chance to read data for this header */
		if (sock->read_code != 0) {
			p11_debug ("received header in wrong thread");

#ifdef OS_UNIX
                        rpc_socket_wait_for_read_code_change_inlock (sock);
#endif
#ifdef OS_WIN32
			/* Used as a simple wait */
			p11_mutex_unlock (&sock->read_lock);
			handle = (HANDLE) _get_osfhandle (sock->read_fd);
			if (!ReadFile (handle, NULL, 0, &mode, NULL))
				p11_message (_("couldn't use select to wait on rpc pipe"));
			p11_mutex_lock (&sock->read_lock);
#endif
		}
	}

	p11_mutex_unlock (&sock->read_lock);
	return ret;
}

static p11_rpc_status
read_at (int fd,
         unsigned char *data,
         size_t len,
         size_t offset,
         size_t *at)
{
	p11_rpc_status status;
	int errn;
	ssize_t num;
	size_t from;

	assert (*at >= offset);

	if (*at >= offset + len)
		return P11_RPC_OK;

	from = *at - offset;
	assert (from < len);

	num = read (fd, data + from, len - from);
	errn = errno;

	/* Update state */
	if (num > 0)
		*at += num;

	/* Completely read out this block */
	if (num == len - from) {
		p11_debug ("ok: read block of %d", (int)num);
		status = P11_RPC_OK;

	/* Partially read out this block */
	} else if (num > 0) {
		p11_debug ("again: partial read of %d", (int)num);
		status = P11_RPC_AGAIN;

	/* End of file, valid if at offset zero */
	} else if (num == 0) {
		if (offset == 0) {
			p11_debug ("eof: read zero bytes");
			status = P11_RPC_EOF;
		} else {
			p11_debug ("error: early truncate");
			errn = EPROTO;
			status = P11_RPC_ERROR;
		}

	/* Didn't read out block due to transient issue */
	} else if (errn == EINTR || errn == EAGAIN || errn == EWOULDBLOCK) {
		p11_debug ("again: due to %d", errn);
		status = P11_RPC_AGAIN;

	/* Failure */
	} else {
		p11_debug ("error: due to %d", errn);
		status = P11_RPC_ERROR;
	}

	errno = errn;
	return status;
}

p11_rpc_status
p11_rpc_transport_read (int fd,
                        size_t *state,
                        int *call_code,
                        p11_buffer *options,
                        p11_buffer *buffer)
{
	unsigned char *header;
	p11_rpc_status status;
	size_t len;

	assert (state != NULL);
	assert (call_code != NULL);
	assert (options != NULL);
	assert (buffer != NULL);

	/* Reading the header, we read it into @buffer */
	if (*state < 12) {
		if (!p11_buffer_reset (buffer, 12))
			return_val_if_reached (P11_RPC_ERROR);
		status = read_at (fd, buffer->data, 12, 0, state);
		if (status != P11_RPC_OK)
			return status;

		/* Parse out the header */
		header = buffer->data;
		*call_code = p11_rpc_buffer_decode_uint32 (header);
		len = p11_rpc_buffer_decode_uint32 (header + 4);
		if (!p11_buffer_reset (options, len))
			return_val_if_reached (P11_RPC_ERROR);
		options->len = len;
		len = p11_rpc_buffer_decode_uint32 (header + 8);
		if (!p11_buffer_reset (buffer, len))
			return_val_if_reached (P11_RPC_ERROR);
		buffer->len = len;
	}

	/* At this point options has a valid len field */
	status = read_at (fd, options->data, options->len, 12, state);
	if (status == P11_RPC_OK) {
		status = read_at (fd, buffer->data, buffer->len,
		                  12 + options->len, state);
	}

	if (status == P11_RPC_OK)
		*state = 0;

	return status;
}

struct _p11_rpc_transport {
	p11_rpc_client_vtable vtable;
	p11_destroyer destroyer;
	rpc_socket *socket;
	p11_buffer options;
};

static void
rpc_transport_disconnect (p11_rpc_client_vtable *vtable,
                          void *init_reserved)
{
	p11_rpc_transport *rpc = (p11_rpc_transport *)vtable;

	if (rpc->socket) {
		rpc_socket_close (rpc->socket);
		rpc_socket_unref (rpc->socket);
		rpc->socket = NULL;
	}
}

static bool
rpc_transport_init (p11_rpc_transport *rpc,
                    const char *module_name,
                    p11_destroyer destroyer)
{
	rpc->destroyer = destroyer;

	p11_buffer_init_null (&rpc->options, 0);
	p11_buffer_add (&rpc->options, module_name, -1);
	return_val_if_fail (p11_buffer_ok (&rpc->options), false);

	return true;
}

static void
rpc_transport_uninit (p11_rpc_transport *rpc)
{
	p11_buffer_uninit (&rpc->options);
}

static CK_RV
rpc_transport_buffer (p11_rpc_client_vtable *vtable,
                      p11_buffer *request,
                      p11_buffer *response)
{
	p11_rpc_transport *rpc = (p11_rpc_transport *)vtable;
	CK_RV rv = CKR_OK;
	rpc_socket *sock;
	int call_code;

	assert (rpc != NULL);
	assert (request != NULL);
	assert (response != NULL);

	sock = rpc->socket;
	assert (sock != NULL);

	p11_mutex_lock (&sock->write_lock);
	assert (sock->refs > 0);
	sock->refs++;

	/* Get the next socket reply code */
	call_code = sock->last_code++;

	if (sock->read_fd == -1)
		rv = CKR_DEVICE_ERROR;
#ifdef OS_WIN32
	if (sock->write_fd == -1)
		rv = CKR_DEVICE_ERROR;
#endif
	if (rv == CKR_OK)
		rv = rpc_socket_write_inlock (sock, call_code, &rpc->options, request);

	/* 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);
	}

	if (rv != CKR_OK && sock->read_fd != -1) {
		p11_message (_("closing socket due to protocol failure"));
		close (sock->read_fd);
		sock->read_fd = -1;
	}
#ifdef OS_WIN32
	if (rv != CKR_OK && sock->write_fd != -1) {
		p11_message (_("closing socket due to protocol failure"));
		close (sock->write_fd);
		sock->write_fd = -1;
	}
#endif

	sock->refs--;
	assert (sock->refs > 0);
	p11_mutex_unlock (&sock->write_lock);

	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;
}

#endif /* OS_UNIX */

#ifdef OS_WIN32

typedef struct {
	p11_rpc_transport base;
	p11_array *argv;
	HANDLE pid;
} rpc_exec;

static void
rpc_exec_wait_or_terminate (HANDLE pid)
{
	DWORD status;
	int ret;
	int i;


	for (i = 0; i < 3 * 1000; i += 100) {
		ret = WaitForSingleObject (pid, 10000);
		if (ret == WAIT_OBJECT_0)
			break;
	}

	if (ret != WAIT_OBJECT_0) {
		p11_message (_("process %p did not exit, terminating"), pid);
		if (!TerminateProcess (pid, SIGTERM))
			p11_message (_("couldn't terminate process %p"), pid);
		ret = WaitForSingleObject (pid, 0);
	}

	if (ret != WAIT_OBJECT_0) {
		p11_message (_("failed to wait for executed child: %p"), pid);
		status = 0;
	} else if (!GetExitCodeProcess (pid, &status)) {
		p11_message (_("failed to get the exit status of %p"), pid);
	} else if (status == 0) {
		p11_debug ("process %p exited with status 0", pid);
	} else {
		p11_message (_("process %p exited with status %lu"), pid, status);
	}

	CloseHandle (pid);
}

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 != INVALID_HANDLE_VALUE)
		rpc_exec_wait_or_terminate (rex->pid);
	rex->pid = INVALID_HANDLE_VALUE;

	/* Do the common disconnect stuff */
	rpc_transport_disconnect (vtable, fini_reserved);
}

static int
set_cloexec_on_fd (int fd)
{
	HANDLE handle;

	handle = (HANDLE) _get_osfhandle (fd);
	if (!SetHandleInformation (handle, HANDLE_FLAG_INHERIT, 0))
		return -1;

	return 0;
}

static CK_RV
rpc_exec_connect (p11_rpc_client_vtable *vtable,
                  void *init_reserved)
{
	rpc_exec *rex = (rpc_exec *)vtable;
	intptr_t pid = -1;
	int pw[2] = { -1, -1 }, pr[2] = { -1, -1 };
	int fds[2] = { -1, -1 };
	CK_RV rv = CKR_OK;

	p11_debug ("executing rpc transport: %s", (char *)rex->argv->elem[0]);

	setvbuf (stdout, NULL, _IONBF, 0 );

	if (_pipe (pw, 256, _O_BINARY) == -1 ||
	    set_cloexec_on_fd (pw[1]) == -1) {
		p11_message_err (errno, _("failed to create pipe for remote"));
		rv = CKR_DEVICE_ERROR;
		goto out;
	}

	if (_pipe (pr, 256, _O_BINARY) == -1 ||
	    set_cloexec_on_fd (pr[0]) == -1) {
		p11_message_err (errno, _("failed to create pipe for remote"));
		rv = CKR_DEVICE_ERROR;
		goto out;
	}

	/* Save the original stdin and stdout */
	fds[0] = dup (STDIN_FILENO);
	if (fds[0] == -1) {
		p11_message_err (errno, _("failed to duplicate stdin"));
		rv = CKR_DEVICE_ERROR;
		goto out;
	}

	fds[1] = dup (STDOUT_FILENO);
	if (fds[1] == -1) {
		p11_message_err (errno, _("failed to duplicate stdout"));
		rv = CKR_DEVICE_ERROR;
		goto out;
	}

	/* Temporarily redirect pipe descriptors to stdin/stdout for child */
	if (dup2 (pw[0], STDIN_FILENO) == -1 ||
	    dup2 (pr[1], STDOUT_FILENO) == -1) {
		p11_message_err (errno, _("failed to duplicate child end of pipe"));
		rv = CKR_DEVICE_ERROR;
		goto out;
	}

	pid = _spawnv (P_NOWAIT,
		       rex->argv->elem[0],
		       (const char * const *)rex->argv->elem);

	if (pid == -1) {
		p11_message_err (errno, _("failed to spawn remote"));
		rv = CKR_DEVICE_ERROR;
		goto out;
	}

	close (pw[0]);
	pw[0] = -1;
	close (pr[1]);
	pr[1] = -1;

	/* Restore the original stdin and stdout */
	if (dup2 (fds[0], STDIN_FILENO) == -1 ||
	    dup2 (fds[1], STDOUT_FILENO) == -1) {
		p11_message_err (errno, _("failed to restore file descriptors"));
		rv = CKR_DEVICE_ERROR;
		goto out;
	}

	close (fds[0]);
	fds[0] = -1;
	close (fds[1]);
	fds[1] = -1;

	rex->pid = (HANDLE) pid;
	rex->base.socket = rpc_socket_new (pr[0]);
	return_val_if_fail (rex->base.socket != NULL, CKR_GENERAL_ERROR);
	rex->base.socket->write_fd = pw[1];

 out:
	if (rv != CKR_OK) {
		if (pid != -1) {
			TerminateProcess ((HANDLE) pid, SIGTERM);
			CloseHandle ((HANDLE) pid);
		}
		if (pw[0] != -1)
			close (pw[0]);
		if (pw[1] != -1)
			close (pw[1]);
		if (pr[0] != -1)
			close (pr[0]);
		if (pr[1] != -1)
			close (pr[1]);
		if (fds[0] != -1)
			close (fds[0]);
		if (fds[1] != -1)
			close (fds[1]);
	}

	return rv;
}

#endif /* OS_WIN32 */

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;
#ifdef OS_WIN32
	rex->pid = INVALID_HANDLE_VALUE;
#endif

	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;
}

#ifdef OS_UNIX

typedef struct {
	p11_rpc_transport base;
	struct sockaddr_un sa;
} rpc_unix;

static CK_RV
rpc_unix_connect (p11_rpc_client_vtable *vtable,
		    void *init_reserved)
{
	rpc_unix *run = (rpc_unix *)vtable;
	int fd;

	fd = socket (AF_UNIX, SOCK_STREAM, 0);
	if (fd < 0) {
		p11_message_err (errno, _("failed to create socket for remote"));
		return CKR_GENERAL_ERROR;
	}

	if (connect (fd, (struct sockaddr *)&run->sa, sizeof (run->sa)) < 0) {
		p11_debug_err (errno, "failed to connect to socket");
		close (fd);
		return CKR_DEVICE_REMOVED;
	}

	run->base.socket = rpc_socket_new (fd);
	return_val_if_fail (run->base.socket != NULL, CKR_GENERAL_ERROR);

	return CKR_OK;
}

static void
rpc_unix_disconnect (p11_rpc_client_vtable *vtable,
                     void *fini_reserved)
{
	rpc_unix *run = (rpc_unix *)vtable;

	if (run->base.socket)
		rpc_socket_close (run->base.socket);

	/* Do the common disconnect stuff */
	rpc_transport_disconnect (vtable, fini_reserved);
}

static void
rpc_unix_free (void *data)
{
	rpc_unix *run = data;
	rpc_unix_disconnect (data, NULL);
	rpc_transport_uninit (&run->base);
	free (run);
}

static p11_rpc_transport *
rpc_unix_init (const char *remote,
	       const char *name)
{
	rpc_unix *run;

	run = calloc (1, sizeof (rpc_unix));
	return_val_if_fail (run != NULL, NULL);

	memset (&run->sa, 0, sizeof (run->sa));
	run->sa.sun_family = AF_UNIX;
	snprintf (run->sa.sun_path, sizeof (run->sa.sun_path), "%s", remote);

	run->base.vtable.connect = rpc_unix_connect;
	run->base.vtable.disconnect = rpc_unix_disconnect;
	run->base.vtable.transport = rpc_transport_buffer;
	rpc_transport_init (&run->base, name, rpc_unix_free);

	p11_debug ("initialized rpc socket: %s", remote);
	return &run->base;
}

#endif /* OS_UNIX */

#ifdef HAVE_VSOCK
#include <linux/vm_sockets.h>
#include <vsock.h>

typedef struct {
	p11_rpc_transport base;
	struct sockaddr_vm sa;
} rpc_vsock;

static CK_RV
rpc_vsock_connect (p11_rpc_client_vtable *vtable,
		   void *init_reserved)
{
	rpc_vsock *run = (rpc_vsock *)vtable;
	int fd;

	fd = socket (AF_VSOCK, SOCK_STREAM, 0);
	if (fd < 0) {
		p11_message_err (errno, _("failed to create socket for remote"));
		return CKR_GENERAL_ERROR;
	}

	if (connect (fd, (struct sockaddr *)&run->sa, sizeof (run->sa)) < 0) {
		p11_debug_err (errno, "failed to connect to socket");
		close (fd);
		return CKR_DEVICE_REMOVED;
	}

	run->base.socket = rpc_socket_new (fd);
	return_val_if_fail (run->base.socket != NULL, CKR_GENERAL_ERROR);

	return CKR_OK;
}

static void
rpc_vsock_disconnect (p11_rpc_client_vtable *vtable,
                      void *fini_reserved)
{
	rpc_vsock *run = (rpc_vsock *)vtable;

	if (run->base.socket)
		rpc_socket_close (run->base.socket);

	/* Do the common disconnect stuff */
	rpc_transport_disconnect (vtable, fini_reserved);
}

static void
rpc_vsock_free (void *data)
{
	rpc_vsock *run = data;
	rpc_vsock_disconnect (data, NULL);
	rpc_transport_uninit (&run->base);
	free (run);
}

static p11_rpc_transport *
rpc_vsock_init (unsigned int cid,
		unsigned int port,
		const char *name)
{
	rpc_vsock *run;

	run = calloc (1, sizeof (rpc_vsock));
	return_val_if_fail (run != NULL, NULL);

	memset (&run->sa, 0, sizeof (run->sa));
	run->sa.svm_family = AF_VSOCK;
	run->sa.svm_cid = cid;
	run->sa.svm_port = port;

	run->base.vtable.connect = rpc_vsock_connect;
	run->base.vtable.disconnect = rpc_vsock_disconnect;
	run->base.vtable.transport = rpc_transport_buffer;
	rpc_transport_init (&run->base, name, rpc_vsock_free);

	p11_debug ("initialized rpc socket: vsock:cid=%u;port=%u",
		   cid, port);
	return &run->base;
}

#endif /* HAVE_VSOCK */

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);

	/* This is a command we can execute */
	if (remote[0] == '|') {
		rpc = rpc_exec_init (remote + 1, name);

#ifdef OS_UNIX
	} else if (strncmp (remote, "unix:path=/", 11) == 0) {
		/* Only absolute path is supported */
		char *path;

		path = p11_path_decode (remote + 10);
		return_val_if_fail (path != NULL, NULL);
		rpc = rpc_unix_init (path, name);
		free (path);
#endif /* OS_UNIX */
#ifdef HAVE_VSOCK
	} else if (strncmp (remote, "vsock:", 6) == 0) {
		unsigned int cid = 0, port = 0;

		if (!p11_vsock_parse_addr (remote + 6, &cid, &port) ||
		    cid == VMADDR_CID_ANY) {
			p11_message (_("failed to parse vsock address: '%s'"),
				     remote + 6);
			return NULL;
		}

		rpc = rpc_vsock_init (cid, port, name);
#endif /* HAVE_VSOCK */
	} else {
		p11_message (_("remote not supported: %s"), remote);
		return NULL;
	}

	return_val_if_fail (rpc != NULL, 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);
	}
}
