#include #include "ckcapi.h" typedef struct _SessionList { CkCapiSession **list; size_t lmax; } SessionList; /* These are protected by global_mutex */ static SessionList the_sessions = { NULL, 0 }; static void object_data_release(CkCapiObjectData* objdata) { ASSERT(objdata->data_funcs.release); (objdata->data_funcs.release)(objdata->data); free(objdata); } CkCapiSession* ckcapi_session_create(void) { CkCapiSession* sess = calloc(1, sizeof(CkCapiSession)); if(!sess) return NULL; sess->object_data = ckcapi_hash_new(); if(!sess->object_data) { free(sess); return NULL; } sess->mutex = CreateMutex(NULL, FALSE, NULL); if(!sess->mutex) { ckcapi_hash_free(sess->object_data, NULL); free(sess); return NULL; } DBGS(sess, "created"); return sess; } CK_RV ckcapi_session_register(CkCapiSession* sess) { CK_ULONG id = 0; CK_RV ret = CKR_OK; size_t i; ASSERT(sess); ASSERT(sess->id == 0 && sess->refs == 0); DBGS(sess, "registering new session"); ckcapi_lock_global(); /* Find a nice session identifier */ while(id == 0) { /* * PKCS#11 GRAY AREA: We're assuming we can reuse session * handles. PKCS#11 spec says they're like file handles, * and file handles get reused :) */ /* Note we never put anything in array position '0' */ for(i = 1; i < the_sessions.lmax; ++i) { /* Any empty position will do */ if(!the_sessions.list[i]) { id = i; break; } } /* Couldn't find a handle, reallocate */ if(id == 0) { CkCapiSession** buf; size_t oldmax, newmax; oldmax = the_sessions.lmax; newmax = oldmax + 16; buf = realloc(the_sessions.list, newmax * sizeof(CkCapiSession*)); if(!buf) { DBGS(sess, ("couldn't allocate session list, out of memory")); ret = CKR_HOST_MEMORY; break; } /* Choose the first of the new block as the id */ id = oldmax; /* Clear new memory */ the_sessions.list = buf; for( ; oldmax < newmax; ++oldmax) buf[oldmax] = NULL; the_sessions.lmax = newmax; DBG(("allocated new session list: %d max", newmax)); } } if(ret == CKR_OK) { ASSERT(id > 0 && id < the_sessions.lmax); ASSERT(the_sessions.list[id] == NULL); /* And assign it to the session handle */ the_sessions.list[id] = sess; sess->id = id; /* The session list reference */ ASSERT(sess->refs == 0); sess->refs++; DBGS(sess, "registered sesson id"); } ckcapi_unlock_global(); return ret; } void ckcapi_session_destroy(CkCapiSession* sess) { ASSERT(sess); ASSERT(sess->refs == 0); /* Ask any pending operations to cleanup */ if(sess->operation_type) { ASSERT(sess->operation_cancel); (sess->operation_cancel)(sess); } ASSERT(sess->operation_type == 0); ASSERT(sess->operation_data == NULL); ASSERT(sess->operation_cancel == NULL); /* Make all the object adat go away */ ASSERT(sess->object_data != NULL); ckcapi_hash_free(sess->object_data, object_data_release); /* And make the mutex go away */ ASSERT(sess->mutex != NULL); CloseHandle(sess->mutex); DBGS(sess, "destroyed"); free(sess); } static CK_RV lock_ref_internal(SessionList* sessions, CK_SESSION_HANDLE id, int remove, CkCapiSession** sess_ret) { CkCapiSession *sess; DWORD r; ASSERT(sessions); ASSERT(sess_ret); if(id >= sessions->lmax) { DBG(("invalid session id: %d", id)); return CKR_SESSION_HANDLE_INVALID; } /* A seemingly valid id */ ASSERT(sessions->list); sess = sessions->list[id]; if(!sess) { DBG(("session does not exist: %d", id)); return CKR_SESSION_HANDLE_INVALID; } ASSERT(sess->id == id); /* Closing takes precedence over active operations */ if(!remove) { /* * An initial check is done to make sure this session is not active. * This is done outside of the lock. The real check is done later * inside a lock. This is so we can return quickly without blocking * in most cases. */ if(sess->in_call) { DBGS(sess, ("an operation is already active in this session")); return CKR_OPERATION_ACTIVE; } } /* Lock the CallCkCapiSession */ r = WaitForSingleObject(sess->mutex, INFINITE); ASSERT(r == WAIT_OBJECT_0); /* Do the real check */ if(!remove && sess->in_call) { ReleaseMutex(sess->mutex); DBGS(sess, ("an operation is already active in this session")); return CKR_OPERATION_ACTIVE; } /* Make sure it doesn't go away */ ASSERT(sess->refs > 0); sess->refs++; DBGS(sess, "found and locked session"); /* And remove it if necessary */ if(remove) { sessions->list[id] = NULL; /* The session list reference */ sess->refs--; ASSERT(sess->refs > 0); DBGS(sess, "removed session from list"); } else { ASSERT(!sess->in_call); sess->in_call = 1; } *sess_ret = sess; return CKR_OK; } CK_RV ckcapi_session_get_lock_ref(CK_ULONG id, int remove, CkCapiSession **sess) { /* This must be called without any locks held */ CK_RV ret = CKR_OK; ASSERT(sess); if(id <= 0) { DBG(("invalid session id passed: %d", id)); return CKR_ARGUMENTS_BAD; } ckcapi_lock_global(); ret = lock_ref_internal (&the_sessions, id, remove, sess); ckcapi_unlock_global(); return ret; } void ckcapi_session_unref_unlock(CkCapiSession* sess) { /* The CallCkCapiSession must be locked at this point */ int refs; BOOL r; ASSERT(sess); ASSERT(sess->refs > 0); sess->refs--; refs = sess->refs; sess->in_call = 0; DBGS(sess, "unlocked session"); r = ReleaseMutex(sess->mutex); ASSERT(r == TRUE); /* * At this point if no references are held, then we can safely * delete. No other thread should be involved. */ if(refs == 0) ckcapi_session_destroy(sess); } void ckcapi_session_close_all() { /* This must be called without any locks held */ SessionList sessions; CkCapiSession *sess; size_t i; CK_RV ret; /* * PKCS#11 GRAY AREA: What happens when this gets called * concurrently? We don't return an error on the second call, * because by the time it returns, all sessions should be closed. */ ckcapi_lock_global(); /* Steal all the session data */ sessions.list = the_sessions.list; the_sessions.list = NULL; sessions.lmax = the_sessions.lmax; the_sessions.lmax = 0; if(sessions.list || sessions.lmax) DBG(("closing all sessions")); ckcapi_unlock_global(); /* Close each session in turn */ for(i = 1; i < sessions.lmax; ++i) { if(!sessions.list[i]) continue; ret = lock_ref_internal (&sessions, i, 1, &sess); ASSERT(ret == CKR_OK); ckcapi_session_unref_unlock(sess); } /* We stole the memory above, free it now */ if(sessions.list) { free(sessions.list); DBG(("freed session list")); } } /* ---------------------------------------------------------------------------- * OBJECT DATA */ CK_RV ckcapi_session_get_object_data(CkCapiSession* sess, CkCapiObject* obj, CkCapiObjectData** objdata) { CK_OBJECT_HANDLE id; CkCapiObjectData* newdata; CK_RV ret; ASSERT(sess); ASSERT(sess->object_data); ASSERT(obj); ASSERT(obj->obj_funcs.load_data); ASSERT(objdata); id = obj->id; *objdata = ckcapi_hash_get(sess->object_data, &id, sizeof(id)); if(*objdata) return CKR_OK; newdata = calloc(sizeof(CkCapiObjectData), 1); if(!newdata) return CKR_HOST_MEMORY; newdata->object = id; ret = (obj->obj_funcs.load_data)(sess, obj, newdata); if(ret != CKR_OK) { free(newdata); return ret; } newdata->object = id; ASSERT(newdata->data); ASSERT(newdata->data_funcs.release); if(!ckcapi_hash_set(sess->object_data, &newdata->object, sizeof(newdata->object), newdata)) { object_data_release(newdata); return CKR_HOST_MEMORY; } *objdata = newdata; return CKR_OK; } void ckcapi_session_clear_object_data(CkCapiSession* sess, CkCapiObject* obj) { CkCapiObjectData* objdata; ASSERT(sess); ASSERT(sess->object_data); ASSERT(obj); objdata = (CkCapiObjectData*)ckcapi_hash_rem(sess->object_data, &obj->id, sizeof(obj->id)); if(objdata) object_data_release(objdata); } void ckcapi_session_enum_object_data(CkCapiSession* sess, CkCapiEnumObjectData enum_func, void* arg) { CK_OBJECT_HANDLE i, max; CkCapiObject* obj; CkCapiObjectData* objdata; ASSERT(sess); ASSERT(sess->object_data); ASSERT(enum_func); max = ckcapi_object_get_max_handle(); for(i = 0; i < max; ++i) { objdata = (CkCapiObjectData*)ckcapi_hash_get(sess->object_data, &i, sizeof(i)); if(!objdata) continue; obj = ckcapi_object_lookup (sess, i); if(!obj) continue; (enum_func)(sess, obj, objdata, arg); } } CK_RV ckcapi_session_get_object_data_for(CkCapiSession* sess, CK_OBJECT_HANDLE hand, CkCapiObjectData** objdata) { CkCapiObject* obj; obj = ckcapi_object_lookup(sess, hand); if(!obj) return CKR_OBJECT_HANDLE_INVALID; return ckcapi_session_get_object_data(sess, obj, objdata); } CK_RV ckcapi_session_set_object_data(CkCapiSession* sess, CkCapiObject* obj, const CkCapiObjectData* objdata) { CkCapiObjectData* newdata; CkCapiObjectData* prev; ASSERT(obj); ASSERT(sess); ASSERT(sess->object_data); ASSERT(objdata); newdata = calloc(sizeof(CkCapiObjectData), 1); if(!newdata) return CKR_HOST_MEMORY; newdata->object = obj->id; newdata->data = objdata->data; newdata->data_funcs = objdata->data_funcs; prev = ckcapi_hash_rem(sess->object_data, &obj->id, sizeof(obj->id)); if(prev) object_data_release(prev); if(!ckcapi_hash_set(sess->object_data, &newdata->object, sizeof(newdata->object), newdata)) { free(newdata); return CKR_HOST_MEMORY; } return CKR_OK; } /* ---------------------------------------------------------------------------- * FIND OPERATION */ static BOOL get_ulong_attribute(CK_ATTRIBUTE_TYPE type, CK_ATTRIBUTE_PTR templ, CK_ULONG count, CK_ULONG* val) { CK_ULONG i; ASSERT(val); ASSERT(!count || templ); for(i = 0; i < count; ++i) { if(templ[i].type == type) { *val = *((CK_ULONG*)templ[i].pValue); return TRUE; } } return FALSE; } static CK_RV gather_objects(CkCapiSession* sess, CK_ATTRIBUTE_PTR match, CK_ULONG count, CkCapiArray* arr) { CK_OBJECT_CLASS ocls = CKO_ANY; CK_RV ret = CKR_OK; get_ulong_attribute(CKA_CLASS, match, count, &ocls); /* Search for builtins */ ret = ckcapi_builtin_find(sess, ocls, match, count, arr); if(ret != CKR_OK) return ret; /* Search for certificates */ ret = ckcapi_cert_find(sess, ocls, match, count, arr); if(ret != CKR_OK) return ret; /* Search through trust objects */ ret = ckcapi_trust_find(sess, ocls, match, count, arr); if(ret != CKR_OK) return ret; return ret; } void cleanup_find_operation(CkCapiSession* sess) { ASSERT(sess->operation_type == OPERATION_FIND); if(sess->operation_data) ckcapi_array_free((CkCapiArray*)sess->operation_data, TRUE); sess->operation_type = OPERATION_NONE; sess->operation_data = NULL; sess->operation_cancel = NULL; } void purge_duplicate_objects(CkCapiArray* arr) { CkCapiHash* checks; CkCapiObject* v; size_t i; checks = ckcapi_hash_new(); if(!checks) return; for(i = 0; i < arr->len; ) { v = ckcapi_array_index(arr, CkCapiObject*, i); if(ckcapi_hash_get(checks, &v, sizeof(CkCapiObject*))) { ckcapi_array_remove_index(arr, i); /* Look at same i again */ } else { if(!ckcapi_hash_set(checks, &v, sizeof(CkCapiObject*), &v)) break; ++i; } } ckcapi_hash_free(checks, NULL); } CK_RV ckcapi_session_find_init(CkCapiSession* sess, CK_ATTRIBUTE_PTR match, CK_ULONG count) { CkCapiArray* arr; CK_RV ret; ASSERT(sess); ASSERT(!count || match); if(sess->operation_type != OPERATION_NONE) return CKR_OPERATION_ACTIVE; arr = ckcapi_array_new(0, 1, sizeof(CkCapiObject*)); if(!arr) return CKR_HOST_MEMORY; ret = gather_objects(sess, match, count, arr); if(ret != CKR_OK) { ckcapi_array_free(arr, TRUE); return ret; } /* Cleanup all duplicates in the array */ purge_duplicate_objects(arr); sess->operation_type = OPERATION_FIND; sess->operation_data = arr; sess->operation_cancel = cleanup_find_operation; return CKR_OK; } CK_RV ckcapi_session_find(CkCapiSession* sess, CK_OBJECT_HANDLE_PTR objects, CK_ULONG max_object_count, CK_ULONG_PTR object_count) { CkCapiArray* arr; size_t i; ASSERT(sess); ASSERT(object_count); ASSERT(!max_object_count || objects); if(sess->operation_type != OPERATION_FIND) return CKR_OPERATION_NOT_INITIALIZED; if(!max_object_count) { *object_count = 0; return CKR_OK; } arr = (CkCapiArray*)sess->operation_data; *object_count = (max_object_count > arr->len ? arr->len : max_object_count); for(i = 0; i < *object_count; ++i) objects[i] = ckcapi_array_index(arr, CkCapiObject*, i)->id; ckcapi_array_remove_range(arr, 0, *object_count); return CKR_OK; } CK_RV ckcapi_session_find_final(CkCapiSession* sess) { ASSERT(sess); if(sess->operation_type != OPERATION_FIND) return CKR_OPERATION_NOT_INITIALIZED; cleanup_find_operation(sess); return CKR_OK; }