2 * Copyright (c) 2004-2008,2011,2014 Apple 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 #include "pcscmonitor.h"
33 #include <security_utilities/logging.h>
36 // Construct a PCSCMonitor.
37 // We strongly assume there's only one of us around here.
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.
44 PCSCMonitor::PCSCMonitor(Server
&server
, const char* pathToCache
, ServiceLevel level
)
45 : Listener(kNotificationDomainPCSC
, SecurityServer::kNotificationAllEvents
),
46 MachServer::Timer(true),
49 mCachePath(pathToCache
),
52 // do all the smartcard-related work once the event loop has started
53 server
.setTimer(this, Time::now()); // ASAP
56 PCSCMonitor::Watcher::Watcher(Server
&server
, TokenCache
&tokenCache
, ReaderMap
& readers
)
57 : mServer(server
), mTokenCache(tokenCache
), mReaders(readers
)
61 // Poll PCSC for smartcard status.
62 // We are enumerating all readers on each call.
64 void PCSCMonitor::Watcher::action()
66 // Associate this watching thread with the server, so that it is possible to call
67 // Server::active() from inside code called from this thread.
68 mServer
.associateThread();
74 // Array of states, userData() points to associated Reader instance,
75 // name points to string held by Reader::name attribute.
76 vector
<PCSC::ReaderState
> states
;
79 // enumerate all current readers.
81 mSession
.listReaders(names
);
82 secinfo("pcsc", "%ld reader(s) in system", names
.size());
84 // Update PCSC states array with new/removed readers.
85 for (vector
<PCSC::ReaderState
>::iterator stateIt
= states
.begin(); stateIt
!= states
.end(); ) {
86 Reader
*reader
= stateIt
->userData
<Reader
>();
87 vector
<string
>::iterator nameIt
= find(names
.begin(), names
.end(), reader
->name());
88 if (nameIt
== names
.end()) {
89 // Reader was removed from the system.
90 if (Reader
*reader
= stateIt
->userData
<Reader
>()) {
91 secinfo("pcsc", "removing reader %s", stateIt
->name());
92 Syslog::notice("Token reader %s removed from system", stateIt
->name());
93 reader
->kill(); // prepare to die
94 mReaders
.erase(reader
->name()); // remove from reader map
95 stateIt
= states
.erase(stateIt
);
98 // This reader is already tracked, copy its signalled state into the last known state.
99 stateIt
->lastKnown(stateIt
->state());
105 // Add states for remaining (newly appeared) reader names.
106 for (vector
<string
>::iterator it
= names
.begin(); it
!= names
.end(); ++it
) {
107 PCSC::ReaderState state
;
109 state
.set(it
->c_str());
110 states
.push_back(state
);
113 // Now ask PCSC for status changes, and wait for them.
114 mSession
.statusChange(states
, INFINITE
);
116 // Go through the states and notify changed readers.
117 for (vector
<PCSC::ReaderState
>::iterator stateIt
= states
.begin(); stateIt
!= states
.end(); stateIt
++) {
118 Reader
*reader
= stateIt
->userData
<Reader
>();
120 reader
= new Reader(mTokenCache
, *stateIt
);
121 stateIt
->userData
<Reader
>() = reader
;
122 stateIt
->name(reader
->name().c_str());
123 mReaders
.insert(make_pair(reader
->name(), reader
));
124 Syslog::notice("Token reader %s inserted into system", stateIt
->name());
127 // if PCSC flags a change, notify the Reader
128 if (stateIt
->changed()) {
129 Syslog::notice("reader %s: state changed %lu -> %lu", stateIt
->name(), stateIt
->lastKnown(), stateIt
->state());
131 reader
->update(*stateIt
);
132 } catch (const exception
&e
) {
133 Syslog::notice("Token in reader %s: %s", stateIt
->name(), e
.what());
138 //wakeup mach server to process notifications
139 ClientSession
session(Allocator::standard(), Allocator::standard());
140 session
.postNotification(kNotificationDomainPCSC
, kNotificationPCSCStateChange
, CssmData());
142 } catch (const exception
&e
) {
143 Syslog::error("An error '%s' occured while tracking token readers", e
.what());
147 TokenCache
& PCSCMonitor::tokenCache()
149 if (mTokenCache
== NULL
)
150 mTokenCache
= new TokenCache(mCachePath
.c_str());
156 // These events are sent by pcscd for our (sole) benefit.
158 void PCSCMonitor::notifyMe(Notification
*message
)
163 // Timer action. Perform the initial PCSC subsystem initialization.
164 // This runs (shortly) after securityd is fully functional and the
165 // server loop has started.
167 void PCSCMonitor::action()
169 switch (mServiceLevel
) {
171 secinfo("pcsc", "smartcard operation is FORCED OFF");
175 secinfo("pcsc", "using PCSC");
178 // Start PCSC reader watching thread.
179 (new Watcher(server
, tokenCache(), mReaders
))->run();
185 // Remove some types of readers
187 void PCSCMonitor::clearReaders(Reader::Type type
)
189 if (!mReaders
.empty()) {
190 secinfo("pcsc", "%ld readers present - clearing type %d", mReaders
.size(), type
);
191 for (ReaderMap::iterator it
= mReaders
.begin(); it
!= mReaders
.end(); ) {
192 ReaderMap::iterator cur
= it
++;
193 Reader
*reader
= cur
->second
;
194 if (reader
->isType(type
)) {
195 secinfo("pcsc", "removing reader %s", reader
->name().c_str());
196 reader
->kill(); // prepare to die
204 // Software token support
206 void PCSCMonitor::startSoftTokens()
208 // clear all software readers. This will kill the respective TokenDaemons
209 clearReaders(Reader::software
);
212 CodeRepository
<Bundle
> candidates("Security/tokend", ".tokend", "TOKENDAEMONPATH", false);
214 for (CodeRepository
<Bundle
>::iterator it
= candidates
.begin(); it
!= candidates
.end(); ++it
) {
215 if (CFTypeRef type
= (*it
)->infoPlistItem("TokendType"))
216 if (CFEqual(type
, CFSTR("software")))
221 void PCSCMonitor::loadSoftToken(Bundle
*tokendBundle
)
224 string bundleName
= tokendBundle
->identifier();
226 // prepare a virtual reader, removing any existing one (this would kill a previous tokend)
227 assert(mReaders
.find(bundleName
) == mReaders
.end()); // not already present
228 RefPointer
<Reader
> reader
= new Reader(tokenCache(), bundleName
);
230 // now launch the tokend
231 RefPointer
<TokenDaemon
> tokend
= new TokenDaemon(tokendBundle
,
232 reader
->name(), reader
->pcscState(), reader
->cache
);
234 if (tokend
->state() == ServerChild::dead
) { // ah well, this one's no good
235 secinfo("pcsc", "softtoken %s tokend launch failed", bundleName
.c_str());
236 Syslog::notice("Software token %s failed to run", tokendBundle
->canonicalPath().c_str());
240 // probe the (single) tokend
241 if (!tokend
->probe()) { // non comprende...
242 secinfo("pcsc", "softtoken %s probe failed", bundleName
.c_str());
243 Syslog::notice("Software token %s refused operation", tokendBundle
->canonicalPath().c_str());
247 // okay, this seems to work. Set it up
248 mReaders
.insert(make_pair(reader
->name(), reader
));
249 reader
->insertToken(tokend
);
250 Syslog::notice("Software token %s activated", bundleName
.c_str());
252 secinfo("pcsc", "exception loading softtoken %s - continuing", tokendBundle
->identifier().c_str());