#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);
static const uint32_t kVendorProductMask = 0x0000FFFF;
static const uint32_t kVendorIDApple = 0x05AC;
static const uint16_t kProductIDBuiltInISight = 0x8501;
enum {
kBuiltIniSightProductID = 0x8501,
kBuiltIniSightWave2ProductID = 0x8502,
kBuiltIniSightWave3ProductID = 0x8505,
kUSBWave4ProductID = 0x8507,
kUSBWave2InK29ProductID = 0x8508,
kUSBWaveReserved1ProductID = 0x8509,
kUSBWaveReserved2ProductID = 0x850a,
kExternaliSightProductID = 0x1111,
kLogitechVendorID = 0x046d
};
PCSCMonitor::PCSCMonitor(Server &server, const char* pathToCache, ServiceLevel level)
: Listener(kNotificationDomainPCSC, SecurityServer::kNotificationAllEvents),
MachServer::Timer(true), server(server),
mServiceLevel(level),
mTimerAction(&PCSCMonitor::initialSetup),
mGoingToSleep(false),
mCachePath(pathToCache),
mTokenCache(NULL)
{
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 0 //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(tokenCache(), 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++) {
switch ((*it)->type()) {
case Reader::pcsc:
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()); break;
case Reader::software:
break;
}
}
}
void PCSCMonitor::clearReaders(Reader::Type type)
{
if (!mReaders.empty()) {
secdebug("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)) {
secdebug("pcsc", "removing reader %s", reader->name().c_str());
reader->kill(); mReaders.erase(cur);
}
}
}
}
TokenCache& PCSCMonitor::tokenCache()
{
if (mTokenCache == NULL)
mTokenCache = new TokenCache(mCachePath.c_str());
return *mTokenCache;
}
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(Notification *message)
{
Server::active().longTermActivity();
StLock<Mutex> _(*this);
assert(mServiceLevel == externalDaemon || Child::state() == alive);
if (message->event == kNotificationPCSCInitialized)
clearReaders(Reader::pcsc);
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();
startSoftTokens();
break;
case externalDaemon:
secdebug("pcsc", "using external pcscd (if any); no launch operations");
startSoftTokens();
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); }
startSoftTokens();
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 (uint32 clas = cfNumber(cfInterface)) {
case kUSBChipSmartCardInterfaceClass: secdebug("scsel", " CCID smartcard reader recognized");
return definite;
case kUSBVendorSpecificInterfaceClass:
secdebug("scsel", " Vendor-specific interface - possible match");
if (isExcludedDevice(dev))
{
secdebug("scsel", " interface class %d is not a smartcard device (excluded)", clas);
return impossible;
}
return possible;
default:
secdebug("scsel", " interface class %d is not a smartcard device", clas);
return impossible;
}
if (CFRef<CFNumberRef> cfDevice = dev.property<CFNumberRef>("bDeviceClass"))
if (cfNumber(cfDevice) == kUSBVendorSpecificClass)
{
if (isExcludedDevice(dev))
{
secdebug("scsel", " device class %d is not a smartcard device (excluded)", cfNumber(cfDevice));
return impossible;
}
secdebug("scsel", " Vendor-specific device - possible match");
return possible;
}
if (CFRef<CFStringRef> ioName = dev.property<CFStringRef>("IOName"))
if (cfString(ioName).find("pccard", 0, 1) == 0) {
secdebug("scsel", " PCCard - possible match");
return possible;
}
return impossible;
} catch (...) {
secdebug("scsel", " exception while examining device - ignoring it");
return impossible;
}
}
bool PCSCMonitor::isExcludedDevice(const IOKit::Device &dev)
{
uint32_t vendorID = 0, productID = 0;
if (CFRef<CFNumberRef> cfVendorID = dev.property<CFNumberRef>(kUSBVendorID))
vendorID = cfNumber(cfVendorID);
if (CFRef<CFNumberRef> cfProductID = dev.property<CFNumberRef>(kUSBProductID))
productID = cfNumber(cfProductID);
secdebug("scsel", " checking device for possible exclusion [vendor id: 0x%08X, product id: 0x%08X]", vendorID, productID);
if ((vendorID & kVendorProductMask) != kVendorIDApple)
return false;
return true;
}
void PCSCMonitor::dying()
{
Server::active().longTermActivity();
StLock<Mutex> _(*this);
assert(Child::state() == dead);
clearReaders(Reader::pcsc);
}
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) { secdebug("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()) { secdebug("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 (...) {
secdebug("pcsc", "exception loading softtoken %s - continuing", tokendBundle->identifier().c_str());
}
}