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