/* 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/. */
/*
 * Initialize the PCKS 11 subsystem
 */
#include "seccomon.h"
#include "secmod.h"
#include "nssilock.h"
#include "secmodi.h"
#include "secmodti.h"
#include "pk11func.h"
#include "pki3hack.h"
#include "secerr.h"
#include "dev.h"
#include "utilpars.h"

/* these are for displaying error messages */

static SECMODModuleList *modules = NULL;
static SECMODModuleList *modulesDB = NULL;
static SECMODModuleList *modulesUnload = NULL;
static SECMODModule *internalModule = NULL;
static SECMODModule *defaultDBModule = NULL;
static SECMODModule *pendingModule = NULL;
static SECMODListLock *moduleLock = NULL;

int secmod_PrivateModuleCount = 0;

extern const PK11DefaultArrayEntry PK11_DefaultArray[];
extern const int num_pk11_default_mechanisms;

void
SECMOD_Init()
{
    /* don't initialize twice */
    if (moduleLock)
        return;

    moduleLock = SECMOD_NewListLock();
    PK11_InitSlotLists();
}

SECStatus
SECMOD_Shutdown()
{
    /* destroy the lock */
    if (moduleLock) {
        SECMOD_DestroyListLock(moduleLock);
        moduleLock = NULL;
    }
    /* free the internal module */
    if (internalModule) {
        SECMOD_DestroyModule(internalModule);
        internalModule = NULL;
    }

    /* free the default database module */
    if (defaultDBModule) {
        SECMOD_DestroyModule(defaultDBModule);
        defaultDBModule = NULL;
    }

    /* destroy the list */
    if (modules) {
        SECMOD_DestroyModuleList(modules);
        modules = NULL;
    }

    if (modulesDB) {
        SECMOD_DestroyModuleList(modulesDB);
        modulesDB = NULL;
    }

    if (modulesUnload) {
        SECMOD_DestroyModuleList(modulesUnload);
        modulesUnload = NULL;
    }

    /* make all the slots and the lists go away */
    PK11_DestroySlotLists();

    nss_DumpModuleLog();

#ifdef DEBUG
    if (PR_GetEnvSecure("NSS_STRICT_SHUTDOWN")) {
        PORT_Assert(secmod_PrivateModuleCount == 0);
    }
#endif
    if (secmod_PrivateModuleCount) {
        PORT_SetError(SEC_ERROR_BUSY);
        return SECFailure;
    }
    return SECSuccess;
}

/*
 * retrieve the internal module
 */
SECMODModule *
SECMOD_GetInternalModule(void)
{
    return internalModule;
}

SECStatus
secmod_AddModuleToList(SECMODModuleList **moduleList, SECMODModule *newModule)
{
    SECMODModuleList *mlp, *newListElement, *last = NULL;

    newListElement = SECMOD_NewModuleListElement();
    if (newListElement == NULL) {
        return SECFailure;
    }

    newListElement->module = SECMOD_ReferenceModule(newModule);

    SECMOD_GetWriteLock(moduleLock);
    /* Added it to the end (This is very inefficient, but Adding a module
     * on the fly should happen maybe 2-3 times through the life this program
     * on a given computer, and this list should be *SHORT*. */
    for (mlp = *moduleList; mlp != NULL; mlp = mlp->next) {
        last = mlp;
    }

    if (last == NULL) {
        *moduleList = newListElement;
    } else {
        SECMOD_AddList(last, newListElement, NULL);
    }
    SECMOD_ReleaseWriteLock(moduleLock);
    return SECSuccess;
}

SECStatus
SECMOD_AddModuleToList(SECMODModule *newModule)
{
    if (newModule->internal && !internalModule) {
        internalModule = SECMOD_ReferenceModule(newModule);
    }
    return secmod_AddModuleToList(&modules, newModule);
}

SECStatus
SECMOD_AddModuleToDBOnlyList(SECMODModule *newModule)
{
    if (defaultDBModule && SECMOD_GetDefaultModDBFlag(newModule)) {
        SECMOD_DestroyModule(defaultDBModule);
        defaultDBModule = SECMOD_ReferenceModule(newModule);
    } else if (defaultDBModule == NULL) {
        defaultDBModule = SECMOD_ReferenceModule(newModule);
    }
    return secmod_AddModuleToList(&modulesDB, newModule);
}

SECStatus
SECMOD_AddModuleToUnloadList(SECMODModule *newModule)
{
    return secmod_AddModuleToList(&modulesUnload, newModule);
}

/*
 * get the list of PKCS11 modules that are available.
 */
SECMODModuleList *
SECMOD_GetDefaultModuleList()
{
    return modules;
}
SECMODModuleList *
SECMOD_GetDeadModuleList()
{
    return modulesUnload;
}
SECMODModuleList *
SECMOD_GetDBModuleList()
{
    return modulesDB;
}

/*
 * This lock protects the global module lists.
 * it also protects changes to the slot array (module->slots[]) and slot count
 * (module->slotCount) in each module. It is a read/write lock with multiple
 * readers or one writer. Writes are uncommon.
 * Because of legacy considerations protection of the slot array and count is
 * only necessary in applications if the application calls
 * SECMOD_UpdateSlotList() or SECMOD_WaitForAnyTokenEvent(), though all new
 * applications are encouraged to acquire this lock when reading the
 * slot array information directly.
 */
SECMODListLock *
SECMOD_GetDefaultModuleListLock()
{
    return moduleLock;
}

/*
 * find a module by name, and add a reference to it.
 * return that module.
 */
SECMODModule *
SECMOD_FindModule(const char *name)
{
    SECMODModuleList *mlp;
    SECMODModule *module = NULL;

    if (!moduleLock) {
        PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
        return module;
    }
    SECMOD_GetReadLock(moduleLock);
    for (mlp = modules; mlp != NULL; mlp = mlp->next) {
        if (PORT_Strcmp(name, mlp->module->commonName) == 0) {
            module = mlp->module;
            SECMOD_ReferenceModule(module);
            break;
        }
    }
    if (module) {
        goto found;
    }
    for (mlp = modulesUnload; mlp != NULL; mlp = mlp->next) {
        if (PORT_Strcmp(name, mlp->module->commonName) == 0) {
            module = mlp->module;
            SECMOD_ReferenceModule(module);
            break;
        }
    }

found:
    SECMOD_ReleaseReadLock(moduleLock);

    return module;
}

/*
 * find a module by ID, and add a reference to it.
 * return that module.
 */
SECMODModule *
SECMOD_FindModuleByID(SECMODModuleID id)
{
    SECMODModuleList *mlp;
    SECMODModule *module = NULL;

    if (!moduleLock) {
        PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
        return module;
    }
    SECMOD_GetReadLock(moduleLock);
    for (mlp = modules; mlp != NULL; mlp = mlp->next) {
        if (id == mlp->module->moduleID) {
            module = mlp->module;
            SECMOD_ReferenceModule(module);
            break;
        }
    }
    SECMOD_ReleaseReadLock(moduleLock);
    if (module == NULL) {
        PORT_SetError(SEC_ERROR_NO_MODULE);
    }
    return module;
}

/*
 * find the function pointer.
 */
SECMODModule *
secmod_FindModuleByFuncPtr(void *funcPtr)
{
    SECMODModuleList *mlp;
    SECMODModule *module = NULL;

    SECMOD_GetReadLock(moduleLock);
    for (mlp = modules; mlp != NULL; mlp = mlp->next) {
        /* paranoia, shouldn't ever happen */
        if (!mlp->module) {
            continue;
        }
        if (funcPtr == mlp->module->functionList) {
            module = mlp->module;
            SECMOD_ReferenceModule(module);
            break;
        }
    }
    SECMOD_ReleaseReadLock(moduleLock);
    if (module == NULL) {
        PORT_SetError(SEC_ERROR_NO_MODULE);
    }
    return module;
}

/*
 * Find the Slot based on ID and the module.
 */
PK11SlotInfo *
SECMOD_FindSlotByID(SECMODModule *module, CK_SLOT_ID slotID)
{
    int i;
    PK11SlotInfo *slot = NULL;

    if (!moduleLock) {
        PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
        return slot;
    }
    SECMOD_GetReadLock(moduleLock);
    for (i = 0; i < module->slotCount; i++) {
        PK11SlotInfo *cSlot = module->slots[i];

        if (cSlot->slotID == slotID) {
            slot = PK11_ReferenceSlot(cSlot);
            break;
        }
    }
    SECMOD_ReleaseReadLock(moduleLock);

    if (slot == NULL) {
        PORT_SetError(SEC_ERROR_NO_SLOT_SELECTED);
    }
    return slot;
}

/*
 * lookup the Slot module based on it's module ID and slot ID.
 */
PK11SlotInfo *
SECMOD_LookupSlot(SECMODModuleID moduleID, CK_SLOT_ID slotID)
{
    SECMODModule *module;
    PK11SlotInfo *slot;

    module = SECMOD_FindModuleByID(moduleID);
    if (module == NULL)
        return NULL;

    slot = SECMOD_FindSlotByID(module, slotID);
    SECMOD_DestroyModule(module);
    return slot;
}

/*
 * find a module by name or module pointer and delete it off the module list.
 * optionally remove it from secmod.db.
 */
SECStatus
SECMOD_DeleteModuleEx(const char *name, SECMODModule *mod,
                      int *type, PRBool permdb)
{
    SECMODModuleList *mlp;
    SECMODModuleList **mlpp;
    SECStatus rv = SECFailure;

    if (!moduleLock) {
        PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
        return rv;
    }

    *type = SECMOD_EXTERNAL;

    SECMOD_GetWriteLock(moduleLock);
    for (mlpp = &modules, mlp = modules;
         mlp != NULL; mlpp = &mlp->next, mlp = *mlpp) {
        if ((name && (PORT_Strcmp(name, mlp->module->commonName) == 0)) ||
            mod == mlp->module) {
            /* don't delete the internal module */
            if (!mlp->module->internal) {
                SECMOD_RemoveList(mlpp, mlp);
                /* delete it after we release the lock */
                rv = STAN_RemoveModuleFromDefaultTrustDomain(mlp->module);
            } else if (mlp->module->isFIPS) {
                *type = SECMOD_FIPS;
            } else {
                *type = SECMOD_INTERNAL;
            }
            break;
        }
    }
    if (mlp) {
        goto found;
    }
    /* not on the internal list, check the unload list */
    for (mlpp = &modulesUnload, mlp = modulesUnload;
         mlp != NULL; mlpp = &mlp->next, mlp = *mlpp) {
        if ((name && (PORT_Strcmp(name, mlp->module->commonName) == 0)) ||
            mod == mlp->module) {
            /* don't delete the internal module */
            if (!mlp->module->internal) {
                SECMOD_RemoveList(mlpp, mlp);
                rv = SECSuccess;
            } else if (mlp->module->isFIPS) {
                *type = SECMOD_FIPS;
            } else {
                *type = SECMOD_INTERNAL;
            }
            break;
        }
    }
found:
    SECMOD_ReleaseWriteLock(moduleLock);

    if (rv == SECSuccess) {
        if (permdb) {
            SECMOD_DeletePermDB(mlp->module);
        }
        SECMOD_DestroyModuleListElement(mlp);
    }
    return rv;
}

/*
 * find a module by name and delete it off the module list
 */
SECStatus
SECMOD_DeleteModule(const char *name, int *type)
{
    return SECMOD_DeleteModuleEx(name, NULL, type, PR_TRUE);
}

/*
 * find a module by name and delete it off the module list
 */
SECStatus
SECMOD_DeleteInternalModule(const char *name)
{
    SECMODModuleList *mlp;
    SECMODModuleList **mlpp;
    SECStatus rv = SECFailure;

    if (pendingModule) {
        PORT_SetError(SEC_ERROR_MODULE_STUCK);
        return rv;
    }
    if (!moduleLock) {
        PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
        return rv;
    }

    SECMOD_GetWriteLock(moduleLock);
    for (mlpp = &modules, mlp = modules;
         mlp != NULL; mlpp = &mlp->next, mlp = *mlpp) {
        if (PORT_Strcmp(name, mlp->module->commonName) == 0) {
            /* don't delete the internal module */
            if (mlp->module->internal) {
                SECMOD_RemoveList(mlpp, mlp);
                rv = STAN_RemoveModuleFromDefaultTrustDomain(mlp->module);
            }
            break;
        }
    }
    SECMOD_ReleaseWriteLock(moduleLock);

    if (rv == SECSuccess) {
        SECMODModule *newModule, *oldModule;

        if (mlp->module->isFIPS) {
            newModule = SECMOD_CreateModule(NULL, SECMOD_INT_NAME,
                                            NULL, SECMOD_INT_FLAGS);
        } else {
            newModule = SECMOD_CreateModule(NULL, SECMOD_FIPS_NAME,
                                            NULL, SECMOD_FIPS_FLAGS);
        }
        if (newModule) {
            PK11SlotInfo *slot;
            newModule->libraryParams =
                PORT_ArenaStrdup(newModule->arena, mlp->module->libraryParams);
            /* if an explicit internal key slot has been set, reset it */
            slot = pk11_SwapInternalKeySlot(NULL);
            if (slot) {
                secmod_SetInternalKeySlotFlag(newModule, PR_TRUE);
            }
            rv = SECMOD_AddModule(newModule);
            if (rv != SECSuccess) {
                /* load failed, restore the internal key slot */
                pk11_SetInternalKeySlot(slot);
                SECMOD_DestroyModule(newModule);
                newModule = NULL;
            }
            /* free the old explicit internal key slot, we now have a new one */
            if (slot) {
                PK11_FreeSlot(slot);
            }
        }
        if (newModule == NULL) {
            SECMODModuleList *last = NULL, *mlp2;
            /* we're in pretty deep trouble if this happens...Security
             * not going to work well... try to put the old module back on
             * the list */
            SECMOD_GetWriteLock(moduleLock);
            for (mlp2 = modules; mlp2 != NULL; mlp2 = mlp->next) {
                last = mlp2;
            }

            if (last == NULL) {
                modules = mlp;
            } else {
                SECMOD_AddList(last, mlp, NULL);
            }
            SECMOD_ReleaseWriteLock(moduleLock);
            return SECFailure;
        }
        pendingModule = oldModule = internalModule;
        internalModule = NULL;
        SECMOD_DestroyModule(oldModule);
        SECMOD_DeletePermDB(mlp->module);
        SECMOD_DestroyModuleListElement(mlp);
        internalModule = newModule; /* adopt the module */
    }
    return rv;
}

SECStatus
SECMOD_AddModule(SECMODModule *newModule)
{
    SECStatus rv;
    SECMODModule *oldModule;

    /* Test if a module w/ the same name already exists */
    /* and return SECWouldBlock if so. */
    /* We should probably add a new return value such as */
    /* SECDublicateModule, but to minimize ripples, I'll */
    /* give SECWouldBlock a new meaning */
    if ((oldModule = SECMOD_FindModule(newModule->commonName)) != NULL) {
        SECMOD_DestroyModule(oldModule);
        return SECWouldBlock;
        /* module already exists. */
    }

    rv = secmod_LoadPKCS11Module(newModule, NULL);
    if (rv != SECSuccess) {
        return rv;
    }

    if (newModule->parent == NULL) {
        newModule->parent = SECMOD_ReferenceModule(defaultDBModule);
    }

    SECMOD_AddPermDB(newModule);
    SECMOD_AddModuleToList(newModule);

    rv = STAN_AddModuleToDefaultTrustDomain(newModule);

    return rv;
}

PK11SlotInfo *
SECMOD_FindSlot(SECMODModule *module, const char *name)
{
    int i;
    char *string;
    PK11SlotInfo *retSlot = NULL;

    if (!moduleLock) {
        PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
        return retSlot;
    }
    SECMOD_GetReadLock(moduleLock);
    for (i = 0; i < module->slotCount; i++) {
        PK11SlotInfo *slot = module->slots[i];

        if (PK11_IsPresent(slot)) {
            string = PK11_GetTokenName(slot);
        } else {
            string = PK11_GetSlotName(slot);
        }
        if (PORT_Strcmp(name, string) == 0) {
            retSlot = PK11_ReferenceSlot(slot);
            break;
        }
    }
    SECMOD_ReleaseReadLock(moduleLock);

    if (retSlot == NULL) {
        PORT_SetError(SEC_ERROR_NO_SLOT_SELECTED);
    }
    return retSlot;
}

SECStatus
PK11_GetModInfo(SECMODModule *mod, CK_INFO *info)
{
    CK_RV crv;

    if (mod->functionList == NULL)
        return SECFailure;
    crv = PK11_GETTAB(mod)->C_GetInfo(info);
    if (crv != CKR_OK) {
        PORT_SetError(PK11_MapError(crv));
    }
    return (crv == CKR_OK) ? SECSuccess : SECFailure;
}

/* Determine if we have the FIP's module loaded as the default
 * module to trigger other bogus FIPS requirements in PKCS #12 and
 * SSL
 */
PRBool
PK11_IsFIPS(void)
{
    SECMODModule *mod = SECMOD_GetInternalModule();

    if (mod && mod->internal) {
        return mod->isFIPS;
    }

    return PR_FALSE;
}

/* combines NewModule() & AddModule */
/* give a string for the module name & the full-path for the dll, */
/* installs the PKCS11 module & update registry */
SECStatus
SECMOD_AddNewModuleEx(const char *moduleName, const char *dllPath,
                      unsigned long defaultMechanismFlags,
                      unsigned long cipherEnableFlags,
                      char *modparms, char *nssparms)
{
    SECMODModule *module;
    SECStatus result = SECFailure;
    int s, i;
    PK11SlotInfo *slot;

    PR_SetErrorText(0, NULL);
    if (!moduleLock) {
        PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
        return result;
    }

    module = SECMOD_CreateModule(dllPath, moduleName, modparms, nssparms);

    if (module == NULL) {
        return result;
    }

    if (module->dllName != NULL) {
        if (module->dllName[0] != 0) {
            result = SECMOD_AddModule(module);
            if (result == SECSuccess) {
                /* turn on SSL cipher enable flags */
                module->ssl[0] = cipherEnableFlags;

                SECMOD_GetReadLock(moduleLock);
                /* check each slot to turn on appropriate mechanisms */
                for (s = 0; s < module->slotCount; s++) {
                    slot = (module->slots)[s];
                    /* for each possible mechanism */
                    for (i = 0; i < num_pk11_default_mechanisms; i++) {
                        /* we are told to turn it on by default ? */
                        PRBool add =
                            (PK11_DefaultArray[i].flag & defaultMechanismFlags) ? PR_TRUE : PR_FALSE;
                        result = PK11_UpdateSlotAttribute(slot,
                                                          &(PK11_DefaultArray[i]), add);
                        if (result != SECSuccess) {
                            SECMOD_ReleaseReadLock(moduleLock);
                            SECMOD_DestroyModule(module);
                            return result;
                        }
                    } /* for each mechanism */
                    /* disable each slot if the defaultFlags say so */
                    if (defaultMechanismFlags & PK11_DISABLE_FLAG) {
                        PK11_UserDisableSlot(slot);
                    }
                } /* for each slot of this module */
                SECMOD_ReleaseReadLock(moduleLock);

                /* delete and re-add module in order to save changes
                 * to the module */
                result = SECMOD_UpdateModule(module);
            }
        }
    }
    SECMOD_DestroyModule(module);
    return result;
}

SECStatus
SECMOD_AddNewModule(const char *moduleName, const char *dllPath,
                    unsigned long defaultMechanismFlags,
                    unsigned long cipherEnableFlags)
{
    return SECMOD_AddNewModuleEx(moduleName, dllPath, defaultMechanismFlags,
                                 cipherEnableFlags,
                                 NULL, NULL); /* don't pass module or nss params */
}

SECStatus
SECMOD_UpdateModule(SECMODModule *module)
{
    SECStatus result;

    result = SECMOD_DeletePermDB(module);

    if (result == SECSuccess) {
        result = SECMOD_AddPermDB(module);
    }
    return result;
}

/* Public & Internal(Security Library)  representation of
 * encryption mechanism flags conversion */

/* Currently, the only difference is that internal representation
 * puts RANDOM_FLAG at bit 31 (Most-significant bit), but
 * public representation puts this bit at bit 28
 */
unsigned long
SECMOD_PubMechFlagstoInternal(unsigned long publicFlags)
{
    unsigned long internalFlags = publicFlags;

    if (publicFlags & PUBLIC_MECH_RANDOM_FLAG) {
        internalFlags &= ~PUBLIC_MECH_RANDOM_FLAG;
        internalFlags |= SECMOD_RANDOM_FLAG;
    }
    return internalFlags;
}

unsigned long
SECMOD_InternaltoPubMechFlags(unsigned long internalFlags)
{
    unsigned long publicFlags = internalFlags;

    if (internalFlags & SECMOD_RANDOM_FLAG) {
        publicFlags &= ~SECMOD_RANDOM_FLAG;
        publicFlags |= PUBLIC_MECH_RANDOM_FLAG;
    }
    return publicFlags;
}

/* Public & Internal(Security Library)  representation of */
/* cipher flags conversion */
/* Note: currently they are just stubs */
unsigned long
SECMOD_PubCipherFlagstoInternal(unsigned long publicFlags)
{
    return publicFlags;
}

unsigned long
SECMOD_InternaltoPubCipherFlags(unsigned long internalFlags)
{
    return internalFlags;
}

/* Funtion reports true if module of modType is installed/configured */
PRBool
SECMOD_IsModulePresent(unsigned long int pubCipherEnableFlags)
{
    PRBool result = PR_FALSE;
    SECMODModuleList *mods;

    if (!moduleLock) {
        PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
        return result;
    }
    SECMOD_GetReadLock(moduleLock);
    mods = SECMOD_GetDefaultModuleList();
    for (; mods != NULL; mods = mods->next) {
        if (mods->module->ssl[0] &
            SECMOD_PubCipherFlagstoInternal(pubCipherEnableFlags)) {
            result = PR_TRUE;
        }
    }

    SECMOD_ReleaseReadLock(moduleLock);
    return result;
}

/* create a new ModuleListElement */
SECMODModuleList *
SECMOD_NewModuleListElement(void)
{
    SECMODModuleList *newModList;

    newModList = (SECMODModuleList *)PORT_Alloc(sizeof(SECMODModuleList));
    if (newModList) {
        newModList->next = NULL;
        newModList->module = NULL;
    }
    return newModList;
}

/*
 * make a new reference to a module so It doesn't go away on us
 */
SECMODModule *
SECMOD_ReferenceModule(SECMODModule *module)
{
    PZ_Lock(module->refLock);
    PORT_Assert(module->refCount > 0);

    module->refCount++;
    PZ_Unlock(module->refLock);
    return module;
}

/* destroy an existing module */
void
SECMOD_DestroyModule(SECMODModule *module)
{
    PRBool willfree = PR_FALSE;
    int slotCount;
    int i;

    PZ_Lock(module->refLock);
    if (module->refCount-- == 1) {
        willfree = PR_TRUE;
    }
    PORT_Assert(willfree || (module->refCount > 0));
    PZ_Unlock(module->refLock);

    if (!willfree) {
        return;
    }

    if (module->parent != NULL) {
        SECMODModule *parent = module->parent;
        /* paranoia, don't loop forever if the modules are looped */
        module->parent = NULL;
        SECMOD_DestroyModule(parent);
    }

    /* slots can't really disappear until our module starts freeing them,
     * so this check is safe */
    slotCount = module->slotCount;
    if (slotCount == 0) {
        SECMOD_SlotDestroyModule(module, PR_FALSE);
        return;
    }

    /* now free all out slots, when they are done, they will cause the
     * module to disappear altogether */
    for (i = 0; i < slotCount; i++) {
        if (!module->slots[i]->disabled) {
            PK11_ClearSlotList(module->slots[i]);
        }
        PK11_FreeSlot(module->slots[i]);
    }
    /* WARNING: once the last slot has been freed is it possible (even likely)
     * that module is no more... touching it now is a good way to go south */
}

/* we can only get here if we've destroyed the module, or some one has
 * erroneously freed a slot that wasn't referenced. */
void
SECMOD_SlotDestroyModule(SECMODModule *module, PRBool fromSlot)
{
    PRBool willfree = PR_FALSE;
    if (fromSlot) {
        PORT_Assert(module->refCount == 0);
        PZ_Lock(module->refLock);
        if (module->slotCount-- == 1) {
            willfree = PR_TRUE;
        }
        PORT_Assert(willfree || (module->slotCount > 0));
        PZ_Unlock(module->refLock);
        if (!willfree)
            return;
    }

    if (module == pendingModule) {
        pendingModule = NULL;
    }

    if (module->loaded) {
        SECMOD_UnloadModule(module);
    }
    PZ_DestroyLock(module->refLock);
    PORT_FreeArena(module->arena, PR_FALSE);
    secmod_PrivateModuleCount--;
}

/* destroy a list element
 * this destroys a single element, and returns the next element
 * on the chain. It makes it easy to implement for loops to delete
 * the chain. It also make deleting a single element easy */
SECMODModuleList *
SECMOD_DestroyModuleListElement(SECMODModuleList *element)
{
    SECMODModuleList *next = element->next;

    if (element->module) {
        SECMOD_DestroyModule(element->module);
        element->module = NULL;
    }
    PORT_Free(element);
    return next;
}

/*
 * Destroy an entire module list
 */
void
SECMOD_DestroyModuleList(SECMODModuleList *list)
{
    SECMODModuleList *lp;

    for (lp = list; lp != NULL; lp = SECMOD_DestroyModuleListElement(lp))
        ;
}

PRBool
SECMOD_CanDeleteInternalModule(void)
{
    return (PRBool)(pendingModule == NULL);
}

/*
 * check to see if the module has added new slots. PKCS 11 v2.20 allows for
 * modules to add new slots, but never remove them. Slots cannot be added
 * between a call to C_GetSlotLlist(Flag, NULL, &count) and the subsequent
 * C_GetSlotList(flag, &data, &count) so that the array doesn't accidently
 * grow on the caller. It is permissible for the slots to increase between
 * successive calls with NULL to get the size.
 */
SECStatus
SECMOD_UpdateSlotList(SECMODModule *mod)
{
    CK_RV crv;
    CK_ULONG count;
    CK_ULONG i, oldCount;
    PRBool freeRef = PR_FALSE;
    void *mark = NULL;
    CK_ULONG *slotIDs = NULL;
    PK11SlotInfo **newSlots = NULL;
    PK11SlotInfo **oldSlots = NULL;

    if (!moduleLock) {
        PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
        return SECFailure;
    }

    /* C_GetSlotList is not a session function, make sure
     * calls are serialized */
    PZ_Lock(mod->refLock);
    freeRef = PR_TRUE;
    /* see if the number of slots have changed */
    crv = PK11_GETTAB(mod)->C_GetSlotList(PR_FALSE, NULL, &count);
    if (crv != CKR_OK) {
        PORT_SetError(PK11_MapError(crv));
        goto loser;
    }
    /* nothing new, blow out early, we want this function to be quick
     * and cheap in the normal case  */
    if (count == mod->slotCount) {
        PZ_Unlock(mod->refLock);
        return SECSuccess;
    }
    if (count < (CK_ULONG)mod->slotCount) {
        /* shouldn't happen with a properly functioning PKCS #11 module */
        PORT_SetError(SEC_ERROR_INCOMPATIBLE_PKCS11);
        goto loser;
    }

    /* get the new slot list */
    slotIDs = PORT_NewArray(CK_SLOT_ID, count);
    if (slotIDs == NULL) {
        goto loser;
    }

    crv = PK11_GETTAB(mod)->C_GetSlotList(PR_FALSE, slotIDs, &count);
    if (crv != CKR_OK) {
        PORT_SetError(PK11_MapError(crv));
        goto loser;
    }
    freeRef = PR_FALSE;
    PZ_Unlock(mod->refLock);
    mark = PORT_ArenaMark(mod->arena);
    if (mark == NULL) {
        goto loser;
    }
    newSlots = PORT_ArenaZNewArray(mod->arena, PK11SlotInfo *, count);

    /* walk down the new slot ID list returned from the module. We keep
     * the old slots which match a returned ID, and we initialize the new
     * slots. */
    for (i = 0; i < count; i++) {
        PK11SlotInfo *slot = SECMOD_FindSlotByID(mod, slotIDs[i]);

        if (!slot) {
            /* we have a new slot create a new slot data structure */
            slot = PK11_NewSlotInfo(mod);
            if (!slot) {
                goto loser;
            }
            PK11_InitSlot(mod, slotIDs[i], slot);
            STAN_InitTokenForSlotInfo(NULL, slot);
        }
        newSlots[i] = slot;
    }
    STAN_ResetTokenInterator(NULL);
    PORT_Free(slotIDs);
    slotIDs = NULL;
    PORT_ArenaUnmark(mod->arena, mark);

    /* until this point we're still using the old slot list. Now we update
     * module slot list. We update the slots (array) first then the count,
     * since we've already guarrenteed that count has increased (just in case
     * someone is looking at the slots field of  module without holding the
     * moduleLock */
    SECMOD_GetWriteLock(moduleLock);
    oldCount = mod->slotCount;
    oldSlots = mod->slots;
    mod->slots = newSlots; /* typical arena 'leak'... old mod->slots is
                            * allocated out of the module arena and won't
                            * be freed until the module is freed */
    mod->slotCount = count;
    SECMOD_ReleaseWriteLock(moduleLock);
    /* free our old references before forgetting about oldSlot*/
    for (i = 0; i < oldCount; i++) {
        PK11_FreeSlot(oldSlots[i]);
    }
    return SECSuccess;

loser:
    if (freeRef) {
        PZ_Unlock(mod->refLock);
    }
    if (slotIDs) {
        PORT_Free(slotIDs);
    }
    /* free all the slots we allocated. newSlots are part of the
     * mod arena. NOTE: the newSlots array contain both new and old
     * slots, but we kept a reference to the old slots when we built the new
     * array, so we need to free all the slots in newSlots array. */
    if (newSlots) {
        for (i = 0; i < count; i++) {
            if (newSlots[i] == NULL) {
                break; /* hit the last one */
            }
            PK11_FreeSlot(newSlots[i]);
        }
    }
    /* must come after freeing newSlots */
    if (mark) {
        PORT_ArenaRelease(mod->arena, mark);
    }
    return SECFailure;
}

/*
 * this handles modules that do not support C_WaitForSlotEvent().
 * The internal flags are stored. Note that C_WaitForSlotEvent() does not
 * have a timeout, so we don't have one for handleWaitForSlotEvent() either.
 */
PK11SlotInfo *
secmod_HandleWaitForSlotEvent(SECMODModule *mod, unsigned long flags,
                              PRIntervalTime latency)
{
    PRBool removableSlotsFound = PR_FALSE;
    int i;
    int error = SEC_ERROR_NO_EVENT;

    if (!moduleLock) {
        PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
        return NULL;
    }
    PZ_Lock(mod->refLock);
    if (mod->evControlMask & SECMOD_END_WAIT) {
        mod->evControlMask &= ~SECMOD_END_WAIT;
        PZ_Unlock(mod->refLock);
        PORT_SetError(SEC_ERROR_NO_EVENT);
        return NULL;
    }
    mod->evControlMask |= SECMOD_WAIT_SIMULATED_EVENT;
    while (mod->evControlMask & SECMOD_WAIT_SIMULATED_EVENT) {
        PZ_Unlock(mod->refLock);
        /* now is a good time to see if new slots have been added */
        SECMOD_UpdateSlotList(mod);

        /* loop through all the slots on a module */
        SECMOD_GetReadLock(moduleLock);
        for (i = 0; i < mod->slotCount; i++) {
            PK11SlotInfo *slot = mod->slots[i];
            PRUint16 series;
            PRBool present;

            /* perm modules do not change */
            if (slot->isPerm) {
                continue;
            }
            removableSlotsFound = PR_TRUE;
            /* simulate the PKCS #11 module flags. are the flags different
             * from the last time we called? */
            series = slot->series;
            present = PK11_IsPresent(slot);
            if ((slot->flagSeries != series) || (slot->flagState != present)) {
                slot->flagState = present;
                slot->flagSeries = series;
                SECMOD_ReleaseReadLock(moduleLock);
                PZ_Lock(mod->refLock);
                mod->evControlMask &= ~SECMOD_END_WAIT;
                PZ_Unlock(mod->refLock);
                return PK11_ReferenceSlot(slot);
            }
        }
        SECMOD_ReleaseReadLock(moduleLock);
        /* if everything was perm modules, don't lock up forever */
        if ((mod->slotCount != 0) && !removableSlotsFound) {
            error = SEC_ERROR_NO_SLOT_SELECTED;
            PZ_Lock(mod->refLock);
            break;
        }
        if (flags & CKF_DONT_BLOCK) {
            PZ_Lock(mod->refLock);
            break;
        }
        PR_Sleep(latency);
        PZ_Lock(mod->refLock);
    }
    mod->evControlMask &= ~SECMOD_END_WAIT;
    PZ_Unlock(mod->refLock);
    PORT_SetError(error);
    return NULL;
}

/*
 * this function waits for a token event on any slot of a given module
 * This function should not be called from more than one thread of the
 * same process (though other threads can make other library calls
 * on this module while this call is blocked).
 */
PK11SlotInfo *
SECMOD_WaitForAnyTokenEvent(SECMODModule *mod, unsigned long flags,
                            PRIntervalTime latency)
{
    CK_SLOT_ID id;
    CK_RV crv;
    PK11SlotInfo *slot;

    if (!pk11_getFinalizeModulesOption() ||
        ((mod->cryptokiVersion.major == 2) &&
         (mod->cryptokiVersion.minor < 1))) {
        /* if we are sharing the module with other software in our
         * address space, we can't reliably use C_WaitForSlotEvent(),
         * and if the module is version 2.0, C_WaitForSlotEvent() doesn't
         * exist */
        return secmod_HandleWaitForSlotEvent(mod, flags, latency);
    }
    /* first the the PKCS #11 call */
    PZ_Lock(mod->refLock);
    if (mod->evControlMask & SECMOD_END_WAIT) {
        goto end_wait;
    }
    mod->evControlMask |= SECMOD_WAIT_PKCS11_EVENT;
    PZ_Unlock(mod->refLock);
    crv = PK11_GETTAB(mod)->C_WaitForSlotEvent(flags, &id, NULL);
    PZ_Lock(mod->refLock);
    mod->evControlMask &= ~SECMOD_WAIT_PKCS11_EVENT;
    /* if we are in end wait, short circuit now, don't even risk
     * going into secmod_HandleWaitForSlotEvent */
    if (mod->evControlMask & SECMOD_END_WAIT) {
        goto end_wait;
    }
    PZ_Unlock(mod->refLock);
    if (crv == CKR_FUNCTION_NOT_SUPPORTED) {
        /* module doesn't support that call, simulate it */
        return secmod_HandleWaitForSlotEvent(mod, flags, latency);
    }
    if (crv != CKR_OK) {
        /* we can get this error if finalize was called while we were
         * still running. This is the only way to force a C_WaitForSlotEvent()
         * to return in PKCS #11. In this case, just return that there
         * was no event. */
        if (crv == CKR_CRYPTOKI_NOT_INITIALIZED) {
            PORT_SetError(SEC_ERROR_NO_EVENT);
        } else {
            PORT_SetError(PK11_MapError(crv));
        }
        return NULL;
    }
    slot = SECMOD_FindSlotByID(mod, id);
    if (slot == NULL) {
        /* possibly a new slot that was added? */
        SECMOD_UpdateSlotList(mod);
        slot = SECMOD_FindSlotByID(mod, id);
    }
    /* if we are in the delay period for the "isPresent" call, reset
     * the delay since we know things have probably changed... */
    if (slot && slot->nssToken && slot->nssToken->slot) {
        nssSlot_ResetDelay(slot->nssToken->slot);
    }
    return slot;

/* must be called with the lock on. */
end_wait:
    mod->evControlMask &= ~SECMOD_END_WAIT;
    PZ_Unlock(mod->refLock);
    PORT_SetError(SEC_ERROR_NO_EVENT);
    return NULL;
}

/*
 * This function "wakes up" WaitForAnyTokenEvent. It's a pretty drastic
 * function, possibly bringing down the pkcs #11 module in question. This
 * should be OK because 1) it does reinitialize, and 2) it should only be
 * called when we are on our way to tear the whole system down anyway.
 */
SECStatus
SECMOD_CancelWait(SECMODModule *mod)
{
    unsigned long controlMask;
    SECStatus rv = SECSuccess;
    CK_RV crv;

    PZ_Lock(mod->refLock);
    mod->evControlMask |= SECMOD_END_WAIT;
    controlMask = mod->evControlMask;
    if (controlMask & SECMOD_WAIT_PKCS11_EVENT) {
        if (!pk11_getFinalizeModulesOption()) {
            /* can't get here unless pk11_getFinalizeModulesOption is set */
            PORT_Assert(0);
            PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
            rv = SECFailure;
            goto loser;
        }
        /* NOTE: this call will drop all transient keys, in progress
         * operations, and any authentication. This is the only documented
         * way to get WaitForSlotEvent to return. Also note: for non-thread
         * safe tokens, we need to hold the module lock, this is not yet at
         * system shutdown/startup time, so we need to protect these calls */
        crv = PK11_GETTAB(mod)->C_Finalize(NULL);
        /* ok, we slammed the module down, now we need to reinit it in case
         * we intend to use it again */
        if (CKR_OK == crv) {
            PRBool alreadyLoaded;
            secmod_ModuleInit(mod, NULL, &alreadyLoaded);
        } else {
            /* Finalized failed for some reason,  notify the application
             * so maybe it has a prayer of recovering... */
            PORT_SetError(PK11_MapError(crv));
            rv = SECFailure;
        }
    } else if (controlMask & SECMOD_WAIT_SIMULATED_EVENT) {
        mod->evControlMask &= ~SECMOD_WAIT_SIMULATED_EVENT;
        /* Simulated events will eventually timeout
         * and wake up in the loop */
    }
loser:
    PZ_Unlock(mod->refLock);
    return rv;
}

/*
 * check to see if the module has removable slots that we may need to
 * watch for.
 */
PRBool
SECMOD_HasRemovableSlots(SECMODModule *mod)
{
    int i;
    PRBool ret = PR_FALSE;

    if (!moduleLock) {
        PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
        return ret;
    }
    SECMOD_GetReadLock(moduleLock);
    for (i = 0; i < mod->slotCount; i++) {
        PK11SlotInfo *slot = mod->slots[i];
        /* perm modules are not inserted or removed */
        if (slot->isPerm) {
            continue;
        }
        ret = PR_TRUE;
        break;
    }
    if (mod->slotCount == 0) {
        ret = PR_TRUE;
    }
    SECMOD_ReleaseReadLock(moduleLock);
    return ret;
}

/*
 * helper function to actually create and destroy user defined slots
 */
static SECStatus
secmod_UserDBOp(PK11SlotInfo *slot, CK_OBJECT_CLASS objClass,
                const char *sendSpec)
{
    CK_OBJECT_HANDLE dummy;
    CK_ATTRIBUTE template[2];
    CK_ATTRIBUTE *attrs = template;
    CK_RV crv;

    PK11_SETATTRS(attrs, CKA_CLASS, &objClass, sizeof(objClass));
    attrs++;
    PK11_SETATTRS(attrs, CKA_NETSCAPE_MODULE_SPEC, (unsigned char *)sendSpec,
                  strlen(sendSpec) + 1);
    attrs++;

    PORT_Assert(attrs - template <= 2);

    PK11_EnterSlotMonitor(slot);
    crv = PK11_CreateNewObject(slot, slot->session,
                               template, attrs - template, PR_FALSE, &dummy);
    PK11_ExitSlotMonitor(slot);

    if (crv != CKR_OK) {
        PORT_SetError(PK11_MapError(crv));
        return SECFailure;
    }
    return SECMOD_UpdateSlotList(slot->module);
}

/*
 * return true if the selected slot ID is not present or doesn't exist
 */
static PRBool
secmod_SlotIsEmpty(SECMODModule *mod, CK_SLOT_ID slotID)
{
    PK11SlotInfo *slot = SECMOD_LookupSlot(mod->moduleID, slotID);
    if (slot) {
        PRBool present = PK11_IsPresent(slot);
        PK11_FreeSlot(slot);
        if (present) {
            return PR_FALSE;
        }
    }
    /* it doesn't exist or isn't present, it's available */
    return PR_TRUE;
}

/*
 * Find an unused slot id in module.
 */
static CK_SLOT_ID
secmod_FindFreeSlot(SECMODModule *mod)
{
    CK_SLOT_ID i, minSlotID, maxSlotID;

    /* look for a free slot id on the internal module */
    if (mod->internal && mod->isFIPS) {
        minSlotID = SFTK_MIN_FIPS_USER_SLOT_ID;
        maxSlotID = SFTK_MAX_FIPS_USER_SLOT_ID;
    } else {
        minSlotID = SFTK_MIN_USER_SLOT_ID;
        maxSlotID = SFTK_MAX_USER_SLOT_ID;
    }
    for (i = minSlotID; i < maxSlotID; i++) {
        if (secmod_SlotIsEmpty(mod, i)) {
            return i;
        }
    }
    PORT_SetError(SEC_ERROR_NO_SLOT_SELECTED);
    return (CK_SLOT_ID)-1;
}

/*
 * Attempt to open a new slot.
 *
 * This works the same os OpenUserDB except it can be called against
 * any module that understands the softoken protocol for opening new
 * slots, not just the softoken itself. If the selected module does not
 * understand the protocol, C_CreateObject will fail with
 * CKR_INVALID_ATTRIBUTE, and SECMOD_OpenNewSlot will return NULL and set
 * SEC_ERROR_BAD_DATA.
 *
 * NewSlots can be closed with SECMOD_CloseUserDB();
 *
 * Modulespec is module dependent.
 */
PK11SlotInfo *
SECMOD_OpenNewSlot(SECMODModule *mod, const char *moduleSpec)
{
    CK_SLOT_ID slotID = 0;
    PK11SlotInfo *slot;
    char *escSpec;
    char *sendSpec;
    SECStatus rv;

    slotID = secmod_FindFreeSlot(mod);
    if (slotID == (CK_SLOT_ID)-1) {
        return NULL;
    }

    if (mod->slotCount == 0) {
        return NULL;
    }

    /* just grab the first slot in the module, any present slot should work */
    slot = PK11_ReferenceSlot(mod->slots[0]);
    if (slot == NULL) {
        return NULL;
    }

    /* we've found the slot, now build the moduleSpec */
    escSpec = NSSUTIL_DoubleEscape(moduleSpec, '>', ']');
    if (escSpec == NULL) {
        PK11_FreeSlot(slot);
        return NULL;
    }
    sendSpec = PR_smprintf("tokens=[0x%x=<%s>]", slotID, escSpec);
    PORT_Free(escSpec);

    if (sendSpec == NULL) {
        /* PR_smprintf does not set SEC_ERROR_NO_MEMORY on failure. */
        PK11_FreeSlot(slot);
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }
    rv = secmod_UserDBOp(slot, CKO_NETSCAPE_NEWSLOT, sendSpec);
    PR_smprintf_free(sendSpec);
    PK11_FreeSlot(slot);
    if (rv != SECSuccess) {
        return NULL;
    }

    slot = SECMOD_FindSlotByID(mod, slotID);
    if (slot) {
        /* if we are in the delay period for the "isPresent" call, reset
         * the delay since we know things have probably changed... */
        if (slot->nssToken && slot->nssToken->slot) {
            nssSlot_ResetDelay(slot->nssToken->slot);
        }
        /* force the slot info structures to properly reset */
        (void)PK11_IsPresent(slot);
    }
    return slot;
}

/*
 * given a module spec, find the slot in the module for it.
 */
PK11SlotInfo *
secmod_FindSlotFromModuleSpec(const char *moduleSpec, SECMODModule *module)
{
    CK_SLOT_ID slot_id = secmod_GetSlotIDFromModuleSpec(moduleSpec, module);
    if (slot_id == -1) {
        return NULL;
    }

    return SECMOD_FindSlotByID(module, slot_id);
}

/*
 * Open a new database using the softoken. The caller is responsible for making
 * sure the module spec is correct and usable. The caller should ask for one
 * new database per call if the caller wants to get meaningful information
 * about the new database.
 *
 * moduleSpec is the same data that you would pass to softoken at
 * initialization time under the 'tokens' options. For example, if you were
 * to specify tokens=<0x4=[configdir='./mybackup' tokenDescription='Backup']>
 * You would specify "configdir='./mybackup' tokenDescription='Backup'" as your
 * module spec here. The slot ID will be calculated for you by
 * SECMOD_OpenUserDB().
 *
 * Typical parameters here are configdir, tokenDescription and flags.
 *
 * a Full list is below:
 *
 *
 *  configDir - The location of the databases for this token. If configDir is
 *         not specified, and noCertDB and noKeyDB is not specified, the load
 *         will fail.
 *   certPrefix - Cert prefix for this token.
 *   keyPrefix - Prefix for the key database for this token. (if not specified,
 *         certPrefix will be used).
 *   tokenDescription - The label value for this token returned in the
 *         CK_TOKEN_INFO structure with an internationalize string (UTF8).
 *         This value will be truncated at 32 bytes (no NULL, partial UTF8
 *         characters dropped). You should specify a user friendly name here
 *         as this is the value the token will be referred to in most
 *         application UI's. You should make sure tokenDescription is unique.
 *   slotDescription - The slotDescription value for this token returned
 *         in the CK_SLOT_INFO structure with an internationalize string
 *         (UTF8). This value will be truncated at 64 bytes (no NULL, partial
 *         UTF8 characters dropped). This name will not change after the
 *         database is closed. It should have some number to make this unique.
 *   minPWLen - minimum password length for this token.
 *   flags - comma separated list of flag values, parsed case-insensitive.
 *         Valid flags are:
 *              readOnly - Databases should be opened read only.
 *              noCertDB - Don't try to open a certificate database.
 *              noKeyDB - Don't try to open a key database.
 *              forceOpen - Don't fail to initialize the token if the
 *                databases could not be opened.
 *              passwordRequired - zero length passwords are not acceptable
 *                (valid only if there is a keyDB).
 *              optimizeSpace - allocate smaller hash tables and lock tables.
 *                When this flag is not specified, Softoken will allocate
 *                large tables to prevent lock contention.
 */
PK11SlotInfo *
SECMOD_OpenUserDB(const char *moduleSpec)
{
    SECMODModule *mod;
    SECMODConfigList *conflist = NULL;
    int count = 0;

    if (moduleSpec == NULL) {
        return NULL;
    }

    /* NOTE: unlike most PK11 function, this does not return a reference
     * to the module */
    mod = SECMOD_GetInternalModule();
    if (!mod) {
        /* shouldn't happen */
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return NULL;
    }

    /* make sure we don't open the same database twice. We only understand 
     * the moduleSpec for internal databases well enough to do this, so only
     * do this in OpenUserDB */
    conflist = secmod_GetConfigList(mod->isFIPS, mod->libraryParams, &count);
    if (conflist) {
        PK11SlotInfo *slot = NULL;
        if (secmod_MatchConfigList(moduleSpec, conflist, count)) {
            slot = secmod_FindSlotFromModuleSpec(moduleSpec, mod);
        }
        secmod_FreeConfigList(conflist, count);
        if (slot) {
            return slot;
        }
    }
    return SECMOD_OpenNewSlot(mod, moduleSpec);
}

/*
 * close an already opened user database. NOTE: the database must be
 * in the internal token, and must be one created with SECMOD_OpenUserDB().
 * Once the database is closed, the slot will remain as an empty slot
 * until it's used again with SECMOD_OpenUserDB() or SECMOD_OpenNewSlot().
 */
SECStatus
SECMOD_CloseUserDB(PK11SlotInfo *slot)
{
    SECStatus rv;
    char *sendSpec;

    sendSpec = PR_smprintf("tokens=[0x%x=<>]", slot->slotID);
    if (sendSpec == NULL) {
        /* PR_smprintf does not set no memory error */
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return SECFailure;
    }
    rv = secmod_UserDBOp(slot, CKO_NETSCAPE_DELSLOT, sendSpec);
    PR_smprintf_free(sendSpec);
    /* if we are in the delay period for the "isPresent" call, reset
     * the delay since we know things have probably changed... */
    if (slot->nssToken && slot->nssToken->slot) {
        nssSlot_ResetDelay(slot->nssToken->slot);
        /* force the slot info structures to properly reset */
        (void)PK11_IsPresent(slot);
    }
    return rv;
}

/*
 * Restart PKCS #11 modules after a fork(). See secmod.h for more information.
 */
SECStatus
SECMOD_RestartModules(PRBool force)
{
    SECMODModuleList *mlp;
    SECStatus rrv = SECSuccess;
    int lastError = 0;

    if (!moduleLock) {
        PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
        return SECFailure;
    }

    /* Only need to restart the PKCS #11 modules that were initialized */
    SECMOD_GetReadLock(moduleLock);
    for (mlp = modules; mlp != NULL; mlp = mlp->next) {
        SECMODModule *mod = mlp->module;
        CK_ULONG count;
        SECStatus rv;
        int i;

        /* If the module needs to be reset, do so */
        if (force || (PK11_GETTAB(mod)->C_GetSlotList(CK_FALSE, NULL, &count) != CKR_OK)) {
            PRBool alreadyLoaded;
            /* first call Finalize. This is not required by PKCS #11, but some
             * older modules require it, and it doesn't hurt (compliant modules
             * will return CKR_NOT_INITIALIZED */
            (void)PK11_GETTAB(mod)->C_Finalize(NULL);
            /* now initialize the module, this function reinitializes
             * a module in place, preserving existing slots (even if they
             * no longer exist) */
            rv = secmod_ModuleInit(mod, NULL, &alreadyLoaded);
            if (rv != SECSuccess) {
                /* save the last error code */
                lastError = PORT_GetError();
                rrv = rv;
                /* couldn't reinit the module, disable all its slots */
                for (i = 0; i < mod->slotCount; i++) {
                    mod->slots[i]->disabled = PR_TRUE;
                    mod->slots[i]->reason = PK11_DIS_COULD_NOT_INIT_TOKEN;
                }
                continue;
            }
            for (i = 0; i < mod->slotCount; i++) {
                /* get new token sessions, bump the series up so that
                 * we refresh other old sessions. This will tell much of
                 * NSS to flush cached handles it may hold as well */
                rv = PK11_InitToken(mod->slots[i], PR_TRUE);
                /* PK11_InitToken could fail if the slot isn't present.
                 * If it is present, though, something is wrong and we should
                 * disable the slot and let the caller know. */
                if (rv != SECSuccess && PK11_IsPresent(mod->slots[i])) {
                    /* save the last error code */
                    lastError = PORT_GetError();
                    rrv = rv;
                    /* disable the token */
                    mod->slots[i]->disabled = PR_TRUE;
                    mod->slots[i]->reason = PK11_DIS_COULD_NOT_INIT_TOKEN;
                }
            }
        }
    }
    SECMOD_ReleaseReadLock(moduleLock);

    /*
     * on multiple failures, we are only returning the lastError. The caller
     * can determine which slots are bad by calling PK11_IsDisabled().
     */
    if (rrv != SECSuccess) {
        /* restore the last error code */
        PORT_SetError(lastError);
    }

    return rrv;
}
