| /* 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 "pk11pub.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 NSSUTF8 * |
| nssSlot_GetTokenName( |
| NSSSlot *slot) |
| { |
| return nssToken_GetName(slot->token); |
| } |
| |
| 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; |
| /* XXX */ |
| nssSession *session; |
| CK_SLOT_INFO slotInfo; |
| void *epv; |
| PRBool isPresent = 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); |
| |
| nssSlot_EnterMonitor(slot); |
| ckrv = CKAPI(epv)->C_GetSlotInfo(slot->slotID, &slotInfo); |
| nssSlot_ExitMonitor(slot); |
| if (ckrv != CKR_OK) { |
| slot->token->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) { |
| if (!slot->token) { |
| /* token was never present */ |
| isPresent = PR_FALSE; |
| goto done; |
| } |
| session = nssToken_GetDefaultSession(slot->token); |
| 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 (slot->token->base.name[0] != 0) { |
| /* notify the high-level cache that the token is removed */ |
| slot->token->base.name[0] = 0; /* XXX */ |
| nssToken_NotifyCertsNotVisible(slot->token); |
| } |
| slot->token->base.name[0] = 0; /* XXX */ |
| /* clear the token cache */ |
| nssToken_Remove(slot->token); |
| 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(slot->token); |
| 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(slot->token); |
| nssToken_Remove(slot->token); |
| /* token has been removed, need to refresh with new session */ |
| nssrv = nssSlot_Refresh(slot); |
| isPresent = PR_TRUE; |
| if (nssrv != PR_SUCCESS) { |
| slot->token->base.name[0] = 0; /* XXX */ |
| slot->ckFlags &= ~CKF_TOKEN_PRESENT; |
| isPresent = PR_FALSE; |
| } |
| done: |
| /* 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)) { |
| /* Even if a token should be present, check `slot->token` too as it |
| * might be gone already. This would happen mostly on shutdown. */ |
| nssSlot_EnterMonitor(slot); |
| if (slot->token) |
| rvToken = nssToken_AddRef(slot->token); |
| nssSlot_ExitMonitor(slot); |
| } |
| |
| 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; |
| } |