1/* 2 * Copyright (c) 2004-2008,2011,2014 Apple 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#include "pcscmonitor.h" 33#include <security_utilities/logging.h> 34 35// 36// Construct a PCSCMonitor. 37// We strongly assume there's only one of us around here. 38// 39// Note that this constructor may well run before the server loop has started. 40// Don't call anything here that requires an active server loop (like Server::active()). 41// In fact, you should push all the hard work into a timer, so as not to hold up the 42// general startup process. 43// 44PCSCMonitor::PCSCMonitor(Server &server, const char* pathToCache, ServiceLevel level) 45 : Listener(kNotificationDomainPCSC, SecurityServer::kNotificationAllEvents), 46 server(server), 47 mServiceLevel(level), 48 mCachePath(pathToCache), 49 mTokenCache(NULL) 50{ 51 // do all the smartcard-related work once the event loop has started 52 server.setTimer(this, Time::now()); // ASAP 53} 54 55PCSCMonitor::Watcher::Watcher(Server &server, TokenCache &tokenCache, ReaderMap& readers) 56 : mServer(server), mTokenCache(tokenCache), mReaders(readers) 57{} 58 59// 60// Poll PCSC for smartcard status. 61// We are enumerating all readers on each call. 62// 63void PCSCMonitor::Watcher::action() 64{ 65 // Associate this watching thread with the server, so that it is possible to call 66 // Server::active() from inside code called from this thread. 67 mServer.associateThread(); 68 69 try { 70 // open PCSC session 71 mSession.open(); 72 73 // Array of states, userData() points to associated Reader instance, 74 // name points to string held by Reader::name attribute. 75 vector<PCSC::ReaderState> states; 76 77 for (;;) { 78 // enumerate all current readers. 79 vector<string> names; 80 mSession.listReaders(names); 81 secdebug("pcsc", "%ld reader(s) in system", names.size()); 82 83 // Update PCSC states array with new/removed readers. 84 for (vector<PCSC::ReaderState>::iterator stateIt = states.begin(); stateIt != states.end(); ) { 85 Reader *reader = stateIt->userData<Reader>(); 86 vector<string>::iterator nameIt = find(names.begin(), names.end(), reader->name()); 87 if (nameIt == names.end()) { 88 // Reader was removed from the system. 89 if (Reader *reader = stateIt->userData<Reader>()) { 90 secdebug("pcsc", "removing reader %s", stateIt->name()); 91 Syslog::notice("Token reader %s removed from system", stateIt->name()); 92 reader->kill(); // prepare to die 93 mReaders.erase(reader->name()); // remove from reader map 94 stateIt = states.erase(stateIt); 95 } 96 } else { 97 // This reader is already tracked, copy its signalled state into the last known state. 98 stateIt->lastKnown(stateIt->state()); 99 names.erase(nameIt); 100 stateIt++; 101 } 102 } 103 104 // Add states for remaining (newly appeared) reader names. 105 for (vector<string>::iterator it = names.begin(); it != names.end(); ++it) { 106 PCSC::ReaderState state; 107 state.clearPod(); 108 state.set(it->c_str()); 109 states.push_back(state); 110 } 111 112 // Now ask PCSC for status changes, and wait for them. 113 mSession.statusChange(states, INFINITE); 114 115 // Go through the states and notify changed readers. 116 for (vector<PCSC::ReaderState>::iterator stateIt = states.begin(); stateIt != states.end(); stateIt++) { 117 Reader *reader = stateIt->userData<Reader>(); 118 if (!reader) { 119 reader = new Reader(mTokenCache, *stateIt); 120 stateIt->userData<Reader>() = reader; 121 stateIt->name(reader->name().c_str()); 122 mReaders.insert(make_pair(reader->name(), reader)); 123 Syslog::notice("Token reader %s inserted into system", stateIt->name()); 124 } 125 126 // if PCSC flags a change, notify the Reader 127 if (stateIt->changed()) { 128 Syslog::notice("reader %s: state changed %lu -> %lu", stateIt->name(), stateIt->lastKnown(), stateIt->state()); 129 try { 130 reader->update(*stateIt); 131 } catch (const exception &e) { 132 Syslog::notice("Token in reader %s: %s", stateIt->name(), e.what()); 133 } 134 } 135 } 136 137 //wakeup mach server to process notifications 138 ClientSession session(Allocator::standard(), Allocator::standard()); 139 session.postNotification(kNotificationDomainPCSC, kNotificationPCSCStateChange, CssmData()); 140 } 141 } catch (const exception &e) { 142 Syslog::error("An error '%s' occured while tracking token readers", e.what()); 143 } 144} 145 146TokenCache& PCSCMonitor::tokenCache() 147{ 148 if (mTokenCache == NULL) 149 mTokenCache = new TokenCache(mCachePath.c_str()); 150 return *mTokenCache; 151} 152 153// 154// Event notifier. 155// These events are sent by pcscd for our (sole) benefit. 156// 157void PCSCMonitor::notifyMe(Notification *message) 158{ 159} 160 161// 162// Timer action. Perform the initial PCSC subsystem initialization. 163// This runs (shortly) after securityd is fully functional and the 164// server loop has started. 165// 166void PCSCMonitor::action() 167{ 168 switch (mServiceLevel) { 169 case forcedOff: 170 secdebug("pcsc", "smartcard operation is FORCED OFF"); 171 break; 172 173 case externalDaemon: 174 secdebug("pcsc", "using PCSC"); 175 startSoftTokens(); 176 177 // Start PCSC reader watching thread. 178 (new Watcher(server, tokenCache(), mReaders))->run(); 179 break; 180 } 181} 182 183 184// 185// Software token support 186// 187void PCSCMonitor::startSoftTokens() 188{ 189 // scan for new ones 190 CodeRepository<Bundle> candidates("Security/tokend", ".tokend", "TOKENDAEMONPATH", false); 191 candidates.update(); 192 for (CodeRepository<Bundle>::iterator it = candidates.begin(); it != candidates.end(); ++it) { 193 if (CFTypeRef type = (*it)->infoPlistItem("TokendType")) 194 if (CFEqual(type, CFSTR("software"))) 195 loadSoftToken(*it); 196 } 197} 198 199void PCSCMonitor::loadSoftToken(Bundle *tokendBundle) 200{ 201 try { 202 string bundleName = tokendBundle->identifier(); 203 204 // prepare a virtual reader, removing any existing one (this would kill a previous tokend) 205 assert(mReaders.find(bundleName) == mReaders.end()); // not already present 206 RefPointer<Reader> reader = new Reader(tokenCache(), bundleName); 207 208 // now launch the tokend 209 RefPointer<TokenDaemon> tokend = new TokenDaemon(tokendBundle, 210 reader->name(), reader->pcscState(), reader->cache); 211 212 if (tokend->state() == ServerChild::dead) { // ah well, this one's no good 213 secdebug("pcsc", "softtoken %s tokend launch failed", bundleName.c_str()); 214 Syslog::notice("Software token %s failed to run", tokendBundle->canonicalPath().c_str()); 215 return; 216 } 217 218 // probe the (single) tokend 219 if (!tokend->probe()) { // non comprende... 220 secdebug("pcsc", "softtoken %s probe failed", bundleName.c_str()); 221 Syslog::notice("Software token %s refused operation", tokendBundle->canonicalPath().c_str()); 222 return; 223 } 224 225 // okay, this seems to work. Set it up 226 mReaders.insert(make_pair(reader->name(), reader)); 227 reader->insertToken(tokend); 228 Syslog::notice("Software token %s activated", bundleName.c_str()); 229 } catch (...) { 230 secdebug("pcsc", "exception loading softtoken %s - continuing", tokendBundle->identifier().c_str()); 231 } 232} 233