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 Copied from USBVideoClass-230.2.3/Digitizers/USBVDC/Camera/USBClient/APW_VDO_USBVDC_USBClient.h
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
73 // Construct a PCSCMonitor.
74 // We strongly assume there's only one of us around here.
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.
81 PCSCMonitor::PCSCMonitor(Server
&server
, const char* pathToCache
, ServiceLevel level
)
82 : Listener(kNotificationDomainPCSC
, SecurityServer::kNotificationAllEvents
),
83 MachServer::Timer(true), // "heavy" timer task
86 mTimerAction(&PCSCMonitor::initialSetup
),
88 mCachePath(pathToCache
),
91 // do all the smartcard-related work once the event loop has started
92 server
.setTimer(this, Time::now()); // ASAP
97 // Poll PCSC for smartcard status.
98 // We are enumerating all readers on each call.
100 void PCSCMonitor::pollReaders()
102 // open PCSC session if it's not already open
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
);
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
118 state
.name(names
[n
].c_str());
119 // lastKnown(PCSC_STATE_UNKNOWN)
120 // userData<Reader>() = NULL
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
;
129 // now ask PCSC for status changes
130 mSession
.statusChange(states
);
132 if (Debug::dumping("pcsc"))
133 for (unsigned int n
= 0; n
< count
; n
++)
137 // make a set of previously known reader objects (to catch those who disappeared)
139 copy_second(mReaders
.begin(), mReaders
.end(), inserter(current
, current
.end()));
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
147 reader
->update(state
);
148 // accounted for this reader
149 current
.erase(reader
);
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
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()) {
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
168 case Reader::software
:
169 // previous software reader - keep
177 // Remove some types of readers
179 void PCSCMonitor::clearReaders(Reader::Type type
)
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
197 // Poll PCSC for smartcard status.
198 // We are enumerating all readers on each call.
200 TokenCache
& PCSCMonitor::tokenCache()
202 if (mTokenCache
== NULL
)
203 mTokenCache
= new TokenCache(mCachePath
.c_str());
209 void PCSCMonitor::launchPcscd()
212 secdebug("pcsc", "launching pcscd to handle smartcard device(s)");
213 assert(Child::state() != alive
);
217 // if pcscd doesn't report a reader found soon, we'll kill it off
223 // Code to launch pcscd (run in child as a result of Child::fork())
225 void PCSCMonitor::childAction()
227 // move aside any old play area
228 const char *aside
= tempnam("/tmp", "pcscd");
229 if (::rename(PCSCD_WORKING_DIR
, aside
))
231 case ENOENT
: // no /tmp/pcsc (fine)
234 secdebug("pcsc", "failed too move %s - errno=%d", PCSCD_WORKING_DIR
, errno
);
238 secdebug("pcsc", "old /tmp/pcsc moved to %s", aside
);
240 // lessen the pain for debugging
242 freopen("/tmp/pcsc.debuglog", "a", stdout
); // shut up pcsc dumps to stdout
245 // execute the daemon
246 const char *pcscdPath
= PCSCD_EXEC_PATH
;
247 if (const char *env
= getenv("PCSCDAEMON"))
249 secdebug("pcsc", "exec(%s,-f)", pcscdPath
);
250 execl(pcscdPath
, pcscdPath
, "-f", NULL
);
256 // These events are sent by pcscd for our (sole) benefit.
258 void PCSCMonitor::notifyMe(Notification
*message
)
260 Server::active().longTermActivity();
261 StLock
<Mutex
> _(*this);
262 assert(mServiceLevel
== externalDaemon
|| Child::state() == alive
);
263 if (message
->event
== kNotificationPCSCInitialized
)
264 clearReaders(Reader::pcsc
);
266 scheduleTimer(mReaders
.empty() && !mGoingToSleep
);
271 // Power event notifications
273 void PCSCMonitor::systemWillSleep()
275 StLock
<Mutex
> _(*this);
276 secdebug("pcsc", "setting sleep marker (%ld readers as of now)", mReaders
.size());
277 mGoingToSleep
= true;
278 server
.clearTimer(this);
281 void PCSCMonitor::systemIsWaking()
283 StLock
<Mutex
> _(*this);
284 secdebug("pcsc", "clearing sleep marker (%ld readers as of now)", mReaders
.size());
285 mGoingToSleep
= false;
286 scheduleTimer(mReaders
.empty());
293 void PCSCMonitor::action()
295 StLock
<Mutex
> _(*this);
296 (this->*mTimerAction
)();
297 mTimerAction
= &PCSCMonitor::noDeviceTimeout
;
302 // Update the timeout timer as requested (and indicated by context)
304 void PCSCMonitor::scheduleTimer(bool enable
)
306 if (Child::state() == alive
) // we ran pcscd; let's manage it
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);
318 // Perform the initial PCSC subsystem initialization.
319 // This runs (shortly) after securityd is fully functional and the
320 // server loop has started.
322 void PCSCMonitor::initialSetup()
324 switch (mServiceLevel
) {
326 secdebug("pcsc", "smartcard operation is FORCED OFF");
330 secdebug("pcsc", "pcscd launch is forced on");
336 secdebug("pcsc", "using external pcscd (if any); no launch operations");
341 secdebug("pcsc", "setting up automatic PCSC management in %s mode",
342 mServiceLevel
== conservative
? "conservative" : "aggressive");
344 // receive Mach-based IOKit notifications through mIOKitNotifier
345 server
.add(mIOKitNotifier
);
347 // receive power event notifications (through our IOPowerWatcher personality)
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
361 // find and start software tokens
367 // we are NOT scanning for PCSC devices here. Pcscd will send us a notification when it's up
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.
375 void PCSCMonitor::noDeviceTimeout()
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
);
385 // IOKit device event notification.
386 // Here we listen for newly inserted devices and check whether to launch pcscd.
388 void PCSCMonitor::ioChange(IOKit::DeviceIterator
&iterator
)
390 assert(mServiceLevel
!= externalDaemon
&& mServiceLevel
!= forcedOff
);
391 if (Child::state() == alive
) {
392 secdebug("pcsc", "pcscd is alive; ignoring device insertion(s)");
395 secdebug("pcsc", "processing device insertion notices");
396 while (IOKit::Device dev
= iterator()) {
398 switch (deviceSupport(dev
)) {
403 launch
= (mServiceLevel
== aggressive
);
413 secdebug("pcsc", "no relevant devices found");
418 // Check an IOKit device that's just come online to see if it's
419 // a smartcard device of some sort.
421 PCSCMonitor::DeviceSupport
PCSCMonitor::deviceSupport(const IOKit::Device
&dev
)
424 secdebug("scsel", "%s", dev
.path().c_str());
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");
432 case kUSBVendorSpecificInterfaceClass
:
433 secdebug("scsel", " Vendor-specific interface - possible match");
434 if (isExcludedDevice(dev
))
436 secdebug("scsel", " interface class %d is not a smartcard device (excluded)", clas
);
441 secdebug("scsel", " interface class %d is not a smartcard device", clas
);
445 // noncomposite USB device
446 if (CFRef
<CFNumberRef
> cfDevice
= dev
.property
<CFNumberRef
>("bDeviceClass"))
447 if (cfNumber(cfDevice
) == kUSBVendorSpecificClass
)
449 if (isExcludedDevice(dev
))
451 secdebug("scsel", " device class %d is not a smartcard device (excluded)", cfNumber(cfDevice
));
454 secdebug("scsel", " Vendor-specific device - possible match");
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");
466 secdebug("scsel", " exception while examining device - ignoring it");
471 bool PCSCMonitor::isExcludedDevice(const IOKit::Device
&dev
)
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
);
478 if (CFRef
<CFNumberRef
> cfProductID
= dev
.property
<CFNumberRef
>(kUSBProductID
))
479 productID
= cfNumber(cfProductID
);
481 secdebug("scsel", " checking device for possible exclusion [vendor id: 0x%08X, product id: 0x%08X]", vendorID
, productID
);
483 if ((vendorID
& kVendorProductMask
) != kVendorIDApple
)
484 return false; // i.e. it is not an excluded device
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
493 // This gets called (by the Unix/Child system) when pcscd has died for any reason
495 void PCSCMonitor::dying()
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...
506 // Software token support
508 void PCSCMonitor::startSoftTokens()
510 // clear all software readers. This will kill the respective TokenDaemons
511 clearReaders(Reader::software
);
514 CodeRepository
<Bundle
> candidates("Security/tokend", ".tokend", "TOKENDAEMONPATH", false);
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")))
523 void PCSCMonitor::loadSoftToken(Bundle
*tokendBundle
)
526 string bundleName
= tokendBundle
->identifier();
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
);
532 // now launch the tokend
533 RefPointer
<TokenDaemon
> tokend
= new TokenDaemon(tokendBundle
,
534 reader
->name(), reader
->pcscState(), reader
->cache
);
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());
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());
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());
554 secdebug("pcsc", "exception loading softtoken %s - continuing", tokendBundle
->identifier().c_str());