#include "pcscmonitor.h"
#include <security_utilities/logging.h>
#include <IOKit/usb/IOUSBLib.h>
static const char PCSCD_EXEC_PATH[] = "/usr/sbin/pcscd"; static const char PCSCD_WORKING_DIR[] = "/var/run/pcscd"; static const Time::Interval PCSCD_IDLE_SHUTDOWN(120);
PCSCMonitor::PCSCMonitor(Server &server, const char* pathToCache, ServiceLevel level)
: Listener(kNotificationDomainPCSC, SecurityServer::kNotificationAllEvents),
MachServer::Timer(true), server(server),
cache (NULL),
cachePath (pathToCache),
mServiceLevel(level),
mTimerAction(&PCSCMonitor::initialSetup),
mGoingToSleep(false)
{
server.setTimer(this, Time::now()); }
void PCSCMonitor::pollReaders()
{
mSession.open();
vector<string> names; mSession.listReaders(names);
size_t count = names.size();
secdebug("pcsc", "%ld reader(s) in system", count);
vector<PCSC::ReaderState> states(count); for (unsigned int n = 0; n < count; n++) {
PCSC::ReaderState &state = states[n];
ReaderMap::iterator it = mReaders.find(names[n]);
if (it == mReaders.end()) { state.clearPod();
state.name(names[n].c_str());
} else {
state = it->second->pcscState();
state.name(names[n].c_str()); state.lastKnown(state.state());
state.userData<Reader>() = it->second;
}
}
mSession.statusChange(states);
#if DEBUGDUMP
if (Debug::dumping("pcsc"))
for (unsigned int n = 0; n < count; n++)
states[n].dump();
#endif
ReaderSet current;
copy_second(mReaders.begin(), mReaders.end(), inserter(current, current.end()));
for (unsigned int n = 0; n < count; n++) {
PCSC::ReaderState &state = states[n];
if (Reader *reader = state.userData<Reader>()) {
if (state.changed())
reader->update(state);
current.erase(reader);
} else {
RefPointer<Reader> newReader = new Reader(getTokenCache (), state);
mReaders.insert(make_pair(state.name(), newReader));
Syslog::notice("Token reader %s inserted into system", state.name());
newReader->update(state); }
}
for (ReaderSet::iterator it = current.begin(); it != current.end(); it++) {
secdebug("pcsc", "removing reader %s", (*it)->name().c_str());
Syslog::notice("Token reader %s removed from system", (*it)->name().c_str());
(*it)->kill(); mReaders.erase((*it)->name()); }
}
TokenCache& PCSCMonitor::getTokenCache ()
{
if (cache == NULL) {
cache = new TokenCache(cachePath.c_str ());
}
return *cache;
}
void PCSCMonitor::launchPcscd()
{
secdebug("pcsc", "launching pcscd to handle smartcard device(s)");
assert(Child::state() != alive);
Child::reset();
Child::fork();
scheduleTimer(true);
}
void PCSCMonitor::childAction()
{
const char *aside = tempnam("/tmp", "pcscd");
if (::rename(PCSCD_WORKING_DIR, aside))
switch (errno) {
case ENOENT: break;
default:
secdebug("pcsc", "failed too move %s - errno=%d", PCSCD_WORKING_DIR, errno);
_exit(101);
}
else
secdebug("pcsc", "old /tmp/pcsc moved to %s", aside);
#if !defined(NDEBUG)
freopen("/tmp/pcsc.debuglog", "a", stdout); #endif //NDEBUG
const char *pcscdPath = PCSCD_EXEC_PATH;
if (const char *env = getenv("PCSCDAEMON"))
pcscdPath = env;
secdebug("pcsc", "exec(%s,-f)", pcscdPath);
execl(pcscdPath, pcscdPath, "-f", NULL);
}
void PCSCMonitor::notifyMe(SecurityServer::NotificationDomain domain,
SecurityServer::NotificationEvent event, const CssmData &data)
{
Server::active().longTermActivity();
StLock<Mutex> _(*this);
assert(mServiceLevel == externalDaemon || Child::state() == alive);
pollReaders();
scheduleTimer(mReaders.empty() && !mGoingToSleep);
}
void PCSCMonitor::systemWillSleep()
{
StLock<Mutex> _(*this);
secdebug("pcsc", "setting sleep marker (%ld readers as of now)", mReaders.size());
mGoingToSleep = true;
server.clearTimer(this);
}
void PCSCMonitor::systemIsWaking()
{
StLock<Mutex> _(*this);
secdebug("pcsc", "clearing sleep marker (%ld readers as of now)", mReaders.size());
mGoingToSleep = false;
scheduleTimer(mReaders.empty());
}
void PCSCMonitor::action()
{
StLock<Mutex> _(*this);
(this->*mTimerAction)();
mTimerAction = &PCSCMonitor::noDeviceTimeout;
}
void PCSCMonitor::scheduleTimer(bool enable)
{
if (Child::state() == alive) if (enable) {
secdebug("pcsc", "setting idle timer for %g seconds", PCSCD_IDLE_SHUTDOWN.seconds());
server.setTimer(this, PCSCD_IDLE_SHUTDOWN);
} else if (Timer::scheduled()) {
secdebug("pcsc", "clearing idle timer");
server.clearTimer(this);
}
}
void PCSCMonitor::initialSetup()
{
switch (mServiceLevel) {
case forcedOff:
secdebug("pcsc", "smartcard operation is FORCED OFF");
break;
case forcedOn:
secdebug("pcsc", "pcscd launch is forced on");
launchPcscd();
break;
case externalDaemon:
secdebug("pcsc", "using external pcscd (if any); no launch operations");
break;
default:
secdebug("pcsc", "setting up automatic PCSC management in %s mode",
mServiceLevel == conservative ? "conservative" : "aggressive");
server.add(mIOKitNotifier);
server.add(this);
IOKit::DeviceMatch usbSelector(kIOUSBInterfaceClassName);
IOKit::DeviceMatch pcCardSelector("IOPCCard16Device");
mIOKitNotifier.add(usbSelector, *this); mIOKitNotifier.add(pcCardSelector, *this); if (mServiceLevel == aggressive) {
IOKit::DeviceMatch customUsbSelector(::IOServiceMatching("IOUSBDevice"));
mIOKitNotifier.add(customUsbSelector, *this); }
break;
}
}
void PCSCMonitor::noDeviceTimeout()
{
secdebug("pcsc", "killing pcscd (no smartcard devices present for %g seconds)",
PCSCD_IDLE_SHUTDOWN.seconds());
assert(mReaders.empty());
Child::kill(SIGTERM);
}
void PCSCMonitor::ioChange(IOKit::DeviceIterator &iterator)
{
assert(mServiceLevel != externalDaemon && mServiceLevel != forcedOff);
if (Child::state() == alive) {
secdebug("pcsc", "pcscd is alive; ignoring device insertion(s)");
return;
}
secdebug("pcsc", "processing device insertion notices");
while (IOKit::Device dev = iterator()) {
bool launch = false;
switch (deviceSupport(dev)) {
case definite:
launch = true;
break;
case possible:
launch = (mServiceLevel == aggressive);
break;
case impossible:
break;
}
if (launch) {
launchPcscd();
return;
}
}
secdebug("pcsc", "no relevant devices found");
}
PCSCMonitor::DeviceSupport PCSCMonitor::deviceSupport(const IOKit::Device &dev)
{
try {
secdebug("scsel", "%s", dev.path().c_str());
if (CFRef<CFNumberRef> cfInterface = dev.property<CFNumberRef>("bInterfaceClass"))
switch (IFDEBUG(uint32 clas =) cfNumber(cfInterface)) {
case kUSBChipSmartCardInterfaceClass: secdebug("scsel", " CCID smartcard reader recognized");
return definite;
case kUSBVendorSpecificInterfaceClass:
secdebug("scsel", " Vendor-specific interface - possible match");
return possible;
default:
secdebug("scsel", " interface class %ld is not a smartcard device", clas);
return impossible;
}
if (CFRef<CFNumberRef> cfDevice = dev.property<CFNumberRef>("bDeviceClass"))
if (cfNumber(cfDevice) == kUSBVendorSpecificClass) {
secdebug("scsel", " Vendor-specific device - possible match");
return possible;
}
return impossible;
} catch (...) {
secdebug("scsel", " exception while examining device - ignoring it");
return impossible;
}
}
void PCSCMonitor::dying()
{
Server::active().longTermActivity();
StLock<Mutex> _(*this);
assert(Child::state() == dead);
if (!mReaders.empty()) {
secdebug("pcsc", "%ld readers were present when pcscd died", mReaders.size());
for (ReaderMap::const_iterator it = mReaders.begin(); it != mReaders.end(); it++) {
Reader *reader = it->second;
secdebug("pcsc", "removing reader %s", reader->name().c_str());
reader->kill(); }
mReaders.erase(mReaders.begin(), mReaders.end());
secdebug("pcsc", "orphaned readers cleared");
}
}