Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | #include "nspr.h" |
michael@0 | 5 | |
michael@0 | 6 | #include "pk11func.h" |
michael@0 | 7 | #include "nsNSSComponent.h" |
michael@0 | 8 | #include "nsSmartCardMonitor.h" |
michael@0 | 9 | #include "nsIDOMSmartCardEvent.h" |
michael@0 | 10 | #include "nsServiceManagerUtils.h" |
michael@0 | 11 | #include "mozilla/unused.h" |
michael@0 | 12 | |
michael@0 | 13 | using namespace mozilla; |
michael@0 | 14 | |
michael@0 | 15 | // |
michael@0 | 16 | // The SmartCard monitoring thread should start up for each module we load |
michael@0 | 17 | // that has removable tokens. This code calls an NSS function which waits |
michael@0 | 18 | // until there is a change in the token state. NSS uses the |
michael@0 | 19 | // C_WaitForSlotEvent() call in PKCS #11 if the module implements the call, |
michael@0 | 20 | // otherwise NSS will poll the token in a loop with a delay of 'latency' |
michael@0 | 21 | // between polls. Note that the C_WaitForSlotEvent() may wake up on any type |
michael@0 | 22 | // of token event, so it's necessary to filter these events down to just the |
michael@0 | 23 | // insertion and removal events we are looking for. |
michael@0 | 24 | // |
michael@0 | 25 | // Once the event is found, It is passed to nsNSSComponent for dispatching |
michael@0 | 26 | // on the UI thread, and forwarding to any interested listeners (including |
michael@0 | 27 | // javascript). |
michael@0 | 28 | // |
michael@0 | 29 | |
michael@0 | 30 | |
michael@0 | 31 | // self linking and removing double linked entry |
michael@0 | 32 | // adopts the thread it is passed. |
michael@0 | 33 | class SmartCardThreadEntry { |
michael@0 | 34 | public: |
michael@0 | 35 | SmartCardThreadEntry *next; |
michael@0 | 36 | SmartCardThreadEntry *prev; |
michael@0 | 37 | SmartCardThreadEntry **head; |
michael@0 | 38 | SmartCardMonitoringThread *thread; |
michael@0 | 39 | SmartCardThreadEntry(SmartCardMonitoringThread *thread_, |
michael@0 | 40 | SmartCardThreadEntry *next_, SmartCardThreadEntry *prev_, |
michael@0 | 41 | SmartCardThreadEntry **head_) : |
michael@0 | 42 | next(next_), prev(prev_), head(head_), thread(thread_) { |
michael@0 | 43 | if (prev) { prev->next = this; } else { *head = this; } |
michael@0 | 44 | if (next) { next->prev = this; } |
michael@0 | 45 | } |
michael@0 | 46 | ~SmartCardThreadEntry() { |
michael@0 | 47 | if (prev) { prev->next = next; } else { *head = next; } |
michael@0 | 48 | if (next) { next->prev = prev; } |
michael@0 | 49 | // NOTE: automatically stops the thread |
michael@0 | 50 | delete thread; |
michael@0 | 51 | } |
michael@0 | 52 | }; |
michael@0 | 53 | |
michael@0 | 54 | // |
michael@0 | 55 | // SmartCardThreadList is a class to help manage the running threads. |
michael@0 | 56 | // That way new threads could be started and old ones terminated as we |
michael@0 | 57 | // load and unload modules. |
michael@0 | 58 | // |
michael@0 | 59 | SmartCardThreadList::SmartCardThreadList() : head(0) |
michael@0 | 60 | { |
michael@0 | 61 | } |
michael@0 | 62 | |
michael@0 | 63 | SmartCardThreadList::~SmartCardThreadList() |
michael@0 | 64 | { |
michael@0 | 65 | // the head is self linking and unlinking, the following |
michael@0 | 66 | // loop removes all entries on the list. |
michael@0 | 67 | // it will also stop the thread if it happens to be running |
michael@0 | 68 | while (head) { |
michael@0 | 69 | delete head; |
michael@0 | 70 | } |
michael@0 | 71 | } |
michael@0 | 72 | |
michael@0 | 73 | void |
michael@0 | 74 | SmartCardThreadList::Remove(SECMODModule *aModule) |
michael@0 | 75 | { |
michael@0 | 76 | for (SmartCardThreadEntry *current = head; current; current = current->next) { |
michael@0 | 77 | if (current->thread->GetModule() == aModule) { |
michael@0 | 78 | // NOTE: automatically stops the thread and dequeues it from the list |
michael@0 | 79 | delete current; |
michael@0 | 80 | return; |
michael@0 | 81 | } |
michael@0 | 82 | } |
michael@0 | 83 | } |
michael@0 | 84 | |
michael@0 | 85 | // adopts the thread passed to it. Starts the thread as well |
michael@0 | 86 | nsresult |
michael@0 | 87 | SmartCardThreadList::Add(SmartCardMonitoringThread *thread) |
michael@0 | 88 | { |
michael@0 | 89 | SmartCardThreadEntry *current = new SmartCardThreadEntry(thread, head, nullptr, |
michael@0 | 90 | &head); |
michael@0 | 91 | // OK to forget current here, it's on the list. |
michael@0 | 92 | unused << current; |
michael@0 | 93 | |
michael@0 | 94 | return thread->Start(); |
michael@0 | 95 | } |
michael@0 | 96 | |
michael@0 | 97 | |
michael@0 | 98 | // We really should have a Unity PL Hash function... |
michael@0 | 99 | static PLHashNumber |
michael@0 | 100 | unity(const void *key) { return PLHashNumber(NS_PTR_TO_INT32(key)); } |
michael@0 | 101 | |
michael@0 | 102 | SmartCardMonitoringThread::SmartCardMonitoringThread(SECMODModule *module_) |
michael@0 | 103 | : mThread(nullptr) |
michael@0 | 104 | { |
michael@0 | 105 | mModule = SECMOD_ReferenceModule(module_); |
michael@0 | 106 | // simple hash functions, most modules have less than 3 slots, so 10 buckets |
michael@0 | 107 | // should be plenty |
michael@0 | 108 | mHash = PL_NewHashTable(10, unity, PL_CompareValues, |
michael@0 | 109 | PL_CompareStrings, nullptr, 0); |
michael@0 | 110 | } |
michael@0 | 111 | |
michael@0 | 112 | // |
michael@0 | 113 | // when we shutdown the thread, be sure to stop it first. If not, it just might |
michael@0 | 114 | // crash when the mModule it is looking at disappears. |
michael@0 | 115 | // |
michael@0 | 116 | SmartCardMonitoringThread::~SmartCardMonitoringThread() |
michael@0 | 117 | { |
michael@0 | 118 | Stop(); |
michael@0 | 119 | SECMOD_DestroyModule(mModule); |
michael@0 | 120 | if (mHash) { |
michael@0 | 121 | PL_HashTableDestroy(mHash); |
michael@0 | 122 | } |
michael@0 | 123 | } |
michael@0 | 124 | |
michael@0 | 125 | nsresult |
michael@0 | 126 | SmartCardMonitoringThread::Start() |
michael@0 | 127 | { |
michael@0 | 128 | if (!mThread) { |
michael@0 | 129 | mThread = PR_CreateThread(PR_SYSTEM_THREAD, LaunchExecute, this, |
michael@0 | 130 | PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, |
michael@0 | 131 | PR_JOINABLE_THREAD, 0); |
michael@0 | 132 | } |
michael@0 | 133 | return mThread ? NS_OK : NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 134 | } |
michael@0 | 135 | |
michael@0 | 136 | // |
michael@0 | 137 | // Should only stop if we are through with the module. |
michael@0 | 138 | // CancelWait has the side effect of losing all the keys and |
michael@0 | 139 | // current operations on the module!. (See the comment in |
michael@0 | 140 | // SECMOD_CancelWait for why this is so..). |
michael@0 | 141 | // |
michael@0 | 142 | void SmartCardMonitoringThread::Stop() |
michael@0 | 143 | { |
michael@0 | 144 | SECStatus rv; |
michael@0 | 145 | |
michael@0 | 146 | rv = SECMOD_CancelWait(mModule); |
michael@0 | 147 | if (rv != SECSuccess) { |
michael@0 | 148 | // we didn't wake up the Wait, so don't try to join the thread |
michael@0 | 149 | // otherwise we will hang forever... |
michael@0 | 150 | return; |
michael@0 | 151 | } |
michael@0 | 152 | |
michael@0 | 153 | // confused about the memory model here? NSPR owns the memory for |
michael@0 | 154 | // threads. non-joinable threads are freed when the thread dies. |
michael@0 | 155 | // joinable threads are freed after the call to PR_JoinThread. |
michael@0 | 156 | // That means if SECMOD_CancelWait fails, we'll leak the mThread |
michael@0 | 157 | // structure. this is considered preferable to hanging (which is |
michael@0 | 158 | // what will happen if we try to join a thread that blocked). |
michael@0 | 159 | if (mThread) { |
michael@0 | 160 | PR_JoinThread(mThread); |
michael@0 | 161 | mThread = 0; |
michael@0 | 162 | } |
michael@0 | 163 | } |
michael@0 | 164 | |
michael@0 | 165 | // |
michael@0 | 166 | // remember the name and series of a token in a particular slot. |
michael@0 | 167 | // This is important because the name is no longer available when |
michael@0 | 168 | // the token is removed. If listeners depended on this information, |
michael@0 | 169 | // They would be out of luck. It also is a handy way of making sure |
michael@0 | 170 | // we don't generate spurious insertion and removal events as the slot |
michael@0 | 171 | // cycles through various states. |
michael@0 | 172 | // |
michael@0 | 173 | void |
michael@0 | 174 | SmartCardMonitoringThread::SetTokenName(CK_SLOT_ID slotid, |
michael@0 | 175 | const char *tokenName, uint32_t series) |
michael@0 | 176 | { |
michael@0 | 177 | if (mHash) { |
michael@0 | 178 | if (tokenName) { |
michael@0 | 179 | int len = strlen(tokenName) + 1; |
michael@0 | 180 | /* this must match the allocator used in |
michael@0 | 181 | * PLHashAllocOps.freeEntry DefaultFreeEntry */ |
michael@0 | 182 | char *entry = (char *)PR_Malloc(len+sizeof(uint32_t)); |
michael@0 | 183 | |
michael@0 | 184 | if (entry) { |
michael@0 | 185 | memcpy(entry,&series,sizeof(uint32_t)); |
michael@0 | 186 | memcpy(&entry[sizeof(uint32_t)],tokenName,len); |
michael@0 | 187 | |
michael@0 | 188 | PL_HashTableAdd(mHash,(void *)(uintptr_t)slotid, entry); /* adopt */ |
michael@0 | 189 | return; |
michael@0 | 190 | } |
michael@0 | 191 | } |
michael@0 | 192 | else { |
michael@0 | 193 | // if tokenName was not provided, remove the old one (implicit delete) |
michael@0 | 194 | PL_HashTableRemove(mHash,(void *)(uintptr_t)slotid); |
michael@0 | 195 | } |
michael@0 | 196 | } |
michael@0 | 197 | } |
michael@0 | 198 | |
michael@0 | 199 | // retrieve the name saved above |
michael@0 | 200 | const char * |
michael@0 | 201 | SmartCardMonitoringThread::GetTokenName(CK_SLOT_ID slotid) |
michael@0 | 202 | { |
michael@0 | 203 | const char *tokenName = nullptr; |
michael@0 | 204 | const char *entry; |
michael@0 | 205 | |
michael@0 | 206 | if (mHash) { |
michael@0 | 207 | entry = (const char *)PL_HashTableLookupConst(mHash,(void *)(uintptr_t)slotid); |
michael@0 | 208 | if (entry) { |
michael@0 | 209 | tokenName = &entry[sizeof(uint32_t)]; |
michael@0 | 210 | } |
michael@0 | 211 | } |
michael@0 | 212 | return tokenName; |
michael@0 | 213 | } |
michael@0 | 214 | |
michael@0 | 215 | // retrieve the series saved in SetTokenName above |
michael@0 | 216 | uint32_t |
michael@0 | 217 | SmartCardMonitoringThread::GetTokenSeries(CK_SLOT_ID slotid) |
michael@0 | 218 | { |
michael@0 | 219 | uint32_t series = 0; |
michael@0 | 220 | const char *entry; |
michael@0 | 221 | |
michael@0 | 222 | if (mHash) { |
michael@0 | 223 | entry = (const char *)PL_HashTableLookupConst(mHash,(void *)(uintptr_t)slotid); |
michael@0 | 224 | if (entry) { |
michael@0 | 225 | memcpy(&series,entry,sizeof(uint32_t)); |
michael@0 | 226 | } |
michael@0 | 227 | } |
michael@0 | 228 | return series; |
michael@0 | 229 | } |
michael@0 | 230 | |
michael@0 | 231 | // |
michael@0 | 232 | // helper function to pass the event off to nsNSSComponent. |
michael@0 | 233 | // |
michael@0 | 234 | nsresult |
michael@0 | 235 | SmartCardMonitoringThread::SendEvent(const nsAString &eventType, |
michael@0 | 236 | const char *tokenName) |
michael@0 | 237 | { |
michael@0 | 238 | static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); |
michael@0 | 239 | |
michael@0 | 240 | nsresult rv; |
michael@0 | 241 | nsCOMPtr<nsINSSComponent> |
michael@0 | 242 | nssComponent(do_GetService(kNSSComponentCID, &rv)); |
michael@0 | 243 | if (NS_FAILED(rv)) |
michael@0 | 244 | return rv; |
michael@0 | 245 | |
michael@0 | 246 | // NSS returns actual UTF8, not ASCII |
michael@0 | 247 | nssComponent->PostEvent(eventType, NS_ConvertUTF8toUTF16(tokenName)); |
michael@0 | 248 | return NS_OK; |
michael@0 | 249 | } |
michael@0 | 250 | |
michael@0 | 251 | // |
michael@0 | 252 | // This is the main loop. |
michael@0 | 253 | // |
michael@0 | 254 | void SmartCardMonitoringThread::Execute() |
michael@0 | 255 | { |
michael@0 | 256 | PK11SlotInfo *slot; |
michael@0 | 257 | const char *tokenName = nullptr; |
michael@0 | 258 | |
michael@0 | 259 | // |
michael@0 | 260 | // populate token names for already inserted tokens. |
michael@0 | 261 | // |
michael@0 | 262 | PK11SlotList *sl = |
michael@0 | 263 | PK11_FindSlotsByNames(mModule->dllName, nullptr, nullptr, true); |
michael@0 | 264 | PK11SlotListElement *sle; |
michael@0 | 265 | |
michael@0 | 266 | if (sl) { |
michael@0 | 267 | for (sle=PK11_GetFirstSafe(sl); sle; |
michael@0 | 268 | sle=PK11_GetNextSafe(sl,sle,false)) { |
michael@0 | 269 | SetTokenName(PK11_GetSlotID(sle->slot), |
michael@0 | 270 | PK11_GetTokenName(sle->slot), PK11_GetSlotSeries(sle->slot)); |
michael@0 | 271 | } |
michael@0 | 272 | PK11_FreeSlotList(sl); |
michael@0 | 273 | } |
michael@0 | 274 | |
michael@0 | 275 | // loop starts.. |
michael@0 | 276 | do { |
michael@0 | 277 | slot = SECMOD_WaitForAnyTokenEvent(mModule, 0, PR_SecondsToInterval(1) ); |
michael@0 | 278 | if (!slot) { |
michael@0 | 279 | break; |
michael@0 | 280 | } |
michael@0 | 281 | |
michael@0 | 282 | // now we have a potential insertion or removal event, see if the slot |
michael@0 | 283 | // is present to determine which it is... |
michael@0 | 284 | if (PK11_IsPresent(slot)) { |
michael@0 | 285 | // insertion |
michael@0 | 286 | CK_SLOT_ID slotID = PK11_GetSlotID(slot); |
michael@0 | 287 | uint32_t series = PK11_GetSlotSeries(slot); |
michael@0 | 288 | |
michael@0 | 289 | // skip spurious insertion events... |
michael@0 | 290 | if (series != GetTokenSeries(slotID)) { |
michael@0 | 291 | // if there's a token name, then we have not yet issued a remove |
michael@0 | 292 | // event for the previous token, do so now... |
michael@0 | 293 | tokenName = GetTokenName(slotID); |
michael@0 | 294 | if (tokenName) { |
michael@0 | 295 | SendEvent(NS_LITERAL_STRING(SMARTCARDEVENT_REMOVE), tokenName); |
michael@0 | 296 | } |
michael@0 | 297 | tokenName = PK11_GetTokenName(slot); |
michael@0 | 298 | // save the token name and series |
michael@0 | 299 | SetTokenName(slotID, tokenName, series); |
michael@0 | 300 | SendEvent(NS_LITERAL_STRING(SMARTCARDEVENT_INSERT), tokenName); |
michael@0 | 301 | } |
michael@0 | 302 | } else { |
michael@0 | 303 | // retrieve token name |
michael@0 | 304 | CK_SLOT_ID slotID = PK11_GetSlotID(slot); |
michael@0 | 305 | tokenName = GetTokenName(slotID); |
michael@0 | 306 | // if there's not a token name, then the software isn't expecting |
michael@0 | 307 | // a (or another) remove event. |
michael@0 | 308 | if (tokenName) { |
michael@0 | 309 | SendEvent(NS_LITERAL_STRING(SMARTCARDEVENT_REMOVE), tokenName); |
michael@0 | 310 | // clear the token name (after we send it) |
michael@0 | 311 | SetTokenName(slotID, nullptr, 0); |
michael@0 | 312 | } |
michael@0 | 313 | } |
michael@0 | 314 | PK11_FreeSlot(slot); |
michael@0 | 315 | |
michael@0 | 316 | } while (1); |
michael@0 | 317 | } |
michael@0 | 318 | |
michael@0 | 319 | // accessor to help searching active Monitoring threads |
michael@0 | 320 | const SECMODModule * SmartCardMonitoringThread::GetModule() |
michael@0 | 321 | { |
michael@0 | 322 | return mModule; |
michael@0 | 323 | } |
michael@0 | 324 | |
michael@0 | 325 | // C-like calling sequence to glue into PR_CreateThread. |
michael@0 | 326 | void SmartCardMonitoringThread::LaunchExecute(void *arg) |
michael@0 | 327 | { |
michael@0 | 328 | PR_SetCurrentThreadName("SmartCard"); |
michael@0 | 329 | |
michael@0 | 330 | ((SmartCardMonitoringThread*)arg)->Execute(); |
michael@0 | 331 | } |
michael@0 | 332 |