/* 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/. */ #include "pkcs11.h" #ifndef DEVM_H #include "devm.h" #endif /* DEVM_H */ #ifndef CKHELPER_H #include "ckhelper.h" #endif /* CKHELPER_H */ #include "pkim.h" #include "dev3hack.h" #include "pk11func.h" /* measured in seconds */ #define NSSSLOT_TOKEN_DELAY_TIME 1 /* this should track global and per-transaction login information */ #define NSSSLOT_IS_FRIENDLY(slot) \ (slot->base.flags & NSSSLOT_FLAGS_FRIENDLY) /* measured as interval */ static PRIntervalTime s_token_delay_time = 0; NSS_IMPLEMENT PRStatus nssSlot_Destroy( NSSSlot *slot) { if (slot) { if (PR_ATOMIC_DECREMENT(&slot->base.refCount) == 0) { PK11_FreeSlot(slot->pk11slot); PZ_DestroyLock(slot->base.lock); PZ_DestroyCondVar(slot->isPresentCondition); PZ_DestroyLock(slot->isPresentLock); return nssArena_Destroy(slot->base.arena); } } return PR_SUCCESS; } void nssSlot_EnterMonitor(NSSSlot *slot) { if (slot->lock) { PZ_Lock(slot->lock); } } void nssSlot_ExitMonitor(NSSSlot *slot) { if (slot->lock) { PZ_Unlock(slot->lock); } } NSS_IMPLEMENT void NSSSlot_Destroy( NSSSlot *slot) { (void)nssSlot_Destroy(slot); } NSS_IMPLEMENT NSSSlot * nssSlot_AddRef( NSSSlot *slot) { PR_ATOMIC_INCREMENT(&slot->base.refCount); return slot; } NSS_IMPLEMENT NSSUTF8 * nssSlot_GetName( NSSSlot *slot) { return slot->base.name; } NSS_IMPLEMENT void nssSlot_ResetDelay( NSSSlot *slot) { PZ_Lock(slot->isPresentLock); slot->lastTokenPingState = nssSlotLastPingState_Reset; PZ_Unlock(slot->isPresentLock); } static PRBool token_status_checked(const NSSSlot *slot) { PRIntervalTime time; int lastPingState = slot->lastTokenPingState; /* When called from the same thread, that means * nssSlot_IsTokenPresent() is called recursively through * nssSlot_Refresh(). Return immediately in that case. */ if (slot->isPresentThread == PR_GetCurrentThread()) { return PR_TRUE; } /* Set the delay time for checking the token presence */ if (s_token_delay_time == 0) { s_token_delay_time = PR_SecondsToInterval(NSSSLOT_TOKEN_DELAY_TIME); } time = PR_IntervalNow(); if ((lastPingState == nssSlotLastPingState_Valid) && ((time - slot->lastTokenPingTime) < s_token_delay_time)) { return PR_TRUE; } return PR_FALSE; } NSS_IMPLEMENT PRBool nssSlot_IsTokenPresent( NSSSlot *slot) { CK_RV ckrv; PRStatus nssrv; NSSToken *nssToken = NULL; /* XXX */ nssSession *session; CK_SLOT_INFO slotInfo; void *epv; PRBool isPresent = PR_FALSE; PRBool doUpdateCachedCerts = PR_FALSE; /* permanent slots are always present unless they're disabled */ if (nssSlot_IsPermanent(slot)) { return !PK11_IsDisabled(slot->pk11slot); } /* avoid repeated calls to check token status within set interval */ PZ_Lock(slot->isPresentLock); if (token_status_checked(slot)) { CK_FLAGS ckFlags = slot->ckFlags; PZ_Unlock(slot->isPresentLock); return ((ckFlags & CKF_TOKEN_PRESENT) != 0); } PZ_Unlock(slot->isPresentLock); /* First obtain the slot epv before we set up the condition * variable, so we can just return if we couldn't get it. */ epv = slot->epv; if (!epv) { return PR_FALSE; } /* set up condition so only one thread is active in this part of the code at a time */ PZ_Lock(slot->isPresentLock); while (slot->isPresentThread) { PR_WaitCondVar(slot->isPresentCondition, PR_INTERVAL_NO_TIMEOUT); } /* if we were one of multiple threads here, the first thread will have * given us the answer, no need to make more queries of the token. */ if (token_status_checked(slot)) { CK_FLAGS ckFlags = slot->ckFlags; PZ_Unlock(slot->isPresentLock); return ((ckFlags & CKF_TOKEN_PRESENT) != 0); } /* this is the winning thread, block all others until we've determined * if the token is present and that it needs initialization. */ slot->lastTokenPingState = nssSlotLastPingState_Update; slot->isPresentThread = PR_GetCurrentThread(); PZ_Unlock(slot->isPresentLock); nssToken = PK11Slot_GetNSSToken(slot->pk11slot); if (!nssToken) { isPresent = PR_FALSE; goto done; } if (PK11_GetSlotInfo(slot->pk11slot, &slotInfo) != SECSuccess) { nssToken->base.name[0] = 0; /* XXX */ isPresent = PR_FALSE; goto done; } slot->ckFlags = slotInfo.flags; /* check for the presence of the token */ if ((slot->ckFlags & CKF_TOKEN_PRESENT) == 0) { session = nssToken_GetDefaultSession(nssToken); if (session) { nssSession_EnterMonitor(session); /* token is not present */ if (session->handle != CK_INVALID_HANDLE) { /* session is valid, close and invalidate it */ CKAPI(epv) ->C_CloseSession(session->handle); session->handle = CK_INVALID_HANDLE; } nssSession_ExitMonitor(session); } if (nssToken->base.name[0] != 0) { /* notify the high-level cache that the token is removed */ nssToken->base.name[0] = 0; /* XXX */ nssToken_NotifyCertsNotVisible(nssToken); } nssToken->base.name[0] = 0; /* XXX */ /* clear the token cache */ nssToken_Remove(nssToken); isPresent = PR_FALSE; goto done; } /* token is present, use the session info to determine if the card * has been removed and reinserted. */ session = nssToken_GetDefaultSession(nssToken); if (session) { PRBool tokenRemoved; nssSession_EnterMonitor(session); if (session->handle != CK_INVALID_HANDLE) { CK_SESSION_INFO sessionInfo; ckrv = CKAPI(epv)->C_GetSessionInfo(session->handle, &sessionInfo); if (ckrv != CKR_OK) { /* session is screwy, close and invalidate it */ CKAPI(epv) ->C_CloseSession(session->handle); session->handle = CK_INVALID_HANDLE; } } tokenRemoved = (session->handle == CK_INVALID_HANDLE); nssSession_ExitMonitor(session); /* token not removed, finished */ if (!tokenRemoved) { isPresent = PR_TRUE; goto done; } } /* the token has been removed, and reinserted, or the slot contains * a token it doesn't recognize. invalidate all the old * information we had on this token, if we can't refresh, clear * the present flag */ nssToken_NotifyCertsNotVisible(nssToken); nssToken_Remove(nssToken); if (nssToken->base.name[0] == 0) { doUpdateCachedCerts = PR_TRUE; } if (PK11_InitToken(slot->pk11slot, PR_FALSE) != SECSuccess) { isPresent = PR_FALSE; goto done; } if (doUpdateCachedCerts) { nssTrustDomain_UpdateCachedTokenCerts(nssToken->trustDomain, nssToken); } nssrv = nssToken_Refresh(nssToken); if (nssrv != PR_SUCCESS) { nssToken->base.name[0] = 0; /* XXX */ slot->ckFlags &= ~CKF_TOKEN_PRESENT; isPresent = PR_FALSE; goto done; } isPresent = PR_TRUE; done: if (nssToken) { (void)nssToken_Destroy(nssToken); } /* Once we've set up the condition variable, * Before returning, it's necessary to: * 1) Set the lastTokenPingTime so that any other threads waiting on this * initialization and any future calls within the initialization window * return the just-computed status. * 2) Indicate we're complete, waking up all other threads that may still * be waiting on initialization can progress. */ PZ_Lock(slot->isPresentLock); /* don't update the time if we were reset while we were * getting the token state */ if (slot->lastTokenPingState == nssSlotLastPingState_Update) { slot->lastTokenPingTime = PR_IntervalNow(); slot->lastTokenPingState = nssSlotLastPingState_Valid; } slot->isPresentThread = NULL; PR_NotifyAllCondVar(slot->isPresentCondition); PZ_Unlock(slot->isPresentLock); return isPresent; } NSS_IMPLEMENT void * nssSlot_GetCryptokiEPV( NSSSlot *slot) { return slot->epv; } NSS_IMPLEMENT NSSToken * nssSlot_GetToken( NSSSlot *slot) { NSSToken *rvToken = NULL; if (nssSlot_IsTokenPresent(slot)) { rvToken = PK11Slot_GetNSSToken(slot->pk11slot); } return rvToken; } NSS_IMPLEMENT PRStatus nssSession_EnterMonitor( nssSession *s) { if (s->lock) PZ_Lock(s->lock); return PR_SUCCESS; } NSS_IMPLEMENT PRStatus nssSession_ExitMonitor( nssSession *s) { return (s->lock) ? PZ_Unlock(s->lock) : PR_SUCCESS; } NSS_EXTERN PRBool nssSession_IsReadWrite( nssSession *s) { return s->isRW; }