nsSmartCardMonitor.cpp 11 KB

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