]> git.saurik.com Git - apple/securityd.git/blob - src/pcscmonitor.cpp
securityd-27887.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
51 //
52 // Construct a PCSCMonitor.
53 // We strongly assume there's only one of us around here.
54 //
55 // Note that this constructor may well run before the server loop has started.
56 // Don't call anything here that requires an active server loop (like Server::active()).
57 // In fact, you should push all the hard work into a timer, so as not to hold up the
58 // general startup process.
59 //
60 PCSCMonitor::PCSCMonitor(Server &server, const char* pathToCache, ServiceLevel level)
61 : Listener(kNotificationDomainPCSC, SecurityServer::kNotificationAllEvents),
62 MachServer::Timer(true), // "heavy" timer task
63 server(server),
64 cache (NULL),
65 cachePath (pathToCache),
66 mServiceLevel(level),
67 mTimerAction(&PCSCMonitor::initialSetup),
68 mGoingToSleep(false)
69 {
70 // do all the smartcard-related work once the event loop has started
71 server.setTimer(this, Time::now()); // ASAP
72 }
73
74
75 //
76 // Poll PCSC for smartcard status.
77 // We are enumerating all readers on each call.
78 //
79 void PCSCMonitor::pollReaders()
80 {
81 // open PCSC session if it's not already open
82 mSession.open();
83
84 // enumerate readers
85 vector<string> names; // will hold reader name C strings throughout
86 mSession.listReaders(names);
87 size_t count = names.size();
88 secdebug("pcsc", "%ld reader(s) in system", count);
89
90 // build the PCSC status inquiry array
91 vector<PCSC::ReaderState> states(count); // reader status array (PCSC style)
92 for (unsigned int n = 0; n < count; n++) {
93 PCSC::ReaderState &state = states[n];
94 ReaderMap::iterator it = mReaders.find(names[n]);
95 if (it == mReaders.end()) { // new reader
96 state.clearPod();
97 state.name(names[n].c_str());
98 // lastKnown(PCSC_STATE_UNKNOWN)
99 // userData<Reader>() = NULL
100 } else {
101 state = it->second->pcscState();
102 state.name(names[n].c_str()); // OUR pointer
103 state.lastKnown(state.state());
104 state.userData<Reader>() = it->second;
105 }
106 }
107
108 // now ask PCSC for status changes
109 mSession.statusChange(states);
110 #if DEBUGDUMP
111 if (Debug::dumping("pcsc"))
112 for (unsigned int n = 0; n < count; n++)
113 states[n].dump();
114 #endif
115
116 // make a set of previously known reader objects (to catch those who disappeared)
117 ReaderSet current;
118 copy_second(mReaders.begin(), mReaders.end(), inserter(current, current.end()));
119
120 // match state array against them
121 for (unsigned int n = 0; n < count; n++) {
122 PCSC::ReaderState &state = states[n];
123 if (Reader *reader = state.userData<Reader>()) {
124 // if PCSC flags a change, notify the Reader
125 if (state.changed())
126 reader->update(state);
127 // accounted for this reader
128 current.erase(reader);
129 } else {
130 RefPointer<Reader> newReader = new Reader(getTokenCache (), state);
131 mReaders.insert(make_pair(state.name(), newReader));
132 Syslog::notice("Token reader %s inserted into system", state.name());
133 newReader->update(state); // initial state setup
134 }
135 }
136
137 // now deal with vanished readers
138 for (ReaderSet::iterator it = current.begin(); it != current.end(); it++) {
139 secdebug("pcsc", "removing reader %s", (*it)->name().c_str());
140 Syslog::notice("Token reader %s removed from system", (*it)->name().c_str());
141 (*it)->kill(); // prepare to die
142 mReaders.erase((*it)->name()); // remove from reader map
143 }
144 }
145
146
147 TokenCache& PCSCMonitor::getTokenCache ()
148 {
149 if (cache == NULL) {
150 cache = new TokenCache(cachePath.c_str ());
151 }
152
153 return *cache;
154 }
155
156
157
158 void PCSCMonitor::launchPcscd()
159 {
160 // launch pcscd
161 secdebug("pcsc", "launching pcscd to handle smartcard device(s)");
162 assert(Child::state() != alive);
163 Child::reset();
164 Child::fork();
165
166 // if pcscd doesn't report a reader found soon, we'll kill it off
167 scheduleTimer(true);
168 }
169
170
171 //
172 // Code to launch pcscd (run in child as a result of Child::fork())
173 //
174 void PCSCMonitor::childAction()
175 {
176 // move aside any old play area
177 const char *aside = tempnam("/tmp", "pcscd");
178 if (::rename(PCSCD_WORKING_DIR, aside))
179 switch (errno) {
180 case ENOENT: // no /tmp/pcsc (fine)
181 break;
182 default:
183 secdebug("pcsc", "failed too move %s - errno=%d", PCSCD_WORKING_DIR, errno);
184 _exit(101);
185 }
186 else
187 secdebug("pcsc", "old /tmp/pcsc moved to %s", aside);
188
189 // lessen the pain for debugging
190 #if !defined(NDEBUG)
191 freopen("/tmp/pcsc.debuglog", "a", stdout); // shut up pcsc dumps to stdout
192 #endif //NDEBUG
193
194 // execute the daemon
195 const char *pcscdPath = PCSCD_EXEC_PATH;
196 if (const char *env = getenv("PCSCDAEMON"))
197 pcscdPath = env;
198 secdebug("pcsc", "exec(%s,-f)", pcscdPath);
199 execl(pcscdPath, pcscdPath, "-f", NULL);
200 }
201
202
203 //
204 // Event notifier.
205 // These events are sent by pcscd for our (sole) benefit.
206 //
207 void PCSCMonitor::notifyMe(SecurityServer::NotificationDomain domain,
208 SecurityServer::NotificationEvent event, const CssmData &data)
209 {
210 Server::active().longTermActivity();
211 StLock<Mutex> _(*this);
212 assert(mServiceLevel == externalDaemon || Child::state() == alive);
213 pollReaders();
214 scheduleTimer(mReaders.empty() && !mGoingToSleep);
215 }
216
217
218 //
219 // Power event notifications
220 //
221 void PCSCMonitor::systemWillSleep()
222 {
223 StLock<Mutex> _(*this);
224 secdebug("pcsc", "setting sleep marker (%ld readers as of now)", mReaders.size());
225 mGoingToSleep = true;
226 server.clearTimer(this);
227 }
228
229 void PCSCMonitor::systemIsWaking()
230 {
231 StLock<Mutex> _(*this);
232 secdebug("pcsc", "clearing sleep marker (%ld readers as of now)", mReaders.size());
233 mGoingToSleep = false;
234 scheduleTimer(mReaders.empty());
235 }
236
237
238 //
239 // Timer action.
240 //
241 void PCSCMonitor::action()
242 {
243 StLock<Mutex> _(*this);
244 (this->*mTimerAction)();
245 mTimerAction = &PCSCMonitor::noDeviceTimeout;
246 }
247
248
249 //
250 // Update the timeout timer as requested (and indicated by context)
251 //
252 void PCSCMonitor::scheduleTimer(bool enable)
253 {
254 if (Child::state() == alive) // we ran pcscd; let's manage it
255 if (enable) {
256 secdebug("pcsc", "setting idle timer for %g seconds", PCSCD_IDLE_SHUTDOWN.seconds());
257 server.setTimer(this, PCSCD_IDLE_SHUTDOWN);
258 } else if (Timer::scheduled()) {
259 secdebug("pcsc", "clearing idle timer");
260 server.clearTimer(this);
261 }
262 }
263
264
265 //
266 // Perform the initial PCSC subsystem initialization.
267 // This runs (shortly) after securityd is fully functional and the
268 // server loop has started.
269 //
270 void PCSCMonitor::initialSetup()
271 {
272 switch (mServiceLevel) {
273 case forcedOff:
274 secdebug("pcsc", "smartcard operation is FORCED OFF");
275 break;
276
277 case forcedOn:
278 secdebug("pcsc", "pcscd launch is forced on");
279 launchPcscd();
280 break;
281
282 case externalDaemon:
283 secdebug("pcsc", "using external pcscd (if any); no launch operations");
284 break;
285
286 default:
287 secdebug("pcsc", "setting up automatic PCSC management in %s mode",
288 mServiceLevel == conservative ? "conservative" : "aggressive");
289
290 // receive Mach-based IOKit notifications through mIOKitNotifier
291 server.add(mIOKitNotifier);
292
293 // receive power event notifications (through our IOPowerWatcher personality)
294 server.add(this);
295
296 // ask for IOKit notifications for all new USB devices and process present ones
297 IOKit::DeviceMatch usbSelector(kIOUSBInterfaceClassName);
298 IOKit::DeviceMatch pcCardSelector("IOPCCard16Device");
299 mIOKitNotifier.add(usbSelector, *this); // this will scan existing USB devices
300 mIOKitNotifier.add(pcCardSelector, *this); // ditto for PC Card devices
301 if (mServiceLevel == aggressive) {
302 // catch custom non-composite USB devices - they don't have IOServices attached
303 IOKit::DeviceMatch customUsbSelector(::IOServiceMatching("IOUSBDevice"));
304 mIOKitNotifier.add(customUsbSelector, *this); // ditto for custom USB devices
305 }
306 break;
307 }
308
309 // we are NOT scanning for PCSC devices here. Pcscd will send us a notification when it's up
310 }
311
312
313 //
314 // This function is called (as a timer function) when there haven't been any (recognized)
315 // smartcard devicees in the system for a while.
316 //
317 void PCSCMonitor::noDeviceTimeout()
318 {
319 secdebug("pcsc", "killing pcscd (no smartcard devices present for %g seconds)",
320 PCSCD_IDLE_SHUTDOWN.seconds());
321 assert(mReaders.empty());
322 Child::kill(SIGTERM);
323 }
324
325
326 //
327 // IOKit device event notification.
328 // Here we listen for newly inserted devices and check whether to launch pcscd.
329 //
330 void PCSCMonitor::ioChange(IOKit::DeviceIterator &iterator)
331 {
332 assert(mServiceLevel != externalDaemon && mServiceLevel != forcedOff);
333 if (Child::state() == alive) {
334 secdebug("pcsc", "pcscd is alive; ignoring device insertion(s)");
335 return;
336 }
337 secdebug("pcsc", "processing device insertion notices");
338 while (IOKit::Device dev = iterator()) {
339 bool launch = false;
340 switch (deviceSupport(dev)) {
341 case definite:
342 launch = true;
343 break;
344 case possible:
345 launch = (mServiceLevel == aggressive);
346 break;
347 case impossible:
348 break;
349 }
350 if (launch) {
351 launchPcscd();
352 return;
353 }
354 }
355 secdebug("pcsc", "no relevant devices found");
356 }
357
358
359 //
360 // Check an IOKit device that's just come online to see if it's
361 // a smartcard device of some sort.
362 //
363 PCSCMonitor::DeviceSupport PCSCMonitor::deviceSupport(const IOKit::Device &dev)
364 {
365 try {
366 secdebug("scsel", "%s", dev.path().c_str());
367
368 // composite USB device with interface class
369 if (CFRef<CFNumberRef> cfInterface = dev.property<CFNumberRef>("bInterfaceClass"))
370 switch (IFDEBUG(uint32 clas =) cfNumber(cfInterface)) {
371 case kUSBChipSmartCardInterfaceClass: // CCID smartcard reader - go
372 secdebug("scsel", " CCID smartcard reader recognized");
373 return definite;
374 case kUSBVendorSpecificInterfaceClass:
375 secdebug("scsel", " Vendor-specific interface - possible match");
376 return possible;
377 default:
378 secdebug("scsel", " interface class %ld is not a smartcard device", clas);
379 return impossible;
380 }
381
382 // noncomposite USB device
383 if (CFRef<CFNumberRef> cfDevice = dev.property<CFNumberRef>("bDeviceClass"))
384 if (cfNumber(cfDevice) == kUSBVendorSpecificClass) {
385 secdebug("scsel", " Vendor-specific device - possible match");
386 return possible;
387 }
388
389 // PCCard (aka PCMCIA aka ...) interface (don't know how to recognize a reader here)
390 if (CFRef<CFStringRef> ioName = dev.property<CFStringRef>("IOName"))
391 if (cfString(ioName).find("pccard", 0, 1) == 0) {
392 secdebug("scsel", " PCCard - possible match");
393 return possible;
394 }
395 return impossible;
396 } catch (...) {
397 secdebug("scsel", " exception while examining device - ignoring it");
398 return impossible;
399 }
400 }
401
402
403 //
404 // This gets called (by the Unix/Child system) when pcscd has died for any reason
405 //
406 void PCSCMonitor::dying()
407 {
408 Server::active().longTermActivity();
409 StLock<Mutex> _(*this);
410 assert(Child::state() == dead);
411 if (!mReaders.empty()) {
412 // uh-oh. We had readers connected when pcscd suddenly left
413 secdebug("pcsc", "%ld readers were present when pcscd died", mReaders.size());
414 for (ReaderMap::const_iterator it = mReaders.begin(); it != mReaders.end(); it++) {
415 Reader *reader = it->second;
416 secdebug("pcsc", "removing reader %s", reader->name().c_str());
417 reader->kill(); // prepare to die
418 }
419 mReaders.erase(mReaders.begin(), mReaders.end());
420 secdebug("pcsc", "orphaned readers cleared");
421 }
422 //@@@ this is where we would attempt a restart, if we wanted to...
423 }