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// 46static const char PCSCD_EXEC_PATH[] = "/usr/sbin/pcscd"; // override with $PCSCDAEMON 47static const char PCSCD_WORKING_DIR[] = "/var/run/pcscd"; // pcscd's working directory 48static 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 52static const uint32_t kVendorProductMask = 0x0000FFFF; 53static const uint32_t kVendorIDApple = 0x05AC; 54static 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 60enum { 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// 81PCSCMonitor::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// 100void 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// 179void 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// 200TokenCache& PCSCMonitor::tokenCache() 201{ 202 if (mTokenCache == NULL) 203 mTokenCache = new TokenCache(mCachePath.c_str()); 204 return *mTokenCache; 205} 206 207 208 209void 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// 225void 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// 258void 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// 273void 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 281void 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// 293void 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// 304void 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// 322void 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// 375void 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// 388void 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// 421PCSCMonitor::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 471bool 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// 495void 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// 508void 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 523void 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} 557