Bug 1914685 - [wpt-sync] Update web-platform-tests to 26c88095d89792c886494e30c85aca3...
[gecko.git] / security / nss / lib / pk11wrap / pk11load.c
blob119c8c5120c9a1e2577eda1db1d79e2b6ae19a71
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 /*
5 * The following handles the loading, unloading and management of
6 * various PCKS #11 modules
7 */
8 #define FORCE_PR_LOG 1
9 #include "base.h"
10 #include "seccomon.h"
11 #include "pkcs11.h"
12 #include "secmod.h"
13 #include "prlink.h"
14 #include "pk11func.h"
15 #include "secmodi.h"
16 #include "secmodti.h"
17 #include "nssilock.h"
18 #include "secerr.h"
19 #include "prenv.h"
20 #include "utilpars.h"
21 #include "prio.h"
22 #include "prprf.h"
23 #include <stdio.h>
24 #include "prsystem.h"
26 #define DEBUG_MODULE 1
28 #ifdef DEBUG_MODULE
29 static char *modToDBG = NULL;
31 #include "debug_module.c"
32 #endif
34 /* build the PKCS #11 2.01 lock files */
35 CK_RV PR_CALLBACK
36 secmodCreateMutext(CK_VOID_PTR_PTR pmutex)
38 *pmutex = (CK_VOID_PTR)PZ_NewLock(nssILockOther);
39 if (*pmutex)
40 return CKR_OK;
41 return CKR_HOST_MEMORY;
44 CK_RV PR_CALLBACK
45 secmodDestroyMutext(CK_VOID_PTR mutext)
47 PZ_DestroyLock((PZLock *)mutext);
48 return CKR_OK;
51 CK_RV PR_CALLBACK
52 secmodLockMutext(CK_VOID_PTR mutext)
54 PZ_Lock((PZLock *)mutext);
55 return CKR_OK;
58 CK_RV PR_CALLBACK
59 secmodUnlockMutext(CK_VOID_PTR mutext)
61 PZ_Unlock((PZLock *)mutext);
62 return CKR_OK;
65 static SECMODModuleID nextModuleID = 1;
66 static const CK_C_INITIALIZE_ARGS secmodLockFunctions = {
67 secmodCreateMutext, secmodDestroyMutext, secmodLockMutext,
68 secmodUnlockMutext, CKF_LIBRARY_CANT_CREATE_OS_THREADS | CKF_OS_LOCKING_OK,
69 NULL
71 static const CK_C_INITIALIZE_ARGS secmodNoLockArgs = {
72 NULL, NULL, NULL, NULL,
73 CKF_LIBRARY_CANT_CREATE_OS_THREADS, NULL
76 static PRBool loadSingleThreadedModules = PR_TRUE;
77 static PRBool enforceAlreadyInitializedError = PR_TRUE;
78 static PRBool finalizeModules = PR_TRUE;
80 /* set global options for NSS PKCS#11 module loader */
81 SECStatus
82 pk11_setGlobalOptions(PRBool noSingleThreadedModules,
83 PRBool allowAlreadyInitializedModules,
84 PRBool dontFinalizeModules)
86 if (noSingleThreadedModules) {
87 loadSingleThreadedModules = PR_FALSE;
88 } else {
89 loadSingleThreadedModules = PR_TRUE;
91 if (allowAlreadyInitializedModules) {
92 enforceAlreadyInitializedError = PR_FALSE;
93 } else {
94 enforceAlreadyInitializedError = PR_TRUE;
96 if (dontFinalizeModules) {
97 finalizeModules = PR_FALSE;
98 } else {
99 finalizeModules = PR_TRUE;
101 return SECSuccess;
104 PRBool
105 pk11_getFinalizeModulesOption(void)
107 return finalizeModules;
111 * Allow specification loading the same module more than once at init time.
112 * This enables 2 things.
114 * 1) we can load additional databases by manipulating secmod.db/pkcs11.txt.
115 * 2) we can handle the case where some library has already initialized NSS
116 * before the main application.
118 * oldModule is the module we have already initialized.
119 * char *modulespec is the full module spec for the library we want to
120 * initialize.
122 static SECStatus
123 secmod_handleReload(SECMODModule *oldModule, SECMODModule *newModule)
125 PK11SlotInfo *slot;
126 char *modulespec;
127 char *newModuleSpec;
128 char **children;
129 CK_SLOT_ID *ids;
130 SECMODConfigList *conflist = NULL;
131 SECStatus rv = SECFailure;
132 int count = 0;
134 /* first look for tokens= key words from the module spec */
135 modulespec = newModule->libraryParams;
136 newModuleSpec = secmod_ParseModuleSpecForTokens(PR_TRUE,
137 newModule->isFIPS, modulespec, &children, &ids);
138 if (!newModuleSpec) {
139 return SECFailure;
143 * We are now trying to open a new slot on an already loaded module.
144 * If that slot represents a cert/key database, we don't want to open
145 * multiple copies of that same database. Unfortunately we understand
146 * the softoken flags well enough to be able to do this, so we can only get
147 * the list of already loaded databases if we are trying to open another
148 * internal module.
150 if (oldModule->internal) {
151 conflist = secmod_GetConfigList(oldModule->isFIPS,
152 oldModule->libraryParams, &count);
155 /* don't open multiple of the same db */
156 if (conflist && secmod_MatchConfigList(newModuleSpec, conflist, count)) {
157 rv = SECSuccess;
158 goto loser;
160 slot = SECMOD_OpenNewSlot(oldModule, newModuleSpec);
161 if (slot) {
162 int newID;
163 char **thisChild;
164 CK_SLOT_ID *thisID;
165 char *oldModuleSpec;
167 if (secmod_IsInternalKeySlot(newModule)) {
168 pk11_SetInternalKeySlotIfFirst(slot);
170 newID = slot->slotID;
171 PK11_FreeSlot(slot);
172 for (thisChild = children, thisID = ids; thisChild && *thisChild;
173 thisChild++, thisID++) {
174 if (conflist &&
175 secmod_MatchConfigList(*thisChild, conflist, count)) {
176 *thisID = (CK_SLOT_ID)-1;
177 continue;
179 slot = SECMOD_OpenNewSlot(oldModule, *thisChild);
180 if (slot) {
181 *thisID = slot->slotID;
182 PK11_FreeSlot(slot);
183 } else {
184 *thisID = (CK_SLOT_ID)-1;
188 /* update the old module initialization string in case we need to
189 * shutdown and reinit the whole mess (this is rare, but can happen
190 * when trying to stop smart card insertion/removal threads)... */
191 oldModuleSpec = secmod_MkAppendTokensList(oldModule->arena,
192 oldModule->libraryParams, newModuleSpec, newID,
193 children, ids);
194 if (oldModuleSpec) {
195 oldModule->libraryParams = oldModuleSpec;
198 rv = SECSuccess;
201 loser:
202 secmod_FreeChildren(children, ids);
203 PORT_Free(newModuleSpec);
204 if (conflist) {
205 secmod_FreeConfigList(conflist, count);
207 return rv;
211 * collect the steps we need to initialize a module in a single function
213 SECStatus
214 secmod_ModuleInit(SECMODModule *mod, SECMODModule **reload,
215 PRBool *alreadyLoaded)
217 CK_C_INITIALIZE_ARGS moduleArgs;
218 CK_VOID_PTR pInitArgs;
219 CK_RV crv;
221 if (reload) {
222 *reload = NULL;
225 if (!mod || !alreadyLoaded) {
226 PORT_SetError(SEC_ERROR_INVALID_ARGS);
227 return SECFailure;
230 if (mod->libraryParams == NULL) {
231 if (mod->isThreadSafe) {
232 pInitArgs = (void *)&secmodLockFunctions;
233 } else {
234 pInitArgs = NULL;
236 } else {
237 if (mod->isThreadSafe) {
238 moduleArgs = secmodLockFunctions;
239 } else {
240 moduleArgs = secmodNoLockArgs;
242 moduleArgs.LibraryParameters = (void *)mod->libraryParams;
243 pInitArgs = &moduleArgs;
245 crv = PK11_GETTAB(mod)->C_Initialize(pInitArgs);
246 if (CKR_CRYPTOKI_ALREADY_INITIALIZED == crv) {
247 SECMODModule *oldModule = NULL;
249 /* Library has already been loaded once, if caller expects it, and it
250 * has additional configuration, try reloading it as well. */
251 if (reload != NULL && mod->libraryParams) {
252 oldModule = secmod_FindModuleByFuncPtr(mod->functionList);
254 /* Library has been loaded by NSS. It means it may be capable of
255 * reloading */
256 if (oldModule) {
257 SECStatus rv;
258 rv = secmod_handleReload(oldModule, mod);
259 if (rv == SECSuccess) {
260 /* This module should go away soon, since we've
261 * simply expanded the slots on the old module.
262 * When it goes away, it should not Finalize since
263 * that will close our old module as well. Setting
264 * the function list to NULL will prevent that close */
265 mod->functionList = NULL;
266 *reload = oldModule;
267 return SECSuccess;
269 SECMOD_DestroyModule(oldModule);
271 /* reload not possible, fall back to old semantics */
272 if (!enforceAlreadyInitializedError) {
273 *alreadyLoaded = PR_TRUE;
274 return SECSuccess;
277 if (crv != CKR_OK) {
278 if (!mod->isThreadSafe ||
279 crv == CKR_NSS_CERTDB_FAILED ||
280 crv == CKR_NSS_KEYDB_FAILED) {
281 PORT_SetError(PK11_MapError(crv));
282 return SECFailure;
284 /* If we had attempted to init a single threaded module "with"
285 * parameters and it failed, should we retry "without" parameters?
286 * (currently we don't retry in this scenario) */
288 if (!loadSingleThreadedModules) {
289 PORT_SetError(SEC_ERROR_INCOMPATIBLE_PKCS11);
290 return SECFailure;
292 /* If we arrive here, the module failed a ThreadSafe init. */
293 mod->isThreadSafe = PR_FALSE;
294 if (!mod->libraryParams) {
295 pInitArgs = NULL;
296 } else {
297 moduleArgs = secmodNoLockArgs;
298 moduleArgs.LibraryParameters = (void *)mod->libraryParams;
299 pInitArgs = &moduleArgs;
301 crv = PK11_GETTAB(mod)->C_Initialize(pInitArgs);
302 if ((CKR_CRYPTOKI_ALREADY_INITIALIZED == crv) &&
303 (!enforceAlreadyInitializedError)) {
304 *alreadyLoaded = PR_TRUE;
305 return SECSuccess;
307 if (crv != CKR_OK) {
308 PORT_SetError(PK11_MapError(crv));
309 return SECFailure;
312 return SECSuccess;
316 * set the hasRootCerts flags in the module so it can be stored back
317 * into the database.
319 void
320 SECMOD_SetRootCerts(PK11SlotInfo *slot, SECMODModule *mod)
322 PK11PreSlotInfo *psi = NULL;
323 int i;
325 if (slot->hasRootCerts) {
326 for (i = 0; i < mod->slotInfoCount; i++) {
327 if (slot->slotID == mod->slotInfo[i].slotID) {
328 psi = &mod->slotInfo[i];
329 break;
332 if (psi == NULL) {
333 /* allocate more slots */
334 PK11PreSlotInfo *psi_list = (PK11PreSlotInfo *)
335 PORT_ArenaAlloc(mod->arena,
336 (mod->slotInfoCount + 1) * sizeof(PK11PreSlotInfo));
337 /* copy the old ones */
338 if (mod->slotInfoCount > 0) {
339 PORT_Memcpy(psi_list, mod->slotInfo,
340 (mod->slotInfoCount) * sizeof(PK11PreSlotInfo));
342 /* assign psi to the last new slot */
343 psi = &psi_list[mod->slotInfoCount];
344 psi->slotID = slot->slotID;
345 psi->askpw = 0;
346 psi->timeout = 0;
347 psi->defaultFlags = 0;
349 /* increment module count & store new list */
350 mod->slotInfo = psi_list;
351 mod->slotInfoCount++;
353 psi->hasRootCerts = 1;
357 #ifndef NSS_STATIC_SOFTOKEN
358 static const char *my_shlib_name =
359 SHLIB_PREFIX "nss" NSS_SHLIB_VERSION "." SHLIB_SUFFIX;
360 static const char *softoken_shlib_name =
361 SHLIB_PREFIX "softokn" SOFTOKEN_SHLIB_VERSION "." SHLIB_SUFFIX;
362 static const PRCallOnceType pristineCallOnce;
363 static PRCallOnceType loadSoftokenOnce;
364 static PRLibrary *softokenLib;
365 static PRInt32 softokenLoadCount;
367 /* This function must be run only once. */
368 /* determine if hybrid platform, then actually load the DSO. */
369 static PRStatus
370 softoken_LoadDSO(void)
372 PRLibrary *handle;
374 handle = PORT_LoadLibraryFromOrigin(my_shlib_name,
375 (PRFuncPtr)&softoken_LoadDSO,
376 softoken_shlib_name);
377 if (handle) {
378 softokenLib = handle;
379 return PR_SUCCESS;
381 return PR_FAILURE;
383 #else
384 CK_RV NSC_GetInterface(CK_UTF8CHAR_PTR pInterfaceName,
385 CK_VERSION_PTR pVersion,
386 CK_INTERFACE_PTR_PTR *ppInterface, CK_FLAGS flags);
387 char **NSC_ModuleDBFunc(unsigned long function, char *parameters, void *args);
388 #endif
391 * load a new module into our address space and initialize it.
393 SECStatus
394 secmod_LoadPKCS11Module(SECMODModule *mod, SECMODModule **oldModule)
396 PRLibrary *library = NULL;
397 CK_C_GetInterface ientry = NULL;
398 CK_C_GetFunctionList fentry = NULL;
399 CK_INFO info;
400 CK_ULONG slotCount = 0;
401 SECStatus rv;
402 PRBool alreadyLoaded = PR_FALSE;
403 char *disableUnload = NULL;
404 #ifndef NSS_STATIC_SOFTOKEN
405 const char *nss_interface;
406 const char *nss_function;
407 #endif
408 CK_INTERFACE_PTR interface;
410 if (mod->loaded)
411 return SECSuccess;
413 mod->fipsIndicator = NULL;
415 /* internal modules get loaded from their internal list */
416 if (mod->internal && (mod->dllName == NULL)) {
417 #ifdef NSS_STATIC_SOFTOKEN
418 ientry = (CK_C_GetInterface)NSC_GetInterface;
419 #else
421 * Loads softoken as a dynamic library,
422 * even though the rest of NSS assumes this as the "internal" module.
424 if (!softokenLib &&
425 PR_SUCCESS != PR_CallOnce(&loadSoftokenOnce, &softoken_LoadDSO))
426 return SECFailure;
428 PR_ATOMIC_INCREMENT(&softokenLoadCount);
430 if (mod->isFIPS) {
431 nss_interface = "FC_GetInterface";
432 nss_function = "FC_GetFunctionList";
433 } else {
434 nss_interface = "NSC_GetInterface";
435 nss_function = "NSC_GetFunctionList";
438 ientry = (CK_C_GetInterface)
439 PR_FindSymbol(softokenLib, nss_interface);
440 if (!ientry) {
441 fentry = (CK_C_GetFunctionList)
442 PR_FindSymbol(softokenLib, nss_function);
443 if (!fentry) {
444 return SECFailure;
447 #endif
449 if (mod->isModuleDB) {
450 mod->moduleDBFunc = (CK_C_GetFunctionList)
451 #ifdef NSS_STATIC_SOFTOKEN
452 NSC_ModuleDBFunc;
453 #else
454 PR_FindSymbol(softokenLib, "NSC_ModuleDBFunc");
455 #endif
458 if (mod->moduleDBOnly) {
459 mod->loaded = PR_TRUE;
460 return SECSuccess;
462 } else {
463 /* Not internal, load the DLL and look up C_GetFunctionList */
464 if (mod->dllName == NULL) {
465 return SECFailure;
468 /* load the library. If this succeeds, then we have to remember to
469 * unload the library if anything goes wrong from here on out...
471 #if defined(_WIN32)
472 if (nssUTF8_Length(mod->dllName, NULL)) {
473 wchar_t *dllNameWide = _NSSUTIL_UTF8ToWide(mod->dllName);
474 if (dllNameWide) {
475 PRLibSpec libSpec;
476 libSpec.type = PR_LibSpec_PathnameU;
477 libSpec.value.pathname_u = dllNameWide;
478 library = PR_LoadLibraryWithFlags(libSpec, 0);
479 PORT_Free(dllNameWide);
482 if (library == NULL) {
483 // fallback to system code page
484 library = PR_LoadLibrary(mod->dllName);
486 #else
487 library = PR_LoadLibrary(mod->dllName);
488 #endif // defined(_WIN32)
489 mod->library = (void *)library;
491 if (library == NULL) {
492 return SECFailure;
496 * now we need to get the entry point to find the function pointers
498 if (!mod->moduleDBOnly) {
499 ientry = (CK_C_GetInterface)
500 PR_FindSymbol(library, "C_GetInterface");
501 if (!ientry) {
502 fentry = (CK_C_GetFunctionList)
503 PR_FindSymbol(library, "C_GetFunctionList");
506 if (mod->isModuleDB) {
507 mod->moduleDBFunc = (void *)
508 PR_FindSymbol(library, "NSS_ReturnModuleSpecData");
510 if (mod->moduleDBFunc == NULL)
511 mod->isModuleDB = PR_FALSE;
512 if ((ientry == NULL) && (fentry == NULL)) {
513 if (mod->isModuleDB) {
514 mod->loaded = PR_TRUE;
515 mod->moduleDBOnly = PR_TRUE;
516 return SECSuccess;
518 PR_UnloadLibrary(library);
519 return SECFailure;
524 * We need to get the function list
526 if (ientry) {
527 /* we first try to get a FORK_SAFE interface */
528 if ((*ientry)((CK_UTF8CHAR_PTR) "PKCS 11", NULL, &interface,
529 CKF_INTERFACE_FORK_SAFE) != CKR_OK) {
530 /* one is not appearantly available, get a non-fork safe version */
531 if ((*ientry)((CK_UTF8CHAR_PTR) "PKCS 11", NULL, &interface, 0) != CKR_OK) {
532 goto fail;
535 mod->functionList = interface->pFunctionList;
536 mod->flags = interface->flags;
537 /* if we have a fips indicator, grab it */
538 if ((*ientry)((CK_UTF8CHAR_PTR) "Vendor NSS FIPS Interface", NULL,
539 &interface, 0) == CKR_OK) {
540 mod->fipsIndicator = ((CK_NSS_FIPS_FUNCTIONS *)(interface->pFunctionList))->NSC_NSSGetFIPSStatus;
542 } else {
543 if ((*fentry)((CK_FUNCTION_LIST_PTR *)&mod->functionList) != CKR_OK)
544 goto fail;
545 mod->flags = 0;
548 #ifdef DEBUG_MODULE
549 modToDBG = PR_GetEnvSecure("NSS_DEBUG_PKCS11_MODULE");
550 if (modToDBG && strcmp(mod->commonName, modToDBG) == 0) {
551 mod->functionList = (void *)nss_InsertDeviceLog(
552 (CK_FUNCTION_LIST_3_0_PTR)mod->functionList);
554 #endif
556 /* This test operation makes sure our locking system is
557 * consistent even if we are using non-thread safe tokens by
558 * simulating unsafe tokens with safe ones. */
559 mod->isThreadSafe = !PR_GetEnvSecure("NSS_FORCE_TOKEN_LOCK");
561 /* Now we initialize the module */
562 rv = secmod_ModuleInit(mod, oldModule, &alreadyLoaded);
563 if (rv != SECSuccess) {
564 goto fail;
567 /* module has been reloaded, this module itself is done,
568 * return to the caller */
569 if (mod->functionList == NULL) {
570 mod->loaded = PR_TRUE; /* technically the module is loaded.. */
571 return SECSuccess;
574 /* check the version number */
575 if (PK11_GETTAB(mod)->C_GetInfo(&info) != CKR_OK)
576 goto fail2;
577 if (info.cryptokiVersion.major < 2)
578 goto fail2;
579 /* all 2.0 are a priori *not* thread safe */
580 if ((info.cryptokiVersion.major == 2) && (info.cryptokiVersion.minor < 1)) {
581 if (!loadSingleThreadedModules) {
582 PORT_SetError(SEC_ERROR_INCOMPATIBLE_PKCS11);
583 goto fail2;
584 } else {
585 mod->isThreadSafe = PR_FALSE;
588 mod->cryptokiVersion = info.cryptokiVersion;
590 /* If we don't have a common name, get it from the PKCS 11 module */
591 if ((mod->commonName == NULL) || (mod->commonName[0] == 0)) {
592 mod->commonName = PK11_MakeString(mod->arena, NULL,
593 (char *)info.libraryDescription, sizeof(info.libraryDescription));
594 if (mod->commonName == NULL)
595 goto fail2;
598 /* initialize the Slots */
599 if (PK11_GETTAB(mod)->C_GetSlotList(CK_FALSE, NULL, &slotCount) == CKR_OK) {
600 CK_SLOT_ID *slotIDs;
601 int i;
602 CK_RV crv;
604 mod->slots = (PK11SlotInfo **)PORT_ArenaAlloc(mod->arena,
605 sizeof(PK11SlotInfo *) * slotCount);
606 if (mod->slots == NULL)
607 goto fail2;
609 slotIDs = (CK_SLOT_ID *)PORT_Alloc(sizeof(CK_SLOT_ID) * slotCount);
610 if (slotIDs == NULL) {
611 goto fail2;
613 crv = PK11_GETTAB(mod)->C_GetSlotList(CK_FALSE, slotIDs, &slotCount);
614 if (crv != CKR_OK) {
615 PORT_Free(slotIDs);
616 goto fail2;
619 /* Initialize each slot */
620 for (i = 0; i < (int)slotCount; i++) {
621 mod->slots[i] = PK11_NewSlotInfo(mod);
622 PK11_InitSlot(mod, slotIDs[i], mod->slots[i]);
623 /* look down the slot info table */
624 PK11_LoadSlotList(mod->slots[i], mod->slotInfo, mod->slotInfoCount);
625 SECMOD_SetRootCerts(mod->slots[i], mod);
626 /* explicitly mark the internal slot as such if IsInternalKeySlot()
627 * is set */
628 if (secmod_IsInternalKeySlot(mod) && (i == (mod->isFIPS ? 0 : 1))) {
629 pk11_SetInternalKeySlotIfFirst(mod->slots[i]);
632 mod->slotCount = slotCount;
633 mod->slotInfoCount = 0;
634 PORT_Free(slotIDs);
637 mod->loaded = PR_TRUE;
638 mod->moduleID = nextModuleID++;
639 return SECSuccess;
640 fail2:
641 if (enforceAlreadyInitializedError || (!alreadyLoaded)) {
642 PK11_GETTAB(mod)->C_Finalize(NULL);
644 fail:
645 mod->functionList = NULL;
646 disableUnload = PR_GetEnvSecure("NSS_DISABLE_UNLOAD");
647 if (library && !disableUnload) {
648 PR_UnloadLibrary(library);
650 return SECFailure;
653 SECStatus
654 SECMOD_UnloadModule(SECMODModule *mod)
656 PRLibrary *library;
657 char *disableUnload = NULL;
659 if (!mod->loaded) {
660 return SECFailure;
662 if (finalizeModules) {
663 if (mod->functionList && !mod->moduleDBOnly) {
664 PK11_GETTAB(mod)->C_Finalize(NULL);
667 mod->moduleID = 0;
668 mod->loaded = PR_FALSE;
670 /* do we want the semantics to allow unloading the internal library?
671 * if not, we should change this to SECFailure and move it above the
672 * mod->loaded = PR_FALSE; */
673 if (mod->internal && (mod->dllName == NULL)) {
674 #ifndef NSS_STATIC_SOFTOKEN
675 if (0 == PR_ATOMIC_DECREMENT(&softokenLoadCount)) {
676 if (softokenLib) {
677 disableUnload = PR_GetEnvSecure("NSS_DISABLE_UNLOAD");
678 if (!disableUnload) {
679 #ifdef DEBUG
680 PRStatus status = PR_UnloadLibrary(softokenLib);
681 PORT_Assert(PR_SUCCESS == status);
682 #else
683 PR_UnloadLibrary(softokenLib);
684 #endif
686 softokenLib = NULL;
688 loadSoftokenOnce = pristineCallOnce;
690 #endif
691 return SECSuccess;
694 library = (PRLibrary *)mod->library;
695 /* paranoia */
696 if (library == NULL) {
697 return SECFailure;
700 disableUnload = PR_GetEnvSecure("NSS_DISABLE_UNLOAD");
701 if (!disableUnload) {
702 PR_UnloadLibrary(library);
704 return SECSuccess;
707 void
708 nss_DumpModuleLog(void)
710 #ifdef DEBUG_MODULE
711 if (modToDBG) {
712 print_final_statistics();
714 #endif