| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| /* |
| * The following handles the loading, unloading and management of |
| * various PCKS #11 modules |
| */ |
| #define FORCE_PR_LOG 1 |
| #include "seccomon.h" |
| #include "pkcs11.h" |
| #include "secmod.h" |
| #include "prlink.h" |
| #include "pk11func.h" |
| #include "secmodi.h" |
| #include "secmodti.h" |
| #include "nssilock.h" |
| #include "secerr.h" |
| #include "prenv.h" |
| #include "utilparst.h" |
| #include "prio.h" |
| #include "prprf.h" |
| #include <stdio.h> |
| #include "prsystem.h" |
| |
| #define DEBUG_MODULE 1 |
| |
| #ifdef DEBUG_MODULE |
| static char *modToDBG = NULL; |
| |
| #include "debug_module.c" |
| #endif |
| |
| /* build the PKCS #11 2.01 lock files */ |
| CK_RV PR_CALLBACK |
| secmodCreateMutext(CK_VOID_PTR_PTR pmutex) |
| { |
| *pmutex = (CK_VOID_PTR)PZ_NewLock(nssILockOther); |
| if (*pmutex) |
| return CKR_OK; |
| return CKR_HOST_MEMORY; |
| } |
| |
| CK_RV PR_CALLBACK |
| secmodDestroyMutext(CK_VOID_PTR mutext) |
| { |
| PZ_DestroyLock((PZLock *)mutext); |
| return CKR_OK; |
| } |
| |
| CK_RV PR_CALLBACK |
| secmodLockMutext(CK_VOID_PTR mutext) |
| { |
| PZ_Lock((PZLock *)mutext); |
| return CKR_OK; |
| } |
| |
| CK_RV PR_CALLBACK |
| secmodUnlockMutext(CK_VOID_PTR mutext) |
| { |
| PZ_Unlock((PZLock *)mutext); |
| return CKR_OK; |
| } |
| |
| static SECMODModuleID nextModuleID = 1; |
| static const CK_C_INITIALIZE_ARGS secmodLockFunctions = { |
| secmodCreateMutext, secmodDestroyMutext, secmodLockMutext, |
| secmodUnlockMutext, CKF_LIBRARY_CANT_CREATE_OS_THREADS | CKF_OS_LOCKING_OK, |
| NULL |
| }; |
| static const CK_C_INITIALIZE_ARGS secmodNoLockArgs = { |
| NULL, NULL, NULL, NULL, |
| CKF_LIBRARY_CANT_CREATE_OS_THREADS, NULL |
| }; |
| |
| static PRBool loadSingleThreadedModules = PR_TRUE; |
| static PRBool enforceAlreadyInitializedError = PR_TRUE; |
| static PRBool finalizeModules = PR_TRUE; |
| |
| /* set global options for NSS PKCS#11 module loader */ |
| SECStatus |
| pk11_setGlobalOptions(PRBool noSingleThreadedModules, |
| PRBool allowAlreadyInitializedModules, |
| PRBool dontFinalizeModules) |
| { |
| if (noSingleThreadedModules) { |
| loadSingleThreadedModules = PR_FALSE; |
| } else { |
| loadSingleThreadedModules = PR_TRUE; |
| } |
| if (allowAlreadyInitializedModules) { |
| enforceAlreadyInitializedError = PR_FALSE; |
| } else { |
| enforceAlreadyInitializedError = PR_TRUE; |
| } |
| if (dontFinalizeModules) { |
| finalizeModules = PR_FALSE; |
| } else { |
| finalizeModules = PR_TRUE; |
| } |
| return SECSuccess; |
| } |
| |
| PRBool |
| pk11_getFinalizeModulesOption(void) |
| { |
| return finalizeModules; |
| } |
| |
| /* |
| * Allow specification loading the same module more than once at init time. |
| * This enables 2 things. |
| * |
| * 1) we can load additional databases by manipulating secmod.db/pkcs11.txt. |
| * 2) we can handle the case where some library has already initialized NSS |
| * before the main application. |
| * |
| * oldModule is the module we have already initialized. |
| * char *modulespec is the full module spec for the library we want to |
| * initialize. |
| */ |
| static SECStatus |
| secmod_handleReload(SECMODModule *oldModule, SECMODModule *newModule) |
| { |
| PK11SlotInfo *slot; |
| char *modulespec; |
| char *newModuleSpec; |
| char **children; |
| CK_SLOT_ID *ids; |
| SECMODConfigList *conflist = NULL; |
| SECStatus rv = SECFailure; |
| int count = 0; |
| |
| /* first look for tokens= key words from the module spec */ |
| modulespec = newModule->libraryParams; |
| newModuleSpec = secmod_ParseModuleSpecForTokens(PR_TRUE, |
| newModule->isFIPS, modulespec, &children, &ids); |
| if (!newModuleSpec) { |
| return SECFailure; |
| } |
| |
| /* |
| * We are now trying to open a new slot on an already loaded module. |
| * If that slot represents a cert/key database, we don't want to open |
| * multiple copies of that same database. Unfortunately we understand |
| * the softoken flags well enough to be able to do this, so we can only get |
| * the list of already loaded databases if we are trying to open another |
| * internal module. |
| */ |
| if (oldModule->internal) { |
| conflist = secmod_GetConfigList(oldModule->isFIPS, |
| oldModule->libraryParams, &count); |
| } |
| |
| /* don't open multiple of the same db */ |
| if (conflist && secmod_MatchConfigList(newModuleSpec, conflist, count)) { |
| rv = SECSuccess; |
| goto loser; |
| } |
| slot = SECMOD_OpenNewSlot(oldModule, newModuleSpec); |
| if (slot) { |
| int newID; |
| char **thisChild; |
| CK_SLOT_ID *thisID; |
| char *oldModuleSpec; |
| |
| if (secmod_IsInternalKeySlot(newModule)) { |
| pk11_SetInternalKeySlotIfFirst(slot); |
| } |
| newID = slot->slotID; |
| PK11_FreeSlot(slot); |
| for (thisChild = children, thisID = ids; thisChild && *thisChild; |
| thisChild++, thisID++) { |
| if (conflist && |
| secmod_MatchConfigList(*thisChild, conflist, count)) { |
| *thisID = (CK_SLOT_ID)-1; |
| continue; |
| } |
| slot = SECMOD_OpenNewSlot(oldModule, *thisChild); |
| if (slot) { |
| *thisID = slot->slotID; |
| PK11_FreeSlot(slot); |
| } else { |
| *thisID = (CK_SLOT_ID)-1; |
| } |
| } |
| |
| /* update the old module initialization string in case we need to |
| * shutdown and reinit the whole mess (this is rare, but can happen |
| * when trying to stop smart card insertion/removal threads)... */ |
| oldModuleSpec = secmod_MkAppendTokensList(oldModule->arena, |
| oldModule->libraryParams, newModuleSpec, newID, |
| children, ids); |
| if (oldModuleSpec) { |
| oldModule->libraryParams = oldModuleSpec; |
| } |
| |
| rv = SECSuccess; |
| } |
| |
| loser: |
| secmod_FreeChildren(children, ids); |
| PORT_Free(newModuleSpec); |
| if (conflist) { |
| secmod_FreeConfigList(conflist, count); |
| } |
| return rv; |
| } |
| |
| /* |
| * collect the steps we need to initialize a module in a single function |
| */ |
| SECStatus |
| secmod_ModuleInit(SECMODModule *mod, SECMODModule **reload, |
| PRBool *alreadyLoaded) |
| { |
| CK_C_INITIALIZE_ARGS moduleArgs; |
| CK_VOID_PTR pInitArgs; |
| CK_RV crv; |
| |
| if (reload) { |
| *reload = NULL; |
| } |
| |
| if (!mod || !alreadyLoaded) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return SECFailure; |
| } |
| |
| if (mod->libraryParams == NULL) { |
| if (mod->isThreadSafe) { |
| pInitArgs = (void *)&secmodLockFunctions; |
| } else { |
| pInitArgs = NULL; |
| } |
| } else { |
| if (mod->isThreadSafe) { |
| moduleArgs = secmodLockFunctions; |
| } else { |
| moduleArgs = secmodNoLockArgs; |
| } |
| moduleArgs.LibraryParameters = (void *)mod->libraryParams; |
| pInitArgs = &moduleArgs; |
| } |
| crv = PK11_GETTAB(mod)->C_Initialize(pInitArgs); |
| if (CKR_CRYPTOKI_ALREADY_INITIALIZED == crv) { |
| SECMODModule *oldModule = NULL; |
| |
| /* Library has already been loaded once, if caller expects it, and it |
| * has additional configuration, try reloading it as well. */ |
| if (reload != NULL && mod->libraryParams) { |
| oldModule = secmod_FindModuleByFuncPtr(mod->functionList); |
| } |
| /* Library has been loaded by NSS. It means it may be capable of |
| * reloading */ |
| if (oldModule) { |
| SECStatus rv; |
| rv = secmod_handleReload(oldModule, mod); |
| if (rv == SECSuccess) { |
| /* This module should go away soon, since we've |
| * simply expanded the slots on the old module. |
| * When it goes away, it should not Finalize since |
| * that will close our old module as well. Setting |
| * the function list to NULL will prevent that close */ |
| mod->functionList = NULL; |
| *reload = oldModule; |
| return SECSuccess; |
| } |
| SECMOD_DestroyModule(oldModule); |
| } |
| /* reload not possible, fall back to old semantics */ |
| if (!enforceAlreadyInitializedError) { |
| *alreadyLoaded = PR_TRUE; |
| return SECSuccess; |
| } |
| } |
| if (crv != CKR_OK) { |
| if (!mod->isThreadSafe || |
| crv == CKR_NSS_CERTDB_FAILED || |
| crv == CKR_NSS_KEYDB_FAILED) { |
| PORT_SetError(PK11_MapError(crv)); |
| return SECFailure; |
| } |
| /* If we had attempted to init a single threaded module "with" |
| * parameters and it failed, should we retry "without" parameters? |
| * (currently we don't retry in this scenario) */ |
| |
| if (!loadSingleThreadedModules) { |
| PORT_SetError(SEC_ERROR_INCOMPATIBLE_PKCS11); |
| return SECFailure; |
| } |
| /* If we arrive here, the module failed a ThreadSafe init. */ |
| mod->isThreadSafe = PR_FALSE; |
| if (!mod->libraryParams) { |
| pInitArgs = NULL; |
| } else { |
| moduleArgs = secmodNoLockArgs; |
| moduleArgs.LibraryParameters = (void *)mod->libraryParams; |
| pInitArgs = &moduleArgs; |
| } |
| crv = PK11_GETTAB(mod)->C_Initialize(pInitArgs); |
| if ((CKR_CRYPTOKI_ALREADY_INITIALIZED == crv) && |
| (!enforceAlreadyInitializedError)) { |
| *alreadyLoaded = PR_TRUE; |
| return SECSuccess; |
| } |
| if (crv != CKR_OK) { |
| PORT_SetError(PK11_MapError(crv)); |
| return SECFailure; |
| } |
| } |
| return SECSuccess; |
| } |
| |
| /* |
| * set the hasRootCerts flags in the module so it can be stored back |
| * into the database. |
| */ |
| void |
| SECMOD_SetRootCerts(PK11SlotInfo *slot, SECMODModule *mod) |
| { |
| PK11PreSlotInfo *psi = NULL; |
| int i; |
| |
| if (slot->hasRootCerts) { |
| for (i = 0; i < mod->slotInfoCount; i++) { |
| if (slot->slotID == mod->slotInfo[i].slotID) { |
| psi = &mod->slotInfo[i]; |
| break; |
| } |
| } |
| if (psi == NULL) { |
| /* allocate more slots */ |
| PK11PreSlotInfo *psi_list = (PK11PreSlotInfo *) |
| PORT_ArenaAlloc(mod->arena, |
| (mod->slotInfoCount + 1) * sizeof(PK11PreSlotInfo)); |
| /* copy the old ones */ |
| if (mod->slotInfoCount > 0) { |
| PORT_Memcpy(psi_list, mod->slotInfo, |
| (mod->slotInfoCount) * sizeof(PK11PreSlotInfo)); |
| } |
| /* assign psi to the last new slot */ |
| psi = &psi_list[mod->slotInfoCount]; |
| psi->slotID = slot->slotID; |
| psi->askpw = 0; |
| psi->timeout = 0; |
| psi->defaultFlags = 0; |
| |
| /* increment module count & store new list */ |
| mod->slotInfo = psi_list; |
| mod->slotInfoCount++; |
| } |
| psi->hasRootCerts = 1; |
| } |
| } |
| |
| #ifndef NSS_STATIC_SOFTOKEN |
| static const char *my_shlib_name = |
| SHLIB_PREFIX "nss" NSS_SHLIB_VERSION "." SHLIB_SUFFIX; |
| static const char *softoken_shlib_name = |
| SHLIB_PREFIX "softokn" SOFTOKEN_SHLIB_VERSION "." SHLIB_SUFFIX; |
| static const PRCallOnceType pristineCallOnce; |
| static PRCallOnceType loadSoftokenOnce; |
| static PRLibrary *softokenLib; |
| static PRInt32 softokenLoadCount; |
| |
| /* This function must be run only once. */ |
| /* determine if hybrid platform, then actually load the DSO. */ |
| static PRStatus |
| softoken_LoadDSO(void) |
| { |
| PRLibrary *handle; |
| |
| handle = PORT_LoadLibraryFromOrigin(my_shlib_name, |
| (PRFuncPtr)&softoken_LoadDSO, |
| softoken_shlib_name); |
| if (handle) { |
| softokenLib = handle; |
| return PR_SUCCESS; |
| } |
| return PR_FAILURE; |
| } |
| #else |
| CK_RV NSC_GetInterface(CK_UTF8CHAR_PTR pInterfaceName, |
| CK_VERSION_PTR pVersion, |
| CK_INTERFACE_PTR_PTR *ppInterface, CK_FLAGS flags); |
| char **NSC_ModuleDBFunc(unsigned long function, char *parameters, void *args); |
| #endif |
| |
| /* |
| * load a new module into our address space and initialize it. |
| */ |
| SECStatus |
| secmod_LoadPKCS11Module(SECMODModule *mod, SECMODModule **oldModule) |
| { |
| PRLibrary *library = NULL; |
| CK_C_GetInterface ientry = NULL; |
| CK_C_GetFunctionList fentry = NULL; |
| CK_INFO info; |
| CK_ULONG slotCount = 0; |
| SECStatus rv; |
| PRBool alreadyLoaded = PR_FALSE; |
| char *disableUnload = NULL; |
| #ifndef NSS_STATIC_SOFTOKEN |
| const char *nss_interface; |
| const char *nss_function; |
| #endif |
| CK_INTERFACE_PTR interface; |
| |
| if (mod->loaded) |
| return SECSuccess; |
| |
| mod->fipsIndicator = NULL; |
| |
| /* internal modules get loaded from their internal list */ |
| if (mod->internal && (mod->dllName == NULL)) { |
| #ifdef NSS_STATIC_SOFTOKEN |
| ientry = (CK_C_GetInterface)NSC_GetInterface; |
| #else |
| /* |
| * Loads softoken as a dynamic library, |
| * even though the rest of NSS assumes this as the "internal" module. |
| */ |
| if (!softokenLib && |
| PR_SUCCESS != PR_CallOnce(&loadSoftokenOnce, &softoken_LoadDSO)) |
| return SECFailure; |
| |
| PR_ATOMIC_INCREMENT(&softokenLoadCount); |
| |
| if (mod->isFIPS) { |
| nss_interface = "FC_GetInterface"; |
| nss_function = "FC_GetFunctionList"; |
| } else { |
| nss_interface = "NSC_GetInterface"; |
| nss_function = "NSC_GetFunctionList"; |
| } |
| |
| ientry = (CK_C_GetInterface) |
| PR_FindSymbol(softokenLib, nss_interface); |
| if (!ientry) { |
| fentry = (CK_C_GetFunctionList) |
| PR_FindSymbol(softokenLib, nss_function); |
| if (!fentry) { |
| return SECFailure; |
| } |
| } |
| #endif |
| |
| if (mod->isModuleDB) { |
| mod->moduleDBFunc = (CK_C_GetFunctionList) |
| #ifdef NSS_STATIC_SOFTOKEN |
| NSC_ModuleDBFunc; |
| #else |
| PR_FindSymbol(softokenLib, "NSC_ModuleDBFunc"); |
| #endif |
| } |
| |
| if (mod->moduleDBOnly) { |
| mod->loaded = PR_TRUE; |
| return SECSuccess; |
| } |
| } else { |
| /* Not internal, load the DLL and look up C_GetFunctionList */ |
| if (mod->dllName == NULL) { |
| return SECFailure; |
| } |
| |
| /* load the library. If this succeeds, then we have to remember to |
| * unload the library if anything goes wrong from here on out... |
| */ |
| library = PR_LoadLibrary(mod->dllName); |
| mod->library = (void *)library; |
| |
| if (library == NULL) { |
| return SECFailure; |
| } |
| |
| /* |
| * now we need to get the entry point to find the function pointers |
| */ |
| if (!mod->moduleDBOnly) { |
| ientry = (CK_C_GetInterface) |
| PR_FindSymbol(library, "C_GetInterface"); |
| if (!ientry) { |
| fentry = (CK_C_GetFunctionList) |
| PR_FindSymbol(library, "C_GetFunctionList"); |
| } |
| } |
| if (mod->isModuleDB) { |
| mod->moduleDBFunc = (void *) |
| PR_FindSymbol(library, "NSS_ReturnModuleSpecData"); |
| } |
| if (mod->moduleDBFunc == NULL) |
| mod->isModuleDB = PR_FALSE; |
| if ((ientry == NULL) && (fentry == NULL)) { |
| if (mod->isModuleDB) { |
| mod->loaded = PR_TRUE; |
| mod->moduleDBOnly = PR_TRUE; |
| return SECSuccess; |
| } |
| PR_UnloadLibrary(library); |
| return SECFailure; |
| } |
| } |
| |
| /* |
| * We need to get the function list |
| */ |
| if (ientry) { |
| /* we first try to get a FORK_SAFE interface */ |
| if ((*ientry)((CK_UTF8CHAR_PTR) "PKCS 11", NULL, &interface, |
| CKF_INTERFACE_FORK_SAFE) != CKR_OK) { |
| /* one is not appearantly available, get a non-fork safe version */ |
| if ((*ientry)((CK_UTF8CHAR_PTR) "PKCS 11", NULL, &interface, 0) != CKR_OK) { |
| goto fail; |
| } |
| } |
| mod->functionList = interface->pFunctionList; |
| mod->flags = interface->flags; |
| /* if we have a fips indicator, grab it */ |
| if ((*ientry)((CK_UTF8CHAR_PTR) "Vendor NSS FIPS Interface", NULL, |
| &interface, 0) == CKR_OK) { |
| mod->fipsIndicator = ((CK_NSS_FIPS_FUNCTIONS *)(interface->pFunctionList))->NSC_NSSGetFIPSStatus; |
| } |
| } else { |
| if ((*fentry)((CK_FUNCTION_LIST_PTR *)&mod->functionList) != CKR_OK) |
| goto fail; |
| mod->flags = 0; |
| } |
| |
| #ifdef DEBUG_MODULE |
| modToDBG = PR_GetEnvSecure("NSS_DEBUG_PKCS11_MODULE"); |
| if (modToDBG && strcmp(mod->commonName, modToDBG) == 0) { |
| mod->functionList = (void *)nss_InsertDeviceLog( |
| (CK_FUNCTION_LIST_3_0_PTR)mod->functionList); |
| } |
| #endif |
| |
| /* This test operation makes sure our locking system is |
| * consistent even if we are using non-thread safe tokens by |
| * simulating unsafe tokens with safe ones. */ |
| mod->isThreadSafe = !PR_GetEnvSecure("NSS_FORCE_TOKEN_LOCK"); |
| |
| /* Now we initialize the module */ |
| rv = secmod_ModuleInit(mod, oldModule, &alreadyLoaded); |
| if (rv != SECSuccess) { |
| goto fail; |
| } |
| |
| /* module has been reloaded, this module itself is done, |
| * return to the caller */ |
| if (mod->functionList == NULL) { |
| mod->loaded = PR_TRUE; /* technically the module is loaded.. */ |
| return SECSuccess; |
| } |
| |
| /* check the version number */ |
| if (PK11_GETTAB(mod)->C_GetInfo(&info) != CKR_OK) |
| goto fail2; |
| if (info.cryptokiVersion.major < 2) |
| goto fail2; |
| /* all 2.0 are a priori *not* thread safe */ |
| if ((info.cryptokiVersion.major == 2) && (info.cryptokiVersion.minor < 1)) { |
| if (!loadSingleThreadedModules) { |
| PORT_SetError(SEC_ERROR_INCOMPATIBLE_PKCS11); |
| goto fail2; |
| } else { |
| mod->isThreadSafe = PR_FALSE; |
| } |
| } |
| mod->cryptokiVersion = info.cryptokiVersion; |
| |
| /* If we don't have a common name, get it from the PKCS 11 module */ |
| if ((mod->commonName == NULL) || (mod->commonName[0] == 0)) { |
| mod->commonName = PK11_MakeString(mod->arena, NULL, |
| (char *)info.libraryDescription, sizeof(info.libraryDescription)); |
| if (mod->commonName == NULL) |
| goto fail2; |
| } |
| |
| /* initialize the Slots */ |
| if (PK11_GETTAB(mod)->C_GetSlotList(CK_FALSE, NULL, &slotCount) == CKR_OK) { |
| CK_SLOT_ID *slotIDs; |
| int i; |
| CK_RV crv; |
| |
| mod->slots = (PK11SlotInfo **)PORT_ArenaAlloc(mod->arena, |
| sizeof(PK11SlotInfo *) * slotCount); |
| if (mod->slots == NULL) |
| goto fail2; |
| |
| slotIDs = (CK_SLOT_ID *)PORT_Alloc(sizeof(CK_SLOT_ID) * slotCount); |
| if (slotIDs == NULL) { |
| goto fail2; |
| } |
| crv = PK11_GETTAB(mod)->C_GetSlotList(CK_FALSE, slotIDs, &slotCount); |
| if (crv != CKR_OK) { |
| PORT_Free(slotIDs); |
| goto fail2; |
| } |
| |
| /* Initialize each slot */ |
| for (i = 0; i < (int)slotCount; i++) { |
| mod->slots[i] = PK11_NewSlotInfo(mod); |
| PK11_InitSlot(mod, slotIDs[i], mod->slots[i]); |
| /* look down the slot info table */ |
| PK11_LoadSlotList(mod->slots[i], mod->slotInfo, mod->slotInfoCount); |
| SECMOD_SetRootCerts(mod->slots[i], mod); |
| /* explicitly mark the internal slot as such if IsInternalKeySlot() |
| * is set */ |
| if (secmod_IsInternalKeySlot(mod) && (i == (mod->isFIPS ? 0 : 1))) { |
| pk11_SetInternalKeySlotIfFirst(mod->slots[i]); |
| } |
| } |
| mod->slotCount = slotCount; |
| mod->slotInfoCount = 0; |
| PORT_Free(slotIDs); |
| } |
| |
| mod->loaded = PR_TRUE; |
| mod->moduleID = nextModuleID++; |
| return SECSuccess; |
| fail2: |
| if (enforceAlreadyInitializedError || (!alreadyLoaded)) { |
| PK11_GETTAB(mod)->C_Finalize(NULL); |
| } |
| fail: |
| mod->functionList = NULL; |
| disableUnload = PR_GetEnvSecure("NSS_DISABLE_UNLOAD"); |
| if (library && !disableUnload) { |
| PR_UnloadLibrary(library); |
| } |
| return SECFailure; |
| } |
| |
| SECStatus |
| SECMOD_UnloadModule(SECMODModule *mod) |
| { |
| PRLibrary *library; |
| char *disableUnload = NULL; |
| |
| if (!mod->loaded) { |
| return SECFailure; |
| } |
| if (finalizeModules) { |
| if (mod->functionList && !mod->moduleDBOnly) { |
| PK11_GETTAB(mod)->C_Finalize(NULL); |
| } |
| } |
| mod->moduleID = 0; |
| mod->loaded = PR_FALSE; |
| |
| /* do we want the semantics to allow unloading the internal library? |
| * if not, we should change this to SECFailure and move it above the |
| * mod->loaded = PR_FALSE; */ |
| if (mod->internal && (mod->dllName == NULL)) { |
| #ifndef NSS_STATIC_SOFTOKEN |
| if (0 == PR_ATOMIC_DECREMENT(&softokenLoadCount)) { |
| if (softokenLib) { |
| disableUnload = PR_GetEnvSecure("NSS_DISABLE_UNLOAD"); |
| if (!disableUnload) { |
| #ifdef DEBUG |
| PRStatus status = PR_UnloadLibrary(softokenLib); |
| PORT_Assert(PR_SUCCESS == status); |
| #else |
| PR_UnloadLibrary(softokenLib); |
| #endif |
| } |
| softokenLib = NULL; |
| } |
| loadSoftokenOnce = pristineCallOnce; |
| } |
| #endif |
| return SECSuccess; |
| } |
| |
| library = (PRLibrary *)mod->library; |
| /* paranoia */ |
| if (library == NULL) { |
| return SECFailure; |
| } |
| |
| disableUnload = PR_GetEnvSecure("NSS_DISABLE_UNLOAD"); |
| if (!disableUnload) { |
| PR_UnloadLibrary(library); |
| } |
| return SECSuccess; |
| } |
| |
| void |
| nss_DumpModuleLog(void) |
| { |
| #ifdef DEBUG_MODULE |
| if (modToDBG) { |
| print_final_statistics(); |
| } |
| #endif |
| } |