1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/security/manager/ssl/src/nsSmartCardMonitor.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,332 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 +#include "nspr.h" 1.8 + 1.9 +#include "pk11func.h" 1.10 +#include "nsNSSComponent.h" 1.11 +#include "nsSmartCardMonitor.h" 1.12 +#include "nsIDOMSmartCardEvent.h" 1.13 +#include "nsServiceManagerUtils.h" 1.14 +#include "mozilla/unused.h" 1.15 + 1.16 +using namespace mozilla; 1.17 + 1.18 +// 1.19 +// The SmartCard monitoring thread should start up for each module we load 1.20 +// that has removable tokens. This code calls an NSS function which waits 1.21 +// until there is a change in the token state. NSS uses the 1.22 +// C_WaitForSlotEvent() call in PKCS #11 if the module implements the call, 1.23 +// otherwise NSS will poll the token in a loop with a delay of 'latency' 1.24 +// between polls. Note that the C_WaitForSlotEvent() may wake up on any type 1.25 +// of token event, so it's necessary to filter these events down to just the 1.26 +// insertion and removal events we are looking for. 1.27 +// 1.28 +// Once the event is found, It is passed to nsNSSComponent for dispatching 1.29 +// on the UI thread, and forwarding to any interested listeners (including 1.30 +// javascript). 1.31 +// 1.32 + 1.33 + 1.34 +// self linking and removing double linked entry 1.35 +// adopts the thread it is passed. 1.36 +class SmartCardThreadEntry { 1.37 +public: 1.38 + SmartCardThreadEntry *next; 1.39 + SmartCardThreadEntry *prev; 1.40 + SmartCardThreadEntry **head; 1.41 + SmartCardMonitoringThread *thread; 1.42 + SmartCardThreadEntry(SmartCardMonitoringThread *thread_, 1.43 + SmartCardThreadEntry *next_, SmartCardThreadEntry *prev_, 1.44 + SmartCardThreadEntry **head_) : 1.45 + next(next_), prev(prev_), head(head_), thread(thread_) { 1.46 + if (prev) { prev->next = this; } else { *head = this; } 1.47 + if (next) { next->prev = this; } 1.48 + } 1.49 + ~SmartCardThreadEntry() { 1.50 + if (prev) { prev->next = next; } else { *head = next; } 1.51 + if (next) { next->prev = prev; } 1.52 + // NOTE: automatically stops the thread 1.53 + delete thread; 1.54 + } 1.55 +}; 1.56 + 1.57 +// 1.58 +// SmartCardThreadList is a class to help manage the running threads. 1.59 +// That way new threads could be started and old ones terminated as we 1.60 +// load and unload modules. 1.61 +// 1.62 +SmartCardThreadList::SmartCardThreadList() : head(0) 1.63 +{ 1.64 +} 1.65 + 1.66 +SmartCardThreadList::~SmartCardThreadList() 1.67 +{ 1.68 + // the head is self linking and unlinking, the following 1.69 + // loop removes all entries on the list. 1.70 + // it will also stop the thread if it happens to be running 1.71 + while (head) { 1.72 + delete head; 1.73 + } 1.74 +} 1.75 + 1.76 +void 1.77 +SmartCardThreadList::Remove(SECMODModule *aModule) 1.78 +{ 1.79 + for (SmartCardThreadEntry *current = head; current; current = current->next) { 1.80 + if (current->thread->GetModule() == aModule) { 1.81 + // NOTE: automatically stops the thread and dequeues it from the list 1.82 + delete current; 1.83 + return; 1.84 + } 1.85 + } 1.86 +} 1.87 + 1.88 +// adopts the thread passed to it. Starts the thread as well 1.89 +nsresult 1.90 +SmartCardThreadList::Add(SmartCardMonitoringThread *thread) 1.91 +{ 1.92 + SmartCardThreadEntry *current = new SmartCardThreadEntry(thread, head, nullptr, 1.93 + &head); 1.94 + // OK to forget current here, it's on the list. 1.95 + unused << current; 1.96 + 1.97 + return thread->Start(); 1.98 +} 1.99 + 1.100 + 1.101 +// We really should have a Unity PL Hash function... 1.102 +static PLHashNumber 1.103 +unity(const void *key) { return PLHashNumber(NS_PTR_TO_INT32(key)); } 1.104 + 1.105 +SmartCardMonitoringThread::SmartCardMonitoringThread(SECMODModule *module_) 1.106 + : mThread(nullptr) 1.107 +{ 1.108 + mModule = SECMOD_ReferenceModule(module_); 1.109 + // simple hash functions, most modules have less than 3 slots, so 10 buckets 1.110 + // should be plenty 1.111 + mHash = PL_NewHashTable(10, unity, PL_CompareValues, 1.112 + PL_CompareStrings, nullptr, 0); 1.113 +} 1.114 + 1.115 +// 1.116 +// when we shutdown the thread, be sure to stop it first. If not, it just might 1.117 +// crash when the mModule it is looking at disappears. 1.118 +// 1.119 +SmartCardMonitoringThread::~SmartCardMonitoringThread() 1.120 +{ 1.121 + Stop(); 1.122 + SECMOD_DestroyModule(mModule); 1.123 + if (mHash) { 1.124 + PL_HashTableDestroy(mHash); 1.125 + } 1.126 +} 1.127 + 1.128 +nsresult 1.129 +SmartCardMonitoringThread::Start() 1.130 +{ 1.131 + if (!mThread) { 1.132 + mThread = PR_CreateThread(PR_SYSTEM_THREAD, LaunchExecute, this, 1.133 + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, 1.134 + PR_JOINABLE_THREAD, 0); 1.135 + } 1.136 + return mThread ? NS_OK : NS_ERROR_OUT_OF_MEMORY; 1.137 +} 1.138 + 1.139 +// 1.140 +// Should only stop if we are through with the module. 1.141 +// CancelWait has the side effect of losing all the keys and 1.142 +// current operations on the module!. (See the comment in 1.143 +// SECMOD_CancelWait for why this is so..). 1.144 +// 1.145 +void SmartCardMonitoringThread::Stop() 1.146 +{ 1.147 + SECStatus rv; 1.148 + 1.149 + rv = SECMOD_CancelWait(mModule); 1.150 + if (rv != SECSuccess) { 1.151 + // we didn't wake up the Wait, so don't try to join the thread 1.152 + // otherwise we will hang forever... 1.153 + return; 1.154 + } 1.155 + 1.156 + // confused about the memory model here? NSPR owns the memory for 1.157 + // threads. non-joinable threads are freed when the thread dies. 1.158 + // joinable threads are freed after the call to PR_JoinThread. 1.159 + // That means if SECMOD_CancelWait fails, we'll leak the mThread 1.160 + // structure. this is considered preferable to hanging (which is 1.161 + // what will happen if we try to join a thread that blocked). 1.162 + if (mThread) { 1.163 + PR_JoinThread(mThread); 1.164 + mThread = 0; 1.165 + } 1.166 +} 1.167 + 1.168 +// 1.169 +// remember the name and series of a token in a particular slot. 1.170 +// This is important because the name is no longer available when 1.171 +// the token is removed. If listeners depended on this information, 1.172 +// They would be out of luck. It also is a handy way of making sure 1.173 +// we don't generate spurious insertion and removal events as the slot 1.174 +// cycles through various states. 1.175 +// 1.176 +void 1.177 +SmartCardMonitoringThread::SetTokenName(CK_SLOT_ID slotid, 1.178 + const char *tokenName, uint32_t series) 1.179 +{ 1.180 + if (mHash) { 1.181 + if (tokenName) { 1.182 + int len = strlen(tokenName) + 1; 1.183 + /* this must match the allocator used in 1.184 + * PLHashAllocOps.freeEntry DefaultFreeEntry */ 1.185 + char *entry = (char *)PR_Malloc(len+sizeof(uint32_t)); 1.186 + 1.187 + if (entry) { 1.188 + memcpy(entry,&series,sizeof(uint32_t)); 1.189 + memcpy(&entry[sizeof(uint32_t)],tokenName,len); 1.190 + 1.191 + PL_HashTableAdd(mHash,(void *)(uintptr_t)slotid, entry); /* adopt */ 1.192 + return; 1.193 + } 1.194 + } 1.195 + else { 1.196 + // if tokenName was not provided, remove the old one (implicit delete) 1.197 + PL_HashTableRemove(mHash,(void *)(uintptr_t)slotid); 1.198 + } 1.199 + } 1.200 +} 1.201 + 1.202 +// retrieve the name saved above 1.203 +const char * 1.204 +SmartCardMonitoringThread::GetTokenName(CK_SLOT_ID slotid) 1.205 +{ 1.206 + const char *tokenName = nullptr; 1.207 + const char *entry; 1.208 + 1.209 + if (mHash) { 1.210 + entry = (const char *)PL_HashTableLookupConst(mHash,(void *)(uintptr_t)slotid); 1.211 + if (entry) { 1.212 + tokenName = &entry[sizeof(uint32_t)]; 1.213 + } 1.214 + } 1.215 + return tokenName; 1.216 +} 1.217 + 1.218 +// retrieve the series saved in SetTokenName above 1.219 +uint32_t 1.220 +SmartCardMonitoringThread::GetTokenSeries(CK_SLOT_ID slotid) 1.221 +{ 1.222 + uint32_t series = 0; 1.223 + const char *entry; 1.224 + 1.225 + if (mHash) { 1.226 + entry = (const char *)PL_HashTableLookupConst(mHash,(void *)(uintptr_t)slotid); 1.227 + if (entry) { 1.228 + memcpy(&series,entry,sizeof(uint32_t)); 1.229 + } 1.230 + } 1.231 + return series; 1.232 +} 1.233 + 1.234 +// 1.235 +// helper function to pass the event off to nsNSSComponent. 1.236 +// 1.237 +nsresult 1.238 +SmartCardMonitoringThread::SendEvent(const nsAString &eventType, 1.239 + const char *tokenName) 1.240 +{ 1.241 + static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); 1.242 + 1.243 + nsresult rv; 1.244 + nsCOMPtr<nsINSSComponent> 1.245 + nssComponent(do_GetService(kNSSComponentCID, &rv)); 1.246 + if (NS_FAILED(rv)) 1.247 + return rv; 1.248 + 1.249 + // NSS returns actual UTF8, not ASCII 1.250 + nssComponent->PostEvent(eventType, NS_ConvertUTF8toUTF16(tokenName)); 1.251 + return NS_OK; 1.252 +} 1.253 + 1.254 +// 1.255 +// This is the main loop. 1.256 +// 1.257 +void SmartCardMonitoringThread::Execute() 1.258 +{ 1.259 + PK11SlotInfo *slot; 1.260 + const char *tokenName = nullptr; 1.261 + 1.262 + // 1.263 + // populate token names for already inserted tokens. 1.264 + // 1.265 + PK11SlotList *sl = 1.266 + PK11_FindSlotsByNames(mModule->dllName, nullptr, nullptr, true); 1.267 + PK11SlotListElement *sle; 1.268 + 1.269 + if (sl) { 1.270 + for (sle=PK11_GetFirstSafe(sl); sle; 1.271 + sle=PK11_GetNextSafe(sl,sle,false)) { 1.272 + SetTokenName(PK11_GetSlotID(sle->slot), 1.273 + PK11_GetTokenName(sle->slot), PK11_GetSlotSeries(sle->slot)); 1.274 + } 1.275 + PK11_FreeSlotList(sl); 1.276 + } 1.277 + 1.278 + // loop starts.. 1.279 + do { 1.280 + slot = SECMOD_WaitForAnyTokenEvent(mModule, 0, PR_SecondsToInterval(1) ); 1.281 + if (!slot) { 1.282 + break; 1.283 + } 1.284 + 1.285 + // now we have a potential insertion or removal event, see if the slot 1.286 + // is present to determine which it is... 1.287 + if (PK11_IsPresent(slot)) { 1.288 + // insertion 1.289 + CK_SLOT_ID slotID = PK11_GetSlotID(slot); 1.290 + uint32_t series = PK11_GetSlotSeries(slot); 1.291 + 1.292 + // skip spurious insertion events... 1.293 + if (series != GetTokenSeries(slotID)) { 1.294 + // if there's a token name, then we have not yet issued a remove 1.295 + // event for the previous token, do so now... 1.296 + tokenName = GetTokenName(slotID); 1.297 + if (tokenName) { 1.298 + SendEvent(NS_LITERAL_STRING(SMARTCARDEVENT_REMOVE), tokenName); 1.299 + } 1.300 + tokenName = PK11_GetTokenName(slot); 1.301 + // save the token name and series 1.302 + SetTokenName(slotID, tokenName, series); 1.303 + SendEvent(NS_LITERAL_STRING(SMARTCARDEVENT_INSERT), tokenName); 1.304 + } 1.305 + } else { 1.306 + // retrieve token name 1.307 + CK_SLOT_ID slotID = PK11_GetSlotID(slot); 1.308 + tokenName = GetTokenName(slotID); 1.309 + // if there's not a token name, then the software isn't expecting 1.310 + // a (or another) remove event. 1.311 + if (tokenName) { 1.312 + SendEvent(NS_LITERAL_STRING(SMARTCARDEVENT_REMOVE), tokenName); 1.313 + // clear the token name (after we send it) 1.314 + SetTokenName(slotID, nullptr, 0); 1.315 + } 1.316 + } 1.317 + PK11_FreeSlot(slot); 1.318 + 1.319 + } while (1); 1.320 +} 1.321 + 1.322 +// accessor to help searching active Monitoring threads 1.323 +const SECMODModule * SmartCardMonitoringThread::GetModule() 1.324 +{ 1.325 + return mModule; 1.326 +} 1.327 + 1.328 +// C-like calling sequence to glue into PR_CreateThread. 1.329 +void SmartCardMonitoringThread::LaunchExecute(void *arg) 1.330 +{ 1.331 + PR_SetCurrentThreadName("SmartCard"); 1.332 + 1.333 + ((SmartCardMonitoringThread*)arg)->Execute(); 1.334 +} 1.335 +