//
// Construct a Reader
// This does not commence state tracking; call update to start up the reader.
//
Reader::Reader(TokenCache &tc, const PCSC::ReaderState &state)
	: cache(tc), mType(pcsc), mToken(NULL)
{
	mName = state.name();	// remember separate copy of name
	mPrintName = mName;		//@@@ how to make this readable? Use IOKit information?
	secdebug("reader", "%p (%s) new PCSC reader", this, name().c_str());
}
Example #2
0
void Connection::open(const PCSC::ReaderState &reader, unsigned share)
{
	// fill in the minimum needed to identify the card
	MSCTokenInfo info;
	
	// set slot name in info
	strncpy(info.slotName, reader.name(), MAX_READERNAME);
	
	// set ATR in info
	assert(reader.length() <= MAX_ATR_SIZE);
	memcpy(info.tokenId, reader.data(), reader.length());
	info.tokenIdLength = (MSCULong32)reader.length();
	
	// establish Muscle-level connection to card
	Error::check(::MSCEstablishConnection(&info, share, NULL, 0, this));
	mIsOpen = true;
	secdebug("muscle", "%p opened %s", this, info.slotName);
	
	// pull initial status
	updateStatus();
}
//
// State transition matrix for a reader, based on PCSC state changes
//
void Reader::update(const PCSC::ReaderState &state)
{
	// set new state
	unsigned long oldState = mState.state();
	mState = state;
	mState.name(mName.c_str());		// (fix name pointer, unchanged)
	
	try {
		if (state.state(SCARD_STATE_UNAVAILABLE)) {
			// reader is unusable (probably being removed)
			secdebug("reader", "%p (%s) unavailable (0x%lx)",
				this, name().c_str(), state.state());
			if (mToken)
				removeToken();
		} else if (state.state(SCARD_STATE_EMPTY)) {
			// reader is empty (no token present)
			secdebug("reader", "%p (%s) empty (0x%lx)",
				this, name().c_str(), state.state());
			if (mToken)
				removeToken();
		} else if (state.state(SCARD_STATE_PRESENT)) {
			// reader has a token inserted
			secdebug("reader", "%p (%s) card present (0x%lx)",
				this, name().c_str(), state.state());
			//@@@ is this hack worth it (with notifications in)??
			if (mToken && CssmData(state) != CssmData(pcscState()))
				removeToken();  // incomplete but better than nothing
			//@@@ or should we call some verify-still-the-same function of tokend?
			//@@@ (I think pcsc will return an error if the card changed?)
			if (!mToken)
				insertToken(NULL);
		} else {
			secdebug("reader", "%p (%s) unexpected state change (0x%lx to 0x%lx)",
				this, name().c_str(), oldState, state.state());
		}
	} catch (...) {
		secdebug("reader", "state update exception (ignored)");
	}
}
//
// Poll PCSC for smartcard status.
// We are enumerating all readers on each call.
//
void PCSCMonitor::Watcher::action()
{
    // Associate this watching thread with the server, so that it is possible to call
    // Server::active() from inside code called from this thread.
    mServer.associateThread();

    try {
        // open PCSC session
        mSession.open();

        // Array of states, userData() points to associated Reader instance,
        // name points to string held by Reader::name attribute.
        vector<PCSC::ReaderState> states;

        for (;;) {
            // enumerate all current readers.
            vector<string> names;
            mSession.listReaders(names);
            secinfo("pcsc", "%ld reader(s) in system", names.size());

            // Update PCSC states array with new/removed readers.
            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()) {
                    // Reader was removed from the system.
                    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();						// prepare to die
                        mReaders.erase(reader->name());		// remove from reader map
                        stateIt = states.erase(stateIt);
                    }
                } else {
                    // This reader is already tracked, copy its signalled state into the last known state.
                    stateIt->lastKnown(stateIt->state());
                    names.erase(nameIt);
                    stateIt++;
                }
            }

            // Add states for remaining (newly appeared) reader names.
            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);
            }

            // Now ask PCSC for status changes, and wait for them.
            mSession.statusChange(states, INFINITE);
            
            // Go through the states and notify changed readers.
            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 PCSC flags a change, notify the Reader
                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());
                    }
                }
            }

            //wakeup mach server to process notifications
            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());
    }
}