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