#include "token.h"
#include "tokendatabase.h"
#include "reader.h"
#include "notifications.h"
#include "child.h"
#include "server.h"
#include <securityd_client/dictionary.h>
#include <security_utilities/coderepository.h>
#include <security_utilities/logging.h>
#include <security_cdsa_client/mdsclient.h>
#include <SecurityTokend/SecTokend.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <grp.h>
#include <pwd.h>
using namespace MDSClient;
Token::SSIDMap Token::mSubservices;
Mutex Token::mSSIDLock;
Token::Token()
: mFaulted(false), mTokend(NULL), mResetLevel(1)
{
secinfo("token", "%p created", this);
}
Token::~Token()
{
secinfo("token", "%p (%s:%d) destroyed",
this, mGuid.toString().c_str(), mSubservice);
}
Reader &Token::reader() const
{
return referent< ::Reader>();
}
TokenDaemon &Token::tokend()
{
StLock<Mutex> _(*this);
if (mFaulted)
CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED);
if (mTokend)
return *mTokend;
else
CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED);
}
GenericHandle Token::tokenHandle() const
{
return noDb; }
AclKind Token::aclKind() const
{
return dbAcl;
}
Token &Token::token()
{
return *this;
}
RefPointer<Token> Token::find(uint32 ssid)
{
StLock<Mutex> _(mSSIDLock);
SSIDMap::const_iterator it = mSubservices.find(ssid);
if (it == mSubservices.end())
CssmError::throwMe(CSSMERR_CSSM_INVALID_SUBSERVICEID);
else
return it->second;
}
void Token::getAcl(const char *tag, uint32 &count, AclEntryInfo *&acls)
{
if (pinFromAclTag(tag, "?")) { AclEntryInfo *racls;
token().tokend().getAcl(aclKind(), tokenHandle(), tag, count, racls);
acls = Allocator::standard().alloc<AclEntryInfo>(count * sizeof(AclEntryInfo));
memcpy(acls, racls, count * sizeof(AclEntryInfo));
ChunkCopyWalker copy;
for (uint32 n = 0; n < count; n++)
walk(copy, acls[n]);
return;
}
TokenAcl::cssmGetAcl(tag, count, acls);
}
Token::ResetGeneration Token::resetGeneration() const
{
return mResetLevel;
}
void Token::resetAcls()
{
CommonSet tmpCommons;
{
StLock<Mutex> _(*this);
mResetLevel++;
secinfo("token", "%p reset (level=%d, propagating to %ld common(s)",
this, mResetLevel, mCommons.size());
tmpCommons = mCommons;
}
for (CommonSet::const_iterator it = tmpCommons.begin(); it != tmpCommons.end();)
RefPointer<TokenDbCommon>(*it++)->resetAcls();
}
void Token::addCommon(TokenDbCommon &dbc)
{
secinfo("token", "%p addCommon TokenDbCommon %p", this, &dbc);
mCommons.insert(&dbc);
}
void Token::removeCommon(TokenDbCommon &dbc)
{
secinfo("token", "%p removeCommon TokenDbCommon %p", this, &dbc);
if (mCommons.find(&dbc) != mCommons.end())
mCommons.erase(&dbc);
}
void Token::insert(::Reader &slot, RefPointer<TokenDaemon> tokend)
{
try {
Server::active().longTermActivity();
referent(slot);
mState = slot.pcscState();
if (tokend == NULL) {
if (!(tokend = chooseTokend())) {
secinfo("token", "%p no token daemons available - faulting this card", this);
fault(false); }
}
StLock<Mutex> _(*this);
Syslog::debug("token inserted into reader %s", slot.name().c_str());
secinfo("token", "%p begin insertion into slot %p (reader %s)",
this, &slot, slot.name().c_str());
tokend->faultRelay(this);
if (tokend->hasTokenUid()) {
secinfo("token", "%p using %s (score=%d, uid=\"%s\")",
this, tokend->bundlePath().c_str(), tokend->score(), tokend->tokenUid().c_str());
mCache = new TokenCache::Token(reader().cache,
tokend->bundleIdentifier() + ":" + tokend->tokenUid());
} else {
secinfo("token", "%p using %s (score=%d, temporary)",
this, tokend->bundlePath().c_str(), tokend->score());
mCache = new TokenCache::Token(reader().cache);
}
secinfo("token", "%p token cache at %s", this, mCache->root().c_str());
mGuid = gGuidAppleSdCSPDL;
mSubservice = mCache->subservice();
char mdsDirectory[PATH_MAX];
char printName[PATH_MAX];
tokend->establish(mGuid, mSubservice,
(mCache->type() != TokenCache::Token::existing ? kSecTokendEstablishNewCache : 0) | kSecTokendEstablishMakeMDS,
mCache->cachePath().c_str(), mCache->workPath().c_str(),
mdsDirectory, printName);
if (mCache->type() == TokenCache::Token::existing) {
mPrintName = mCache->printName();
if (mPrintName.empty())
mPrintName = printName;
} else
mPrintName = printName;
if (mPrintName.empty()) {
snprintf(printName, sizeof(printName), "smart card #%d", mSubservice);
mPrintName = printName;
}
if (mCache->type() != TokenCache::Token::existing)
mCache->printName(mPrintName);
secinfo("token", "%p installing MDS from %s(%s)", this,
tokend->bundlePath().c_str(),
mdsDirectory[0] ? mdsDirectory : "ALL");
string holdGuid = mGuid.toString(); string holdTokenUid;
if (tokend->hasTokenUid())
holdTokenUid = tokend->tokenUid();
string holdPrintName = this->printName();
MDS_InstallDefaults mdsDefaults = {
holdGuid.c_str(),
mSubservice,
holdTokenUid.c_str(),
holdPrintName.c_str()
};
mds().install(&mdsDefaults,
tokend->bundlePath().c_str(),
mdsDirectory[0] ? mdsDirectory : NULL,
NULL);
{
StLock<Mutex> _(mSSIDLock);
assert(mSubservices.find(mSubservice) == mSubservices.end());
mSubservices.insert(make_pair(mSubservice, this));
}
mTokend = tokend;
notify(kNotificationCDSAInsertion);
Syslog::notice("reader %s inserted token \"%s\" (%s) subservice %d using driver %s",
slot.name().c_str(), mPrintName.c_str(),
mTokend->hasTokenUid() ? mTokend->tokenUid().c_str() : "NO UID",
mSubservice, mTokend->bundleIdentifier().c_str());
secinfo("token", "%p inserted as %s:%d", this, mGuid.toString().c_str(), mSubservice);
} catch (const CommonError &err) {
Syslog::notice("token in reader %s cannot be used (error %d)", slot.name().c_str(), err.osStatus());
secinfo("token", "exception during insertion processing");
fault(false);
} catch (...) {
Syslog::notice("token in reader %s cannot be used", slot.name().c_str());
secinfo("token", "exception during insertion processing");
fault(false);
}
}
void Token::remove()
{
StLock<Mutex> _(*this);
Syslog::notice("reader %s removed token \"%s\" (%s) subservice %d",
reader().name().c_str(), mPrintName.c_str(),
mTokend
? (mTokend->hasTokenUid() ? mTokend->tokenUid().c_str() : "NO UID")
: "NO tokend",
mSubservice);
secinfo("token", "%p begin removal from slot %p (reader %s)",
this, &reader(), reader().name().c_str());
if (mTokend)
mTokend->faultRelay(NULL); mds().uninstall(mGuid.toString().c_str(), mSubservice);
secinfo("token", "%p mds uninstall complete", this);
this->kill();
secinfo("token", "%p kill complete", this);
notify(kNotificationCDSARemoval);
secinfo("token", "%p removal complete", this);
}
void Token::fault(bool async)
{
StLock<Mutex> _(*this);
if (!mFaulted) { secinfo("token", "%p %s FAULT", this, async ? "ASYNCHRONOUS" : "SYNCHRONOUS");
mFaulted = true;
notify(kNotificationCDSAFailure);
}
if (!async)
CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED);
}
void Token::relayFault(bool async)
{
secinfo("token", "%p fault relayed from tokend", this);
this->fault(async);
}
void Token::kill()
{
{
StLock<Mutex> _(*this);
if (mTokend)
{
mTokend = NULL; StLock<Mutex> _(mSSIDLock);
SSIDMap::iterator it = mSubservices.find(mSubservice);
assert(it != mSubservices.end() && it->second == this);
if (it != mSubservices.end() && it->second == this)
mSubservices.erase(it);
}
}
resetAcls(); PerGlobal::kill();
}
void Token::notify(NotificationEvent event)
{
NameValueDictionary nvd;
CssmSubserviceUid ssuid(mGuid, NULL, h2n (mSubservice),
h2n(CSSM_SERVICE_DL | CSSM_SERVICE_CSP));
nvd.Insert(new NameValuePair(SSUID_KEY, CssmData::wrap(ssuid)));
CssmData data;
nvd.Export(data);
Listener::notify(kNotificationDomainCDSA, event, data);
free (data.data());
}
RefPointer<TokenDaemon> Token::chooseTokend()
{
CodeRepository<Bundle> candidates("Security/tokend", ".tokend", "TOKENDAEMONPATH", false);
candidates.update();
string chosenIdentifier;
set<string> candidateIdentifiers;
RefPointer<TokenDaemon> leader;
for (CodeRepository<Bundle>::const_iterator it = candidates.begin();
it != candidates.end(); it++) {
RefPointer<Bundle> candidate = *it;
try {
if (CFTypeRef type = (*it)->infoPlistItem("TokendType"))
if (CFEqual(type, CFSTR("software")))
continue;
RefPointer<TokenDaemon> tokend = new TokenDaemon(candidate,
reader().name(), reader().pcscState(), reader().cache);
candidateIdentifiers.insert(tokend->bundleIdentifier());
if (tokend->state() == ServerChild::dead) continue;
if (!tokend->probe()) continue;
if (!leader || tokend->score() > leader->score()) {
leader = tokend; chosenIdentifier = leader->bundleIdentifier();
}
} catch (...) {
secinfo("token", "exception setting up %s (moving on)", candidate->canonicalPath().c_str());
}
}
string identifiers;
for (set<string>::const_iterator i = candidateIdentifiers.begin(), e = candidateIdentifiers.end(); i != e; ++i) {
if (i != candidateIdentifiers.begin())
identifiers.append(";");
identifiers.append(*i);
}
return leader;
}
Token::Access::Access(Token &myToken)
: token(myToken)
{
mTokend = &token.tokend(); }
Token::Access::~Access()
{
}
#if defined(DEBUGDUMP)
void Token::dumpNode()
{
PerGlobal::dumpNode();
Debug::dump(" %s[%d] tokend=%p",
mGuid.toString().c_str(), mSubservice, mTokend.get());
}
#endif //DEBUGDUMP