diff --git a/config.h b/config.h
index 42a2a029918a4e7f8afe2144a5af9e636a5f5360..88806d5099a1956ce2a80352fd5a37c33b84ecd1 100644
--- a/config.h
+++ b/config.h
@@ -4,6 +4,7 @@
 # define DEBUG_OUTPUT 0 // change to 1 to enable debugging
 
 # define PKCS11PROXY_LISTEN_BACKLOG 128
+# define PKCS11PROXY_MAX_SESSION_COUNT 256
 
 #ifdef __MINGW32__
 
diff --git a/gck-rpc-dispatch.c b/gck-rpc-dispatch.c
index a3b1b85da47212e2bf50b366080a59ab45876893..624d9d06a5d7157a7759d40226560c7335951704 100644
--- a/gck-rpc-dispatch.c
+++ b/gck-rpc-dispatch.c
@@ -59,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;
@@ -70,6 +75,10 @@ typedef struct _CallState {
         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 {
@@ -872,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;
 
@@ -888,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;
 		}
 	}
 
@@ -1067,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;
 }
@@ -1078,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;
 }