#include <pwd.h>
#include <signal.h> // SIGTERM
#include <Security/AuthorizationPriv.h> // kAuthorizationFlagLeastPrivileged
#include "session.h"
#include "connection.h"
#include "database.h"
#include "server.h"
#include <security_utilities/logging.h>
#include <agentquery.h>
using namespace CommonCriteria;
Session::SessionMap Session::mSessions;
Mutex Session::mSessionLock(Mutex::recursive);
const char Session::kUsername[] = "username";
const char Session::kRealname[] = "realname";
Session::Session(const AuditInfo &audit, Server &server)
: mAudit(audit), mSecurityAgent(NULL), mAuthHost(NULL), mKeybagState(0)
{
parent(server);
StLock<Mutex> _(mSessionLock);
assert(!mSessions[audit.sessionId()]);
mSessions[audit.sessionId()] = this;
SECURITYD_SESSION_CREATE(this, this->sessionId(), &mAudit, sizeof(mAudit));
Syslog::notice("Session %d created", this->sessionId());
}
Session::~Session()
{
SECURITYD_SESSION_DESTROY(this, this->sessionId());
Syslog::notice("Session %d destroyed", this->sessionId());
}
Server &Session::server() const
{
return parent<Server>();
}
Session &Session::find(pid_t id, bool create)
{
if (id == callerSecuritySession)
return Server::session();
StLock<Mutex> _(mSessionLock);
SessionMap::iterator it = mSessions.find(id);
if (it != mSessions.end())
return *it->second;
if (!create)
CssmError::throwMe(errSessionInvalidId);
AuditInfo info;
info.get(id);
assert(info.sessionId() == id);
RefPointer<Session> session = new DynamicSession(info);
mSessions.insert(make_pair(id, session));
return *session;
}
void Session::destroy(SessionId id)
{
bool unlocked = false;
RefPointer<Session> session = NULL;
{
StLock<Mutex> _(mSessionLock);
SessionMap::iterator it = mSessions.find(id);
if (it != mSessions.end()) {
session = it->second;
assert(session->sessionId() == id);
mSessions.erase(it);
for (SessionMap::iterator kb_it = mSessions.begin(); kb_it != mSessions.end(); kb_it++) {
RefPointer<Session> kb_session = kb_it->second;
if (kb_session->originatorUid() == session->originatorUid()) {
if (kb_session->keybagGetState(session_keybag_unlocked)) unlocked = true;
}
}
}
}
if (session.get()) {
if (!unlocked) {
service_context_t context = session->get_current_service_context();
service_client_kb_lock(&context);
}
session->kill();
}
}
void Session::kill()
{
StLock<Mutex> _(*this); SECURITYD_SESSION_KILL(this, this->sessionId());
invalidateSessionAuthHosts();
{
StLock<Mutex> _(mCredsLock);
IFDEBUG(if (!mSessionCreds.empty())
secdebug("SSauth", "session %p clearing %d shared credentials",
this, int(mSessionCreds.size())));
for (CredentialSet::iterator it = mSessionCreds.begin(); it != mSessionCreds.end(); it++)
(*it)->invalidate();
}
PerSession::kill();
}
void Session::updateAudit() const
{
CommonCriteria::AuditInfo info;
try {
info.get(mAudit.sessionId());
} catch (...) {
return;
}
mAudit = info;
}
void Session::verifyKeyStorePassphrase(int32_t retries)
{
QueryKeybagPassphrase keybagQuery(*this, retries);
keybagQuery.inferHints(Server::process());
if (keybagQuery.query() != SecurityAgent::noReason) {
CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
}
}
void Session::changeKeyStorePassphrase()
{
service_context_t context = get_current_service_context();
QueryKeybagNewPassphrase keybagQuery(*this);
keybagQuery.inferHints(Server::process());
CssmAutoData pass(Allocator::standard(Allocator::sensitive));
CssmAutoData oldPass(Allocator::standard(Allocator::sensitive));
SecurityAgent::Reason queryReason = keybagQuery.query(oldPass, pass);
if (queryReason == SecurityAgent::noReason) {
service_client_kb_change_secret(&context, oldPass.data(), (int)oldPass.length(), pass.data(), (int)pass.length());
} else {
CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
}
}
void Session::resetKeyStorePassphrase(const CssmData &passphrase)
{
service_context_t context = get_current_service_context();
service_client_kb_reset(&context, passphrase.data(), (int)passphrase.length());
}
service_context_t Session::get_current_service_context()
{
service_context_t context = { sessionId(), originatorUid(), {} }; return context;
}
void Session::keybagClearState(int state)
{
mKeybagState &= ~state;
}
void Session::keybagSetState(int state)
{
mKeybagState |= state;
}
bool Session::keybagGetState(int state)
{
return mKeybagState & state;
}
void Session::invalidateSessionAuthHosts()
{
StLock<Mutex> _(mAuthHostLock);
Syslog::warning("Killing auth hosts");
if (mSecurityAgent) mSecurityAgent->UnixPlusPlus::Child::kill(SIGTERM);
if (mAuthHost) mAuthHost->UnixPlusPlus::Child::kill(SIGTERM);
mSecurityAgent = NULL;
mAuthHost = NULL;
}
void Session::invalidateAuthHosts()
{
StLock<Mutex> _(mSessionLock);
for (SessionMap::const_iterator it = mSessions.begin(); it != mSessions.end(); it++)
it->second->invalidateSessionAuthHosts();
}
void Session::processSystemSleep()
{
SecurityAgentXPCQuery::killAllXPCClients();
StLock<Mutex> _(mSessionLock);
for (SessionMap::const_iterator it = mSessions.begin(); it != mSessions.end(); it++)
it->second->allReferences(&DbCommon::sleepProcessing);
}
void Session::processLockAll()
{
allReferences(&DbCommon::lockProcessing);
}
RootSession::RootSession(uint64_t attributes, Server &server)
: Session(AuditInfo::current(), server)
{
ref(); mAudit.ai_flags |= attributes; }
DynamicSession::DynamicSession(const AuditInfo &audit)
: Session(audit, Server::active())
{
}
OSStatus Session::authCreate(const AuthItemSet &rights,
const AuthItemSet &environment,
AuthorizationFlags flags,
AuthorizationBlob &newHandle,
const audit_token_t &auditToken)
{
CredentialSet resultCreds;
auto_ptr<AuthorizationToken> auth(new AuthorizationToken(*this, resultCreds, auditToken, (flags&kAuthorizationFlagLeastPrivileged)));
SECURITYD_AUTH_CREATE(this, auth.get());
CredentialSet sessionCreds;
{
StLock<Mutex> _(mCredsLock);
sessionCreds = mSessionCreds;
}
AuthItemSet outRights;
OSStatus result = Server::authority().authorize(rights, environment, flags,
&sessionCreds, &resultCreds, outRights, *auth);
newHandle = auth->handle();
if ((flags & kAuthorizationFlagExtendRights) &&
!(flags & kAuthorizationFlagDestroyRights))
{
StLock<Mutex> _(mCredsLock);
mergeCredentials(resultCreds);
auth->mergeCredentials(resultCreds);
}
Server::process().addAuthorization(auth.get());
auth.release();
return result;
}
void Session::authFree(const AuthorizationBlob &authBlob, AuthorizationFlags flags)
{
AuthorizationToken::Deleter deleter(authBlob);
AuthorizationToken &auth = deleter;
Process &process = Server::process();
process.checkAuthorization(&auth);
if (flags & kAuthorizationFlagDestroyRights) {
for (CredentialSet::const_iterator it = auth.begin(); it != auth.end(); it++)
if ((*it)->isShared())
(*it)->invalidate();
}
if (process.removeAuthorization(&auth))
deleter.remove();
}
OSStatus Session::authGetRights(const AuthorizationBlob &authBlob,
const AuthItemSet &rights, const AuthItemSet &environment,
AuthorizationFlags flags,
AuthItemSet &grantedRights)
{
AuthorizationToken &auth = authorization(authBlob);
return auth.session().authGetRights(auth, rights, environment, flags, grantedRights);
}
OSStatus Session::authGetRights(AuthorizationToken &auth,
const AuthItemSet &rights, const AuthItemSet &environment,
AuthorizationFlags flags,
AuthItemSet &grantedRights)
{
CredentialSet resultCreds;
CredentialSet effective;
{
StLock<Mutex> _(mCredsLock);
effective = auth.effectiveCreds();
}
OSStatus result = Server::authority().authorize(rights, environment, flags,
&effective, &resultCreds, grantedRights, auth);
if ((flags & kAuthorizationFlagExtendRights) && !(flags & kAuthorizationFlagDestroyRights))
{
StLock<Mutex> _(mCredsLock);
mergeCredentials(resultCreds);
auth.mergeCredentials(resultCreds);
}
secdebug("SSauth", "Authorization %p copyRights asked for %d got %d",
&auth, int(rights.size()), int(grantedRights.size()));
return result;
}
OSStatus Session::authGetInfo(const AuthorizationBlob &authBlob,
const char *tag,
AuthItemSet &contextInfo)
{
AuthorizationToken &auth = authorization(authBlob);
secdebug("SSauth", "Authorization %p get-info", &auth);
contextInfo = auth.infoSet(tag);
return noErr;
}
OSStatus Session::authExternalize(const AuthorizationBlob &authBlob,
AuthorizationExternalForm &extForm)
{
const AuthorizationToken &auth = authorization(authBlob);
StLock<Mutex> _(*this);
if (auth.mayExternalize(Server::process())) {
memset(&extForm, 0, sizeof(extForm));
AuthorizationExternalBlob &extBlob =
reinterpret_cast<AuthorizationExternalBlob &>(extForm);
extBlob.blob = auth.handle();
extBlob.session = this->sessionId();
secdebug("SSauth", "Authorization %p externalized", &auth);
return noErr;
} else
return errAuthorizationExternalizeNotAllowed;
}
OSStatus Session::authInternalize(const AuthorizationExternalForm &extForm,
AuthorizationBlob &authBlob)
{
const AuthorizationExternalBlob &extBlob =
reinterpret_cast<const AuthorizationExternalBlob &>(extForm);
AuthorizationToken &sourceAuth = AuthorizationToken::find(extBlob.blob);
if (sourceAuth.mayInternalize(Server::process(), true)) {
StLock<Mutex> _(*this);
authBlob = extBlob.blob;
Server::process().addAuthorization(&sourceAuth);
secdebug("SSauth", "Authorization %p internalized", &sourceAuth);
return noErr;
} else
return errAuthorizationInternalizeNotAllowed;
}
void Session::setAttributes(SessionAttributeBits bits)
{
StLock<Mutex> _(*this);
updateAudit();
mAudit.ai_flags = bits;
mAudit.set();
}
void Session::setupAttributes(SessionCreationFlags flags, SessionAttributeBits attrs)
{
MacOSError::throwMe(errSessionAuthorizationDenied);
}
uid_t Session::originatorUid()
{
if (mAudit.uid() == AU_DEFAUDITID) {
StLock<Mutex> _(*this);
updateAudit();
}
return mAudit.uid();
}
OSStatus Session::authorizationdbGet(AuthorizationString inRightName, CFDictionaryRef *rightDict)
{
string rightName(inRightName);
return Server::authority().getRule(rightName, rightDict);
}
OSStatus Session::authorizationdbSet(const AuthorizationBlob &authBlob, AuthorizationString inRightName, CFDictionaryRef rightDict)
{
CredentialSet resultCreds;
AuthorizationToken &auth = authorization(authBlob);
CredentialSet effective;
{
StLock<Mutex> _(mCredsLock);
effective = auth.effectiveCreds();
}
OSStatus result = Server::authority().setRule(inRightName, rightDict, &effective, &resultCreds, auth);
{
StLock<Mutex> _(mCredsLock);
mergeCredentials(resultCreds);
auth.mergeCredentials(resultCreds);
}
secdebug("SSauth", "Authorization %p authorizationdbSet %s (result=%d)",
&authorization(authBlob), inRightName, int32_t(result));
return result;
}
OSStatus Session::authorizationdbRemove(const AuthorizationBlob &authBlob, AuthorizationString inRightName)
{
CredentialSet resultCreds;
AuthorizationToken &auth = authorization(authBlob);
CredentialSet effective;
{
StLock<Mutex> _(mCredsLock);
effective = auth.effectiveCreds();
}
OSStatus result = Server::authority().removeRule(inRightName, &effective, &resultCreds, auth);
{
StLock<Mutex> _(mCredsLock);
mergeCredentials(resultCreds);
auth.mergeCredentials(resultCreds);
}
secdebug("SSauth", "Authorization %p authorizationdbRemove %s (result=%d)",
&authorization(authBlob), inRightName, int32_t(result));
return result;
}
void Session::mergeCredentials(CredentialSet &creds)
{
secdebug("SSsession", "%p merge creds @%p", this, &creds);
CredentialSet updatedCredentials = creds;
for (CredentialSet::const_iterator it = creds.begin(); it != creds.end(); it++)
if ((*it)->isShared() && (*it)->isValid()) {
CredentialSet::iterator old = mSessionCreds.find(*it);
if (old == mSessionCreds.end()) {
mSessionCreds.insert(*it);
} else {
(*old)->merge(**it);
updatedCredentials.erase(*it);
updatedCredentials.insert(*old);
}
}
creds.swap(updatedCredentials);
}
AuthorizationToken &Session::authorization(const AuthorizationBlob &blob)
{
AuthorizationToken &auth = AuthorizationToken::find(blob);
Server::process().checkAuthorization(&auth);
return auth;
}
OSStatus Session::authCheckRight(string &rightName, Connection &connection, bool allowUI)
{
AuthorizationItem rightItem = { rightName.c_str(), 0, NULL, 0 };
AuthorizationItemSet rightItemSet = { 1, &rightItem };
AuthItemSet rightAuthItemSet(&rightItemSet);
AuthItemSet envAuthItemSet(kAuthorizationEmptyEnvironment);
AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagExtendRights;
if (true == allowUI)
flags |= kAuthorizationFlagInteractionAllowed;
AuthorizationBlob dummyHandle;
const audit_token_t *at = connection.auditToken();
return authCreate(rightAuthItemSet, envAuthItemSet, flags, dummyHandle, *at);
}
bool Session::isRightAuthorized(string &rightName, Connection &connection, bool allowUI)
{
bool isAuthorized = false;
try {
OSStatus status = authCheckRight(rightName, connection, allowUI);
if (errAuthorizationSuccess == status)
isAuthorized = true;
}
catch (...) {
}
return isAuthorized;
}
RefPointer<AuthHostInstance>
Session::authhost(const AuthHostType hostType, const bool restart)
{
StLock<Mutex> _(mAuthHostLock);
if (hostType == privilegedAuthHost)
{
if (restart || !mAuthHost || (mAuthHost->state() != Security::UnixPlusPlus::Child::alive))
{
if (mAuthHost)
PerSession::kill(*mAuthHost);
mAuthHost = new AuthHostInstance(*this, hostType);
}
return mAuthHost;
}
else
{
if (restart || !mSecurityAgent || (mSecurityAgent->state() != Security::UnixPlusPlus::Child::alive))
{
if (mSecurityAgent)
PerSession::kill(*mSecurityAgent);
mSecurityAgent = new AuthHostInstance(*this, hostType);
}
return mSecurityAgent;
}
}
void DynamicSession::setUserPrefs(CFDataRef userPrefsDict)
{
if (Server::process().uid() != 0)
MacOSError::throwMe(errSessionAuthorizationDenied);
StLock<Mutex> _(*this);
mSessionAgentPrefs = userPrefsDict;
}
CFDataRef DynamicSession::copyUserPrefs()
{
StLock<Mutex> _(*this);
if (mSessionAgentPrefs)
CFRetain(mSessionAgentPrefs);
return mSessionAgentPrefs;
}
#if defined(DEBUGDUMP)
void Session::dumpNode()
{
PerSession::dumpNode();
Debug::dump(" auid=%d attrs=%#x authhost=%p securityagent=%p",
this->sessionId(), uint32_t(this->attributes()), mAuthHost, mSecurityAgent);
}
#endif //DEBUGDUMP