michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: #include "nspr.h" michael@0: michael@0: #include "pk11func.h" michael@0: #include "nsNSSComponent.h" michael@0: #include "nsSmartCardMonitor.h" michael@0: #include "nsIDOMSmartCardEvent.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "mozilla/unused.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: // michael@0: // The SmartCard monitoring thread should start up for each module we load michael@0: // that has removable tokens. This code calls an NSS function which waits michael@0: // until there is a change in the token state. NSS uses the michael@0: // C_WaitForSlotEvent() call in PKCS #11 if the module implements the call, michael@0: // otherwise NSS will poll the token in a loop with a delay of 'latency' michael@0: // between polls. Note that the C_WaitForSlotEvent() may wake up on any type michael@0: // of token event, so it's necessary to filter these events down to just the michael@0: // insertion and removal events we are looking for. michael@0: // michael@0: // Once the event is found, It is passed to nsNSSComponent for dispatching michael@0: // on the UI thread, and forwarding to any interested listeners (including michael@0: // javascript). michael@0: // michael@0: michael@0: michael@0: // self linking and removing double linked entry michael@0: // adopts the thread it is passed. michael@0: class SmartCardThreadEntry { michael@0: public: michael@0: SmartCardThreadEntry *next; michael@0: SmartCardThreadEntry *prev; michael@0: SmartCardThreadEntry **head; michael@0: SmartCardMonitoringThread *thread; michael@0: SmartCardThreadEntry(SmartCardMonitoringThread *thread_, michael@0: SmartCardThreadEntry *next_, SmartCardThreadEntry *prev_, michael@0: SmartCardThreadEntry **head_) : michael@0: next(next_), prev(prev_), head(head_), thread(thread_) { michael@0: if (prev) { prev->next = this; } else { *head = this; } michael@0: if (next) { next->prev = this; } michael@0: } michael@0: ~SmartCardThreadEntry() { michael@0: if (prev) { prev->next = next; } else { *head = next; } michael@0: if (next) { next->prev = prev; } michael@0: // NOTE: automatically stops the thread michael@0: delete thread; michael@0: } michael@0: }; michael@0: michael@0: // michael@0: // SmartCardThreadList is a class to help manage the running threads. michael@0: // That way new threads could be started and old ones terminated as we michael@0: // load and unload modules. michael@0: // michael@0: SmartCardThreadList::SmartCardThreadList() : head(0) michael@0: { michael@0: } michael@0: michael@0: SmartCardThreadList::~SmartCardThreadList() michael@0: { michael@0: // the head is self linking and unlinking, the following michael@0: // loop removes all entries on the list. michael@0: // it will also stop the thread if it happens to be running michael@0: while (head) { michael@0: delete head; michael@0: } michael@0: } michael@0: michael@0: void michael@0: SmartCardThreadList::Remove(SECMODModule *aModule) michael@0: { michael@0: for (SmartCardThreadEntry *current = head; current; current = current->next) { michael@0: if (current->thread->GetModule() == aModule) { michael@0: // NOTE: automatically stops the thread and dequeues it from the list michael@0: delete current; michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // adopts the thread passed to it. Starts the thread as well michael@0: nsresult michael@0: SmartCardThreadList::Add(SmartCardMonitoringThread *thread) michael@0: { michael@0: SmartCardThreadEntry *current = new SmartCardThreadEntry(thread, head, nullptr, michael@0: &head); michael@0: // OK to forget current here, it's on the list. michael@0: unused << current; michael@0: michael@0: return thread->Start(); michael@0: } michael@0: michael@0: michael@0: // We really should have a Unity PL Hash function... michael@0: static PLHashNumber michael@0: unity(const void *key) { return PLHashNumber(NS_PTR_TO_INT32(key)); } michael@0: michael@0: SmartCardMonitoringThread::SmartCardMonitoringThread(SECMODModule *module_) michael@0: : mThread(nullptr) michael@0: { michael@0: mModule = SECMOD_ReferenceModule(module_); michael@0: // simple hash functions, most modules have less than 3 slots, so 10 buckets michael@0: // should be plenty michael@0: mHash = PL_NewHashTable(10, unity, PL_CompareValues, michael@0: PL_CompareStrings, nullptr, 0); michael@0: } michael@0: michael@0: // michael@0: // when we shutdown the thread, be sure to stop it first. If not, it just might michael@0: // crash when the mModule it is looking at disappears. michael@0: // michael@0: SmartCardMonitoringThread::~SmartCardMonitoringThread() michael@0: { michael@0: Stop(); michael@0: SECMOD_DestroyModule(mModule); michael@0: if (mHash) { michael@0: PL_HashTableDestroy(mHash); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: SmartCardMonitoringThread::Start() michael@0: { michael@0: if (!mThread) { michael@0: mThread = PR_CreateThread(PR_SYSTEM_THREAD, LaunchExecute, this, michael@0: PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, michael@0: PR_JOINABLE_THREAD, 0); michael@0: } michael@0: return mThread ? NS_OK : NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: // michael@0: // Should only stop if we are through with the module. michael@0: // CancelWait has the side effect of losing all the keys and michael@0: // current operations on the module!. (See the comment in michael@0: // SECMOD_CancelWait for why this is so..). michael@0: // michael@0: void SmartCardMonitoringThread::Stop() michael@0: { michael@0: SECStatus rv; michael@0: michael@0: rv = SECMOD_CancelWait(mModule); michael@0: if (rv != SECSuccess) { michael@0: // we didn't wake up the Wait, so don't try to join the thread michael@0: // otherwise we will hang forever... michael@0: return; michael@0: } michael@0: michael@0: // confused about the memory model here? NSPR owns the memory for michael@0: // threads. non-joinable threads are freed when the thread dies. michael@0: // joinable threads are freed after the call to PR_JoinThread. michael@0: // That means if SECMOD_CancelWait fails, we'll leak the mThread michael@0: // structure. this is considered preferable to hanging (which is michael@0: // what will happen if we try to join a thread that blocked). michael@0: if (mThread) { michael@0: PR_JoinThread(mThread); michael@0: mThread = 0; michael@0: } michael@0: } michael@0: michael@0: // michael@0: // remember the name and series of a token in a particular slot. michael@0: // This is important because the name is no longer available when michael@0: // the token is removed. If listeners depended on this information, michael@0: // They would be out of luck. It also is a handy way of making sure michael@0: // we don't generate spurious insertion and removal events as the slot michael@0: // cycles through various states. michael@0: // michael@0: void michael@0: SmartCardMonitoringThread::SetTokenName(CK_SLOT_ID slotid, michael@0: const char *tokenName, uint32_t series) michael@0: { michael@0: if (mHash) { michael@0: if (tokenName) { michael@0: int len = strlen(tokenName) + 1; michael@0: /* this must match the allocator used in michael@0: * PLHashAllocOps.freeEntry DefaultFreeEntry */ michael@0: char *entry = (char *)PR_Malloc(len+sizeof(uint32_t)); michael@0: michael@0: if (entry) { michael@0: memcpy(entry,&series,sizeof(uint32_t)); michael@0: memcpy(&entry[sizeof(uint32_t)],tokenName,len); michael@0: michael@0: PL_HashTableAdd(mHash,(void *)(uintptr_t)slotid, entry); /* adopt */ michael@0: return; michael@0: } michael@0: } michael@0: else { michael@0: // if tokenName was not provided, remove the old one (implicit delete) michael@0: PL_HashTableRemove(mHash,(void *)(uintptr_t)slotid); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // retrieve the name saved above michael@0: const char * michael@0: SmartCardMonitoringThread::GetTokenName(CK_SLOT_ID slotid) michael@0: { michael@0: const char *tokenName = nullptr; michael@0: const char *entry; michael@0: michael@0: if (mHash) { michael@0: entry = (const char *)PL_HashTableLookupConst(mHash,(void *)(uintptr_t)slotid); michael@0: if (entry) { michael@0: tokenName = &entry[sizeof(uint32_t)]; michael@0: } michael@0: } michael@0: return tokenName; michael@0: } michael@0: michael@0: // retrieve the series saved in SetTokenName above michael@0: uint32_t michael@0: SmartCardMonitoringThread::GetTokenSeries(CK_SLOT_ID slotid) michael@0: { michael@0: uint32_t series = 0; michael@0: const char *entry; michael@0: michael@0: if (mHash) { michael@0: entry = (const char *)PL_HashTableLookupConst(mHash,(void *)(uintptr_t)slotid); michael@0: if (entry) { michael@0: memcpy(&series,entry,sizeof(uint32_t)); michael@0: } michael@0: } michael@0: return series; michael@0: } michael@0: michael@0: // michael@0: // helper function to pass the event off to nsNSSComponent. michael@0: // michael@0: nsresult michael@0: SmartCardMonitoringThread::SendEvent(const nsAString &eventType, michael@0: const char *tokenName) michael@0: { michael@0: static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr michael@0: nssComponent(do_GetService(kNSSComponentCID, &rv)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // NSS returns actual UTF8, not ASCII michael@0: nssComponent->PostEvent(eventType, NS_ConvertUTF8toUTF16(tokenName)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // This is the main loop. michael@0: // michael@0: void SmartCardMonitoringThread::Execute() michael@0: { michael@0: PK11SlotInfo *slot; michael@0: const char *tokenName = nullptr; michael@0: michael@0: // michael@0: // populate token names for already inserted tokens. michael@0: // michael@0: PK11SlotList *sl = michael@0: PK11_FindSlotsByNames(mModule->dllName, nullptr, nullptr, true); michael@0: PK11SlotListElement *sle; michael@0: michael@0: if (sl) { michael@0: for (sle=PK11_GetFirstSafe(sl); sle; michael@0: sle=PK11_GetNextSafe(sl,sle,false)) { michael@0: SetTokenName(PK11_GetSlotID(sle->slot), michael@0: PK11_GetTokenName(sle->slot), PK11_GetSlotSeries(sle->slot)); michael@0: } michael@0: PK11_FreeSlotList(sl); michael@0: } michael@0: michael@0: // loop starts.. michael@0: do { michael@0: slot = SECMOD_WaitForAnyTokenEvent(mModule, 0, PR_SecondsToInterval(1) ); michael@0: if (!slot) { michael@0: break; michael@0: } michael@0: michael@0: // now we have a potential insertion or removal event, see if the slot michael@0: // is present to determine which it is... michael@0: if (PK11_IsPresent(slot)) { michael@0: // insertion michael@0: CK_SLOT_ID slotID = PK11_GetSlotID(slot); michael@0: uint32_t series = PK11_GetSlotSeries(slot); michael@0: michael@0: // skip spurious insertion events... michael@0: if (series != GetTokenSeries(slotID)) { michael@0: // if there's a token name, then we have not yet issued a remove michael@0: // event for the previous token, do so now... michael@0: tokenName = GetTokenName(slotID); michael@0: if (tokenName) { michael@0: SendEvent(NS_LITERAL_STRING(SMARTCARDEVENT_REMOVE), tokenName); michael@0: } michael@0: tokenName = PK11_GetTokenName(slot); michael@0: // save the token name and series michael@0: SetTokenName(slotID, tokenName, series); michael@0: SendEvent(NS_LITERAL_STRING(SMARTCARDEVENT_INSERT), tokenName); michael@0: } michael@0: } else { michael@0: // retrieve token name michael@0: CK_SLOT_ID slotID = PK11_GetSlotID(slot); michael@0: tokenName = GetTokenName(slotID); michael@0: // if there's not a token name, then the software isn't expecting michael@0: // a (or another) remove event. michael@0: if (tokenName) { michael@0: SendEvent(NS_LITERAL_STRING(SMARTCARDEVENT_REMOVE), tokenName); michael@0: // clear the token name (after we send it) michael@0: SetTokenName(slotID, nullptr, 0); michael@0: } michael@0: } michael@0: PK11_FreeSlot(slot); michael@0: michael@0: } while (1); michael@0: } michael@0: michael@0: // accessor to help searching active Monitoring threads michael@0: const SECMODModule * SmartCardMonitoringThread::GetModule() michael@0: { michael@0: return mModule; michael@0: } michael@0: michael@0: // C-like calling sequence to glue into PR_CreateThread. michael@0: void SmartCardMonitoringThread::LaunchExecute(void *arg) michael@0: { michael@0: PR_SetCurrentThreadName("SmartCard"); michael@0: michael@0: ((SmartCardMonitoringThread*)arg)->Execute(); michael@0: } michael@0: