]> git.saurik.com Git - apple/security.git/blob - securityd/src/pcscmonitor.cpp
Security-58286.251.4.tar.gz
[apple/security.git] / securityd / src / pcscmonitor.cpp
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 //
44 PCSCMonitor::PCSCMonitor(Server &server, const char* pathToCache, ServiceLevel level)
45 : Listener(kNotificationDomainPCSC, SecurityServer::kNotificationAllEvents),
46 MachServer::Timer(true),
47 server(server),
48 mServiceLevel(level),
49 mCachePath(pathToCache),
50 mTokenCache(NULL)
51 {
52 // do all the smartcard-related work once the event loop has started
53 server.setTimer(this, Time::now()); // ASAP
54 }
55
56 PCSCMonitor::Watcher::Watcher(Server &server, TokenCache &tokenCache, ReaderMap& readers)
57 : mServer(server), mTokenCache(tokenCache), mReaders(readers)
58 {}
59
60 //
61 // Poll PCSC for smartcard status.
62 // We are enumerating all readers on each call.
63 //
64 void PCSCMonitor::Watcher::action()
65 {
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();
69
70 try {
71 // open PCSC session
72 mSession.open();
73
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;
77
78 for (;;) {
79 // enumerate all current readers.
80 vector<string> names;
81 mSession.listReaders(names);
82 secinfo("pcsc", "%ld reader(s) in system", names.size());
83
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);
96 }
97 } else {
98 // This reader is already tracked, copy its signalled state into the last known state.
99 stateIt->lastKnown(stateIt->state());
100 names.erase(nameIt);
101 stateIt++;
102 }
103 }
104
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;
108 state.clearPod();
109 state.set(it->c_str());
110 states.push_back(state);
111 }
112
113 // Now ask PCSC for status changes, and wait for them.
114 mSession.statusChange(states, INFINITE);
115
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>();
119 if (!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());
125 }
126
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());
130 try {
131 reader->update(*stateIt);
132 } catch (const exception &e) {
133 Syslog::notice("Token in reader %s: %s", stateIt->name(), e.what());
134 }
135 }
136 }
137
138 //wakeup mach server to process notifications
139 ClientSession session(Allocator::standard(), Allocator::standard());
140 session.postNotification(kNotificationDomainPCSC, kNotificationPCSCStateChange, CssmData());
141 }
142 } catch (const exception &e) {
143 Syslog::error("An error '%s' occured while tracking token readers", e.what());
144 }
145 }
146
147 TokenCache& PCSCMonitor::tokenCache()
148 {
149 if (mTokenCache == NULL)
150 mTokenCache = new TokenCache(mCachePath.c_str());
151 return *mTokenCache;
152 }
153
154 //
155 // Event notifier.
156 // These events are sent by pcscd for our (sole) benefit.
157 //
158 void PCSCMonitor::notifyMe(Notification *message)
159 {
160 }
161
162 //
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.
166 //
167 void PCSCMonitor::action()
168 {
169 switch (mServiceLevel) {
170 case forcedOff:
171 secinfo("pcsc", "smartcard operation is FORCED OFF");
172 break;
173
174 case externalDaemon:
175 secinfo("pcsc", "using PCSC");
176 startSoftTokens();
177
178 // Start PCSC reader watching thread.
179 (new Watcher(server, tokenCache(), mReaders))->run();
180 break;
181 }
182 }
183
184 //
185 // Remove some types of readers
186 //
187 void PCSCMonitor::clearReaders(Reader::Type type)
188 {
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
197 mReaders.erase(cur);
198 }
199 }
200 }
201 }
202
203 //
204 // Software token support
205 //
206 void PCSCMonitor::startSoftTokens()
207 {
208 // clear all software readers. This will kill the respective TokenDaemons
209 clearReaders(Reader::software);
210
211 // scan for new ones
212 CodeRepository<Bundle> candidates("Security/tokend", ".tokend", "TOKENDAEMONPATH", false);
213 candidates.update();
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")))
217 loadSoftToken(*it);
218 }
219 }
220
221 void PCSCMonitor::loadSoftToken(Bundle *tokendBundle)
222 {
223 try {
224 string bundleName = tokendBundle->identifier();
225
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);
229
230 // now launch the tokend
231 RefPointer<TokenDaemon> tokend = new TokenDaemon(tokendBundle,
232 reader->name(), reader->pcscState(), reader->cache);
233
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());
237 return;
238 }
239
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());
244 return;
245 }
246
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());
251 } catch (...) {
252 secinfo("pcsc", "exception loading softtoken %s - continuing", tokendBundle->identifier().c_str());
253 }
254 }