Skip to content
Snippets Groups Projects
gck-rpc-module.c 60.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • Corentin Chary's avatar
    Corentin Chary committed
    /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
    /* gkr-pkcs11-rpc-module.c - a PKCS#11 module which communicates with another process
    
       Copyright (C) 2008, Stefan Walter
    
       The Gnome Keyring Library is free software; you can redistribute it and/or
       modify it under the terms of the GNU Library General Public License as
       published by the Free Software Foundation; either version 2 of the
       License, or (at your option) any later version.
    
       The Gnome Keyring Library is distributed in the hope that it will be useful,
       but WITHOUT ANY WARRANTY; without even the implied warranty of
       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       Library General Public License for more details.
    
       You should have received a copy of the GNU Library General Public
       License along with the Gnome Library; see the file COPYING.LIB.  If not,
       write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
       Boston, MA 02111-1307, USA.
    
       Author: Stef Walter <stef@memberwebs.com>
    */
    
    #include "config.h"
    
    #include "gck-rpc-layer.h"
    #include "gck-rpc-private.h"
    
    #include "pkcs11/pkcs11.h"
    
    #include <sys/types.h>
    #include <sys/param.h>
    
    Corentin Chary's avatar
    Corentin Chary committed
    #ifdef __MINGW32__
    # include <winsock2.h>
    #else
    # include <sys/socket.h>
    # include <sys/un.h>
    
    Corentin Chary's avatar
    Corentin Chary committed
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <netinet/tcp.h>
    
    Corentin Chary's avatar
    Corentin Chary committed
    #endif
    
    Corentin Chary's avatar
    Corentin Chary committed
    
    #include <stdlib.h>
    #include <limits.h>
    #include <ctype.h>
    #include <stdint.h>
    #include <pthread.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <stdarg.h>
    #include <stdio.h>
    #include <string.h>
    
    /* -------------------------------------------------------------------
     * GLOBALS / DEFINES
     */
    
    /* Various mutexes */
    static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
    
    /* Whether we've been initialized, and on what process id it happened */
    static int pkcs11_initialized = 0;
    static pid_t pkcs11_initialized_pid = 0;
    
    static uint64_t pkcs11_app_id = 0;
    
    Corentin Chary's avatar
    Corentin Chary committed
    
    /* The socket to connect to */
    static char pkcs11_socket_path[MAXPATHLEN] = { 0, };
    
    /* The error used by us when parsing of rpc message fails */
    #define PARSE_ERROR   CKR_DEVICE_ERROR
    
    /* -----------------------------------------------------------------------------
     * LOGGING and DEBUGGING
     */
    #define DEBUG_OUTPUT 0
    #if DEBUG_OUTPUT
    #define debug(x) gck_rpc_debug x
    #else
    #define debug(x)
    #endif
    #define warning(x) gck_rpc_warn x
    
    #define return_val_if_fail(x, v) \
    	if (!(x)) { gck_rpc_warn ("'%s' not true at %s", #x, __func__); return v; }
    
    
    void gck_rpc_log(const char *msg, ...)
    
    Corentin Chary's avatar
    Corentin Chary committed
    {
    
    	va_list ap;
    
    	va_start(ap, msg);
    	vfprintf(stderr, msg, ap);
    
    Corentin Chary's avatar
    Corentin Chary committed
    	fprintf(stderr, "\n");
    
    Corentin Chary's avatar
    Corentin Chary committed
    }
    
    /* -----------------------------------------------------------------------------
     * MODULE ARGUMENTS
     */
    
    static void parse_argument(char *arg)
    {
    	char *value;
    
    	value = arg + strcspn(arg, ":=");
    	if (!*value)
    		value = NULL;
    	else
    		*(value++) = 0;
    
    	/* Setup the socket path from the arguments */
    	if (strcmp(arg, "socket") == 0)
    		snprintf(pkcs11_socket_path, sizeof(pkcs11_socket_path), "%s",
    			 value);
    	else
    		warning(("unrecognized argument: %s", arg));
    }
    
    static void parse_arguments(const char *string)
    {
    	char quote = '\0';
    	char *src, *dup, *at, *arg;
    
    	if (!string)
    		return;
    
    	src = dup = strdup(string);
    	if (!dup) {
    		warning(("couldn't allocate memory for argument string"));
    		return;
    	}
    
    
    	for (arg = at = src; *src; src++) {
    
    Corentin Chary's avatar
    Corentin Chary committed
    
    		/* Matching quote */
    		if (quote == *src) {
    			quote = '\0';
    
    			/* Inside of quotes */
    		} else if (quote != '\0') {
    			if (*src == '\\') {
    				*at++ = *src++;
    				if (!*src) {
    					warning(("couldn't parse argument string: %s", string));
    					goto done;
    				}
    				if (*src != quote)
    					*at++ = '\\';
    			}
    			*at++ = *src;
    
    			/* Space, not inside of quotes */
    		} else if (isspace(*src)) {
    			*at = 0;
    			parse_argument(arg);
    			arg = at;
    
    			/* Other character outside of quotes */
    		} else {
    			switch (*src) {
    			case '\'':
    			case '"':
    				quote = *src;
    				break;
    			case '\\':
    				*at++ = *src++;
    				if (!*src) {
    					warning(("couldn't parse argument string: %s", string));
    					goto done;
    				}
    				/* fall through */
    			default:
    				*at++ = *src;
    				break;
    			}
    		}
    	}
    
    	if (at != arg)
    		parse_argument(arg);
    
    done:
    	free(dup);
    }
    
    /* -----------------------------------------------------------------------------
     * CALL SESSION
     */
    
    enum CallStatus {
    	CALL_INVALID,
    	CALL_READY,
    	CALL_PREP,
    	CALL_TRANSIT,
    	CALL_PARSE
    };
    
    typedef struct _CallState {
    	int socket;		/* The connection we're sending on */
    	GckRpcMessage *req;	/* The current request */
    	GckRpcMessage *resp;	/* The current response */
    	int call_status;
    	struct _CallState *next;	/* For pooling of completed sockets */
    } CallState;
    
    /* Maximum number of idle calls */
    #define MAX_CALL_STATE_POOL 8
    
    /* All call unused call states are in this list */
    static CallState *call_state_pool = NULL;
    static unsigned int n_call_state_pool = 0;
    
    /* Mutex to protect above call state list */
    static pthread_mutex_t call_state_mutex = PTHREAD_MUTEX_INITIALIZER;
    
    /* Allocator for call session buffers */
    static void *call_allocator(void *p, size_t sz)
    {
    	void *res = realloc(p, (size_t) sz);
    	if (!res && sz)
    		warning(("memory allocation of %lu bytes failed", sz));
    	return res;
    }
    
    
    static void call_disconnect(CallState * cs)
    {
    	assert(cs);
    
    	if (cs->socket != -1) {
    		debug(("disconnected socket"));
    		close(cs->socket);
    		cs->socket = -1;
    	}
    }
    
    /* Write all data to session socket.  */
    static CK_RV call_write(CallState * cs, unsigned char *data, size_t len)
    {
    	int fd, r;
    
    	assert(cs);
    	assert(data);
    	assert(len > 0);
    
    	while (len > 0) {
    
    		fd = cs->socket;
    		if (fd == -1) {
    			warning(("couldn't send data: socket has been closed"));
    			return CKR_DEVICE_ERROR;
    		}
    
    
    Corentin Chary's avatar
    Corentin Chary committed
                    r = send(fd, (void *)data, len, 0);
    
    
    		if (r == -1) {
    			if (errno == EPIPE) {
    				warning(("couldn't send data: daemon closed connection"));
    				call_disconnect(cs);
    				return CKR_DEVICE_ERROR;
    			} else if (errno != EAGAIN && errno != EINTR) {
    				warning(("couldn't send data: %s",
    					 strerror(errno)));
    				return CKR_DEVICE_ERROR;
    			}
    		} else {
    			debug(("wrote %d bytes", r));
    			data += r;
    			len -= r;
    		}
    	}
    
    	return CKR_OK;
    }
    
    /* Read a certain amount of data from session socket. */
    static CK_RV call_read(CallState * cs, unsigned char *data, size_t len)
    {
    	int fd, r;
    
    	assert(cs);
    	assert(data);
    	assert(len > 0);
    
    	while (len > 0) {
    
    		fd = cs->socket;
    		if (fd == -1) {
    			warning(("couldn't receive data: session socket has been closed"));
    			return CKR_DEVICE_ERROR;
    		}
    
    
    Corentin Chary's avatar
    Corentin Chary committed
                    r = recv(fd, (void *)data, len, 0);
    
    
    		if (r == 0) {
    			warning(("couldn't receive data: daemon closed connection"));
    			call_disconnect(cs);
    			return CKR_DEVICE_ERROR;
    		} else if (r == -1) {
    			if (errno != EAGAIN && errno != EINTR) {
    				warning(("couldn't receive data: %s",
    					 strerror(errno)));
    				return CKR_DEVICE_ERROR;
    			}
    		} else {
    			debug(("read %d bytes", r));
    			data += r;
    			len -= r;
    		}
    	}
    
    	return CKR_OK;
    }
    
    
    static int _connect_to_host_port(char *host, char *port)
    {
    	char hoststr[NI_MAXHOST], portstr[NI_MAXSERV], hostport[NI_MAXHOST + NI_MAXSERV + 1];
    	struct addrinfo *ai, *first, hints;
    	int res, sock, one = 1;
    
    	memset(&hints, 0, sizeof(struct addrinfo));
    	hints.ai_family = AF_UNSPEC;		/* Either IPv4 or IPv6 */
    	hints.ai_socktype = SOCK_STREAM;	/* Only stream oriented sockets */
    
    	if ((res = getaddrinfo(host, port, &hints, &ai)) < 0) {
    		gck_rpc_warn("couldn't resolve host '%.100s' or service '%.100s' : %.100s\n",
    			     host, port, gai_strerror(res));
    		return -1;
    	}
    
    	sock = -1;
    	first = ai;
    
    	/* Loop through the sockets returned and see if we can find one that accepts
    	 * our options and connect()
    	 */
    	while (ai) {
    		if ((res = getnameinfo(ai->ai_addr, ai->ai_addrlen,
    				       hoststr, sizeof(hoststr), portstr, sizeof(portstr),
    				       NI_NUMERICHOST | NI_NUMERICSERV)) != 0) {
    			gck_rpc_warn("couldn't call getnameinfo on pkcs11 socket (%.100s %.100s): %.100s",
    				     host, port, gai_strerror(res));
    			sock = -1;
    			continue;
    		}
    
    		snprintf(hostport, sizeof(hostport),
    			 (ai->ai_family == AF_INET6) ? "[%s]:%s" : "%s:%s", hoststr, portstr);
    
    		sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
    
    		if (sock >= 0) {
    			if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
    				       (char *)&one, sizeof (one)) == -1) {
    				gck_rpc_warn("couldn't set pkcs11 "
    					     "socket protocol options (%.100s): %.100s",
    					     hostport, strerror (errno));
    				goto next;
    			}
    
    #ifndef __MINGW32__
    			/* close on exec */
    			if (fcntl(sock, F_SETFD, 1) == -1) {
    				gck_rpc_warn("couldn't secure socket (%.100s): %.100s",
    					     hostport, strerror(errno));
    				goto next;
    			}
    #endif
    
    			if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0) {
    				close(sock);
    				warning(("couldn't connect (%.100s): %s",
    					 hostport, strerror(errno)));
    				goto next;
    			}
    
    
    		next:
    			close(sock);
    			sock = -1;
    		}
    		ai = ai->ai_next;
    	}
    
    	if (sock < 0) {
    		gck_rpc_warn("couldn't create pkcs11 socket (%.100s): %.100s\n",
    			     pkcs11_socket_path, strerror(errno));
    		sock = -1;
    		goto out;
    	}
    
     out:
    	freeaddrinfo(first);
    
    	return sock;
    }
    
    
    Corentin Chary's avatar
    Corentin Chary committed
    static CK_RV call_connect(CallState * cs)
    {
    	struct sockaddr_un addr;
    	int sock;
    
    	assert(cs);
    	assert(cs->socket == -1);
    	assert(cs->call_status == CALL_INVALID);
    	assert(pkcs11_socket_path[0]);
    
    	debug(("connecting to: %s", pkcs11_socket_path));
    
    	memset(&addr, 0, sizeof(addr));
    
    	if (!strncmp("tcp://", pkcs11_socket_path, 6)) {
    
    Corentin Chary's avatar
    Corentin Chary committed
    
    
    		if (! gck_rpc_parse_host_port(pkcs11_socket_path + 6, &host, &port)) {
    			gck_rpc_warn("failed parsing pkcs11 socket : %s",
    
    Corentin Chary's avatar
    Corentin Chary committed
    				     pkcs11_socket_path);
    
    			return CKR_DEVICE_ERROR;
    
    Corentin Chary's avatar
    Corentin Chary committed
    		}
    
    
    		if ((sock = _connect_to_host_port(host, port)) == -1) {
    			free(host);
    
    			return CKR_DEVICE_ERROR;
    
    Corentin Chary's avatar
    Corentin Chary committed
    		}
    
    
    Corentin Chary's avatar
    Corentin Chary committed
    	} else {
    		addr.sun_family = AF_UNIX;
    		strncpy(addr.sun_path, pkcs11_socket_path,
    			sizeof(addr.sun_path));
    
    		sock = socket(AF_UNIX, SOCK_STREAM, 0);
    		if (sock < 0) {
    			warning(("couldn't open socket: %s", strerror(errno)));
    			return CKR_DEVICE_ERROR;
    		}
    
    Corentin Chary's avatar
    Corentin Chary committed
    
    
    Corentin Chary's avatar
    Corentin Chary committed
    #ifndef __MINGW32__
    
    		/* close on exec */
    		if (fcntl(sock, F_SETFD, 1) == -1) {
    			close(sock);
    			warning(("couldn't secure socket: %s", strerror(errno)));
    			return CKR_DEVICE_ERROR;
    		}
    
    Corentin Chary's avatar
    Corentin Chary committed
    #endif
    
    Corentin Chary's avatar
    Corentin Chary committed
    
    
    		if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
    			close(sock);
    			warning(("couldn't connect to: %s: %s", pkcs11_socket_path,
    				 strerror(errno)));
    			return CKR_DEVICE_ERROR;
    		}
    
    Corentin Chary's avatar
    Corentin Chary committed
    	}
    
    	cs->socket = sock;
    	cs->call_status = CALL_READY;
    	debug(("connected socket"));
    
    
    	return call_write(cs, (unsigned char*)&pkcs11_app_id,
    			  sizeof(pkcs11_app_id));
    
    Corentin Chary's avatar
    Corentin Chary committed
    }
    
    static void call_destroy(void *value)
    {
    	CallState *cs = value;
    
    	if (value) {
    		call_disconnect(cs);
    		assert(cs->socket == -1);
    
    		gck_rpc_message_free(cs->req);
    		gck_rpc_message_free(cs->resp);
    
    		free(cs);
    
    		debug(("destroyed state"));
    	}
    }
    
    static CK_RV call_lookup(CallState ** ret)
    {
    	CallState *cs = NULL;
    	CK_RV rv;
    
    	assert(ret);
    
    	pthread_mutex_lock(&call_state_mutex);
    
    	/* Pop one from the pool if possible */
    	if (call_state_pool != NULL) {
    		cs = call_state_pool;
    		call_state_pool = cs->next;
    		cs->next = NULL;
    		assert(n_call_state_pool > 0);
    		--n_call_state_pool;
    	}
    
    	pthread_mutex_unlock(&call_state_mutex);
    
    	if (cs == NULL) {
    		cs = calloc(1, sizeof(CallState));
    		if (cs == NULL)
    			return CKR_HOST_MEMORY;
    		cs->socket = -1;
    		cs->call_status = CALL_INVALID;
    
    		/* Try to connect the call */
    		rv = call_connect(cs);
    		if (rv != CKR_OK) {
    			free(cs);
    			return rv;
    		}
    	}
    
    	assert(cs->call_status == CALL_READY);
    	assert(cs->socket != -1);
    	assert(cs->next == NULL);
    	*ret = cs;
    	return CKR_OK;
    }
    
    /* Perform the initial setup for a new call. */
    static CK_RV call_prepare(CallState * cs, int call_id)
    {
    	assert(cs);
    	assert(cs->call_status == CALL_READY);
    
    	/* Allocate a new request if we've lost the old one */
    	if (!cs->req) {
    		cs->req = gck_rpc_message_new(call_allocator);
    		if (!cs->req) {
    			warning(("cannot allocate request buffer: out of memory"));
    			return CKR_HOST_MEMORY;
    		}
    	}
    
    	/* Put in the Call ID and signature */
    	gck_rpc_message_reset(cs->req);
    	if (!gck_rpc_message_prep(cs->req, call_id, GCK_RPC_REQUEST))
    		return CKR_HOST_MEMORY;
    
    	debug(("prepared call: %d", call_id));
    
    	/* Ready to fill in arguments */
    	cs->call_status = CALL_PREP;
    	return CKR_OK;
    }
    
    /*
     * Used by call_session_do_call() to actually send the message to the daemon.
     * Note how we unlock and relock the session during the call.
     */
    static CK_RV call_send_recv(CallState * cs)
    {
    	GckRpcMessage *req, *resp;
    	unsigned char buf[4];
    	uint32_t len;
    	CK_RV ret;
    
    	assert(cs);
    	assert(cs->req);
    	assert(cs->call_status == CALL_PREP);
    
    	cs->call_status = CALL_TRANSIT;
    
    	/* Setup the response buffer properly */
    	if (!cs->resp) {
    		/* TODO: Do secrets or passwords ever flow through here? */
    		cs->resp = gck_rpc_message_new(call_allocator);
    		if (!cs->resp) {
    			warning(("couldn't allocate response buffer: out of memory"));
    			return CKR_HOST_MEMORY;
    		}
    	}
    	gck_rpc_message_reset(cs->resp);
    
    	/*
    	 * Now as an additional check to make sure nothing nasty will
    	 * happen while we are unlocked, we remove the request and
    	 * response from the session during the action.
    	 */
    	req = cs->req;
    	resp = cs->resp;
    	cs->req = cs->resp = NULL;
    
    	/* Send the number of bytes, and then the data */
    
    	egg_buffer_encode_uint32(buf, req->buffer.len);
    	ret = call_write(cs, buf, 4);
    	if (ret != CKR_OK)
    		goto cleanup;
    	ret = call_write(cs, req->buffer.buf, req->buffer.len);
    	if (ret != CKR_OK)
    		goto cleanup;
    
    	/* Now read out the number of bytes, and then the data */
    	ret = call_read(cs, buf, 4);
    	if (ret != CKR_OK)
    		goto cleanup;
    
    	len = egg_buffer_decode_uint32(buf);
    	if (!egg_buffer_reserve(&resp->buffer, len + resp->buffer.len)) {
    		warning(("couldn't allocate %u byte response area: out of memory", len));
    		ret = CKR_HOST_MEMORY;
    		goto cleanup;
    	}
    
    	ret = call_read(cs, resp->buffer.buf, len);
    	if (ret != CKR_OK)
    		goto cleanup;
    
    	egg_buffer_add_empty(&resp->buffer, len);
    	if (!gck_rpc_message_parse(resp, GCK_RPC_RESPONSE))
    		goto cleanup;
    
    	debug(("received response from daemon"));
    
    cleanup:
    	/* Make sure nobody else used this thread while unlocked */
    	assert(cs->call_status == CALL_TRANSIT);
    	assert(cs->resp == NULL);
    	cs->resp = resp;
    	assert(cs->req == NULL);
    	cs->req = req;
    
    	return ret;
    }
    
    /*
     * At this point the request is ready. So we validate it, and we send it to
     * the daemon for a response.
     */
    static CK_RV call_run(CallState * cs)
    {
    	CK_RV ret = CKR_OK;
    	CK_ULONG ckerr;
    
    	assert(cs);
    	assert(cs->req);
    	assert(cs->call_status == CALL_PREP);
    	assert(cs->socket != -1);
    
    	/* Did building the call fail? */
    	if (gck_rpc_message_buffer_error(cs->req)) {
    		warning(("couldn't allocate request area: out of memory"));
    		return CKR_HOST_MEMORY;
    	}
    
    	/* Make sure that the signature is valid */
    	assert(gck_rpc_message_is_verified(cs->req));
    
    	/* Do the dialog with daemon */
    	ret = call_send_recv(cs);
    
    	cs->call_status = CALL_PARSE;
    
    	if (ret != CKR_OK)
    		return ret;
    
    	/* If it's an error code then return it */
    	if (cs->resp->call_id == GCK_RPC_CALL_ERROR) {
    
    		if (!gck_rpc_message_read_ulong(cs->resp, &ckerr)) {
    			warning(("invalid error response from gnome-keyring-daemon: too short"));
    			return CKR_DEVICE_ERROR;
    		}
    
    		if (ckerr <= CKR_OK) {
    			warning(("invalid error response from gnome-keyring-daemon: bad error code"));
    			return CKR_DEVICE_ERROR;
    		}
    
    		/* An error code from the daemon */
    		return (CK_RV) ckerr;
    	}
    
    	/* Make sure daemon answered the right call */
    	if (cs->req->call_id != cs->resp->call_id) {
    		warning(("invalid response from gnome-keyring-daemon: call mismatch"));
    		return CKR_DEVICE_ERROR;
    	}
    
    	assert(!gck_rpc_message_buffer_error(cs->resp));
    	debug(("parsing response values"));
    
    	return CKR_OK;
    }
    
    static CK_RV call_done(CallState * cs, CK_RV ret)
    {
    	assert(cs);
    	assert(cs->call_status > CALL_INVALID);
    
    	if (cs->call_status == CALL_PARSE && cs->req && cs->resp) {
    
    		/* Check for parsing errors that were not caught elsewhere */
    		if (ret == CKR_OK) {
    
    			if (gck_rpc_message_buffer_error(cs->resp)) {
    				warning(("invalid response from gnome-keyring-daemon: bad argument data"));
    				ret = CKR_GENERAL_ERROR;
    			} else {
    				/* Double check that the signature matched our decoding */
    				assert(gck_rpc_message_is_verified(cs->resp));
    			}
    		}
    	}
    
    	/* Certain error codes cause us to discard the conenction */
    	if (ret != CKR_DEVICE_ERROR && ret != CKR_DEVICE_REMOVED
    	    && cs->socket != -1) {
    
    		/* Try and stash it away for later use */
    		pthread_mutex_lock(&call_state_mutex);
    
    		if (n_call_state_pool < MAX_CALL_STATE_POOL) {
    			cs->call_status = CALL_READY;
    			assert(cs->next == NULL);
    			cs->next = call_state_pool;
    			call_state_pool = cs;
    			++n_call_state_pool;
    			cs = NULL;
    		}
    
    		pthread_mutex_unlock(&call_state_mutex);
    	}
    
    	if (cs != NULL)
    		call_destroy(cs);
    
    	return ret;
    }
    
    /* -----------------------------------------------------------------------------
     * MODULE SPECIFIC PROTOCOL CODE
     */
    
    static CK_RV
    proto_read_attribute_array(GckRpcMessage * msg, CK_ATTRIBUTE_PTR arr,
    			   CK_ULONG len)
    {
    	uint32_t i, num, value, type;
    	CK_ATTRIBUTE_PTR attr;
    	const unsigned char *attrval;
    	size_t attrlen;
    	unsigned char validity;
    	CK_RV ret;
    
    
    	/* Removed assertion. len == 0 is valid for some ret's,
    	 * see proto_write_attribute_array().
    	 * assert(len);
    	 */
    
    Corentin Chary's avatar
    Corentin Chary committed
    	assert(msg);
    
    	/* Make sure this is in the right order */
    	assert(!msg->signature || gck_rpc_message_verify_part(msg, "aA"));
    
    	/* Get the number of items. We need this value to be correct */
    	if (!egg_buffer_get_uint32
    	    (&msg->buffer, msg->parsed, &msg->parsed, &num))
    		return PARSE_ERROR;
    
    	if (len != num) {
    
    		/*
    		 * This should never happen in normal operation. It denotes a goof up
    		 * on the other side of our RPC. We should be indicating the exact number
    		 * of attributes to the other side. And it should respond with the same
    		 * number.
    		 */
    
    		warning(("received an attribute array with wrong number of attributes"));
    		return PARSE_ERROR;
    	}
    
    	ret = CKR_OK;
    
    	/* We need to go ahead and read everything in all cases */
    	for (i = 0; i < num; ++i) {
    
    		/* The attribute type */
    		egg_buffer_get_uint32(&msg->buffer, msg->parsed,
    				      &msg->parsed, &type);
    
    		/* Attribute validity */
    		egg_buffer_get_byte(&msg->buffer, msg->parsed,
    				    &msg->parsed, &validity);
    
    		/* And the data itself */
    		if (validity) {
    			if (egg_buffer_get_uint32
    			    (&msg->buffer, msg->parsed, &msg->parsed, &value)
    			    && egg_buffer_get_byte_array(&msg->buffer,
    							 msg->parsed,
    							 &msg->parsed, &attrval,
    							 &attrlen)) {
    				if (attrval && value != attrlen) {
    					warning(("attribute length does not match attribute data"));
    					return PARSE_ERROR;
    				}
    				attrlen = value;
    
    			} else {
    				warning(("failed reading byte array"));
    				return PARSE_ERROR;
    
    Corentin Chary's avatar
    Corentin Chary committed
    			}
    		}
    
    		/* Don't act on this data unless no errors */
    		if (egg_buffer_has_error(&msg->buffer))
    			break;
    
    		/* Try and stuff it in the output data */
    		if (arr) {
    			attr = &(arr[i]);
    			if (attr->type != type) {
    				warning(("returned attributes in invalid order"));
    				return PARSE_ERROR;
    			}
    
    			if (validity) {
    				/* Just requesting the attribute size */
    				if (!attr->pValue) {
    					attr->ulValueLen = attrlen;
    
    					/* Wants attribute data, but too small */
    				} else if (attr->ulValueLen < attrlen) {
    					attr->ulValueLen = attrlen;
    					ret = CKR_BUFFER_TOO_SMALL;
    
    					/* Wants attribute data, value is null */
    				} else if (attrval == NULL) {
    					attr->ulValueLen = 0;
    
    					/* Wants attribute data, enough space */
    				} else {
    
    Corentin Chary's avatar
    Corentin Chary committed
    					/* Attribute len is an integer, but
    					 * does not match CK_ULONG size, it's certainly
    					 * a CK_ULONG from a different platform */
    					if (attrlen == sizeof(uint64_t) &&
    
    					    sizeof(CK_ULONG) != sizeof(uint64_t) &&
    					    gck_rpc_has_ulong_parameter(attr->type)) {
    
    Corentin Chary's avatar
    Corentin Chary committed
    						attrlen = sizeof(CK_ULONG);
    						a = *(uint64_t *) attrval;
    						attrval = (unsigned char *)&a;
    					}
    					attr->ulValueLen = attrlen;
    					memcpy(attr->pValue, attrval, attrlen);
    				}
    
    				/* Not a valid attribute */
    			} else {
    				attr->ulValueLen = ((CK_ULONG) - 1);
    			}
    		}
    	}
    
    	if (egg_buffer_has_error(&msg->buffer))
    		return PARSE_ERROR;
    
    	/* Read in the code that goes along with these attributes */
    	if (!gck_rpc_message_read_ulong(msg, &ret))
    		return PARSE_ERROR;
    
    	return ret;
    }
    
    static CK_RV
    proto_read_byte_array(GckRpcMessage * msg, CK_BYTE_PTR arr,
    		      CK_ULONG_PTR len, CK_ULONG max)
    {
    	const unsigned char *val;
    	unsigned char valid;
    	size_t vlen;
    
    	assert(len);
    	assert(msg);
    
    	/* Make sure this is in the right order */
    	assert(!msg->signature || gck_rpc_message_verify_part(msg, "ay"));
    
    	/* A single byte which determines whether valid or not */
    	if (!egg_buffer_get_byte
    	    (&msg->buffer, msg->parsed, &msg->parsed, &valid))
    		return PARSE_ERROR;
    
    	/* If not valid, then just the length is encoded, this can signify CKR_BUFFER_TOO_SMALL */
    	if (!valid) {
    
    Corentin Chary's avatar
    Corentin Chary committed
    		if (!egg_buffer_get_uint32
    		    (&msg->buffer, msg->parsed, &msg->parsed,
    
    Corentin Chary's avatar
    Corentin Chary committed
    			return PARSE_ERROR;
    
    
    Corentin Chary's avatar
    Corentin Chary committed
    
    		if (arr)
    			return CKR_BUFFER_TOO_SMALL;
    		else
    			return CKR_OK;
    	}
    
    	/* Get the actual bytes */
    	if (!egg_buffer_get_byte_array
    	    (&msg->buffer, msg->parsed, &msg->parsed, &val, &vlen))
    		return PARSE_ERROR;
    
    	*len = vlen;
    
    	/* Just asking us for size */
    	if (!arr)
    		return CKR_OK;
    
    	if (max < vlen)
    		return CKR_BUFFER_TOO_SMALL;
    
    	/* Enough space, yay */
    	memcpy(arr, val, vlen);
    	return CKR_OK;
    }
    
    static CK_RV
    proto_read_ulong_array(GckRpcMessage * msg, CK_ULONG_PTR arr,
    		       CK_ULONG_PTR len, CK_ULONG max)
    {
    	uint32_t i, num;
    	uint64_t val;
    	unsigned char valid;
    
    	assert(len);
    	assert(msg);
    
    	/* Make sure this is in the right order */
    	assert(!msg->signature || gck_rpc_message_verify_part(msg, "au"));
    
    	/* A single byte which determines whether valid or not */
    	if (!egg_buffer_get_byte
    	    (&msg->buffer, msg->parsed, &msg->parsed, &valid))
    		return PARSE_ERROR;
    
    	/* Get the number of items. */
    	if (!egg_buffer_get_uint32
    	    (&msg->buffer, msg->parsed, &msg->parsed, &num))
    		return PARSE_ERROR;
    
    	*len = num;
    
    	/* If not valid, then just the length is encoded, this can signify CKR_BUFFER_TOO_SMALL */
    	if (!valid) {
    		if (arr)
    			return CKR_BUFFER_TOO_SMALL;
    		else
    			return CKR_OK;
    	}
    
    	if (max < num)
    		return CKR_BUFFER_TOO_SMALL;
    
    	/* We need to go ahead and read everything in all cases */
    	for (i = 0; i < num; ++i) {
    		egg_buffer_get_uint64(&msg->buffer, msg->parsed, &msg->parsed,
    				      &val);
    		if (arr)
    			arr[i] = (CK_ULONG) val;
    	}
    
    	return egg_buffer_has_error(&msg->buffer) ? PARSE_ERROR : CKR_OK;
    }
    
    static CK_RV proto_write_mechanism(GckRpcMessage * msg, CK_MECHANISM_PTR mech)
    {
    	assert(msg);
    	assert(mech);
    
    	/* Make sure this is in the right order */
    	assert(!msg->signature || gck_rpc_message_verify_part(msg, "M"));
    
    	/* The mechanism type */
    	egg_buffer_add_uint32(&msg->buffer, mech->mechanism);
    
    	/*
    	 * PKCS#11 mechanism parameters are not easy to serialize. They're
    	 * completely different for so many mechanisms, they contain
    	 * pointers to arbitrary memory, and many callers don't initialize
    	 * them completely or properly.
    	 *
    	 * We only support certain mechanisms.
    	 *
    	 * Also callers do yucky things like leaving parts of the structure
    	 * pointing to garbage if they don't think it's going to be used.
    	 */
    
    	if (gck_rpc_mechanism_has_no_parameters(mech->mechanism))
    		egg_buffer_add_byte_array(&msg->buffer, NULL, 0);
    	else if (gck_rpc_mechanism_has_sane_parameters(mech->mechanism))
    		egg_buffer_add_byte_array(&msg->buffer, mech->pParameter,
    					  mech->ulParameterLen);