]> git.saurik.com Git - apple/securityd.git/blob - src/pcscmonitor.cpp
securityd-36489.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 // Construct a PCSCMonitor.
58 // We strongly assume there's only one of us around here.
59 //
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.
64 //
65 PCSCMonitor::PCSCMonitor(Server &server, const char* pathToCache, ServiceLevel level)
66 : Listener(kNotificationDomainPCSC, SecurityServer::kNotificationAllEvents),
67 MachServer::Timer(true), // "heavy" timer task
68 server(server),
69 mServiceLevel(level),
70 mTimerAction(&PCSCMonitor::initialSetup),
71 mGoingToSleep(false),
72 mCachePath(pathToCache),
73 mTokenCache(NULL)
74 {
75 // do all the smartcard-related work once the event loop has started
76 server.setTimer(this, Time::now()); // ASAP
77 }
78
79
80 //
81 // Poll PCSC for smartcard status.
82 // We are enumerating all readers on each call.
83 //
84 void PCSCMonitor::pollReaders()
85 {
86 // open PCSC session if it's not already open
87 mSession.open();
88
89 // enumerate readers
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);
94
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
101 state.clearPod();
102 state.name(names[n].c_str());
103 // lastKnown(PCSC_STATE_UNKNOWN)
104 // userData<Reader>() = NULL
105 } else {
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;
110 }
111 }
112
113 // now ask PCSC for status changes
114 mSession.statusChange(states);
115 #if DEBUGDUMP
116 if (Debug::dumping("pcsc"))
117 for (unsigned int n = 0; n < count; n++)
118 states[n].dump();
119 #endif
120
121 // make a set of previously known reader objects (to catch those who disappeared)
122 ReaderSet current;
123 copy_second(mReaders.begin(), mReaders.end(), inserter(current, current.end()));
124
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
130 if (state.changed())
131 reader->update(state);
132 // accounted for this reader
133 current.erase(reader);
134 } else {
135 RefPointer<Reader> newReader = new Reader(tokenCache(), 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
139 }
140 }
141
142 // now deal with known readers that PCSC did not report
143 for (ReaderSet::iterator it = current.begin(); it != current.end(); it++) {
144 switch ((*it)->type()) {
145 case Reader::pcsc:
146 // previous PCSC reader - was removed from system
147 secdebug("pcsc", "removing reader %s", (*it)->name().c_str());
148 Syslog::notice("Token reader %s removed from system", (*it)->name().c_str());
149 (*it)->kill(); // prepare to die
150 mReaders.erase((*it)->name()); // remove from reader map
151 break;
152 case Reader::software:
153 // previous software reader - keep
154 break;
155 }
156 }
157 }
158
159
160 //
161 // Remove some types of readers
162 //
163 void PCSCMonitor::clearReaders(Reader::Type type)
164 {
165 if (!mReaders.empty()) {
166 secdebug("pcsc", "%ld readers present - clearing type %d", mReaders.size(), type);
167 for (ReaderMap::iterator it = mReaders.begin(); it != mReaders.end(); ) {
168 ReaderMap::iterator cur = it++;
169 Reader *reader = cur->second;
170 if (reader->isType(type)) {
171 secdebug("pcsc", "removing reader %s", reader->name().c_str());
172 reader->kill(); // prepare to die
173 mReaders.erase(cur);
174 }
175 }
176 }
177 }
178
179
180 //
181 // Poll PCSC for smartcard status.
182 // We are enumerating all readers on each call.
183 //
184 TokenCache& PCSCMonitor::tokenCache()
185 {
186 if (mTokenCache == NULL)
187 mTokenCache = new TokenCache(mCachePath.c_str());
188 return *mTokenCache;
189 }
190
191
192
193 void PCSCMonitor::launchPcscd()
194 {
195 // launch pcscd
196 secdebug("pcsc", "launching pcscd to handle smartcard device(s)");
197 assert(Child::state() != alive);
198 Child::reset();
199 Child::fork();
200
201 // if pcscd doesn't report a reader found soon, we'll kill it off
202 scheduleTimer(true);
203 }
204
205
206 //
207 // Code to launch pcscd (run in child as a result of Child::fork())
208 //
209 void PCSCMonitor::childAction()
210 {
211 // move aside any old play area
212 const char *aside = tempnam("/tmp", "pcscd");
213 if (::rename(PCSCD_WORKING_DIR, aside))
214 switch (errno) {
215 case ENOENT: // no /tmp/pcsc (fine)
216 break;
217 default:
218 secdebug("pcsc", "failed too move %s - errno=%d", PCSCD_WORKING_DIR, errno);
219 _exit(101);
220 }
221 else
222 secdebug("pcsc", "old /tmp/pcsc moved to %s", aside);
223
224 // lessen the pain for debugging
225 #if !defined(NDEBUG)
226 freopen("/tmp/pcsc.debuglog", "a", stdout); // shut up pcsc dumps to stdout
227 #endif //NDEBUG
228
229 // execute the daemon
230 const char *pcscdPath = PCSCD_EXEC_PATH;
231 if (const char *env = getenv("PCSCDAEMON"))
232 pcscdPath = env;
233 secdebug("pcsc", "exec(%s,-f)", pcscdPath);
234 execl(pcscdPath, pcscdPath, "-f", NULL);
235 }
236
237
238 //
239 // Event notifier.
240 // These events are sent by pcscd for our (sole) benefit.
241 //
242 void PCSCMonitor::notifyMe(Notification *message)
243 {
244 Server::active().longTermActivity();
245 StLock<Mutex> _(*this);
246 assert(mServiceLevel == externalDaemon || Child::state() == alive);
247 if (message->event == kNotificationPCSCInitialized)
248 clearReaders(Reader::pcsc);
249 pollReaders();
250 scheduleTimer(mReaders.empty() && !mGoingToSleep);
251 }
252
253
254 //
255 // Power event notifications
256 //
257 void PCSCMonitor::systemWillSleep()
258 {
259 StLock<Mutex> _(*this);
260 secdebug("pcsc", "setting sleep marker (%ld readers as of now)", mReaders.size());
261 mGoingToSleep = true;
262 server.clearTimer(this);
263 }
264
265 void PCSCMonitor::systemIsWaking()
266 {
267 StLock<Mutex> _(*this);
268 secdebug("pcsc", "clearing sleep marker (%ld readers as of now)", mReaders.size());
269 mGoingToSleep = false;
270 scheduleTimer(mReaders.empty());
271 }
272
273
274 //
275 // Timer action.
276 //
277 void PCSCMonitor::action()
278 {
279 StLock<Mutex> _(*this);
280 (this->*mTimerAction)();
281 mTimerAction = &PCSCMonitor::noDeviceTimeout;
282 }
283
284
285 //
286 // Update the timeout timer as requested (and indicated by context)
287 //
288 void PCSCMonitor::scheduleTimer(bool enable)
289 {
290 if (Child::state() == alive) // we ran pcscd; let's manage it
291 if (enable) {
292 secdebug("pcsc", "setting idle timer for %g seconds", PCSCD_IDLE_SHUTDOWN.seconds());
293 server.setTimer(this, PCSCD_IDLE_SHUTDOWN);
294 } else if (Timer::scheduled()) {
295 secdebug("pcsc", "clearing idle timer");
296 server.clearTimer(this);
297 }
298 }
299
300
301 //
302 // Perform the initial PCSC subsystem initialization.
303 // This runs (shortly) after securityd is fully functional and the
304 // server loop has started.
305 //
306 void PCSCMonitor::initialSetup()
307 {
308 switch (mServiceLevel) {
309 case forcedOff:
310 secdebug("pcsc", "smartcard operation is FORCED OFF");
311 break;
312
313 case forcedOn:
314 secdebug("pcsc", "pcscd launch is forced on");
315 launchPcscd();
316 startSoftTokens();
317 break;
318
319 case externalDaemon:
320 secdebug("pcsc", "using external pcscd (if any); no launch operations");
321 startSoftTokens();
322 break;
323
324 default:
325 secdebug("pcsc", "setting up automatic PCSC management in %s mode",
326 mServiceLevel == conservative ? "conservative" : "aggressive");
327
328 // receive Mach-based IOKit notifications through mIOKitNotifier
329 server.add(mIOKitNotifier);
330
331 // receive power event notifications (through our IOPowerWatcher personality)
332 server.add(this);
333
334 // ask for IOKit notifications for all new USB devices and process present ones
335 IOKit::DeviceMatch usbSelector(kIOUSBInterfaceClassName);
336 IOKit::DeviceMatch pcCardSelector("IOPCCard16Device");
337 mIOKitNotifier.add(usbSelector, *this); // this will scan existing USB devices
338 mIOKitNotifier.add(pcCardSelector, *this); // ditto for PC Card devices
339 if (mServiceLevel == aggressive) {
340 // catch custom non-composite USB devices - they don't have IOServices attached
341 IOKit::DeviceMatch customUsbSelector(::IOServiceMatching("IOUSBDevice"));
342 mIOKitNotifier.add(customUsbSelector, *this); // ditto for custom USB devices
343 }
344
345 // find and start software tokens
346 startSoftTokens();
347
348 break;
349 }
350
351 // we are NOT scanning for PCSC devices here. Pcscd will send us a notification when it's up
352 }
353
354
355 //
356 // This function is called (as a timer function) when there haven't been any (recognized)
357 // smartcard devicees in the system for a while.
358 //
359 void PCSCMonitor::noDeviceTimeout()
360 {
361 secdebug("pcsc", "killing pcscd (no smartcard devices present for %g seconds)",
362 PCSCD_IDLE_SHUTDOWN.seconds());
363 assert(mReaders.empty());
364 Child::kill(SIGTERM);
365 }
366
367
368 //
369 // IOKit device event notification.
370 // Here we listen for newly inserted devices and check whether to launch pcscd.
371 //
372 void PCSCMonitor::ioChange(IOKit::DeviceIterator &iterator)
373 {
374 assert(mServiceLevel != externalDaemon && mServiceLevel != forcedOff);
375 if (Child::state() == alive) {
376 secdebug("pcsc", "pcscd is alive; ignoring device insertion(s)");
377 return;
378 }
379 secdebug("pcsc", "processing device insertion notices");
380 while (IOKit::Device dev = iterator()) {
381 bool launch = false;
382 switch (deviceSupport(dev)) {
383 case definite:
384 launch = true;
385 break;
386 case possible:
387 launch = (mServiceLevel == aggressive);
388 break;
389 case impossible:
390 break;
391 }
392 if (launch) {
393 launchPcscd();
394 return;
395 }
396 }
397 secdebug("pcsc", "no relevant devices found");
398 }
399
400
401 //
402 // Check an IOKit device that's just come online to see if it's
403 // a smartcard device of some sort.
404 //
405 PCSCMonitor::DeviceSupport PCSCMonitor::deviceSupport(const IOKit::Device &dev)
406 {
407 try {
408 secdebug("scsel", "%s", dev.path().c_str());
409
410 // composite USB device with interface class
411 if (CFRef<CFNumberRef> cfInterface = dev.property<CFNumberRef>("bInterfaceClass"))
412 switch (IFDEBUG(uint32 clas =) cfNumber(cfInterface)) {
413 case kUSBChipSmartCardInterfaceClass: // CCID smartcard reader - go
414 secdebug("scsel", " CCID smartcard reader recognized");
415 return definite;
416 case kUSBVendorSpecificInterfaceClass:
417 secdebug("scsel", " Vendor-specific interface - possible match");
418 if (isExcludedDevice(dev))
419 {
420 secdebug("scsel", " interface class %d is not a smartcard device (excluded)", clas);
421 return impossible;
422 }
423 return possible;
424 default:
425 secdebug("scsel", " interface class %d is not a smartcard device", clas);
426 return impossible;
427 }
428
429 // noncomposite USB device
430 if (CFRef<CFNumberRef> cfDevice = dev.property<CFNumberRef>("bDeviceClass"))
431 if (cfNumber(cfDevice) == kUSBVendorSpecificClass)
432 {
433 if (isExcludedDevice(dev))
434 {
435 secdebug("scsel", " device class %d is not a smartcard device (excluded)", cfNumber(cfDevice));
436 return impossible;
437 }
438 secdebug("scsel", " Vendor-specific device - possible match");
439 return possible;
440 }
441
442 // PCCard (aka PCMCIA aka ...) interface (don't know how to recognize a reader here)
443 if (CFRef<CFStringRef> ioName = dev.property<CFStringRef>("IOName"))
444 if (cfString(ioName).find("pccard", 0, 1) == 0) {
445 secdebug("scsel", " PCCard - possible match");
446 return possible;
447 }
448 return impossible;
449 } catch (...) {
450 secdebug("scsel", " exception while examining device - ignoring it");
451 return impossible;
452 }
453 }
454
455 bool PCSCMonitor::isExcludedDevice(const IOKit::Device &dev)
456 {
457 uint32_t vendorID = 0, productID = 0;
458 // Simplified version of getVendorAndProductID in pcscd
459 if (CFRef<CFNumberRef> cfVendorID = dev.property<CFNumberRef>(kUSBVendorID))
460 vendorID = cfNumber(cfVendorID);
461
462 if (CFRef<CFNumberRef> cfProductID = dev.property<CFNumberRef>(kUSBProductID))
463 productID = cfNumber(cfProductID);
464
465 secdebug("scsel", " checking device for possible exclusion [vendor id: 0x%08X, product id: 0x%08X]", vendorID, productID);
466 return ((vendorID & kVendorProductMask) == kVendorIDApple && (productID & kVendorProductMask) == kProductIDBuiltInISight);
467 }
468
469 //
470 // This gets called (by the Unix/Child system) when pcscd has died for any reason
471 //
472 void PCSCMonitor::dying()
473 {
474 Server::active().longTermActivity();
475 StLock<Mutex> _(*this);
476 assert(Child::state() == dead);
477 clearReaders(Reader::pcsc);
478 //@@@ this is where we would attempt a restart, if we wanted to...
479 }
480
481
482 //
483 // Software token support
484 //
485 void PCSCMonitor::startSoftTokens()
486 {
487 // clear all software readers. This will kill the respective TokenDaemons
488 clearReaders(Reader::software);
489
490 // scan for new ones
491 CodeRepository<Bundle> candidates("Security/tokend", ".tokend", "TOKENDAEMONPATH", false);
492 candidates.update();
493 for (CodeRepository<Bundle>::iterator it = candidates.begin(); it != candidates.end(); ++it) {
494 if (CFTypeRef type = (*it)->infoPlistItem("TokendType"))
495 if (CFEqual(type, CFSTR("software")))
496 loadSoftToken(*it);
497 }
498 }
499
500 void PCSCMonitor::loadSoftToken(Bundle *tokendBundle)
501 {
502 try {
503 string bundleName = tokendBundle->identifier();
504
505 // prepare a virtual reader, removing any existing one (this would kill a previous tokend)
506 assert(mReaders.find(bundleName) == mReaders.end()); // not already present
507 RefPointer<Reader> reader = new Reader(tokenCache(), bundleName);
508
509 // now launch the tokend
510 RefPointer<TokenDaemon> tokend = new TokenDaemon(tokendBundle,
511 reader->name(), reader->pcscState(), reader->cache);
512
513 if (tokend->state() == ServerChild::dead) { // ah well, this one's no good
514 secdebug("pcsc", "softtoken %s tokend launch failed", bundleName.c_str());
515 Syslog::notice("Software token %s failed to run", tokendBundle->canonicalPath().c_str());
516 return;
517 }
518
519 // probe the (single) tokend
520 if (!tokend->probe()) { // non comprende...
521 secdebug("pcsc", "softtoken %s probe failed", bundleName.c_str());
522 Syslog::notice("Software token %s refused operation", tokendBundle->canonicalPath().c_str());
523 return;
524 }
525
526 // okay, this seems to work. Set it up
527 mReaders.insert(make_pair(reader->name(), reader));
528 reader->insertToken(tokend);
529 Syslog::notice("Software token %s activated", bundleName.c_str());
530 } catch (...) {
531 secdebug("pcsc", "exception loading softtoken %s - continuing", tokendBundle->identifier().c_str());
532 }
533 }