]> git.saurik.com Git - apple/securityd.git/blob - src/pcscmonitor.cpp
securityd-55199.tar.gz
[apple/securityd.git] / src / pcscmonitor.cpp
1 /*
2 * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25 //
26 // pcscmonitor - use PCSC to monitor smartcard reader/card state for securityd
27 //
28 // PCSCMonitor is the "glue" between PCSC and the securityd objects representing
29 // smartcard-related things. Its job is to manage the daemon and translate real-world
30 // events (such as card and device insertions) into the securityd object web.
31 //
32 // PCSCMonitor uses multiple inheritance to the hilt. It is (among others)
33 // (*) A notification listener, to listen to pcscd state notifications
34 // (*) A MachServer::Timer, to handle timed actions
35 // (*) A NotificationPort::Receiver, to get IOKit notifications of device insertions
36 // (*) A Child, to watch and manage the pcscd process
37 //
38 #include "pcscmonitor.h"
39 #include <security_utilities/logging.h>
40 #include <IOKit/usb/IOUSBLib.h>
41
42
43 //
44 // Fixed configuration parameters
45 //
46 static const char PCSCD_EXEC_PATH[] = "/usr/sbin/pcscd"; // override with $PCSCDAEMON
47 static const char PCSCD_WORKING_DIR[] = "/var/run/pcscd"; // pcscd's working directory
48 static const Time::Interval PCSCD_IDLE_SHUTDOWN(120); // kill daemon if no devices present
49
50 // Apple built-in iSight Device VendorID/ProductID: 0x05AC/0x8501
51
52 static const uint32_t kVendorProductMask = 0x0000FFFF;
53 static const uint32_t kVendorIDApple = 0x05AC;
54 static const uint16_t kProductIDBuiltInISight = 0x8501;
55
56 /*
57 Copied from USBVideoClass-230.2.3/Digitizers/USBVDC/Camera/USBClient/APW_VDO_USBVDC_USBClient.h
58 */
59
60 enum {
61 kBuiltIniSightProductID = 0x8501,
62 kBuiltIniSightWave2ProductID = 0x8502,
63 kBuiltIniSightWave3ProductID = 0x8505,
64 kUSBWave4ProductID = 0x8507,
65 kUSBWave2InK29ProductID = 0x8508,
66 kUSBWaveReserved1ProductID = 0x8509,
67 kUSBWaveReserved2ProductID = 0x850a,
68 kExternaliSightProductID = 0x1111,
69 kLogitechVendorID = 0x046d
70 };
71
72 //
73 // Construct a PCSCMonitor.
74 // We strongly assume there's only one of us around here.
75 //
76 // Note that this constructor may well run before the server loop has started.
77 // Don't call anything here that requires an active server loop (like Server::active()).
78 // In fact, you should push all the hard work into a timer, so as not to hold up the
79 // general startup process.
80 //
81 PCSCMonitor::PCSCMonitor(Server &server, const char* pathToCache, ServiceLevel level)
82 : Listener(kNotificationDomainPCSC, SecurityServer::kNotificationAllEvents),
83 MachServer::Timer(true), // "heavy" timer task
84 server(server),
85 mServiceLevel(level),
86 mTimerAction(&PCSCMonitor::initialSetup),
87 mGoingToSleep(false),
88 mCachePath(pathToCache),
89 mTokenCache(NULL)
90 {
91 // do all the smartcard-related work once the event loop has started
92 server.setTimer(this, Time::now()); // ASAP
93 }
94
95
96 //
97 // Poll PCSC for smartcard status.
98 // We are enumerating all readers on each call.
99 //
100 void PCSCMonitor::pollReaders()
101 {
102 // open PCSC session if it's not already open
103 mSession.open();
104
105 // enumerate readers
106 vector<string> names; // will hold reader name C strings throughout
107 mSession.listReaders(names);
108 size_t count = names.size();
109 secdebug("pcsc", "%ld reader(s) in system", count);
110
111 // build the PCSC status inquiry array
112 vector<PCSC::ReaderState> states(count); // reader status array (PCSC style)
113 for (unsigned int n = 0; n < count; n++) {
114 PCSC::ReaderState &state = states[n];
115 ReaderMap::iterator it = mReaders.find(names[n]);
116 if (it == mReaders.end()) { // new reader
117 state.clearPod();
118 state.name(names[n].c_str());
119 // lastKnown(PCSC_STATE_UNKNOWN)
120 // userData<Reader>() = NULL
121 } else {
122 state = it->second->pcscState();
123 state.name(names[n].c_str()); // OUR pointer
124 state.lastKnown(state.state());
125 state.userData<Reader>() = it->second;
126 }
127 }
128
129 // now ask PCSC for status changes
130 mSession.statusChange(states);
131 #if 0 //DEBUGDUMP
132 if (Debug::dumping("pcsc"))
133 for (unsigned int n = 0; n < count; n++)
134 states[n].dump();
135 #endif
136
137 // make a set of previously known reader objects (to catch those who disappeared)
138 ReaderSet current;
139 copy_second(mReaders.begin(), mReaders.end(), inserter(current, current.end()));
140
141 // match state array against them
142 for (unsigned int n = 0; n < count; n++) {
143 PCSC::ReaderState &state = states[n];
144 if (Reader *reader = state.userData<Reader>()) {
145 // if PCSC flags a change, notify the Reader
146 if (state.changed())
147 reader->update(state);
148 // accounted for this reader
149 current.erase(reader);
150 } else {
151 RefPointer<Reader> newReader = new Reader(tokenCache(), state);
152 mReaders.insert(make_pair(state.name(), newReader));
153 Syslog::notice("Token reader %s inserted into system", state.name());
154 newReader->update(state); // initial state setup
155 }
156 }
157
158 // now deal with known readers that PCSC did not report
159 for (ReaderSet::iterator it = current.begin(); it != current.end(); it++) {
160 switch ((*it)->type()) {
161 case Reader::pcsc:
162 // previous PCSC reader - was removed from system
163 secdebug("pcsc", "removing reader %s", (*it)->name().c_str());
164 Syslog::notice("Token reader %s removed from system", (*it)->name().c_str());
165 (*it)->kill(); // prepare to die
166 mReaders.erase((*it)->name()); // remove from reader map
167 break;
168 case Reader::software:
169 // previous software reader - keep
170 break;
171 }
172 }
173 }
174
175
176 //
177 // Remove some types of readers
178 //
179 void PCSCMonitor::clearReaders(Reader::Type type)
180 {
181 if (!mReaders.empty()) {
182 secdebug("pcsc", "%ld readers present - clearing type %d", mReaders.size(), type);
183 for (ReaderMap::iterator it = mReaders.begin(); it != mReaders.end(); ) {
184 ReaderMap::iterator cur = it++;
185 Reader *reader = cur->second;
186 if (reader->isType(type)) {
187 secdebug("pcsc", "removing reader %s", reader->name().c_str());
188 reader->kill(); // prepare to die
189 mReaders.erase(cur);
190 }
191 }
192 }
193 }
194
195
196 //
197 // Poll PCSC for smartcard status.
198 // We are enumerating all readers on each call.
199 //
200 TokenCache& PCSCMonitor::tokenCache()
201 {
202 if (mTokenCache == NULL)
203 mTokenCache = new TokenCache(mCachePath.c_str());
204 return *mTokenCache;
205 }
206
207
208
209 void PCSCMonitor::launchPcscd()
210 {
211 // launch pcscd
212 secdebug("pcsc", "launching pcscd to handle smartcard device(s)");
213 assert(Child::state() != alive);
214 Child::reset();
215 Child::fork();
216
217 // if pcscd doesn't report a reader found soon, we'll kill it off
218 scheduleTimer(true);
219 }
220
221
222 //
223 // Code to launch pcscd (run in child as a result of Child::fork())
224 //
225 void PCSCMonitor::childAction()
226 {
227 // move aside any old play area
228 const char *aside = tempnam("/tmp", "pcscd");
229 if (::rename(PCSCD_WORKING_DIR, aside))
230 switch (errno) {
231 case ENOENT: // no /tmp/pcsc (fine)
232 break;
233 default:
234 secdebug("pcsc", "failed too move %s - errno=%d", PCSCD_WORKING_DIR, errno);
235 _exit(101);
236 }
237 else
238 secdebug("pcsc", "old /tmp/pcsc moved to %s", aside);
239
240 // lessen the pain for debugging
241 #if !defined(NDEBUG)
242 freopen("/tmp/pcsc.debuglog", "a", stdout); // shut up pcsc dumps to stdout
243 #endif //NDEBUG
244
245 // execute the daemon
246 const char *pcscdPath = PCSCD_EXEC_PATH;
247 if (const char *env = getenv("PCSCDAEMON"))
248 pcscdPath = env;
249 secdebug("pcsc", "exec(%s,-f)", pcscdPath);
250 execl(pcscdPath, pcscdPath, "-f", NULL);
251 }
252
253
254 //
255 // Event notifier.
256 // These events are sent by pcscd for our (sole) benefit.
257 //
258 void PCSCMonitor::notifyMe(Notification *message)
259 {
260 Server::active().longTermActivity();
261 StLock<Mutex> _(*this);
262 assert(mServiceLevel == externalDaemon || Child::state() == alive);
263 if (message->event == kNotificationPCSCInitialized)
264 clearReaders(Reader::pcsc);
265 pollReaders();
266 scheduleTimer(mReaders.empty() && !mGoingToSleep);
267 }
268
269
270 //
271 // Power event notifications
272 //
273 void PCSCMonitor::systemWillSleep()
274 {
275 StLock<Mutex> _(*this);
276 secdebug("pcsc", "setting sleep marker (%ld readers as of now)", mReaders.size());
277 mGoingToSleep = true;
278 server.clearTimer(this);
279 }
280
281 void PCSCMonitor::systemIsWaking()
282 {
283 StLock<Mutex> _(*this);
284 secdebug("pcsc", "clearing sleep marker (%ld readers as of now)", mReaders.size());
285 mGoingToSleep = false;
286 scheduleTimer(mReaders.empty());
287 }
288
289
290 //
291 // Timer action.
292 //
293 void PCSCMonitor::action()
294 {
295 StLock<Mutex> _(*this);
296 (this->*mTimerAction)();
297 mTimerAction = &PCSCMonitor::noDeviceTimeout;
298 }
299
300
301 //
302 // Update the timeout timer as requested (and indicated by context)
303 //
304 void PCSCMonitor::scheduleTimer(bool enable)
305 {
306 if (Child::state() == alive) // we ran pcscd; let's manage it
307 if (enable) {
308 secdebug("pcsc", "setting idle timer for %g seconds", PCSCD_IDLE_SHUTDOWN.seconds());
309 server.setTimer(this, PCSCD_IDLE_SHUTDOWN);
310 } else if (Timer::scheduled()) {
311 secdebug("pcsc", "clearing idle timer");
312 server.clearTimer(this);
313 }
314 }
315
316
317 //
318 // Perform the initial PCSC subsystem initialization.
319 // This runs (shortly) after securityd is fully functional and the
320 // server loop has started.
321 //
322 void PCSCMonitor::initialSetup()
323 {
324 switch (mServiceLevel) {
325 case forcedOff:
326 secdebug("pcsc", "smartcard operation is FORCED OFF");
327 break;
328
329 case forcedOn:
330 secdebug("pcsc", "pcscd launch is forced on");
331 launchPcscd();
332 startSoftTokens();
333 break;
334
335 case externalDaemon:
336 secdebug("pcsc", "using external pcscd (if any); no launch operations");
337 startSoftTokens();
338 break;
339
340 default:
341 secdebug("pcsc", "setting up automatic PCSC management in %s mode",
342 mServiceLevel == conservative ? "conservative" : "aggressive");
343
344 // receive Mach-based IOKit notifications through mIOKitNotifier
345 server.add(mIOKitNotifier);
346
347 // receive power event notifications (through our IOPowerWatcher personality)
348 server.add(this);
349
350 // ask for IOKit notifications for all new USB devices and process present ones
351 IOKit::DeviceMatch usbSelector(kIOUSBInterfaceClassName);
352 IOKit::DeviceMatch pcCardSelector("IOPCCard16Device");
353 mIOKitNotifier.add(usbSelector, *this); // this will scan existing USB devices
354 mIOKitNotifier.add(pcCardSelector, *this); // ditto for PC Card devices
355 if (mServiceLevel == aggressive) {
356 // catch custom non-composite USB devices - they don't have IOServices attached
357 IOKit::DeviceMatch customUsbSelector(::IOServiceMatching("IOUSBDevice"));
358 mIOKitNotifier.add(customUsbSelector, *this); // ditto for custom USB devices
359 }
360
361 // find and start software tokens
362 startSoftTokens();
363
364 break;
365 }
366
367 // we are NOT scanning for PCSC devices here. Pcscd will send us a notification when it's up
368 }
369
370
371 //
372 // This function is called (as a timer function) when there haven't been any (recognized)
373 // smartcard devicees in the system for a while.
374 //
375 void PCSCMonitor::noDeviceTimeout()
376 {
377 secdebug("pcsc", "killing pcscd (no smartcard devices present for %g seconds)",
378 PCSCD_IDLE_SHUTDOWN.seconds());
379 assert(mReaders.empty());
380 Child::kill(SIGTERM);
381 }
382
383
384 //
385 // IOKit device event notification.
386 // Here we listen for newly inserted devices and check whether to launch pcscd.
387 //
388 void PCSCMonitor::ioChange(IOKit::DeviceIterator &iterator)
389 {
390 assert(mServiceLevel != externalDaemon && mServiceLevel != forcedOff);
391 if (Child::state() == alive) {
392 secdebug("pcsc", "pcscd is alive; ignoring device insertion(s)");
393 return;
394 }
395 secdebug("pcsc", "processing device insertion notices");
396 while (IOKit::Device dev = iterator()) {
397 bool launch = false;
398 switch (deviceSupport(dev)) {
399 case definite:
400 launch = true;
401 break;
402 case possible:
403 launch = (mServiceLevel == aggressive);
404 break;
405 case impossible:
406 break;
407 }
408 if (launch) {
409 launchPcscd();
410 return;
411 }
412 }
413 secdebug("pcsc", "no relevant devices found");
414 }
415
416
417 //
418 // Check an IOKit device that's just come online to see if it's
419 // a smartcard device of some sort.
420 //
421 PCSCMonitor::DeviceSupport PCSCMonitor::deviceSupport(const IOKit::Device &dev)
422 {
423 try {
424 secdebug("scsel", "%s", dev.path().c_str());
425
426 // composite USB device with interface class
427 if (CFRef<CFNumberRef> cfInterface = dev.property<CFNumberRef>("bInterfaceClass"))
428 switch (uint32 clas = cfNumber(cfInterface)) {
429 case kUSBChipSmartCardInterfaceClass: // CCID smartcard reader - go
430 secdebug("scsel", " CCID smartcard reader recognized");
431 return definite;
432 case kUSBVendorSpecificInterfaceClass:
433 secdebug("scsel", " Vendor-specific interface - possible match");
434 if (isExcludedDevice(dev))
435 {
436 secdebug("scsel", " interface class %d is not a smartcard device (excluded)", clas);
437 return impossible;
438 }
439 return possible;
440 default:
441 secdebug("scsel", " interface class %d is not a smartcard device", clas);
442 return impossible;
443 }
444
445 // noncomposite USB device
446 if (CFRef<CFNumberRef> cfDevice = dev.property<CFNumberRef>("bDeviceClass"))
447 if (cfNumber(cfDevice) == kUSBVendorSpecificClass)
448 {
449 if (isExcludedDevice(dev))
450 {
451 secdebug("scsel", " device class %d is not a smartcard device (excluded)", cfNumber(cfDevice));
452 return impossible;
453 }
454 secdebug("scsel", " Vendor-specific device - possible match");
455 return possible;
456 }
457
458 // PCCard (aka PCMCIA aka ...) interface (don't know how to recognize a reader here)
459 if (CFRef<CFStringRef> ioName = dev.property<CFStringRef>("IOName"))
460 if (cfString(ioName).find("pccard", 0, 1) == 0) {
461 secdebug("scsel", " PCCard - possible match");
462 return possible;
463 }
464 return impossible;
465 } catch (...) {
466 secdebug("scsel", " exception while examining device - ignoring it");
467 return impossible;
468 }
469 }
470
471 bool PCSCMonitor::isExcludedDevice(const IOKit::Device &dev)
472 {
473 uint32_t vendorID = 0, productID = 0;
474 // Simplified version of getVendorAndProductID in pcscd
475 if (CFRef<CFNumberRef> cfVendorID = dev.property<CFNumberRef>(kUSBVendorID))
476 vendorID = cfNumber(cfVendorID);
477
478 if (CFRef<CFNumberRef> cfProductID = dev.property<CFNumberRef>(kUSBProductID))
479 productID = cfNumber(cfProductID);
480
481 secdebug("scsel", " checking device for possible exclusion [vendor id: 0x%08X, product id: 0x%08X]", vendorID, productID);
482
483 if ((vendorID & kVendorProductMask) != kVendorIDApple)
484 return false; // i.e. it is not an excluded device
485
486 // Since Apple does not manufacture smartcard readers, just exclude
487 // If we even start making them, we should make it a CCID reader anyway
488
489 return true;
490 }
491
492 //
493 // This gets called (by the Unix/Child system) when pcscd has died for any reason
494 //
495 void PCSCMonitor::dying()
496 {
497 Server::active().longTermActivity();
498 StLock<Mutex> _(*this);
499 assert(Child::state() == dead);
500 clearReaders(Reader::pcsc);
501 //@@@ this is where we would attempt a restart, if we wanted to...
502 }
503
504
505 //
506 // Software token support
507 //
508 void PCSCMonitor::startSoftTokens()
509 {
510 // clear all software readers. This will kill the respective TokenDaemons
511 clearReaders(Reader::software);
512
513 // scan for new ones
514 CodeRepository<Bundle> candidates("Security/tokend", ".tokend", "TOKENDAEMONPATH", false);
515 candidates.update();
516 for (CodeRepository<Bundle>::iterator it = candidates.begin(); it != candidates.end(); ++it) {
517 if (CFTypeRef type = (*it)->infoPlistItem("TokendType"))
518 if (CFEqual(type, CFSTR("software")))
519 loadSoftToken(*it);
520 }
521 }
522
523 void PCSCMonitor::loadSoftToken(Bundle *tokendBundle)
524 {
525 try {
526 string bundleName = tokendBundle->identifier();
527
528 // prepare a virtual reader, removing any existing one (this would kill a previous tokend)
529 assert(mReaders.find(bundleName) == mReaders.end()); // not already present
530 RefPointer<Reader> reader = new Reader(tokenCache(), bundleName);
531
532 // now launch the tokend
533 RefPointer<TokenDaemon> tokend = new TokenDaemon(tokendBundle,
534 reader->name(), reader->pcscState(), reader->cache);
535
536 if (tokend->state() == ServerChild::dead) { // ah well, this one's no good
537 secdebug("pcsc", "softtoken %s tokend launch failed", bundleName.c_str());
538 Syslog::notice("Software token %s failed to run", tokendBundle->canonicalPath().c_str());
539 return;
540 }
541
542 // probe the (single) tokend
543 if (!tokend->probe()) { // non comprende...
544 secdebug("pcsc", "softtoken %s probe failed", bundleName.c_str());
545 Syslog::notice("Software token %s refused operation", tokendBundle->canonicalPath().c_str());
546 return;
547 }
548
549 // okay, this seems to work. Set it up
550 mReaders.insert(make_pair(reader->name(), reader));
551 reader->insertToken(tokend);
552 Syslog::notice("Software token %s activated", bundleName.c_str());
553 } catch (...) {
554 secdebug("pcsc", "exception loading softtoken %s - continuing", tokendBundle->identifier().c_str());
555 }
556 }