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