2  * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. 
   4  * @APPLE_LICENSE_HEADER_START@ 
   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 
  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. 
  21  * @APPLE_LICENSE_HEADER_END@ 
  26 // pcscmonitor - use PCSC to monitor smartcard reader/card state for securityd 
  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. 
  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 
  38 #include "pcscmonitor.h" 
  39 #include <security_utilities/logging.h> 
  40 #include <IOKit/usb/IOUSBLib.h> 
  44 // Fixed configuration parameters 
  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 
  50 // Apple built-in iSight Device VendorID/ProductID: 0x05AC/0x8501 
  52 static const uint32_t kVendorProductMask 
= 0x0000FFFF; 
  53 static const uint32_t kVendorIDApple 
= 0x05AC; 
  54 static const uint16_t kProductIDBuiltInISight 
= 0x8501; 
  57 // Construct a PCSCMonitor. 
  58 // We strongly assume there's only one of us around here. 
  60 // Note that this constructor may well run before the server loop has started. 
  61 // Don't call anything here that requires an active server loop (like Server::active()). 
  62 // In fact, you should push all the hard work into a timer, so as not to hold up the 
  63 // general startup process. 
  65 PCSCMonitor::PCSCMonitor(Server 
&server
, const char* pathToCache
, ServiceLevel level
) 
  66         : Listener(kNotificationDomainPCSC
, SecurityServer::kNotificationAllEvents
), 
  67           MachServer::Timer(true), // "heavy" timer task 
  70           cachePath (pathToCache
), 
  72           mTimerAction(&PCSCMonitor::initialSetup
), 
  75         // do all the smartcard-related work once the event loop has started 
  76         server
.setTimer(this, Time::now());             // ASAP 
  81 // Poll PCSC for smartcard status. 
  82 // We are enumerating all readers on each call. 
  84 void PCSCMonitor::pollReaders() 
  86         // open PCSC session if it's not already open 
  90         vector
<string
> names
;  // will hold reader name C strings throughout 
  91         mSession
.listReaders(names
); 
  92         size_t count 
= names
.size(); 
  93         secdebug("pcsc", "%ld reader(s) in system", count
); 
  95         // build the PCSC status inquiry array 
  96         vector
<PCSC::ReaderState
> states(count
); // reader status array (PCSC style) 
  97         for (unsigned int n 
= 0; n 
< count
; n
++) { 
  98                 PCSC::ReaderState 
&state 
= states
[n
]; 
  99                 ReaderMap::iterator it 
= mReaders
.find(names
[n
]); 
 100                 if (it 
== mReaders
.end()) { // new reader 
 102                         state
.name(names
[n
].c_str()); 
 103                         // lastKnown(PCSC_STATE_UNKNOWN) 
 104                         // userData<Reader>() = NULL 
 106                         state 
= it
->second
->pcscState(); 
 107                         state
.name(names
[n
].c_str());  // OUR pointer 
 108                         state
.lastKnown(state
.state()); 
 109                         state
.userData
<Reader
>() = it
->second
; 
 113         // now ask PCSC for status changes 
 114         mSession
.statusChange(states
); 
 116         if (Debug::dumping("pcsc")) 
 117                 for (unsigned int n 
= 0; n 
< count
; n
++) 
 121         // make a set of previously known reader objects (to catch those who disappeared) 
 123         copy_second(mReaders
.begin(), mReaders
.end(), inserter(current
, current
.end())); 
 125         // match state array against them 
 126         for (unsigned int n 
= 0; n 
< count
; n
++) { 
 127                 PCSC::ReaderState 
&state 
= states
[n
]; 
 128                 if (Reader 
*reader 
= state
.userData
<Reader
>()) { 
 129                         // if PCSC flags a change, notify the Reader 
 131                                 reader
->update(state
); 
 132                         // accounted for this reader 
 133                         current
.erase(reader
); 
 135                         RefPointer
<Reader
> newReader 
= new Reader(getTokenCache (), state
); 
 136                         mReaders
.insert(make_pair(state
.name(), newReader
)); 
 137                         Syslog::notice("Token reader %s inserted into system", state
.name()); 
 138                         newReader
->update(state
);               // initial state setup 
 142         // now deal with vanished readers 
 143         for (ReaderSet::iterator it 
= current
.begin(); it 
!= current
.end(); it
++) { 
 144                 secdebug("pcsc", "removing reader %s", (*it
)->name().c_str()); 
 145                 Syslog::notice("Token reader %s removed from system", (*it
)->name().c_str()); 
 146                 (*it
)->kill();                                          // prepare to die 
 147                 mReaders
.erase((*it
)->name());          // remove from reader map 
 152 // Poll PCSC for smartcard status. 
 153 // We are enumerating all readers on each call. 
 155 void PCSCMonitor::clearReaders() 
 157         if (!mReaders
.empty()) { 
 158                 // uh-oh. We had readers connected when pcscd suddenly left 
 159                 secdebug("pcsc", "%ld readers were present when pcscd died", mReaders
.size()); 
 160                 for (ReaderMap::const_iterator it 
= mReaders
.begin(); it 
!= mReaders
.end(); it
++) { 
 161                         Reader 
*reader 
= it
->second
; 
 162                         secdebug("pcsc", "removing reader %s", reader
->name().c_str()); 
 163                         reader
->kill();                                         // prepare to die 
 165                 mReaders
.erase(mReaders
.begin(), mReaders
.end()); 
 166                 secdebug("pcsc", "orphaned readers cleared"); 
 170 TokenCache
& PCSCMonitor::getTokenCache () 
 173                 cache 
= new TokenCache(cachePath
.c_str ()); 
 181 void PCSCMonitor::launchPcscd() 
 184         secdebug("pcsc", "launching pcscd to handle smartcard device(s)"); 
 185         assert(Child::state() != alive
); 
 189         // if pcscd doesn't report a reader found soon, we'll kill it off 
 195 // Code to launch pcscd (run in child as a result of Child::fork()) 
 197 void PCSCMonitor::childAction() 
 199         // move aside any old play area 
 200         const char *aside 
= tempnam("/tmp", "pcscd"); 
 201         if (::rename(PCSCD_WORKING_DIR
, aside
)) 
 203                 case ENOENT
:            // no /tmp/pcsc (fine) 
 206                         secdebug("pcsc", "failed too move %s - errno=%d", PCSCD_WORKING_DIR
, errno
); 
 210                 secdebug("pcsc", "old /tmp/pcsc moved to %s", aside
); 
 212         // lessen the pain for debugging 
 214         freopen("/tmp/pcsc.debuglog", "a", stdout
);     // shut up pcsc dumps to stdout 
 217         // execute the daemon 
 218         const char *pcscdPath 
= PCSCD_EXEC_PATH
; 
 219         if (const char *env 
= getenv("PCSCDAEMON")) 
 221         secdebug("pcsc", "exec(%s,-f)", pcscdPath
); 
 222         execl(pcscdPath
, pcscdPath
, "-f", NULL
); 
 228 // These events are sent by pcscd for our (sole) benefit. 
 230 void PCSCMonitor::notifyMe(Notification 
*message
) 
 232         Server::active().longTermActivity(); 
 233         StLock
<Mutex
> _(*this); 
 234         assert(mServiceLevel 
== externalDaemon 
|| Child::state() == alive
); 
 235         if (message
->event 
== kNotificationPCSCInitialized
) 
 241         scheduleTimer(mReaders
.empty() && !mGoingToSleep
); 
 246 // Power event notifications 
 248 void PCSCMonitor::systemWillSleep() 
 250         StLock
<Mutex
> _(*this); 
 251         secdebug("pcsc", "setting sleep marker (%ld readers as of now)", mReaders
.size()); 
 252         mGoingToSleep 
= true; 
 253         server
.clearTimer(this); 
 256 void PCSCMonitor::systemIsWaking() 
 258         StLock
<Mutex
> _(*this); 
 259         secdebug("pcsc", "clearing sleep marker (%ld readers as of now)", mReaders
.size()); 
 260         mGoingToSleep 
= false; 
 261         scheduleTimer(mReaders
.empty()); 
 268 void PCSCMonitor::action() 
 270         StLock
<Mutex
> _(*this); 
 271         (this->*mTimerAction
)(); 
 272         mTimerAction 
= &PCSCMonitor::noDeviceTimeout
; 
 277 // Update the timeout timer as requested (and indicated by context) 
 279 void PCSCMonitor::scheduleTimer(bool enable
) 
 281         if (Child::state() == alive
)    // we ran pcscd; let's manage it 
 283                         secdebug("pcsc", "setting idle timer for %g seconds", PCSCD_IDLE_SHUTDOWN
.seconds()); 
 284                         server
.setTimer(this, PCSCD_IDLE_SHUTDOWN
); 
 285                 } else if (Timer::scheduled()) { 
 286                         secdebug("pcsc", "clearing idle timer"); 
 287                         server
.clearTimer(this); 
 293 // Perform the initial PCSC subsystem initialization. 
 294 // This runs (shortly) after securityd is fully functional and the 
 295 // server loop has started. 
 297 void PCSCMonitor::initialSetup() 
 299         switch (mServiceLevel
) { 
 301                 secdebug("pcsc", "smartcard operation is FORCED OFF"); 
 305                 secdebug("pcsc", "pcscd launch is forced on"); 
 310                 secdebug("pcsc", "using external pcscd (if any); no launch operations"); 
 314                 secdebug("pcsc", "setting up automatic PCSC management in %s mode", 
 315                         mServiceLevel 
== conservative 
? "conservative" : "aggressive"); 
 317                 // receive Mach-based IOKit notifications through mIOKitNotifier 
 318                 server
.add(mIOKitNotifier
); 
 320                 // receive power event notifications (through our IOPowerWatcher personality) 
 323                 // ask for IOKit notifications for all new USB devices and process present ones 
 324                 IOKit::DeviceMatch 
usbSelector(kIOUSBInterfaceClassName
); 
 325                 IOKit::DeviceMatch 
pcCardSelector("IOPCCard16Device"); 
 326                 mIOKitNotifier
.add(usbSelector
, *this); // this will scan existing USB devices 
 327                 mIOKitNotifier
.add(pcCardSelector
, *this);      // ditto for PC Card devices 
 328                 if (mServiceLevel 
== aggressive
) { 
 329                         // catch custom non-composite USB devices - they don't have IOServices attached 
 330                         IOKit::DeviceMatch 
customUsbSelector(::IOServiceMatching("IOUSBDevice")); 
 331                         mIOKitNotifier
.add(customUsbSelector
, *this);   // ditto for custom USB devices 
 336         // we are NOT scanning for PCSC devices here. Pcscd will send us a notification when it's up 
 341 // This function is called (as a timer function) when there haven't been any (recognized) 
 342 // smartcard devicees in the system for a while. 
 344 void PCSCMonitor::noDeviceTimeout() 
 346         secdebug("pcsc", "killing pcscd (no smartcard devices present for %g seconds)", 
 347                 PCSCD_IDLE_SHUTDOWN
.seconds()); 
 348         assert(mReaders
.empty()); 
 349         Child::kill(SIGTERM
); 
 354 // IOKit device event notification. 
 355 // Here we listen for newly inserted devices and check whether to launch pcscd. 
 357 void PCSCMonitor::ioChange(IOKit::DeviceIterator 
&iterator
) 
 359         assert(mServiceLevel 
!= externalDaemon 
&& mServiceLevel 
!= forcedOff
); 
 360         if (Child::state() == alive
) { 
 361                 secdebug("pcsc", "pcscd is alive; ignoring device insertion(s)"); 
 364         secdebug("pcsc", "processing device insertion notices"); 
 365         while (IOKit::Device dev 
= iterator()) { 
 367                 switch (deviceSupport(dev
)) { 
 372                         launch 
= (mServiceLevel 
== aggressive
); 
 382         secdebug("pcsc", "no relevant devices found"); 
 387 // Check an IOKit device that's just come online to see if it's 
 388 // a smartcard device of some sort. 
 390 PCSCMonitor::DeviceSupport 
PCSCMonitor::deviceSupport(const IOKit::Device 
&dev
) 
 393                 secdebug("scsel", "%s", dev
.path().c_str()); 
 395                // composite USB device with interface class 
 396                 if (CFRef
<CFNumberRef
> cfInterface 
= dev
.property
<CFNumberRef
>("bInterfaceClass")) 
 397                         switch (IFDEBUG(uint32 clas 
=) cfNumber(cfInterface
)) { 
 398                         case kUSBChipSmartCardInterfaceClass
:           // CCID smartcard reader - go 
 399                                 secdebug("scsel", "  CCID smartcard reader recognized"); 
 401                         case kUSBVendorSpecificInterfaceClass
: 
 402                                 secdebug("scsel", "  Vendor-specific interface - possible match"); 
 403                                 if (isExcludedDevice(dev
)) 
 405                                         secdebug("scsel", "  interface class %d is not a smartcard device (excluded)", clas
); 
 410                                 secdebug("scsel", "  interface class %d is not a smartcard device", clas
); 
 414                // noncomposite USB device 
 415                 if (CFRef
<CFNumberRef
> cfDevice 
= dev
.property
<CFNumberRef
>("bDeviceClass")) 
 416                         if (cfNumber(cfDevice
) == kUSBVendorSpecificClass
) 
 418                                 if (isExcludedDevice(dev
)) 
 420                                         secdebug("scsel", "  device class %d is not a smartcard device (excluded)", cfNumber(cfDevice
)); 
 423                                 secdebug("scsel", "  Vendor-specific device - possible match"); 
 427               // PCCard (aka PCMCIA aka ...) interface (don't know how to recognize a reader here) 
 428                if (CFRef
<CFStringRef
> ioName 
= dev
.property
<CFStringRef
>("IOName")) 
 429                        if (cfString(ioName
).find("pccard", 0, 1) == 0) { 
 430                                secdebug("scsel", "  PCCard - possible match"); 
 435                 secdebug("scsel", "  exception while examining device - ignoring it"); 
 440 bool PCSCMonitor::isExcludedDevice(const IOKit::Device 
&dev
) 
 442         uint32_t vendorID 
= 0, productID 
= 0; 
 443         // Simplified version of getVendorAndProductID in pcscd 
 444         if (CFRef
<CFNumberRef
> cfVendorID 
= dev
.property
<CFNumberRef
>(kUSBVendorID
)) 
 445                 vendorID 
= cfNumber(cfVendorID
); 
 447         if (CFRef
<CFNumberRef
> cfProductID 
= dev
.property
<CFNumberRef
>(kUSBProductID
)) 
 448                 productID 
= cfNumber(cfProductID
); 
 450         secdebug("scsel", "  checking device for possible exclusion [vendor id: 0x%08X, product id: 0x%08X]", vendorID
, productID
); 
 451         return ((vendorID 
& kVendorProductMask
) == kVendorIDApple 
&& (productID 
& kVendorProductMask
) == kProductIDBuiltInISight
); 
 455 // This gets called (by the Unix/Child system) when pcscd has died for any reason 
 457 void PCSCMonitor::dying() 
 459         Server::active().longTermActivity(); 
 460         StLock
<Mutex
> _(*this); 
 461         assert(Child::state() == dead
); 
 463         //@@@ this is where we would attempt a restart, if we wanted to...