#include "pcscmonitor.h"
#include <security_utilities/logging.h>
PCSCMonitor::PCSCMonitor(Server &server, const char* pathToCache, ServiceLevel level)
: Listener(kNotificationDomainPCSC, SecurityServer::kNotificationAllEvents),
MachServer::Timer(true),
server(server),
mServiceLevel(level),
mCachePath(pathToCache),
mTokenCache(NULL)
{
server.setTimer(this, Time::now()); }
PCSCMonitor::Watcher::Watcher(Server &server, TokenCache &tokenCache, ReaderMap& readers)
: mServer(server), mTokenCache(tokenCache), mReaders(readers)
{}
void PCSCMonitor::Watcher::action()
{
mServer.associateThread();
try {
mSession.open();
vector<PCSC::ReaderState> states;
for (;;) {
vector<string> names;
mSession.listReaders(names);
secinfo("pcsc", "%ld reader(s) in system", names.size());
for (vector<PCSC::ReaderState>::iterator stateIt = states.begin(); stateIt != states.end(); ) {
Reader *reader = stateIt->userData<Reader>();
vector<string>::iterator nameIt = find(names.begin(), names.end(), reader->name());
if (nameIt == names.end()) {
if (Reader *reader = stateIt->userData<Reader>()) {
secinfo("pcsc", "removing reader %s", stateIt->name());
Syslog::notice("Token reader %s removed from system", stateIt->name());
reader->kill(); mReaders.erase(reader->name()); stateIt = states.erase(stateIt);
}
} else {
stateIt->lastKnown(stateIt->state());
names.erase(nameIt);
stateIt++;
}
}
for (vector<string>::iterator it = names.begin(); it != names.end(); ++it) {
PCSC::ReaderState state;
state.clearPod();
state.set(it->c_str());
states.push_back(state);
}
mSession.statusChange(states, INFINITE);
for (vector<PCSC::ReaderState>::iterator stateIt = states.begin(); stateIt != states.end(); stateIt++) {
Reader *reader = stateIt->userData<Reader>();
if (!reader) {
reader = new Reader(mTokenCache, *stateIt);
stateIt->userData<Reader>() = reader;
stateIt->name(reader->name().c_str());
mReaders.insert(make_pair(reader->name(), reader));
Syslog::notice("Token reader %s inserted into system", stateIt->name());
}
if (stateIt->changed()) {
Syslog::notice("reader %s: state changed %lu -> %lu", stateIt->name(), stateIt->lastKnown(), stateIt->state());
try {
reader->update(*stateIt);
} catch (const exception &e) {
Syslog::notice("Token in reader %s: %s", stateIt->name(), e.what());
}
}
}
ClientSession session(Allocator::standard(), Allocator::standard());
session.postNotification(kNotificationDomainPCSC, kNotificationPCSCStateChange, CssmData());
}
} catch (const exception &e) {
Syslog::error("An error '%s' occured while tracking token readers", e.what());
}
}
TokenCache& PCSCMonitor::tokenCache()
{
if (mTokenCache == NULL)
mTokenCache = new TokenCache(mCachePath.c_str());
return *mTokenCache;
}
void PCSCMonitor::notifyMe(Notification *message)
{
}
void PCSCMonitor::action()
{
switch (mServiceLevel) {
case forcedOff:
secinfo("pcsc", "smartcard operation is FORCED OFF");
break;
case externalDaemon:
secinfo("pcsc", "using PCSC");
startSoftTokens();
(new Watcher(server, tokenCache(), mReaders))->run();
break;
}
}
void PCSCMonitor::clearReaders(Reader::Type type)
{
if (!mReaders.empty()) {
secinfo("pcsc", "%ld readers present - clearing type %d", mReaders.size(), type);
for (ReaderMap::iterator it = mReaders.begin(); it != mReaders.end(); ) {
ReaderMap::iterator cur = it++;
Reader *reader = cur->second;
if (reader->isType(type)) {
secinfo("pcsc", "removing reader %s", reader->name().c_str());
reader->kill(); mReaders.erase(cur);
}
}
}
}
void PCSCMonitor::startSoftTokens()
{
clearReaders(Reader::software);
CodeRepository<Bundle> candidates("Security/tokend", ".tokend", "TOKENDAEMONPATH", false);
candidates.update();
for (CodeRepository<Bundle>::iterator it = candidates.begin(); it != candidates.end(); ++it) {
if (CFTypeRef type = (*it)->infoPlistItem("TokendType"))
if (CFEqual(type, CFSTR("software")))
loadSoftToken(*it);
}
}
void PCSCMonitor::loadSoftToken(Bundle *tokendBundle)
{
try {
string bundleName = tokendBundle->identifier();
assert(mReaders.find(bundleName) == mReaders.end()); RefPointer<Reader> reader = new Reader(tokenCache(), bundleName);
RefPointer<TokenDaemon> tokend = new TokenDaemon(tokendBundle,
reader->name(), reader->pcscState(), reader->cache);
if (tokend->state() == ServerChild::dead) { secinfo("pcsc", "softtoken %s tokend launch failed", bundleName.c_str());
Syslog::notice("Software token %s failed to run", tokendBundle->canonicalPath().c_str());
return;
}
if (!tokend->probe()) { secinfo("pcsc", "softtoken %s probe failed", bundleName.c_str());
Syslog::notice("Software token %s refused operation", tokendBundle->canonicalPath().c_str());
return;
}
mReaders.insert(make_pair(reader->name(), reader));
reader->insertToken(tokend);
Syslog::notice("Software token %s activated", bundleName.c_str());
} catch (...) {
secinfo("pcsc", "exception loading softtoken %s - continuing", tokendBundle->identifier().c_str());
}
}