#include "xdatabase.h"
#include "agentquery.h"
#include "key.h"
#include "server.h"
#include "cfnotifier.h" // legacy
#include "notifications.h"
#include "SecurityAgentClient.h"
#include <Security/acl_any.h> // for default owner ACLs
Mutex Database::commonLock;
Database::CommonMap Database::commons;
Database::Database(const DLDbIdentifier &id, const DBParameters ¶ms, Process &proc,
const AccessCredentials *cred, const AclEntryPrototype *owner)
: SecurityServerAcl(dbAcl, CssmAllocator::standard()), process(proc),
mValidData(false), version(0), mBlob(NULL)
{
mCred = DataWalkers::copy(cred, CssmAllocator::standard());
Signature newSig;
Server::active().random(newSig.bytes);
DbIdentifier ident(id, newSig);
common = new Common(ident);
StLock<Mutex> _(*common);
{ StLock<Mutex> _(commonLock);
assert(commons.find(ident) == commons.end()); commons[ident] = common = new Common(ident);
common->useCount++;
}
common->mParams = params;
common->setupKeys(cred);
if (owner)
cssmSetInitial(*owner);
else
cssmSetInitial(new AnyAclSubject());
mValidData = true;
encode();
process.addDatabase(this);
IFDEBUG(debug("SSdb", "database %s(%p) created, common at %p",
common->dbName(), this, common));
IFDUMPING("SSdb", debugDump("creation complete"));
}
Database::Database(const DLDbIdentifier &id, const DbBlob *blob, Process &proc,
const AccessCredentials *cred)
: SecurityServerAcl(dbAcl, CssmAllocator::standard()), process(proc),
mValidData(false), version(0)
{
assert(blob);
blob->validate(CSSMERR_APPLEDL_INVALID_DATABASE_BLOB);
switch (blob->version) {
#if defined(COMPAT_OSX_10_0)
case blob->version_MacOS_10_0:
break;
#endif
case blob->version_MacOS_10_1:
break;
default:
CssmError::throwMe(CSSMERR_APPLEDL_INCOMPATIBLE_DATABASE_BLOB);
}
mCred = DataWalkers::copy(cred, CssmAllocator::standard());
DbIdentifier ident(id, blob->randomSignature);
StLock<Mutex> mapLock(commonLock);
CommonMap::iterator it = commons.find(ident);
if (it != commons.end()) {
common = it->second; StLock<Mutex> _(*common); common->useCount++;
IFDEBUG(debug("SSdb",
"open database %s(%p) version %lx at known common %p(%d)",
common->dbName(), this, blob->version, common, int(common->useCount)));
} else {
commons[ident] = common = new Common(ident);
common->mParams = blob->params;
common->useCount++;
IFDEBUG(debug("SSdb", "open database %s(%p) version %lx with new common %p",
common->dbName(), this, blob->version, common));
}
process.addDatabase(this);
mBlob = blob->copy();
IFDUMPING("SSdb", debugDump("end of decode"));
}
Database::~Database()
{
IFDEBUG(debug("SSdb", "deleting database %s(%p) common %p (%d refs)",
common->dbName(), this, common, int(common->useCount)));
IFDUMPING("SSdb", debugDump("deleting database instance"));
process.removeDatabase(this);
CssmAllocator::standard().free(mCred);
StLock<Mutex> __(commonLock);
if (--common->useCount == 0 && common->isLocked()) {
IFDUMPING("SSdb", debugDump("discarding common"));
discard(common);
} else if (common->useCount == 0)
IFDUMPING("SSdb", debugDump("retained because it's unlocked"));
}
void Database::authenticate(const AccessCredentials *cred)
{
StLock<Mutex> _(*common);
CssmAllocator::standard().free(mCred);
mCred = DataWalkers::copy(cred, CssmAllocator::standard());
}
DbBlob *Database::encode()
{
StLock<Mutex> _(*common);
if (!validBlob()) {
makeUnlocked();
DbBlob *blob = common->encode(*this);
CssmAllocator::standard().free(mBlob);
mBlob = blob;
version = common->version;
debug("SSdb", "encoded database %p(%s) version %ld", this, dbName(), version);
}
activity();
assert(mBlob);
return mBlob;
}
void Database::changePassphrase(const AccessCredentials *cred)
{
StLock<Mutex> _(*common);
if (isLocked()) {
CssmAutoData passphrase(CssmAllocator::standard(CssmAllocator::sensitive));
if (getBatchPassphrase(cred, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, passphrase)) {
makeUnlocked(passphrase);
} else {
makeUnlocked();
}
} else if (!mValidData) decode(common->passphrase);
Process &cltProc = Server::active().connection().process;
IFDEBUG(debug("SSdb", "New passphrase query from PID %d (UID %d)", cltProc.pid(), cltProc.uid()));
QueryNewPassphrase query(cltProc.uid(), cltProc.session, *common, SecurityAgent::changePassphrase);
query(cred, common->passphrase);
common->version++; IFDEBUG(debug("SSdb", "Database %s(%p) passphrase changed", common->dbName(), this));
KeychainNotifier::passphraseChanged(identifier());
notify(passphraseChangedEvent);
activity();
}
void Database::unlock()
{
StLock<Mutex> _(*common);
makeUnlocked();
}
void Database::makeUnlocked()
{
IFDUMPING("SSdb", debugDump("default procedures unlock"));
if (isLocked()) {
assert(mBlob || (mValidData && common->passphrase));
Process &cltProc = Server::active().connection().process;
IFDEBUG(debug("SSdb", "Unlock query from process %d (UID %d)", cltProc.pid(), cltProc.uid()));
QueryUnlock query(cltProc.uid(), cltProc.session, *this);
query(mCred);
if (isLocked()) CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
activity(); } else if (!mValidData) decode(common->passphrase);
}
void Database::unlock(const CssmData &passphrase)
{
StLock<Mutex> _(*common);
makeUnlocked(passphrase);
}
void Database::makeUnlocked(const CssmData &passphrase)
{
if (isLocked()) {
if (decode(passphrase))
return;
else
CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
} else if (!mValidData)
decode(common->passphrase);
}
bool Database::decode(const CssmData &passphrase)
{
if (mValidData && common->passphrase) { return common->unlock(passphrase);
} else { assert(mBlob);
void *privateAclBlob;
if (common->unlock(mBlob, passphrase, &privateAclBlob)) {
if (!mValidData) {
importBlob(mBlob->publicAclBlob(), privateAclBlob);
mValidData = true;
}
CssmAllocator::standard().free(privateAclBlob);
return true;
}
}
return false;
}
bool Database::validatePassphrase(const CssmData &passphrase) const
{
assert(!isLocked());
return passphrase == common->passphrase;
}
void Database::lock()
{
common->lock();
}
void Database::lockAllDatabases(bool forSleep)
{
StLock<Mutex> _(commonLock); debug("SSdb", "locking all %d known databases", int(commons.size()));
for (CommonMap::iterator it = commons.begin(); it != commons.end(); it++)
it->second->lock(true, forSleep); }
KeyBlob *Database::encodeKey(const CssmKey &key, const CssmData &pubAcl, const CssmData &privAcl)
{
makeUnlocked();
return common->encodeKeyCore(key, pubAcl, privAcl);
}
void Database::decodeKey(KeyBlob *blob, CssmKey &key,
void * &pubAcl, void * &privAcl)
{
makeUnlocked();
common->decodeKeyCore(blob, key, pubAcl, privAcl);
activity();
}
void Database::setParameters(const DBParameters ¶ms)
{
StLock<Mutex> _(*common);
makeUnlocked();
common->mParams = params;
common->version++; activity();
}
void Database::getParameters(DBParameters ¶ms)
{
StLock<Mutex> _(*common);
makeUnlocked();
params = common->mParams;
}
void Database::instantiateAcl()
{
StLock<Mutex> _(*common);
makeUnlocked();
}
void Database::noticeAclChange()
{
StLock<Mutex> _(*common);
version = 0;
}
const Database *Database::relatedDatabase() const
{ return this; }
#if defined(DEBUGDUMP)
void Database::debugDump(const char *msg)
{
assert(common);
const Signature &sig = common->identifier();
uint32 sig4; memcpy(&sig4, sig.bytes, sizeof(sig4));
Debug::dump("** %s(%8.8lx) common=%p(%ld) %s\n",
common->dbName(), sig4, common, common->useCount, msg);
if (isLocked())
Debug::dump(" locked");
else {
Time::Absolute when = common->when();
time_t whenTime = time_t(when);
Debug::dump(" UNLOCKED(%24.24s/%.2g)", ctime(&whenTime),
(when - Time::now()).seconds());
}
Debug::dump(" %s blobversion=%ld/%ld %svalidData",
(common->isValid() ? "validkeys" : "!validkeys"),
version, common->version,
(mValidData ? "" : "!"));
Debug::dump(" Params=(%ld %d)\n",
common->mParams.idleTimeout, common->mParams.lockOnSleep);
}
#endif //DEBUGDUMP
Database::Common::Common(const DbIdentifier &id)
: mIdentifier(id), sequence(0), passphrase(CssmAllocator::standard(CssmAllocator::sensitive)),
useCount(0), version(1),
mIsLocked(true)
{ }
Database::Common::~Common()
{
Server::active().clearTimer(this);
}
void Database::discard(Common *common)
{
debug("SSdb", "discarding dbcommon %p (no users, locked)", common);
commons.erase(common->identifier());
delete common;
}
bool Database::Common::unlock(DbBlob *blob, const CssmData &passphrase,
void **privateAclBlob)
{
try {
decodeCore(blob, passphrase, privateAclBlob);
} catch (...) {
return false;
}
this->passphrase = passphrase;
mParams = blob->params;
mIsLocked = false;
activity();
KeychainNotifier::unlock(identifier());
notify(unlockedEvent);
return true;
}
bool Database::Common::unlock(const CssmData &passphrase)
{
assert(isValid());
if (isLocked()) {
if (passphrase == this->passphrase) {
mIsLocked = false;
KeychainNotifier::unlock(identifier());
notify(unlockedEvent);
return true; } else
return false; } else
return true; }
void Database::Common::lock(bool holdingCommonLock, bool forSleep)
{
StLock<Mutex> locker(*this);
if (!isLocked()) {
if (forSleep && !mParams.lockOnSleep)
return;
mIsLocked = true;
KeychainNotifier::lock(identifier());
notify(lockedEvent);
StLock<Mutex> _(commonLock, false);
if (!holdingCommonLock)
_.lock();
if (useCount == 0) {
locker.unlock(); discard(this);
}
}
}
DbBlob *Database::Common::encode(Database &db)
{
assert(!isLocked());
CssmData pubAcl, privAcl;
db.exportBlob(pubAcl, privAcl);
DbBlob form;
form.randomSignature = identifier();
form.sequence = sequence;
form.params = mParams;
DbBlob *blob = encodeCore(form, passphrase, pubAcl, privAcl);
db.allocator.free(pubAcl);
db.allocator.free(privAcl);
return blob;
}
void Database::Common::notify(Listener::Event event)
{
IFDEBUG(debug("SSdb", "common %s(%p) sending event %ld", dbName(), this, event));
DLDbFlatIdentifier flatId(mIdentifier); CssmAutoData data(CssmAllocator::standard());
copy(&flatId, CssmAllocator::standard(), data.get());
Listener::notify(Listener::databaseNotifications, event, data);
}
void Database::Common::setupKeys(const AccessCredentials *cred)
{
Process &cltProc = Server::active().connection().process;
IFDEBUG(debug("SSdb", "New passphrase request from process %d (UID %d)", cltProc.pid(), cltProc.uid()));
QueryNewPassphrase query(cltProc.uid(), cltProc.session, *this, SecurityAgent::newDatabase);
query(cred, passphrase);
generateNewSecrets();
mIsLocked = false;
activity();
}
void Database::Common::action()
{
IFDEBUG(debug("SSdb", "common %s(%p) locked by timer (%d refs)",
dbName(), this, int(useCount)));
lock();
}
void Database::Common::activity()
{
if (!isLocked())
Server::active().setTimer(this, int(mParams.idleTimeout));
}