/* * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved. * * The contents of this file constitute Original Code as defined in and are * subject to the Apple Public Source License Version 1.2 (the 'License'). * You may not use this file except in compliance with the License. Please obtain * a copy of the License at http://www.apple.com/publicsource and read it before * using this file. * * This Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the * specific language governing rights and limitations under the License. */ /* * AuthorizationEngine.cpp * Authorization * * Created by Michael Brouwer on Thu Oct 12 2000. * Copyright (c) 2000 Apple Computer Inc. All rights reserved. * */ #include "AuthorizationEngine.h" #include #include "server.h" #include "authority.h" #include #include #include #include #include "session.h" #include #include #include #include #include #include #include #include #include #include // checkpw() that uses provided struct passwd extern "C" { int checkpw_internal( const char* userName, const char* password, const struct passwd *pw ); } namespace Authorization { // // Errors to be thrown // Error::Error(int err) : error(err) { } const char *Error::what() const throw() { return "Authorization error"; } CSSM_RETURN Error::cssmError() const throw() { return error; } // @@@ eventually... OSStatus Error::osStatus() const throw() { return error; } void Error::throwMe(int err) { throw Error(err); } // // CredentialImpl class // // only for testing whether this credential is usable CredentialImpl::CredentialImpl(const string &username, const uid_t uid, const gid_t gid, bool shared) : mUsername(username), mShared(shared), mUid(uid), mGid(gid), mCreationTime(CFAbsoluteTimeGetCurrent()), mValid(true) { } // credential with validity based on username/password combination. CredentialImpl::CredentialImpl(const string &username, const string &password, bool shared) : mShared(shared), mCreationTime(CFAbsoluteTimeGetCurrent()), mValid(false) { // try short name first const char *user = username.c_str(); struct passwd *pw = getpwnam(user); do { if (!pw) { debug("autheval", "user %s not found, creating invalid credential", user); break; } const char *passwd = password.c_str(); int checkpw_status = checkpw_internal(user, passwd, pw); if (checkpw_status != CHECKPW_SUCCESS) { debug("autheval", "checkpw() for user %s failed with error %d, creating invalid credential", user, checkpw_status); break; } debug("autheval", "checkpw() for user %s succeeded, creating%s credential", user, mShared ? " shared" : ""); mUsername = string ( pw->pw_name ); mUid = pw->pw_uid; mGid = pw->pw_gid; mValid = true; endpwent(); } while (0); } CredentialImpl::~CredentialImpl() { } bool CredentialImpl::operator < (const CredentialImpl &other) const { if (!mShared && other.mShared) return true; if (!other.mShared && mShared) return false; return mUsername < other.mUsername; } // Returns true if this CredentialImpl should be shared. bool CredentialImpl::isShared() const { return mShared; } // Merge with other void CredentialImpl::merge(const CredentialImpl &other) { assert(mUsername == other.mUsername); if (other.mValid && (!mValid || mCreationTime < other.mCreationTime)) { mCreationTime = other.mCreationTime; mUid = other.mUid; mGid = other.mGid; mValid = true; } } // The time at which this credential was obtained. CFAbsoluteTime CredentialImpl::creationTime() const { return mCreationTime; } // Return true iff this credential is valid. bool CredentialImpl::isValid() const { return mValid; } void CredentialImpl::invalidate() { mValid = false; } // // Credential class // Credential::Credential() : RefPointer(NULL) { } Credential::Credential(CredentialImpl *impl) : RefPointer(impl) { } Credential::Credential(const string &username, const uid_t uid, const gid_t gid, bool shared) : RefPointer(new CredentialImpl(username, uid, gid, shared)) { } Credential::Credential(const string &username, const string &password, bool shared) : RefPointer(new CredentialImpl(username, password, shared)) { } Credential::~Credential() { } bool Credential::operator < (const Credential &other) const { if (!*this) return other; if (!other) return false; return (**this) < (*other); } // // Rule class // CFStringRef Rule::kUserInGroupID = CFSTR("group"); CFStringRef Rule::kTimeoutID = CFSTR("timeout"); CFStringRef Rule::kSharedID = CFSTR("shared"); CFStringRef Rule::kAllowRootID = CFSTR("allow-root"); CFStringRef Rule::kDenyID = CFSTR("deny"); CFStringRef Rule::kAllowID = CFSTR("allow"); CFStringRef Rule::kEvalMechID = CFSTR("eval"); Rule::Rule() : mType(kUserInGroup), mGroupName("admin"), mMaxCredentialAge(300.0), mShared(true), mAllowRoot(false) { // @@@ Default rule is shared admin group with 5 minute timeout } Rule::Rule(CFTypeRef cfRule) { // @@@ This code is ugly. Serves me right for using CF. if (CFGetTypeID(cfRule) == CFStringGetTypeID()) { CFStringRef tag = reinterpret_cast(cfRule); if (CFEqual(kAllowID, tag)) { debug("authrule", "rule always allow"); mType = kAllow; } else if (CFEqual(kDenyID, tag)) { debug("authrule", "rule always deny"); mType = kDeny; } else Error::throwMe(); } else if (CFGetTypeID(cfRule) == CFDictionaryGetTypeID()) { CFDictionaryRef dict = reinterpret_cast(cfRule); CFTypeRef groupTag = CFDictionaryGetValue(dict, kUserInGroupID); // Probably a user in group rule if (groupTag) { if (CFGetTypeID(groupTag) != CFStringGetTypeID()) Error::throwMe(); mType = kUserInGroup; CFStringRef group = reinterpret_cast(groupTag); char buffer[512]; const char *ptr = CFStringGetCStringPtr(group, kCFStringEncodingUTF8); if (ptr == NULL) { if (CFStringGetCString(group, buffer, 512, kCFStringEncodingUTF8)) ptr = buffer; else Error::throwMe(); } mGroupName = string(ptr); mMaxCredentialAge = DBL_MAX; CFTypeRef timeoutTag = CFDictionaryGetValue(dict, kTimeoutID); if (timeoutTag) { if (CFGetTypeID(timeoutTag) != CFNumberGetTypeID()) Error::throwMe(); CFNumberGetValue(reinterpret_cast(timeoutTag), kCFNumberDoubleType, &mMaxCredentialAge); } CFTypeRef sharedTag = CFDictionaryGetValue(dict, kSharedID); mShared = false; if (sharedTag) { if (CFGetTypeID(sharedTag) != CFBooleanGetTypeID()) Error::throwMe(); mShared = CFBooleanGetValue(reinterpret_cast(sharedTag)); } CFTypeRef allowRootTag = CFDictionaryGetValue(dict, kAllowRootID); mAllowRoot = false; if (allowRootTag) { if (CFGetTypeID(allowRootTag) != CFBooleanGetTypeID()) Error::throwMe(); mAllowRoot = CFBooleanGetValue(reinterpret_cast(allowRootTag)); } debug("authrule", "rule user in group \"%s\" timeout %g%s%s", mGroupName.c_str(), mMaxCredentialAge, mShared ? " shared" : "", mAllowRoot ? " allow-root" : ""); } else { CFTypeRef mechTag = CFDictionaryGetValue(dict, kEvalMechID); if (mechTag) { if (CFGetTypeID(mechTag) != CFStringGetTypeID()) Error::throwMe(); mType = kEvalMech; CFStringRef eval = reinterpret_cast(mechTag); char buffer[512]; const char *ptr = CFStringGetCStringPtr(eval, kCFStringEncodingUTF8); if (ptr == NULL) { if (CFStringGetCString(eval, buffer, 512, kCFStringEncodingUTF8)) ptr = buffer; else Error::throwMe(); } mEvalDef = string(ptr); } else Error::throwMe(); } } } Rule::Rule(const Rule &other) : mType(other.mType), mGroupName(other.mGroupName), mMaxCredentialAge(other.mMaxCredentialAge), mShared(other.mShared), mAllowRoot(other.mAllowRoot), mEvalDef(other.mEvalDef) { } Rule & Rule::operator = (const Rule &other) { mType = other.mType; mGroupName = other.mGroupName; mMaxCredentialAge = other.mMaxCredentialAge; mShared = other.mShared; mAllowRoot = other.mAllowRoot; mEvalDef = other.mEvalDef; return *this; } Rule::~Rule() { } OSStatus Rule::evaluateMechanism(const AuthorizationEnvironment *environment, AuthorizationToken &auth, CredentialSet &outCredentials) { assert(mType == kEvalMech); if (mEvalDef.length() == 0) // no definition return kAuthorizationResultAllow; // mechanisms are split by commas vector mechanismNames; { string::size_type cursor = 0, comma = 0; string token = ""; while (cursor < mEvalDef.length()) { comma = mEvalDef.find(',', cursor); if (comma == string::npos) comma = mEvalDef.length(); token = mEvalDef.substr(cursor, comma - cursor); // skip empty tokens if (token.length() > 0) mechanismNames.push_back(token); cursor = comma + 1; } } // @@@ configuration does not support arguments const AuthorizationValueVector arguments = { 0, NULL }; MutableAuthItemSet *context = NULL; AuthItemSet *hints = NULL; AuthorizationItemSet *outHints = NULL, *outContext = NULL; bool userInteraction = true; CssmAllocator& alloc = CssmAllocator::standard(); AuthorizationResult result = kAuthorizationResultAllow; vector::iterator currentMechanism = mechanismNames.begin(); while ( (result == kAuthorizationResultAllow) && (currentMechanism != mechanismNames.end()) ) // iterate mechanisms { AuthorizationItemSet *inHints, *inContext; // release after invocation, ignored for first pass if (outContext) { inContext = outContext; debug("SSevalMech", "set up context %p as input", inContext); delete context; context = new MutableAuthItemSet(inContext); } else { inContext = &auth.infoSet(); // returns deep copy debug("SSevalMech", "set up stored context %p as input", inContext); delete context; context = new MutableAuthItemSet(inContext); } if (outHints) { inHints = outHints; debug("SSevalMech", "set up hints %p as input", inHints); delete hints; hints = new AuthItemSet(outHints); } else { inHints = NULL; debug("SSevalMech", "set up environment hints %p as input", environment); delete hints; hints = new AuthItemSet(environment); } string::size_type extPlugin = currentMechanism->find(':'); if (extPlugin != string::npos) { // no whitespace removal string pluginIn(currentMechanism->substr(0, extPlugin)); string mechanismIn(currentMechanism->substr(extPlugin + 1)); debug("SSevalMech", "external mech %s:%s", pluginIn.c_str(), mechanismIn.c_str()); bool mechExecOk = false; // successfully ran a mechanism try { Process &cltProc = Server::active().connection().process; // Authorization preserves creator's UID in setuid processes uid_t cltUid = (cltProc.uid() != 0) ? cltProc.uid() : auth.creatorUid(); debug("SSevalMech", "Mechanism invocation by process %d (UID %d)", cltProc.pid(), cltUid); QueryInvokeMechanism client(cltUid, auth); mechExecOk = client(pluginIn, mechanismIn, &arguments, *hints, *context, &result, outHints, outContext); debug("SSevalMech", "new context %p, new hints %p", outContext, outHints); } catch (...) { debug("SSevalMech", "exception from mech eval or client death"); // various server problems, but only if it really failed if (mechExecOk != true) result = kAuthorizationResultUndefined; } debug("SSevalMech", "evaluate(plugin: %s, mechanism: %s) %s, result: %lu.", pluginIn.c_str(), mechanismIn.c_str(), (mechExecOk == true) ? "succeeded" : "failed", result); debug("SSevalMech", "mech eval okay"); // Things worked and there is new context, so get rid of old if (mechExecOk) { if (inContext) { debug("SSevalMech", "release input context %p", inContext); alloc.free(inContext); } if (inHints) { debug("SSevalMech", "release input hints %p", inHints); alloc.free(inHints); } } else { // reset previous context and hints debug("SSevalMech", "resetting previous input context %p and hints %p", inContext, inHints); outContext = inContext; outHints = inHints; } } else { // internal mechanisms - no glue if (*currentMechanism == "authinternal") { debug("SSevalMech", "evaluate authinternal"); result = kAuthorizationResultDeny; do { MutableAuthItemSet::iterator found = find_if(context->begin(), context->end(), FindAuthItemByRightName(kAuthorizationEnvironmentUsername) ); if (found == context->end()) break; string username(static_cast(found->argument()), found->argumentLength()); debug("SSevalMech", "found username"); found = find_if(context->begin(), context->end(), FindAuthItemByRightName(kAuthorizationEnvironmentPassword) ); if (found == context->end()) break; string password(static_cast(found->argument()), found->argumentLength()); debug("SSevalMech", "found password"); Credential newCredential(username, password, true); // create a new shared credential if (newCredential->isValid()) { outCredentials.clear(); // only keep last one debug("SSevalMech", "inserting new credential"); outCredentials.insert(newCredential); result = kAuthorizationResultAllow; } else result = kAuthorizationResultDeny; } while (0); } else if (*currentMechanism == "push_hints_to_context") { debug("SSevalMech", "evaluate push_hints_to_context"); userInteraction = false; // we can't talk to the user result = kAuthorizationResultAllow; // snarfcredential doesn't block evaluation, ever, it may restart // clean up current context if (inContext) { debug("SSevalMech", "release input context %p", inContext); alloc.free(inContext); } // create out context from input hints, no merge // @@@ global copy template not being invoked... outContext = Copier(*hints).keep(); } else if (*currentMechanism == "switch_to_user") { try { Process &cltProc = Server::active().connection().process; // Authorization preserves creator's UID in setuid processes uid_t cltUid = (cltProc.uid() != 0) ? cltProc.uid() : auth.creatorUid(); debug("SSevalMech", "terminating agent at request of process %d (UID %d)\n", cltProc.pid(), cltUid); QueryTerminateAgent client(cltUid, auth); client(); } catch (...) { // Not our agent } result = kAuthorizationResultAllow; } } // we own outHints and outContext switch(result) { case kAuthorizationResultAllow: debug("SSevalMech", "result allow"); currentMechanism++; break; case kAuthorizationResultDeny: debug("SSevalMech", "result deny"); if (inContext) { debug("SSevalMech", "abort eval, release input context %p", inContext); alloc.free(inContext); } if (inHints) { debug("SSevalMech", "abort eval, release input hints %p", inHints); alloc.free(inHints); } outContext = outHints = NULL; // making sure things get reset if (userInteraction) { currentMechanism = mechanismNames.begin(); result = kAuthorizationResultAllow; // stay in loop } break; case kAuthorizationResultUndefined: debug("SSevalMech", "result undefined"); break; // abort evaluation case kAuthorizationResultUserCanceled: debug("SSevalMech", "result canceled"); break; // stop evaluation, return some sideband default: break; // abort evaluation } } // End of evaluation, if last step produced meaningful data, incorporate if ((result == kAuthorizationResultAllow) || (result == kAuthorizationResultUserCanceled)) // @@@ can only pass back sideband through context { debug("SSevalMech", "make new context %p available", outContext); auth.setInfoSet(*outContext); outContext = NULL; } // clean up last outContext and outHints, if any if (outContext) { debug("SSevalMech", "release output context %p", outContext); alloc.free(outContext); } if (outHints) { debug("SSevalMech", "release output hints %p", outHints); alloc.free(outHints); } // deny on user cancel switch(result) { case kAuthorizationResultUndefined: return errAuthorizationDenied; case kAuthorizationResultDeny: return errAuthorizationDenied; default: return errAuthorizationSuccess; // @@@ cancel should return cancelled } } OSStatus Rule::evaluate(const Right &inRight, const AuthorizationEnvironment *environment, AuthorizationFlags flags, CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials, AuthorizationToken &auth) { switch (mType) { case kAllow: debug("autheval", "rule is always allow"); return errAuthorizationSuccess; case kDeny: debug("autheval", "rule is always deny"); return errAuthorizationDenied; case kUserInGroup: debug("autheval", "rule is user in group"); break; case kEvalMech: debug("autheval", "rule evalutes mechanisms"); return evaluateMechanism(environment, auth, credentials); default: Error::throwMe(); } // If we got here, this is a kUserInGroup type rule, let's start looking for a // credential that is satisfactory // Zeroth -- Here is an extra special saucy ugly hack to allow authorizations // created by a proccess running as root to automatically get a right. if (mAllowRoot && auth.creatorUid() == 0) { debug("autheval", "creator of authorization has uid == 0 granting right %s", inRight.rightName()); return errAuthorizationSuccess; } // First -- go though the credentials we either already used or obtained during this authorize operation. for (CredentialSet::const_iterator it = credentials.begin(); it != credentials.end(); ++it) { OSStatus status = evaluate(inRight, environment, now, *it, true); if (status != errAuthorizationDenied) { // add credential to authinfo auth.setCredentialInfo(*it); return status; } } // Second -- go though the credentials passed in to this authorize operation by the state management layer. if (inCredentials) { for (CredentialSet::const_iterator it = inCredentials->begin(); it != inCredentials->end(); ++it) { OSStatus status = evaluate(inRight, environment, now, *it, false); if (status == errAuthorizationSuccess) { // Add the credential we used to the output set. // @@@ Deal with potential credential merges. credentials.insert(*it); // add credential to authinfo auth.setCredentialInfo(*it); return status; } else if (status != errAuthorizationDenied) return status; } } // Finally -- We didn't find the credential in our passed in credential lists. Obtain a new credential if // our flags let us do so. if (!(flags & kAuthorizationFlagExtendRights)) return errAuthorizationDenied; if (!(flags & kAuthorizationFlagInteractionAllowed)) return errAuthorizationInteractionNotAllowed; Process &cltProc = Server::active().connection().process; // Authorization preserves creator's UID in setuid processes uid_t cltUid = (cltProc.uid() != 0) ? cltProc.uid() : auth.creatorUid(); IFDEBUG(debug("autheval", "Auth query from process %d (UID %d)", cltProc.pid(), cltUid)); QueryAuthorizeByGroup query(cltUid, auth); string usernamehint; // username hint is taken from the user who created the authorization, unless it's clearly ineligible if (uid_t uid = auth.creatorUid()) { struct passwd *pw = getpwuid(uid); if (pw != NULL) { // avoid hinting a locked account (ie. root) if ( (pw->pw_passwd == NULL) || strcmp(pw->pw_passwd, "*") ) { // Check if username will authorize the request and set username to // be used as a hint to the user if so if (evaluate(inRight, environment, now, Credential(pw->pw_name, pw->pw_uid, pw->pw_gid, mShared), true) == errAuthorizationSuccess) { // user long name as hint usernamehint = string( pw->pw_gecos ); #if 0 // minus other gecos crud size_t comma = usernamehint.find(','); if (comma) usernamehint = usernamehint.substr(0, comma); // or fallback to short username #endif if (usernamehint.size() == 0) usernamehint = string( pw->pw_name ); } //fi } //fi endpwent(); } } Credential newCredential; // @@@ Keep the default reason the same, so the agent only gets userNotInGroup or invalidPassphrase SecurityAgent::Reason reason = SecurityAgent::userNotInGroup; // @@@ Hardcoded 3 tries to avoid infinite loops. for (int tryCount = 0; tryCount < 3; ++tryCount) { // Obtain a new credential. Anything but success is considered an error. OSStatus status = obtainCredential(query, inRight, environment, usernamehint.c_str(), newCredential, reason); if (status) return status; // Now we have successfully obtained a credential we need to make sure it authorizes the requested right if (!newCredential->isValid()) reason = SecurityAgent::invalidPassphrase; else { status = evaluate(inRight, environment, now, newCredential, true); if (status == errAuthorizationSuccess) { // Add the new credential we obtained to the output set. // @@@ Deal with potential credential merges. credentials.insert(newCredential); query.done(); // add credential to authinfo auth.setCredentialInfo(newCredential); return errAuthorizationSuccess; } else if (status != errAuthorizationDenied) return status; reason = SecurityAgent::userNotInGroup; } } query.cancel(SecurityAgent::tooManyTries); return errAuthorizationDenied; } // Return errAuthorizationSuccess if this rule allows access based on the specified credential, // return errAuthorizationDenied otherwise. OSStatus Rule::evaluate(const Right &inRight, const AuthorizationEnvironment *environment, CFAbsoluteTime now, const Credential &credential, bool ignoreShared) { assert(mType == kUserInGroup); // Get the username from the credential const char *user = credential->username().c_str(); // If the credential is not valid or it's age is more than the allowed maximum age // for a credential, deny. if (!credential->isValid()) { debug("autheval", "credential for user %s is invalid, denying right %s", user, inRight.rightName()); return errAuthorizationDenied; } if (now - credential->creationTime() > mMaxCredentialAge) { debug("autheval", "credential for user %s has expired, denying right %s", user, inRight.rightName()); return errAuthorizationDenied; } if (!ignoreShared && !mShared && credential->isShared()) { debug("autheval", "shared credential for user %s cannot be used, denying right %s", user, inRight.rightName()); return errAuthorizationDenied; } // A root (uid == 0) user can do anything if (credential->uid() == 0) { debug("autheval", "user %s has uid 0, granting right %s", user, inRight.rightName()); return errAuthorizationSuccess; } const char *groupname = mGroupName.c_str(); struct group *gr = getgrnam(groupname); if (!gr) return errAuthorizationDenied; // Is this the default group of this user? // PR-2875126 declares gr_gid int, as opposed to advertised (getgrent(3)) gid_t // When this is fixed this warning should go away. if (credential->gid() == gr->gr_gid) { debug("autheval", "user %s has group %s(%d) as default group, granting right %s", user, groupname, gr->gr_gid, inRight.rightName()); endgrent(); return errAuthorizationSuccess; } for (char **group = gr->gr_mem; *group; ++group) { if (!strcmp(*group, user)) { debug("autheval", "user %s is a member of group %s, granting right %s", user, groupname, inRight.rightName()); endgrent(); return errAuthorizationSuccess; } } debug("autheval", "user %s is not a member of group %s, denying right %s", user, groupname, inRight.rightName()); endgrent(); return errAuthorizationDenied; } OSStatus Rule::obtainCredential(QueryAuthorizeByGroup &query, const Right &inRight, const AuthorizationEnvironment *environment, const char *usernameHint, Credential &outCredential, SecurityAgent::Reason reason) { char nameBuffer[SecurityAgent::maxUsernameLength]; char passphraseBuffer[SecurityAgent::maxPassphraseLength]; OSStatus status = errAuthorizationDenied; try { if (query(mGroupName.c_str(), usernameHint, nameBuffer, passphraseBuffer, reason)) status = noErr; } catch (const CssmCommonError &err) { status = err.osStatus(); } catch (...) { status = errAuthorizationInternal; } if (status == CSSM_ERRCODE_USER_CANCELED) { debug("auth", "canceled obtaining credential for user in group %s", mGroupName.c_str()); return errAuthorizationCanceled; } if (status == CSSM_ERRCODE_NO_USER_INTERACTION) { debug("auth", "user interaction not possible obtaining credential for user in group %s", mGroupName.c_str()); return errAuthorizationInteractionNotAllowed; } if (status != noErr) { debug("auth", "failed obtaining credential for user in group %s", mGroupName.c_str()); return status; } debug("auth", "obtained credential for user %s", nameBuffer); string username(nameBuffer); string password(passphraseBuffer); outCredential = Credential(username, password, mShared); return errAuthorizationSuccess; } // // Engine class // Engine::Engine(const char *configFile) : mLastChecked(DBL_MIN) { mRulesFileName = new char[strlen(configFile) + 1]; strcpy(mRulesFileName, configFile); memset(&mRulesFileMtimespec, 0, sizeof(mRulesFileMtimespec)); } Engine::~Engine() { delete[] mRulesFileName; } void Engine::updateRules(CFAbsoluteTime now) { StLock _(mLock); if (mRules.empty()) readRules(); else { // Don't do anything if we checked the timestamp less than 5 seconds ago if (mLastChecked > now - 5.0) return; struct stat st; if (stat(mRulesFileName, &st)) { Syslog::error("Stating rules file \"%s\": %s", mRulesFileName, strerror(errno)); /* @@@ No rules file found, use defaults: admin group for everything. */ //UnixError::throwMe(errno); } else { // @@@ Make sure this is the right way to compare 2 struct timespec thingies // Technically we should check st_dev and st_ino as well since if either of those change // we are looking at a different file too. if (memcmp(&st.st_mtimespec, &mRulesFileMtimespec, sizeof(mRulesFileMtimespec))) readRules(); } } mLastChecked = now; } void Engine::readRules() { // Make an entry in the mRules map that matches every right to the default Rule. mRules.clear(); mRules.insert(RuleMap::value_type(string(), Rule())); int fd = open(mRulesFileName, O_RDONLY, 0); if (fd == -1) { Syslog::error("Opening rules file \"%s\": %s", mRulesFileName, strerror(errno)); return; } try { struct stat st; if (fstat(fd, &st)) UnixError::throwMe(errno); mRulesFileMtimespec = st.st_mtimespec; off_t fileSize = st.st_size; CFRef xmlData(CFDataCreateMutable(NULL, fileSize)); CFDataSetLength(xmlData, fileSize); void *buffer = CFDataGetMutableBytePtr(xmlData); size_t bytesRead = read(fd, buffer, fileSize); if (bytesRead != fileSize) { if (bytesRead == static_cast(-1)) { Syslog::error("Reading rules file \"%s\": %s", mRulesFileName, strerror(errno)); return; } Syslog::error("Could only read %ul out of %ul bytes from rules file \"%s\"", bytesRead, fileSize, mRulesFileName); return; } CFStringRef errorString; CFRef newRoot(reinterpret_cast (CFPropertyListCreateFromXMLData(NULL, xmlData, kCFPropertyListImmutable, &errorString))); if (!newRoot) { char buffer[512]; const char *error = CFStringGetCStringPtr(errorString, kCFStringEncodingUTF8); if (error == NULL) { if (CFStringGetCString(errorString, buffer, 512, kCFStringEncodingUTF8)) error = buffer; } Syslog::error("Parsing rules file \"%s\": %s", mRulesFileName, error); return; } if (CFGetTypeID(newRoot) != CFDictionaryGetTypeID()) { Syslog::error("Rules file \"%s\": is not a dictionary", mRulesFileName); return; } parseRules(newRoot); } catch(...) { close(fd); } close(fd); } void Engine::parseRules(CFDictionaryRef rules) { CFDictionaryApplyFunction(rules, parseRuleCallback, this); } void Engine::parseRuleCallback(const void *key, const void *value, void *context) { Engine *engine = reinterpret_cast(context); if (CFGetTypeID(key) != CFStringGetTypeID()) return; CFStringRef right = reinterpret_cast(key); engine->parseRule(right, reinterpret_cast(value)); } void Engine::parseRule(CFStringRef cfRight, CFTypeRef cfRule) { char buffer[512]; const char *ptr = CFStringGetCStringPtr(cfRight, kCFStringEncodingUTF8); if (ptr == NULL) { if (CFStringGetCString(cfRight, buffer, 512, kCFStringEncodingUTF8)) ptr = buffer; } string right(ptr); try { mRules[right] = Rule(cfRule); debug("authrule", "added rule for right \"%s\"", right.c_str()); } catch (...) { Syslog::error("Rules file \"%s\" right \"%s\": rule is invalid", mRulesFileName, ptr); } } /*! @function AuthorizationEngine::getRule Look up the Rule for a given right. @param inRight (input) the right for which we want a rule. @results The Rule for right */ Rule Engine::getRule(const Right &inRight) const { string key(inRight.rightName()); // Lock the rulemap StLock _(mLock); for (;;) { RuleMap::const_iterator it = mRules.find(key); if (it != mRules.end()) { debug("authrule", "right \"%s\" using right expression \"%s\"", inRight.rightName(), key.c_str()); return it->second; } // no default rule assert (key.size()); // any reduction of a combination of two chars is futile if (key.size() > 2) { // find last dot with exception of possible dot at end string::size_type index = key.rfind('.', key.size() - 2); // cut right after found dot, or make it match default rule key = key.substr(0, index == string::npos ? 0 : index + 1); } else key.erase(); } } /*! @function AuthorizationEngine::authorize @@@. @param inRights (input) List of rights being requested for authorization. @param environment (optional/input) Environment containing information to be used during evaluation. @param flags (input) Optional flags @@@ see AuthorizationCreate for a description. @param inCredentials (input) Credentials already held by the caller. @param outCredentials (output/optional) Credentials obtained, used or refreshed during this call to authorize the requested rights. @param outRights (output/optional) Subset of inRights which were actually authorized. @results Returns errAuthorizationSuccess if all rights requested are authorized, or if the kAuthorizationFlagPartialRights flag was specified. Might return other status values like errAuthorizationDenied, errAuthorizationCanceled or errAuthorizationInteractionNotAllowed */ OSStatus Engine::authorize(const RightSet &inRights, const AuthorizationEnvironment *environment, AuthorizationFlags flags, const CredentialSet *inCredentials, CredentialSet *outCredentials, MutableRightSet *outRights, AuthorizationToken &auth) { CredentialSet credentials; MutableRightSet rights; OSStatus status = errAuthorizationSuccess; // Get current time of day. CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); // Update rules from database if needed updateRules(now); // Check if a credential was passed into the environment and we were asked to extend the rights if (environment && (flags & kAuthorizationFlagExtendRights)) { const AuthorizationItem *username = NULL, *password = NULL; bool shared = false; for (UInt32 ix = 0; ix < environment->count; ++ix) { const AuthorizationItem &item = environment->items[ix]; if (!strcmp(item.name, kAuthorizationEnvironmentUsername)) username = &item; if (!strcmp(item.name, kAuthorizationEnvironmentPassword)) password = &item; if (!strcmp(item.name, kAuthorizationEnvironmentShared)) shared = true; } if (username && password) { // Let's create a credential from the passed in username and password. Credential newCredential(string(reinterpret_cast(username->value), username->valueLength), string(reinterpret_cast(password->value), password->valueLength), shared); // If it's valid insert it into the credentials list. Normally this is // only done if it actually authorizes a requested right, but for this // special case (environment) we do it even when no rights are being requested. if (newCredential->isValid()) credentials.insert(newCredential); } } RightSet::const_iterator end = inRights.end(); for (RightSet::const_iterator it = inRights.begin(); it != end; ++it) { // Get the rule for each right we are trying to obtain. OSStatus result = getRule(*it).evaluate(*it, environment, flags, now, inCredentials, credentials, auth); if (result == errAuthorizationSuccess) rights.push_back(*it); else if (result == errAuthorizationDenied || result == errAuthorizationInteractionNotAllowed) { if (!(flags & kAuthorizationFlagPartialRights)) { status = result; break; } } else if (result == errAuthorizationCanceled) { status = result; break; } else { Syslog::error("Engine::authorize: Rule::evaluate returned %ld returning errAuthorizationInternal", result); status = errAuthorizationInternal; break; } } if (outCredentials) outCredentials->swap(credentials); if (outRights) outRights->swap(rights); return status; } } // end namespace Authorization