]>
Commit | Line | Data |
---|---|---|
d8f41ccd A |
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), | |
5c19dc3a | 46 | MachServer::Timer(true), |
d8f41ccd A |
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); | |
fa7225c8 | 82 | secinfo("pcsc", "%ld reader(s) in system", names.size()); |
d8f41ccd A |
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>()) { | |
fa7225c8 | 91 | secinfo("pcsc", "removing reader %s", stateIt->name()); |
d8f41ccd A |
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: | |
fa7225c8 | 171 | secinfo("pcsc", "smartcard operation is FORCED OFF"); |
d8f41ccd A |
172 | break; |
173 | ||
174 | case externalDaemon: | |
fa7225c8 | 175 | secinfo("pcsc", "using PCSC"); |
d8f41ccd A |
176 | startSoftTokens(); |
177 | ||
178 | // Start PCSC reader watching thread. | |
179 | (new Watcher(server, tokenCache(), mReaders))->run(); | |
180 | break; | |
181 | } | |
182 | } | |
183 | ||
5c19dc3a A |
184 | // |
185 | // Remove some types of readers | |
186 | // | |
187 | void PCSCMonitor::clearReaders(Reader::Type type) | |
188 | { | |
189 | if (!mReaders.empty()) { | |
fa7225c8 | 190 | secinfo("pcsc", "%ld readers present - clearing type %d", mReaders.size(), type); |
5c19dc3a A |
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)) { | |
fa7225c8 | 195 | secinfo("pcsc", "removing reader %s", reader->name().c_str()); |
5c19dc3a A |
196 | reader->kill(); // prepare to die |
197 | mReaders.erase(cur); | |
198 | } | |
199 | } | |
200 | } | |
201 | } | |
d8f41ccd A |
202 | |
203 | // | |
204 | // Software token support | |
205 | // | |
206 | void PCSCMonitor::startSoftTokens() | |
207 | { | |
5c19dc3a A |
208 | // clear all software readers. This will kill the respective TokenDaemons |
209 | clearReaders(Reader::software); | |
210 | ||
d8f41ccd A |
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 | |
fa7225c8 | 235 | secinfo("pcsc", "softtoken %s tokend launch failed", bundleName.c_str()); |
d8f41ccd A |
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... | |
fa7225c8 | 242 | secinfo("pcsc", "softtoken %s probe failed", bundleName.c_str()); |
d8f41ccd A |
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 (...) { | |
fa7225c8 | 252 | secinfo("pcsc", "exception loading softtoken %s - continuing", tokendBundle->identifier().c_str()); |
d8f41ccd A |
253 | } |
254 | } |