diff --git a/config.h b/config.h
index 671c0eb88e837098473b7e2bd100f9ab3bf4b27f..88806d5099a1956ce2a80352fd5a37c33b84ecd1 100644
--- a/config.h
+++ b/config.h
@@ -3,6 +3,9 @@
 
 # define DEBUG_OUTPUT 0 // change to 1 to enable debugging
 
+# define PKCS11PROXY_LISTEN_BACKLOG 128
+# define PKCS11PROXY_MAX_SESSION_COUNT 256
+
 #ifdef __MINGW32__
 
 # include <stdint.h>
diff --git a/gck-rpc-daemon-standalone.c b/gck-rpc-daemon-standalone.c
index ae10932b2b5a2817ae2b916b5f23b5c53cecba52..9b89f50c46bd8809553b4271e061f838bc4907be 100644
--- a/gck-rpc-daemon-standalone.c
+++ b/gck-rpc-daemon-standalone.c
@@ -170,6 +170,7 @@ int main(int argc, char *argv[])
 	fd_set read_fds;
 	int sock, ret;
 	CK_RV rv;
+	CK_C_INITIALIZE_ARGS init_args;
 
 
         if (install_syscall_reporter())
@@ -212,7 +213,10 @@ int main(int argc, char *argv[])
 	}
 
 	/* RPC layer expects initialized module */
-	rv = (funcs->C_Initialize) (NULL /* &p11_init_args */);
+	memset(&init_args, 0, sizeof(init_args));
+	init_args.flags = CKF_OS_LOCKING_OK;
+
+	rv = (funcs->C_Initialize) (&init_args);
 	if (rv != CKR_OK) {
 		fprintf(stderr, "couldn't initialize module: %s: 0x%08x\n",
 			argv[1], (int)rv);
diff --git a/gck-rpc-dispatch.c b/gck-rpc-dispatch.c
index a0152f3880d011dc94719e4e01b8c574a109d128..a6f76343986527374e91b6fc46f54b86c34fa0b9 100644
--- a/gck-rpc-dispatch.c
+++ b/gck-rpc-dispatch.c
@@ -40,6 +40,8 @@
 # include <arpa/inet.h>
 # include <netinet/in.h>
 # include <netinet/tcp.h>
+# include <sys/types.h>
+# include <netdb.h>
 #endif
 #include <pthread.h>
 
@@ -57,6 +59,11 @@ static CK_FUNCTION_LIST_PTR pkcs11_module = NULL;
 #define PARSE_ERROR CKR_DEVICE_ERROR
 #define PREP_ERROR  CKR_DEVICE_MEMORY
 
+typedef struct {
+	CK_SESSION_HANDLE id;
+	CK_SLOT_ID slot;
+} SessionState;
+
 typedef struct _CallState {
 	GckRpcMessage *req;
 	GckRpcMessage *resp;
@@ -66,6 +73,12 @@ typedef struct _CallState {
 	int sock;
         int (*read)(int, unsigned char *,size_t);
         int (*write)(int, unsigned char *,size_t);
+	struct sockaddr_storage addr;
+	socklen_t addrlen;
+	/* XXX Maybe sessions should be a linked list instead, to remove the hard
+	 * upper limit and reduce typical memory use.
+	 */
+	SessionState sessions[PKCS11PROXY_MAX_SESSION_COUNT];
 } CallState;
 
 typedef struct _DispatchState {
@@ -460,6 +473,16 @@ proto_read_attribute_array(CallState * cs, CK_ATTRIBUTE_PTR * result,
 	    (&msg->buffer, msg->parsed, &msg->parsed, &n_attrs))
 		return PARSE_ERROR;
 
+	if (! n_attrs) {
+		/* If there are no attributes, it makes most sense to make result
+		 * a NULL pointer. What use could one have of a potentially dangling
+		 * pointer anyways?
+		 */
+		*result = NULL_PTR;
+		*n_result = n_attrs;
+		return CKR_OK;
+	}
+
 	/* Allocate memory for the attribute structures */
 	attrs = call_alloc(cs, n_attrs * sizeof(CK_ATTRIBUTE));
 	if (!attrs)
@@ -549,7 +572,7 @@ proto_write_attribute_array(CallState * cs, CK_ATTRIBUTE_PTR array,
 	return CKR_OK;
 }
 
-static CK_RV proto_read_null_string(CallState * cs, CK_UTF8CHAR_PTR * val)
+static CK_RV proto_read_space_string(CallState * cs, CK_UTF8CHAR_PTR * val, CK_ULONG length)
 {
 	GckRpcMessage *msg;
 	const unsigned char *data;
@@ -561,19 +584,18 @@ static CK_RV proto_read_null_string(CallState * cs, CK_UTF8CHAR_PTR * val)
 	msg = cs->req;
 
 	/* Check that we're supposed to have this at this point */
-	assert(!msg->signature || gck_rpc_message_verify_part(msg, "z"));
+	assert(!msg->signature || gck_rpc_message_verify_part(msg, "s"));
 
 	if (!egg_buffer_get_byte_array
 	    (&msg->buffer, msg->parsed, &msg->parsed, &data, &n_data))
 		return PARSE_ERROR;
 
-	/* Allocate a block of memory for it. The +1 accomodates the NULL byte. */
-	*val = call_alloc(cs, n_data + 1);
+	/* Allocate a block of memory for it. */
+	*val = call_alloc(cs, n_data);
 	if (!*val)
 		return CKR_DEVICE_MEMORY;
 
 	memcpy(*val, data, n_data);
-	(*val)[n_data] = 0;
 
 	return CKR_OK;
 }
@@ -754,8 +776,8 @@ static CK_RV proto_write_session_info(CallState * cs, CK_SESSION_INFO_PTR info)
 	if (!gck_rpc_message_read_ulong (cs->req, &val)) \
 		{ _ret = PARSE_ERROR; goto _cleanup; }
 
-#define IN_STRING(val) \
-	_ret = proto_read_null_string (cs, &val); \
+#define IN_SPACE_STRING(val, len)			   \
+	_ret = proto_read_space_string (cs, &val, len);	   \
 	if (_ret != CKR_OK) goto _cleanup;
 
 #define IN_BYTE_BUFFER(buffer, buffer_len_ptr) \
@@ -838,6 +860,7 @@ static CK_RV rpc_C_Initialize(CallState * cs)
 
 		/* Check to make sure the header matches */
 		if (n_handshake != GCK_RPC_HANDSHAKE_LEN ||
+		    handshake == NULL_PTR ||
 		    memcmp(handshake, GCK_RPC_HANDSHAKE, n_handshake) != 0) {
 			gck_rpc_warn
 			    ("invalid handshake received from connecting module");
@@ -858,9 +881,7 @@ static CK_RV rpc_C_Initialize(CallState * cs)
 
 static CK_RV rpc_C_Finalize(CallState * cs)
 {
-	CK_SLOT_ID_PTR slots;
-	CK_ULONG n_slots, i;
-	CK_SLOT_ID appartment;
+	CK_ULONG i;
 	CK_RV ret;
 	DispatchState *ds, *next;
 
@@ -874,27 +895,17 @@ static CK_RV rpc_C_Finalize(CallState * cs)
 	 * We don't actually C_Finalize lower layers, since this would finalize
 	 * for all appartments, client applications. Anyway this is done by
 	 * the code that loaded us.
-	 *
-	 * But we do need to cleanup resources used by this client, so instead
-	 * we call C_CloseAllSessions for each appartment for this client.
 	 */
 
-	ret = (pkcs11_module->C_GetSlotList) (TRUE, NULL, &n_slots);
-	if (ret == CKR_OK) {
-		slots = calloc(n_slots, sizeof(CK_SLOT_ID));
-		if (slots == NULL) {
-			ret = CKR_DEVICE_MEMORY;
-		} else {
-			ret =
-			    (pkcs11_module->C_GetSlotList) (TRUE, slots,
-							    &n_slots);
-			for (i = 0; ret == CKR_OK && i < n_slots; ++i) {
-				appartment = slots[i];
-				ret =
-				    (pkcs11_module->
-				     C_CloseAllSessions) (appartment);
-			}
-			free(slots);
+	/* Close all sessions that have been opened by this thread, regardless of slot */
+	for (i = 0; i < PKCS11PROXY_MAX_SESSION_COUNT; i++) {
+		if (cs->sessions[i].id) {
+			gck_rpc_log("Closing session %li on position %i", cs->sessions[i].id, i);
+
+			ret = (pkcs11_module->C_CloseSession) (cs->sessions[i].id);
+			if (ret != CKR_OK)
+				break;
+			cs->sessions[i].id = 0;
 		}
 	}
 
@@ -1021,7 +1032,7 @@ static CK_RV rpc_C_InitToken(CallState * cs)
 	BEGIN_CALL(C_InitToken);
 	IN_ULONG(slot_id);
 	IN_BYTE_ARRAY(pin, pin_len);
-	IN_STRING(label);
+	IN_SPACE_STRING(label, 32);
 	PROCESS_CALL((slot_id, pin, pin_len, label));
 	END_CALL;
 }
@@ -1053,6 +1064,21 @@ static CK_RV rpc_C_OpenSession(CallState * cs)
 	IN_ULONG(slot_id);
 	IN_ULONG(flags);
 	PROCESS_CALL((slot_id, flags, NULL, NULL, &session));
+	if (_ret == CKR_OK) {
+		int i;
+		/* Remember this thread opened this session. Needed for C_CloseAllSessions. */
+		for (i = 0; i < PKCS11PROXY_MAX_SESSION_COUNT; i++) {
+			if (! cs->sessions[i].id) {
+				cs->sessions[i].id = session;
+				cs->sessions[i].slot = slot_id;
+				gck_rpc_log("Session %li stored in position %i", session, i);
+				break;
+			}
+		}
+		if (i == PKCS11PROXY_MAX_SESSION_COUNT) {
+			_ret = CKR_SESSION_COUNT; goto _cleanup;
+		}
+	}
 	OUT_ULONG(session);
 	END_CALL;
 }
@@ -1064,18 +1090,61 @@ static CK_RV rpc_C_CloseSession(CallState * cs)
 	BEGIN_CALL(C_CloseSession);
 	IN_ULONG(session);
 	PROCESS_CALL((session));
+	if (_ret == CKR_OK) {
+		int i;
+		/* Remove this session from this threads list */
+		for (i = 0; i < PKCS11PROXY_MAX_SESSION_COUNT; i++) {
+			if (cs->sessions[i].id == session) {
+				gck_rpc_log("Session %li removed from position %i", session, i);
+				cs->sessions[i].id = 0;
+				break;
+			}
+		}
+		if (i == PKCS11PROXY_MAX_SESSION_COUNT) {
+			/* Ignore errors, like with close() */
+			gck_rpc_log("C_CloseSession on unknown session");
+		}
+	}
 	END_CALL;
 }
 
 static CK_RV rpc_C_CloseAllSessions(CallState * cs)
 {
 	CK_SLOT_ID slot_id;
+	CK_SLOT_INFO slotInfo;
+	int i;
 
-	/* Slot id becomes appartment so lower layers can tell clients apart. */
+	/* Close all sessions that have been opened by this thread. PKCS#11 (v2.2) says
+	 * C_CloseAllSessions closes all the sessions opened by one application, leaving
+	 * sessions opened by other applications alone even if the sessions share slot.
+	 *
+	 * Each application on the client side of pkcs11-proxy will mean different thread
+	 * on the server side, so we should close all sessions for a slot opened in this
+	 * thread.
+	 */
 
 	BEGIN_CALL(C_CloseAllSessions);
 	IN_ULONG(slot_id);
-	PROCESS_CALL((slot_id));
+
+	/* To emulate real C_CloseAllSessions (well, the SoftHSM one) we check if slot_id is valid. */
+	_ret = pkcs11_module->C_GetSlotInfo(slot_id, &slotInfo);
+	if (_ret != CKR_OK)
+		goto _cleanup;
+
+	for (i = 0; i < PKCS11PROXY_MAX_SESSION_COUNT; i++) {
+		if (cs->sessions[i].id && (cs->sessions[i].slot == slot_id)) {
+			gck_rpc_log("Closing session %li on position %i with slot %i", cs->sessions[i].id, i, slot_id);
+
+			_ret = (pkcs11_module->C_CloseSession) (cs->sessions[i].id);
+			if (_ret == CKR_OK ||
+			    _ret == CKR_SESSION_CLOSED ||
+			    _ret == CKR_SESSION_HANDLE_INVALID) {
+				cs->sessions[i].id = 0;
+			}
+			if (_ret != CKR_OK)
+				goto _cleanup;
+		}
+	}
 	END_CALL;
 }
 
@@ -1909,6 +1978,9 @@ static CK_RV rpc_C_GenerateRandom(CallState * cs)
 	BEGIN_CALL(C_GenerateRandom);
 	IN_ULONG(session);
 	IN_BYTE_BUFFER(random_data, random_len);
+	if (random_len == NULL_PTR) {
+		_ret = PARSE_ERROR; goto _cleanup;
+	}
 	PROCESS_CALL((session, random_data, *random_len));
 	OUT_BYTE_ARRAY(random_data, random_len);
 	END_CALL;
@@ -2127,17 +2199,27 @@ static int write_all(int sock, unsigned char *data, size_t len)
 static void run_dispatch_loop(CallState *cs)
 {
 	unsigned char buf[4];
-	uint32_t len;
+	uint32_t len, res;
+	char hoststr[NI_MAXHOST], portstr[NI_MAXSERV];
 
 	assert(cs->sock != -1);
 
+	if ((res = getnameinfo((struct sockaddr *) & cs->addr, cs->addrlen,
+			       hoststr, sizeof(hoststr), portstr, sizeof(portstr),
+			       NI_NUMERICHOST | NI_NUMERICSERV)) != 0) {
+		gck_rpc_warn("couldn't call getnameinfo on client addr: %.100s",
+			     gai_strerror(res));
+		hoststr[0] = portstr[0] = '\0';
+	}
+
 	/* The client application */
 	if (!cs->read(cs->sock, (unsigned char *)&cs->appid, sizeof (cs->appid))) {
 		gck_rpc_warn("Can't read appid\n");
 		return ;
 	}
-	gck_rpc_log("New session %d-%d\n", (uint32_t) (cs->appid >> 32),
-		    (uint32_t) cs->appid);
+
+	gck_rpc_log("New session %d-%d (client %s, port %s)\n", (uint32_t) (cs->appid >> 32),
+		    (uint32_t) cs->appid, hoststr, portstr);
 
 	/* Setup our buffers */
 	if (!call_init(cs)) {
@@ -2219,7 +2301,7 @@ static char pkcs11_socket_path[MAXPATHLEN] = { 0, };
 
 void gck_rpc_layer_accept(void)
 {
-	struct sockaddr_un addr;
+	struct sockaddr_storage addr;
 	DispatchState *ds, **here;
 	int error;
 	socklen_t addrlen;
@@ -2258,6 +2340,8 @@ void gck_rpc_layer_accept(void)
 	ds->cs.sock = new_fd;
         ds->cs.read = &read_all;
         ds->cs.write = &write_all;
+	ds->cs.addr = addr;
+	ds->cs.addrlen = addrlen;
 
 	error = pthread_create(&ds->thread, NULL,
 			       run_dispatch_thread, &(ds->cs));
@@ -2287,6 +2371,99 @@ void gck_rpc_layer_inetd(CK_FUNCTION_LIST_PTR module)
    run_dispatch_thread(&cs);
 }
 
+/*
+ * Try to get a listening socket for host and port (host may be either a name or
+ * an IP address, as string) and port (a string with a service name or a port
+ * number).
+ *
+ * Returns -1 on failure, and the socket fd otherwise.
+ */
+static int _get_listening_socket(char *host, char *port)
+{
+	char hoststr[NI_MAXHOST], portstr[NI_MAXSERV];
+	struct addrinfo *ai, *first, hints;
+	int res, sock, one = 1;
+
+	memset(&hints, 0, sizeof(struct addrinfo));
+	hints.ai_flags = AI_PASSIVE;		/* Want addr for bind() */
+	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 bind()
+	 */
+	while (ai) {
+		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): %.100s",
+					     host, port, strerror (errno));
+				goto next;
+			}
+
+			if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+				       (char *)&one, sizeof(one)) == -1) {
+				gck_rpc_warn
+					("couldn't set pkcs11 socket options (%.100s %.100s): %.100s",
+					 host, port, strerror(errno));
+				goto next;
+			}
+
+			if (bind(sock, ai->ai_addr, ai->ai_addrlen) == 0)
+				break;
+
+		next:
+			close(sock);
+			sock = -1;
+		}
+		ai = ai->ai_next;
+	}
+
+	if (sock < 0) {
+		gck_rpc_warn("couldn't create pkcs11 socket (%.100s %.100s): %.100s\n",
+			     host, port, strerror(errno));
+		sock = -1;
+		goto out;
+	}
+
+	if (listen(sock, PKCS11PROXY_LISTEN_BACKLOG) < 0) {
+		gck_rpc_warn("couldn't listen on pkcs11 socket (%.100s %.100s): %.100s",
+			     host, port, strerror(errno));
+		sock = -1;
+		goto out;
+	}
+
+	/* Format a string describing the socket we're listening on into pkcs11_socket_path */
+	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;
+		goto out;
+	}
+
+	snprintf(pkcs11_socket_path, sizeof(pkcs11_socket_path),
+		 (ai->ai_family == AF_INET6) ? "[%s]:%s" : "%s:%s", hoststr, portstr);
+
+ out:
+	freeaddrinfo(first);
+
+	return sock;
+}
+
 int gck_rpc_layer_initialize(const char *prefix, CK_FUNCTION_LIST_PTR module)
 {
 	struct sockaddr_un addr;
@@ -2320,60 +2497,26 @@ int gck_rpc_layer_initialize(const char *prefix, CK_FUNCTION_LIST_PTR module)
 #endif
 
 	if (!strncmp("tcp://", prefix, 6)) {
-		int one = 1, port;
-		char *p = NULL;
-		char *ip;
-
-		ip = strdup(prefix + 6);
-		if (ip == NULL) {
-			gck_rpc_warn("out of memory");
-			return -1;
-		}
-
-		p = strchr(ip, ':');
-
-		if (p) {
-			*p = '\0';
-			port = strtol(p + 1, NULL, 0);
-		} else
-			port = 0;
-
-		sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
-
-		if (sock < 0) {
-			gck_rpc_warn("couldn't create pkcs11 socket: %s",
-				     strerror(errno));
-			return -1;
-		}
-
-                if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
-                               (char *)&one, sizeof (one)) == -1) {
-                        gck_rpc_warn("couldn't set pkcs11 "
-				 "socket options : %s", strerror (errno));
-                        return -1;
-                }
+		/*
+		 * TCP socket
+		 */
+		char *host, *port;
 
-		if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
-                               (char *)&one, sizeof(one)) == -1) {
-			gck_rpc_warn
-			    ("couldn't set pkcs11 socket options : %s",
-			     strerror(errno));
+		if (! gck_rpc_parse_host_port(prefix + 6, &host, &port)) {
+			free(host);
 			return -1;
 		}
 
-		addr.sun_family = AF_INET;
-		if (inet_aton(ip, &((struct sockaddr_in *)&addr)->sin_addr) ==
-		    0) {
-			gck_rpc_warn("bad inet address : %s", ip);
+		if ((sock = _get_listening_socket(host, port)) == -1) {
+			free(host);
 			return -1;
 		}
-		((struct sockaddr_in *)&addr)->sin_port = htons(port);
 
-		snprintf(pkcs11_socket_path, sizeof(pkcs11_socket_path),
-			 "%s", prefix);
-
-		free(ip);
+		free(host);
 	} else {
+		/*
+		 * UNIX domain socket
+		 */
 		snprintf(pkcs11_socket_path, sizeof(pkcs11_socket_path),
 			 "%s/socket.pkcs11", prefix);
 
@@ -2389,34 +2532,18 @@ int gck_rpc_layer_initialize(const char *prefix, CK_FUNCTION_LIST_PTR module)
 		unlink(pkcs11_socket_path);
 		strncpy(addr.sun_path, pkcs11_socket_path,
 			sizeof(addr.sun_path));
-	}
-
-	if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
-		gck_rpc_warn("couldn't bind to pkcs11 socket: %s: %s",
-			     pkcs11_socket_path, strerror(errno));
-		return -1;
-	}
-
-	if (listen(sock, 128) < 0) {
-		gck_rpc_warn("couldn't listen on pkcs11 socket: %s: %s",
-			     pkcs11_socket_path, strerror(errno));
-		return -1;
-	}
-
-	if (!strncmp("tcp://", prefix, 6)) {
-		struct sockaddr_in sa;
-		socklen_t sa_len;
-
-		sa_len = sizeof(sa);
 
-		if (getsockname(sock, (struct sockaddr *)&sa, &sa_len) == -1) {
-			gck_rpc_warn("getsockname failed on pkcs11 socket: %s: %s",
+		if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+			gck_rpc_warn("couldn't bind to pkcs11 socket: %s: %s",
 				     pkcs11_socket_path, strerror(errno));
 			return -1;
 		}
 
-		snprintf(pkcs11_socket_path, sizeof(pkcs11_socket_path),
-			 "%s:%d", inet_ntoa(sa.sin_addr), ntohs(sa.sin_port));
+		if (listen(sock, PKCS11PROXY_LISTEN_BACKLOG) < 0) {
+			gck_rpc_warn("couldn't listen on pkcs11 socket: %s: %s",
+				     pkcs11_socket_path, strerror(errno));
+			return -1;
+		}
 	}
 
 	gck_rpc_log("Listening on: %s\n", pkcs11_socket_path);
diff --git a/gck-rpc-message.c b/gck-rpc-message.c
index 440d88dbdbae992ea942072da563685bc806d2fe..9bb10f18614dabaf5ae8c3a6a6b566497fa2d9aa 100644
--- a/gck-rpc-message.c
+++ b/gck-rpc-message.c
@@ -475,15 +475,8 @@ gck_rpc_message_write_space_string(GckRpcMessage * msg, CK_UTF8CHAR * buffer,
 
 	assert(!msg->signature || gck_rpc_message_verify_part(msg, "s"));
 
+	/* XXX it's not really right to treat UTF-8 input as a byte buffer,
+	 * although CK_UTF8CHAR is currently typedef'd to unsigned char in pkcs11.h.
+	 */
 	return egg_buffer_add_byte_array(&msg->buffer, buffer, length);
 }
-
-int gck_rpc_message_write_zero_string(GckRpcMessage * msg, CK_UTF8CHAR * string)
-{
-	assert(msg);
-	assert(string);
-
-	assert(!msg->signature || gck_rpc_message_verify_part(msg, "z"));
-
-	return egg_buffer_add_string(&msg->buffer, (const char *)string);
-}
diff --git a/gck-rpc-module.c b/gck-rpc-module.c
index fe315ac5b3d016ae5f990bc3754de2769a2a5225..1ce0665af712e9d1fc5569cc11895d35abfd3026 100644
--- a/gck-rpc-module.c
+++ b/gck-rpc-module.c
@@ -38,6 +38,7 @@
 #include <arpa/inet.h>
 #include <netinet/in.h>
 #include <netinet/tcp.h>
+# include <netdb.h>
 #endif
 
 #include <stdlib.h>
@@ -130,8 +131,7 @@ static void parse_arguments(const char *string)
 		return;
 	}
 
-	arg = at = src;
-	for (src = dup; *src; src++) {
+	for (arg = at = src; *src; src++) {
 
 		/* Matching quote */
 		if (quote == *src) {
@@ -312,6 +312,89 @@ static CK_RV call_read(CallState * cs, unsigned char *data, size_t len)
 	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;
+			}
+
+			break;
+		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;
+}
+
 static CK_RV call_connect(CallState * cs)
 {
 	struct sockaddr_un addr;
@@ -327,45 +410,20 @@ static CK_RV call_connect(CallState * cs)
 	memset(&addr, 0, sizeof(addr));
 
 	if (!strncmp("tcp://", pkcs11_socket_path, 6)) {
-		int one = 1, port;
-		char *p = NULL;
-		const char *ip;
+		char *host, *port;
 
-		ip = strdup(pkcs11_socket_path + 6);
-		if (ip)
-			p = strchr(ip, ':');
-
-		if (!p || !ip) {
-			gck_rpc_warn("invalid syntax for pkcs11 socket : %s",
+		if (! gck_rpc_parse_host_port(pkcs11_socket_path + 6, &host, &port)) {
+			gck_rpc_warn("failed parsing pkcs11 socket : %s",
 				     pkcs11_socket_path);
 			return CKR_DEVICE_ERROR;
 		}
-		*p = '\0';
-		port = strtol(p + 1, NULL, 0);
-
-		sock = socket(AF_INET, SOCK_STREAM, 0);
 
-		if (sock < 0) {
-			gck_rpc_warn("couldn't create pkcs11 socket: %s",
-				     strerror(errno));
+		if ((sock = _connect_to_host_port(host, port)) == -1) {
+			free(host);
 			return CKR_DEVICE_ERROR;
 		}
 
-		if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
-			       (char *)&one, sizeof(one)) == -1) {
-			gck_rpc_warn
-			    ("couldn't create set pkcs11 socket options : %s",
-			     strerror(errno));
-			return CKR_DEVICE_ERROR;
-		}
-
-		addr.sun_family = AF_INET;
-		if (inet_aton(ip, &((struct sockaddr_in *)&addr)->sin_addr) ==
-		    0) {
-			gck_rpc_warn("bad inet address : %s", ip);
-			return CKR_DEVICE_ERROR;
-		}
-		((struct sockaddr_in *)&addr)->sin_port = htons(port);
+		free(host);
 	} else {
 		addr.sun_family = AF_UNIX;
 		strncpy(addr.sun_path, pkcs11_socket_path,
@@ -376,22 +434,23 @@ static CK_RV call_connect(CallState * cs)
 			warning(("couldn't open socket: %s", strerror(errno)));
 			return CKR_DEVICE_ERROR;
 		}
-	}
+
 
 #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;
-	}
+		/* close on exec */
+		if (fcntl(sock, F_SETFD, 1) == -1) {
+			close(sock);
+			warning(("couldn't secure socket: %s", strerror(errno)));
+			return CKR_DEVICE_ERROR;
+		}
 #endif
 
-	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;
+		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;
+		}
 	}
 
 	cs->socket = sock;
@@ -741,6 +800,9 @@ proto_read_attribute_array(GckRpcMessage * msg, CK_ATTRIBUTE_PTR arr,
 					return PARSE_ERROR;
 				}
 				attrlen = value;
+			} else {
+				warning(("failed reading byte array"));
+				return PARSE_ERROR;
 			}
 		}
 
@@ -1068,8 +1130,8 @@ proto_read_sesssion_info(GckRpcMessage * msg, CK_SESSION_INFO_PTR info)
 	if (!gck_rpc_message_write_ulong (_cs->req, val)) \
 		{ _ret = CKR_HOST_MEMORY; goto _cleanup; }
 
-#define IN_STRING(val) \
-	if (!gck_rpc_message_write_zero_string (_cs->req, val)) \
+#define IN_SPACE_STRING(val, len)						\
+	if (!gck_rpc_message_write_space_string (_cs->req, val, len))	\
 		{ _ret = CKR_HOST_MEMORY; goto _cleanup; }
 
 #define IN_BYTE_BUFFER(arr, len) \
@@ -1085,6 +1147,9 @@ proto_read_sesssion_info(GckRpcMessage * msg, CK_SESSION_INFO_PTR info)
 #define IN_ULONG_BUFFER(arr, len) \
 	if (len == NULL) \
 		{ _ret = CKR_ARGUMENTS_BAD; goto _cleanup; } \
+	IN_ULONG_BUFFER2(arr, len);
+
+#define IN_ULONG_BUFFER2(arr, len) \
 	if (!gck_rpc_message_write_ulong_buffer (_cs->req, arr ? *len : 0)) \
 		{ _ret = CKR_HOST_MEMORY; goto _cleanup; }
 
@@ -1127,7 +1192,10 @@ proto_read_sesssion_info(GckRpcMessage * msg, CK_SESSION_INFO_PTR info)
 #define OUT_BYTE_ARRAY(arr, len)  \
 	if (len == NULL) \
 		_ret = CKR_ARGUMENTS_BAD; \
-	if (_ret == CKR_OK) \
+	OUT_BYTE_ARRAY2(arr, len);
+
+#define OUT_BYTE_ARRAY2(arr, len)  \
+	if (_ret == CKR_OK)		\
 		_ret = proto_read_byte_array (_cs->resp, (arr), (len), *(len));
 
 #define OUT_ULONG_ARRAY(a, len) \
@@ -1204,6 +1272,7 @@ static CK_RV rpc_C_Initialize(CK_VOID_PTR init_args)
 		/* pReserved must be NULL */
 		args = init_args;
 
+		/* XXX since we're never going to call the supplied mutex functions, shouldn't we reject them? */
 		/* ALL supplied function pointers need to have the value either NULL or non-NULL. */
 		supplied_ok = (args->CreateMutex == NULL
 			       && args->DestroyMutex == NULL
@@ -1247,9 +1316,8 @@ static CK_RV rpc_C_Initialize(CK_VOID_PTR init_args)
 		}
 	}
 
-	/* Lookup the socket path, append '.pkcs11' */
+	/* Lookup the socket path, append '.pkcs11' if it is a domain socket. */
 	if (pkcs11_socket_path[0] == 0) {
-		pkcs11_socket_path[0] = 0;
 		path = getenv("PKCS11_PROXY_SOCKET");
 		if (path && path[0]) {
 			if (!strncmp("tcp://", path, 6))
@@ -1431,7 +1499,7 @@ rpc_C_InitToken(CK_SLOT_ID id, CK_UTF8CHAR_PTR pin, CK_ULONG pin_len,
 	BEGIN_CALL(C_InitToken);
 	IN_ULONG(id);
 	IN_BYTE_ARRAY(pin, pin_len);
-	IN_STRING(label);
+	IN_SPACE_STRING(label, 32);
 	PROCESS_CALL;
 	END_CALL;
 }
@@ -1713,7 +1781,7 @@ rpc_C_FindObjects(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE_PTR objects,
 
 	BEGIN_CALL(C_FindObjects);
 	IN_ULONG(session);
-	IN_ULONG_BUFFER(objects, &max_count);
+	IN_ULONG_BUFFER2(objects, &max_count);
 	PROCESS_CALL;
 	*count = max_count;
 	OUT_ULONG_ARRAY(objects, count);
@@ -2272,7 +2340,7 @@ rpc_C_GenerateRandom(CK_SESSION_HANDLE session, CK_BYTE_PTR random_data,
 	IN_ULONG(session);
 	IN_BYTE_BUFFER(random_data, &random_len);
 	PROCESS_CALL;
-	OUT_BYTE_ARRAY(random_data, &random_len);
+	OUT_BYTE_ARRAY2(random_data, &random_len);
 	END_CALL;
 }
 
diff --git a/gck-rpc-private.h b/gck-rpc-private.h
index 02c1e9d2a591c22793ac8cabe8c02ccc8967aa05..f888c418b6cb29472b296bc420fc94a758feb109 100644
--- a/gck-rpc-private.h
+++ b/gck-rpc-private.h
@@ -130,7 +130,6 @@ typedef struct _GckRpcCall {
  *  s  = space padded string
  *  v  = CK_VERSION
  *  y  = CK_BYTE
- *  z  = null terminated string
  */
 
 static const GckRpcCall gck_rpc_calls[] = {
@@ -144,7 +143,7 @@ static const GckRpcCall gck_rpc_calls[] = {
 	 "ssssuuuuuuuuuuuvvs"},
 	{GCK_RPC_CALL_C_GetMechanismList, "C_GetMechanismList", "ufu", "au"},
 	{GCK_RPC_CALL_C_GetMechanismInfo, "C_GetMechanismInfo", "uu", "uuu"},
-	{GCK_RPC_CALL_C_InitToken, "C_InitToken", "uayz", ""},
+	{GCK_RPC_CALL_C_InitToken, "C_InitToken", "uays", ""},
 	{GCK_RPC_CALL_C_WaitForSlotEvent, "C_WaitForSlotEvent", "u", "u"},
 	{GCK_RPC_CALL_C_OpenSession, "C_OpenSession", "uu", "u"},
 	{GCK_RPC_CALL_C_CloseSession, "C_CloseSession", "u", ""},
@@ -216,7 +215,7 @@ static const GckRpcCall gck_rpc_calls[] = {
 #endif
 
 #define GCK_RPC_HANDSHAKE \
-	"PRIVATE-GNOME-KEYRING-PKCS11-PROTOCOL-V-2"
+	"PRIVATE-GNOME-KEYRING-PKCS11-PROTOCOL-V-3"
 #define GCK_RPC_HANDSHAKE_LEN \
 	(sizeof (GCK_RPC_HANDSHAKE) - 1)
 
@@ -263,9 +262,6 @@ int gck_rpc_message_write_byte(GckRpcMessage * msg, CK_BYTE val);
 
 int gck_rpc_message_write_ulong(GckRpcMessage * msg, CK_ULONG val);
 
-int gck_rpc_message_write_zero_string(GckRpcMessage * msg,
-				      CK_UTF8CHAR * string);
-
 int gck_rpc_message_write_space_string(GckRpcMessage * msg,
 				       CK_UTF8CHAR * buffer, CK_ULONG length);
 
@@ -328,4 +324,7 @@ int gck_rpc_mechanism_has_no_parameters(CK_MECHANISM_TYPE mech);
 int gck_rpc_has_bad_sized_ulong_parameter(CK_ATTRIBUTE_PTR attr);
 int gck_rpc_has_ulong_parameter(CK_ATTRIBUTE_TYPE type);
 
+/* Parses strings (prefix) to host and port components. */
+int gck_rpc_parse_host_port(const char *prefix, char **host, char **port);
+
 #endif /* GCK_RPC_CALLS_H */
diff --git a/gck-rpc-util.c b/gck-rpc-util.c
index 85a458755df9d9232499d15e3cf0ae2b4e3d85b8..cf3c745cd3be996ca03094e2a089e427f4b738fa 100644
--- a/gck-rpc-util.c
+++ b/gck-rpc-util.c
@@ -229,3 +229,55 @@ gck_rpc_has_bad_sized_ulong_parameter(CK_ATTRIBUTE_PTR attr)
 		return 0;
 	return gck_rpc_has_ulong_parameter(attr->type);
 }
+
+/*
+ * Parses prefix into two strings (host and port). Port may be a NULL pointer
+ * if none is specified. Since this code does not decode port in any way, a
+ * service name works too (but requires other code (like
+ * _get_listening_socket()) able to resolve service names).
+ *
+ * This should work for IPv4 and IPv6 inputs :
+ *
+ *   0.0.0.0:2345
+ *   0.0.0.0
+ *   [::]:2345
+ *   [::]
+ *   [::1]:2345
+ *   localhost:2345
+ *   localhost
+ *   localhost:p11proxy   (if p11proxy is a known service name)
+ *
+ * Returns 0 on failure, and 1 on success.
+ */
+int gck_rpc_parse_host_port(const char *prefix, char **host, char **port)
+{
+	char *p = NULL;
+	int is_ipv6;
+
+	is_ipv6 = (prefix[0] == '[') ? 1 : 0;
+
+	*host = strdup(prefix + is_ipv6);
+	*port = NULL;
+
+	if (*host == NULL) {
+		gck_rpc_warn("out of memory");
+		return 0;
+	}
+
+	if (is_ipv6 && prefix[0] == '[')
+		p = strchr(*host, ']');
+	else
+		p = strchr(*host, ':');
+
+	if (p) {
+		is_ipv6 = (*p == ']'); /* remember if separator was ']' */
+
+		*p = '\0'; /* replace separator will NULL to terminate *host */
+		*port = p + 1;
+
+		if (is_ipv6 && (**port == ':'))
+			*port = p + 2;
+	}
+
+	return 1;
+}
diff --git a/p11proxy-mitm b/p11proxy-mitm
new file mode 100755
index 0000000000000000000000000000000000000000..a64015093ebd3d03c181ff41a36d1d7548461d91
--- /dev/null
+++ b/p11proxy-mitm
@@ -0,0 +1,528 @@
+#!/usr/bin/python
+#
+# Copyright (c) 2012, 2013, NORDUnet A/S
+# All rights reserved.
+#
+#   Redistribution and use in source and binary forms, with or
+#   without modification, are permitted provided that the following
+#   conditions are met:
+#
+#     1. Redistributions of source code must retain the above copyright
+#        notice, this list of conditions and the following disclaimer.
+#     2. 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.
+#     3. Neither the name of the NORDUnet nor the names of its
+#        contributors may 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 HOLDER 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 : Fredrik Thulin <fredrik@thulin.net>
+#
+"""
+MITM (man in the middle) proxy of pkcs11-proxy. Written while learning
+how the (not very documented) wire format worked, as well as to help
+diagnose (and fix) a couple of bugs in the C implementation.
+
+Usage :
+
+ On console 1 :
+
+   $ PKCS11_DAEMON_SOCKET="tcp://127.0.0.1:2345" pkcs11-daemon /usr/lib/libsofthsm.so
+
+ On console 2 :
+
+   $ ./p11proxy-mitm --debug
+
+ On console 3 :
+
+   $ PKCS11_PROXY_SOCKET="tcp://127.0.0.1:2344" pkcs11-tool --show-info --module libpkcs11-proxy.so
+
+"""
+
+import sys
+import struct
+import socket
+import logging
+import argparse
+import SocketServer
+import PyKCS11 # used to get all the PKCS#11 defines
+
+default_host = "localhost"
+default_port_in = 2344
+default_port_out = 2345
+default_timeout = 3
+default_debug = False
+
+args = None
+yhsm = None
+logger = None
+
+# Extracted from gck-rpc-private.h. The last two elements of the tuples describe
+# the requests/responses.
+#
+# /*
+#  *  a_ = prefix denotes array of _
+#  *  A  = CK_ATTRIBUTE
+#  *  f_ = prefix denotes buffer for _
+#  *  M  = CK_MECHANISM
+#  *  u  = CK_ULONG
+#  *  s  = space padded string
+#  *  v  = CK_VERSION
+#  *  y  = CK_BYTE
+#  *  z  = null terminated string
+#  */
+request_list = [
+    (0, "ERROR", None, None),
+    (1, "C_Initialize", "ay", ""),
+    (2, "C_Finalize", "", ""),
+    (3, "C_GetInfo", "", "vsusv"),
+    (4, "C_GetSlotList", "yfu", "au"),
+    (5, "C_GetSlotInfo", "u", "ssuvv"),
+    (6, "C_GetTokenInfo", "u", "ssssuuuuuuuuuuuvvs"),
+    (7, "C_GetMechanismList", "ufu", "au"),
+    (8, "C_GetMechanismInfo", "uu", "uuu"),
+    (9, "C_InitToken", "uays", ""),
+    (10, "C_WaitForSlotEvent", "u", "u"),
+    (11, "C_OpenSession", "uu", "u"),
+    (12, "C_CloseSession", "u", ""),
+    (13, "C_CloseAllSessions", "u", ""),
+    (14, "C_GetFunctionStatus", "u", ""),
+    (15, "C_CancelFunction", "u", ""),
+    (16, "C_GetSessionInfo", "u", "uuuu"),
+    (17, "C_InitPIN", "uay", ""),
+    (18, "C_SetPIN", "uayay", ""),
+    (19, "C_GetOperationState", "ufy", "ay"),
+    (20, "C_SetOperationState", "uayuu", ""),
+    (21, "C_Login", "uuay", ""),
+    (22, "C_Logout", "u", ""),
+    (23, "C_CreateObject", "uaA", "u"),
+    (24, "C_CopyObject", "uuaA", "u"),
+    (25, "C_DestroyObject", "uu", ""),
+    (26, "C_GetObjectSize", "uu", "u"),
+    (27, "C_GetAttributeValue", "uufA", "aAu"),
+    (28, "C_SetAttributeValue", "uuaA", ""),
+    (29, "C_FindObjectsInit", "uaA", ""),
+    (30, "C_FindObjects", "ufu", "au"),
+    (31, "C_FindObjectsFinal", "u", ""),
+    (32, "C_EncryptInit", "uMu", ""),
+    (33, "C_Encrypt", "uayfy", "ay"),
+    (34, "C_EncryptUpdate", "uayfy", "ay"),
+    (35, "C_EncryptFinal", "ufy", "ay"),
+    (36, "C_DecryptInit", "uMu", ""),
+    (37, "C_Decrypt", "uayfy", "ay"),
+    (38, "C_DecryptUpdate", "uayfy", "ay"),
+    (39, "C_DecryptFinal", "ufy", "ay"),
+    (40, "C_DigestInit", "uM", ""),
+    (41, "C_Digest", "uayfy", "ay"),
+    (42, "C_DigestUpdate", "uay", ""),
+    (43, "C_DigestKey", "uu", ""),
+    (44, "C_DigestFinal", "ufy", "ay"),
+    (45, "C_SignInit", "uMu", ""),
+    (46, "C_Sign", "uayfy", "ay"),
+    (47, "C_SignUpdate", "uay", ""),
+    (48, "C_SignFinal", "ufy", "ay"),
+    (49, "C_SignRecoverInit", "uMu", ""),
+    (50, "C_SignRecover", "uayfy", "ay"),
+    (51, "C_VerifyInit", "uMu", ""),
+    (52, "C_Verify", "uayay", ""),
+    (53, "C_VerifyUpdate", "uay", ""),
+    (54, "C_VerifyFinal", "uay", ""),
+    (55, "C_VerifyRecoverInit", "uMu", ""),
+    (56, "C_VerifyRecover", "uayfy", "ay"),
+    (57, "C_DigestEncryptUpdate", "uayfy", "ay"),
+    (58, "C_DecryptDigestUpdate", "uayfy", "ay"),
+    (59, "C_SignEncryptUpdate", "uayfy", "ay"),
+    (60, "C_DecryptVerifyUpdate", "uayfy", "ay"),
+    (61, "C_GenerateKey", "uMaA", "u"),
+    (62, "C_GenerateKeyPair", "uMaAaA", "uu"),
+    (63, "C_WrapKey", "uMuufy", "ay"),
+    (64, "C_UnwrapKey", "uMuayaA", "u"),
+    (65, "C_DeriveKey", "uMuaA", "u"),
+    (66, "C_SeedRandom", "uay", ""),
+    (67, "C_GenerateRandom", "ufy", "ay"),
+    ]
+
+def parse_args():
+    """
+    Parse the command line arguments.
+    """
+    parser = argparse.ArgumentParser(description = "pkcs11-proxy man-in-the-middle",
+                                     add_help = True,
+                                     formatter_class = argparse.ArgumentDefaultsHelpFormatter,
+                                     )
+
+    parser.add_argument('-H', '--host',
+                        dest='listen_host',
+                        default=default_host,
+                        help='Host address to listen on',
+                        metavar='HOST',
+                        )
+
+    parser.add_argument('--host-out',
+                        dest='connect_to_host',
+                        default=default_host,
+                        help='Host address to connect to',
+                        metavar='HOST',
+                        )
+
+    parser.add_argument('--port-in',
+                        dest='listen_port',
+                        type=int,
+                        default=default_port_in,
+                        help='Port to listen on',
+                        metavar='PORT',
+                        )
+
+    parser.add_argument('--port-out',
+                        dest='connect_to_port',
+                        type=int,
+                        default=default_port_out,
+                        help='Port to connect to',
+                        metavar='PORT',
+                        )
+
+    parser.add_argument('--timeout',
+                        dest='timeout',
+                        type=int, default=default_timeout,
+                        required=False,
+                        help='Request timeout in seconds',
+                        metavar='SECONDS',
+                        )
+
+    parser.add_argument('--debug',
+                        dest='debug',
+                        action='store_true', default=default_debug,
+                        help='Enable debug operation',
+                        )
+
+    return parser.parse_args()
+
+class ProxyHandler(SocketServer.StreamRequestHandler):
+    """
+    The RequestHandler class for our server.
+
+    It is instantiated once per connection to the server, and must
+    override the handle() method to implement communication to the
+    client.
+    """
+
+    def __init__(self, *other_args, **kwargs):
+        self.timeout = args.timeout
+
+        # Outgoing connection to the real server (pkcs11-daemon)
+        self.p11proxy = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.p11proxy.connect((args.connect_to_host, args.connect_to_port))
+
+        self.logger = logger
+        SocketServer.BaseRequestHandler.__init__(self, *other_args, **kwargs)
+
+    def handle(self):
+        """
+        Handle an incoming connection. Read requests/responses and decode them after passing
+        them on to the real pkcs11-proxy client/server.
+        """
+        # self.request is the TCP socket connected to the client
+        try:
+            # 64 bits app id. Randomized on the client, and not really used in the server.
+            self.appid = self.request.recv(8)
+            self.p11proxy.send(self.appid)
+
+            while True:
+                # Read request. First 32 bits are length.
+                length = self.request.recv(4)
+                if not length:
+                    break
+                # Pass length to real server.
+                self.p11proxy.send(length)
+                self.logger.debug("\n\nRequest length is {}".format(length.encode('hex')))
+                self._handle_request(struct.unpack('>L', length)[0])
+
+                # Read response. Again, first 32 bits are length.
+                length = self.p11proxy.recv(4)
+                # Pass length to client.
+                self.request.sendall(length)
+                self.logger.debug("\n\nAnswer length is {}".format(length.encode('hex')))
+                self._handle_answer(struct.unpack('>L', length)[0])
+        except Exception, e:
+            self.logger.exception("Got exception handling request from {}".format(self.client_address[0]))
+            return None
+
+    def _handle_request(self, length):
+        """
+        Given expected length, read the rest of a request, pass it on and decode whatever we can.
+        """
+        self.logger.debug("Request({})".format(length))
+        data = self.request.recv(length)
+        self.logger.debug(" R: {}".format(data.encode('hex')))
+        self.p11proxy.send(data)
+        # parse request
+        (call_id, rest) = self._get_uint32(data)
+        if call_id > len(request_list):
+            return None
+        self.logger.debug(" R: call_id {}".format(call_id))
+        rinfo = request_list[call_id]
+        self.logger.debug(" R: name {}".format(rinfo[1]))
+        fmt = rinfo[2]
+        if fmt:
+            self.logger.debug(" R: fmt {} ({})".format(fmt, fmt.encode('hex')))
+            return self._parse_message('R', fmt, rest)
+
+    def _handle_answer(self, length):
+        """
+        Given expected length, read the rest of a response, pass it on and decode whatever we can.
+        """
+        self.logger.debug("Answer({})".format(length))
+        data = self.p11proxy.recv(length)
+        self.logger.debug(" A: {}".format(data.encode('hex')))
+        self.request.sendall(data)
+        (call_id, rest) = self._get_uint32(data)
+        if call_id > len(request_list):
+            return None
+        # parse answer
+        (call_id, rest) = self._get_uint32(data)
+        if call_id > len(request_list):
+            return None
+        self.logger.debug(" A: call_id {}".format(call_id))
+        rinfo = request_list[call_id]
+        self.logger.debug(" A: name {}".format(rinfo[1]))
+        if not call_id:
+            # This is ERROR
+            (ckr, _) = self._get_uint32(data[-4:])
+            self.logger.debug(" A: {}".format(PyKCS11.CKR[ckr]))
+        fmt = rinfo[3]
+        if fmt:
+            self.logger.debug(" A: fmt {} ({})".format(fmt, fmt.encode('hex')))
+            self._parse_message('A', fmt, rest)
+
+    def _parse_message(self, msgtype, fmt, data):
+        """
+        Parse the data in the request/response, which share format.
+        """
+        # Check that the data is of the format we expect it to be. The format of every request/answer
+        # is redundantly included in the data sent over the network, although we already know what
+        # it should be. I guess this is a way to reduce the likeliness of undetected stream de-sync,
+        # because it would be wasteful if it was just used to make developers forget to update the
+        # handshake string when changing the format for a request/response.
+        (parsed_fmt, rest) = self._get_byte_array(data)
+        if parsed_fmt != fmt:
+            self.logger.error(" {}: Format mismatch, mine is {} and received is {}".format(msgtype, fmt, parsed_fmt))
+            return None
+        self.logger.debug(" {}: format match".format(msgtype))
+        try:
+            res = self._value_dump(msgtype, parsed_fmt, rest)
+            return res
+        except Exception:
+            self.logger.exception("Got exception trying to dump all data")
+            return []
+
+    def _value_dump(self, msgtype, fmt, data, res = []):
+        """
+        Decode the data in a request/answer according to fmt.
+
+        Will output the decoded data using self.logger.debug().
+        """
+        if not fmt:
+            if data:
+                self.logger.warning("{} bytes left after processing : {}".format(len(data), data.encode('hex')))
+            return []
+        #self.logger.debug("PARSING {} FROM {}".format(fmt[0], data.encode('hex')))
+        if fmt[0] == 'u':
+            (this, rest) = self._get_uint64(data)
+            res.append(this)
+            self.logger.debug(" {}: uint64 {}".format(msgtype, hex(this)))
+        elif fmt[0] == 'a':
+            #self.logger.debug("PARSING {} FROM {}".format(fmt[0], data.encode('hex')))
+            if fmt[1] == 'A':
+                valid = 1
+                rest = data
+            else:
+                (valid, rest) = self._get_uint8(data)
+                self.logger.debug(" {}: Byte-Array valid : {}".format(msgtype, valid))
+                array_num = 0
+            (array_num, rest) = self._get_uint32(rest)
+            self.logger.debug(" {}: Byte-Array length : {}/{}".format(msgtype, array_num, hex(array_num)))
+            if array_num > 256:
+                raise Exception("Unreasonable array length : {}".format(hex(array_num)))
+            if not valid:
+                #  /* If not valid, then just the length is encoded, this can signify CKR_BUFFER_TOO_SMALL */
+                self.logger.debug(" {}: Array length {}/{}, not valid - maybe CKR_BUFFER_TOO_SMALL".format(msgtype, array_num, array_num))
+                # array is now fully handled, remove next element of fmt
+                fmt = fmt[1:]
+                if rest:
+                    self.logger.debug(" {}: CKR_BUFFER_TOO_SMALL leaving data {} ({} bytes) for fmt {}".format(msgtype, rest.encode('hex'), len(rest), fmt[1:]))
+            else:
+                if fmt[1] == 'y':
+                    # simple byte array (i.e. string to us)
+                    rest = struct.pack('>L', array_num) + rest  # restore length before calling _get_byte_array
+                    (this, rest) = self._get_byte_array(rest)
+                    self.logger.debug(" {}: Byte-Array({}) {}".format(msgtype, len(this), repr(this)))
+                    res.append(this)
+                    fmt = fmt[1:]
+                else:
+                    if array_num > 0:
+                        # modify fmt to match the array length
+                        placeholder = 'X'
+                        array_elements = fmt[1] * (array_num - 1)
+                        fmt_rest = fmt[1:]
+                        fmt = placeholder + array_elements + fmt_rest
+                    else:
+                        # chomp the next fmt too
+                        fmt = fmt[1:]
+                    self.logger.debug(" {}: Array length {}, new fmt {}".format(msgtype, array_num, fmt))
+        elif fmt[0] == 's':
+            (this, rest) = self._get_byte_array(data)
+            res.append(this)
+            self.logger.debug(" {}: string({}/{}) {}".format(msgtype, len(this), hex(len(this)), repr(this)))
+        elif fmt[0] == 'v':
+            # version, struct ck_version from pkcs11.h
+            (major, rest) = self._get_uint8(data)
+            (minor, rest) = self._get_uint8(rest)
+            self.logger.debug(" {}: CK_Version {}.{}".format(msgtype, major, minor))
+        elif fmt[0] == 'y':
+            (this, rest) = self._get_uint8(data)
+            self.logger.debug(" {}: CK_Byte {}".format(msgtype, hex(this)))
+        elif fmt[0] == 'z':
+            # NULL-terminated string, len == -1 for empty string
+            (length, rest) = self._get_uint32(data)
+            if length == 0xffffffff:
+                res.append('')
+            else:
+                (this, rest) = self._get_byte_array(data)
+                res.append(this)
+                self.logger.debug(" {}: NULL-string({}/{}) {}".format(msgtype, len(this), hex(len(this)), repr(this)))
+        elif fmt[0] == 'M':
+            # Mechanism
+            #self.logger.debug("PARSING {} FROM {}".format(fmt[0], data.encode('hex')))
+            (mech_type, rest) = self._get_uint32(data)
+            self.logger.debug(" {}: Mechanism {}/{}".format(msgtype, repr(mech_type), PyKCS11.CKM.get(mech_type)))
+            (this, rest) = self._get_byte_array(rest)
+            self.logger.debug(" {}: Mechanism parameter {}".format(msgtype, repr(this)))
+            res.append((mech_type, this))
+        elif fmt[0] == 'A':
+            # Attribute
+            (this, rest) = self._get_attribute(data)
+            res.append(this)
+            #self.logger.debug(" {}: Attribute {}".format(msgtype, this))
+            vl = "None"
+            if this['uValueLen'] is not None:
+                vl = hex(this['uValueLen'])
+            self.logger.debug(" {}: Attribute type {}/{}, valid {}, len {}, data {}".format(\
+                    msgtype, hex(this['type']), PyKCS11.CKA.get(this['type']), this['valid'], vl, this['data'].encode('hex')))
+        elif fmt[0] == 'f':
+            # array buffer
+            if fmt[1] == 'y':
+                (flags, rest) = self._get_uint8(data)
+            else:
+                rest = data
+            (num_items, rest) = self._get_uint32(rest)
+            if fmt[1] == 'A':
+                self.logger.debug(" {}: Attribute buffer, {} elements".format(msgtype, hex(num_items)))
+                while num_items:
+                    (attr_type, rest) = self._get_uint32(rest)
+                    (buffer_len, rest) = self._get_uint32(rest)
+                    self.logger.debug(" {}:   type {}/{} len {}".format(msgtype, hex(attr_type), PyKCS11.CKA.get(attr_type), hex(buffer_len)))
+                    res.append(('attribute buffer', attr_type, buffer_len))
+                    num_items -= 1
+            elif fmt[1] == 'y':
+                self.logger.debug(" {}: Byte buffer, length {} (flags {})".format(msgtype, hex(num_items), flags))
+                # just a number of bytes to alloc
+                pass
+            elif fmt[1] == 'u':
+                # ulong buffer, just an uint32 (?)
+                self.logger.debug("PARSING {} FROM {}".format(fmt[0], data.encode('hex')))
+                (this, rest) = self._get_uint32(data)
+                self.logger.debug(" {}: ulong buffer {}".format(msgtype, hex(this)))
+            else:
+                raise Exception("Unknown array buffer fmt '{}'".format(fmt[1]))
+            # need to munch an extra fmt
+            fmt = fmt[1:]
+        else:
+            self.logger.warn(" {}: STOPPING at fmt {}, data {}".format(msgtype, fmt[0], data.encode('hex')))
+            return []
+        return self._value_dump(msgtype, fmt[1:], rest, res)
+
+    def _get_uint64(self, data):
+        (a, rest) = self._get_uint32(data)
+        (b, rest) = self._get_uint32(rest)
+        return (a << 32 | b, data[8:])
+
+    def _get_uint32(self, data):
+        res = struct.unpack('>L', data[:4])[0]
+        return (res, data[4:])
+
+    def _get_uint8(self, data):
+        res = ord(data[0])
+        return (res, data[1:])
+
+    def _get_byte_array(self, data):
+        (length, rest) = self._get_uint32(data)
+        if length == 0xffffffff:
+            return('', rest)
+        if length > len(rest):
+            raise Exception('Parse error, not enough bytes left for byte-array')
+        res = rest[:length]
+        return (res, rest[length:])
+
+    def _get_attribute(self, data):
+        attr = {}
+        (attr_type, rest) = self._get_uint32(data)
+        (attr_valid, rest) = self._get_uint8(rest)
+        attr_vallen = None
+        data = ''
+        if attr_valid:
+            (attr_vallen, rest) = self._get_uint32(rest)
+            # length doubly included, in byte array again?
+            (data, rest) = self._get_byte_array(rest)
+            # read data here!
+        attr['type'] = attr_type
+        attr['valid'] = attr_valid
+        attr['uValueLen'] = attr_vallen
+        attr['data'] = data
+        return (attr, rest)
+
+def main():
+    global args
+    args = parse_args()
+
+    level = logging.INFO
+    if args.debug:
+        level = logging.DEBUG
+    logging.basicConfig(level=level)
+    global logger
+    logger = logging.getLogger('p11proxy-mitm')
+
+    # Create the server
+    server = SocketServer.TCPServer((args.listen_host, args.listen_port), \
+                                        ProxyHandler, \
+                                        bind_and_activate=False, \
+                                        )
+    server.allow_reuse_address = True
+    server.timeout = args.timeout
+    server.server_bind()     # Manually bind, to support allow_reuse_address
+    server.server_activate() # (see above comment)
+
+    # Activate the server; this will keep running until you
+    # interrupt the program with Ctrl-C
+    server.serve_forever()
+
+if __name__ == '__main__':
+    try:
+        if main():
+            sys.exit(0)
+        sys.exit(1)
+    except KeyboardInterrupt:
+        pass