X-Git-Url: https://git.saurik.com/apple/securityd.git/blobdiff_plain/f7aa9f666a1c7ab343b4ce8f1677ea253c4e126e..4cd1cad0dea00daa03e1b54fdf2797a02373ad5b:/src/AuthorizationRule.cpp diff --git a/src/AuthorizationRule.cpp b/src/AuthorizationRule.cpp index 1fc8290..451618f 100644 --- a/src/AuthorizationRule.cpp +++ b/src/AuthorizationRule.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2004 Apple Computer, Inc. All Rights Reserved. + * Copyright (c) 2003-2004,2008-2009 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * @@ -31,14 +31,15 @@ #include #include #include -#include #include +#include "ccaudit_extensions.h" #include "authority.h" #include "server.h" #include "process.h" #include "agentquery.h" #include "AuthorizationMechEval.h" +#include #include #include #include @@ -48,6 +49,8 @@ extern "C" { #include } +using namespace CommonCriteria::Securityd; + // // Rule class // @@ -61,7 +64,9 @@ CFStringRef RuleImpl::kMechanismsID = CFSTR(kAuthorizationRuleParameterMechanism CFStringRef RuleImpl::kSessionOwnerID = CFSTR(kAuthorizationRuleParameterCredentialSessionOwner); CFStringRef RuleImpl::kKofNID = CFSTR(kAuthorizationRuleParameterKofN); CFStringRef RuleImpl::kPromptID = CFSTR(kAuthorizationRuleParameterDefaultPrompt); +CFStringRef RuleImpl::kButtonID = CFSTR(kAuthorizationRuleParameterDefaultButton); CFStringRef RuleImpl::kTriesID = CFSTR("tries"); // XXX/cs move to AuthorizationTagsPriv.h +CFStringRef RuleImpl::kExtractPasswordID = CFSTR(kAuthorizationRuleParameterExtractPassword); CFStringRef RuleImpl::kRuleClassID = CFSTR(kAuthorizationRuleClass); CFStringRef RuleImpl::kRuleAllowID = CFSTR(kAuthorizationRuleClassAllow); @@ -73,7 +78,7 @@ CFStringRef RuleImpl::kRuleAuthenticateUserID = CFSTR(kAuthorizationRuleParamete string -RuleImpl::Attribute::getString(CFDictionaryRef config, CFStringRef key, bool required = false, char *defaultValue = "") +RuleImpl::Attribute::getString(CFDictionaryRef config, CFStringRef key, bool required = false, const char *defaultValue = "") { CFTypeRef value = CFDictionaryGetValue(config, key); if (value && (CFGetTypeID(value) == CFStringGetTypeID())) @@ -86,7 +91,10 @@ RuleImpl::Attribute::getString(CFDictionaryRef config, CFStringRef key, bool req if (CFStringGetCString(stringValue, buffer, sizeof(buffer), kCFStringEncodingUTF8)) ptr = buffer; else - MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule + { + Syslog::alert("Could not convert CFString to C string"); + MacOSError::throwMe(errAuthorizationInternal); + } } return string(ptr); @@ -95,7 +103,10 @@ RuleImpl::Attribute::getString(CFDictionaryRef config, CFStringRef key, bool req if (!required) return string(defaultValue); else - MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule + { + Syslog::alert("Failed to get rule string"); + MacOSError::throwMe(errAuthorizationInternal); + } } double @@ -112,7 +123,10 @@ RuleImpl::Attribute::getDouble(CFDictionaryRef config, CFStringRef key, bool req if (!required) return defaultValue; else - MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule + { + Syslog::alert("Failed to get rule double value"); + MacOSError::throwMe(errAuthorizationInternal); + } return doubleValue; } @@ -131,7 +145,10 @@ RuleImpl::Attribute::getBool(CFDictionaryRef config, CFStringRef key, bool requi if (!required) return defaultValue; else - MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule + { + Syslog::alert("Failed to get rule bool value"); + MacOSError::throwMe(errAuthorizationInternal); + } return boolValue; } @@ -146,7 +163,8 @@ RuleImpl::Attribute::getVector(CFDictionaryRef config, CFStringRef key, bool req { CFArrayRef evalArray = reinterpret_cast(value); - for (int index=0; index < CFArrayGetCount(evalArray); index++) + CFIndex numItems = CFArrayGetCount(evalArray); + for (CFIndex index=0; index < numItems; index++) { CFTypeRef arrayValue = CFArrayGetValueAtIndex(evalArray, index); if (arrayValue && (CFGetTypeID(arrayValue) == CFStringGetTypeID())) @@ -159,7 +177,10 @@ RuleImpl::Attribute::getVector(CFDictionaryRef config, CFStringRef key, bool req if (CFStringGetCString(stringValue, buffer, sizeof(buffer), kCFStringEncodingUTF8)) ptr = buffer; else - MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule + { + Syslog::alert("Failed to convert CFString to C string for item %u in array", index); + MacOSError::throwMe(errAuthorizationInternal); + } } valueArray.push_back(string(ptr)); } @@ -167,19 +188,22 @@ RuleImpl::Attribute::getVector(CFDictionaryRef config, CFStringRef key, bool req } else if (required) - MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule + { + Syslog::alert("Value for key either not present or not a CFArray"); + MacOSError::throwMe(errAuthorizationInternal); + } return valueArray; } -bool RuleImpl::Attribute::getLocalizedPrompts(CFDictionaryRef config, map &localizedPrompts) +bool RuleImpl::Attribute::getLocalizedText(CFDictionaryRef config, map &localizedPrompts, CFStringRef dictKey, const char *descriptionKey) { CFIndex numberOfPrompts = 0; CFDictionaryRef promptsDict; - if (CFDictionaryContainsKey(config, kPromptID)) + if (CFDictionaryContainsKey(config, dictKey)) { - promptsDict = reinterpret_cast(CFDictionaryGetValue(config, kPromptID)); + promptsDict = reinterpret_cast(CFDictionaryGetValue(config, dictKey)); if (promptsDict && (CFGetTypeID(promptsDict) == CFDictionaryGetTypeID())) numberOfPrompts = CFDictionaryGetCount(promptsDict); } @@ -194,13 +218,15 @@ bool RuleImpl::Attribute::getLocalizedPrompts(CFDictionaryRef config, map(keys[numberOfPrompts]); CFStringRef valueRef = reinterpret_cast(values[numberOfPrompts]); - if (!keyRef || (CFGetTypeID(keyRef) != CFStringGetTypeID())) + if (!keyRef || (CFGetTypeID(keyRef) != CFStringGetTypeID())) { continue; - if (!valueRef || (CFGetTypeID(valueRef) != CFStringGetTypeID())) + } + if (!valueRef || (CFGetTypeID(valueRef) != CFStringGetTypeID())) { continue; + } string key = cfString(keyRef); string value = cfString(valueRef); - localizedPrompts[kAuthorizationRuleParameterDescription+key] = value; + localizedPrompts[descriptionKey + key] = value; } return true; @@ -209,19 +235,22 @@ bool RuleImpl::Attribute::getLocalizedPrompts(CFDictionaryRef config, mapc_str()); + MacOSError::throwMe(errAuthorizationInternal); + } mRuleDef.push_back(Rule(*it, cfRuleDef, cfRules)); } } @@ -314,8 +351,9 @@ RuleImpl::RuleImpl(const string &inRightName, CFDictionaryRef cfRight, CFDiction } else { - secdebug("authrule", "%s : rule class unknown %s.", inRightName.c_str(), classTag.c_str()); - MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule + secdebug("authrule", "%s : rule class '%s' unknown.", inRightName.c_str(), classTag.c_str()); + Syslog::alert("%s : rule class '%s' unknown", inRightName.c_str(), classTag.c_str()); + MacOSError::throwMe(errAuthorizationInternal); } } else @@ -332,11 +370,15 @@ RuleImpl::RuleImpl(const string &inRightName, CFDictionaryRef cfRight, CFDiction if (ruleNameRef) CFRelease(ruleNameRef); if (!cfRuleDef || CFGetTypeID(cfRuleDef) != CFDictionaryGetTypeID()) - MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule + { + Syslog::alert("Rule '%s' for right '%s' does not exist or is not properly formed", ruleName.c_str(), inRightName.c_str()); + MacOSError::throwMe(errAuthorizationInternal); + } mRuleDef.push_back(Rule(ruleName, cfRuleDef, cfRules)); } - Attribute::getLocalizedPrompts(cfRight, mLocalizedPrompts); + Attribute::getLocalizedText(cfRight, mLocalizedPrompts, kPromptID, kAuthorizationRuleParameterDescription); + Attribute::getLocalizedText(cfRight, mLocalizedButtons, kButtonID, kAuthorizationRuleParameterButton); } /* @@ -356,6 +398,10 @@ RuleImpl::setAgentHints(const AuthItemRef &inRight, const Rule &inTopLevelRule, environmentToClient.erase(AuthItemRef(AGENT_HINT_CREATOR_PID)); environmentToClient.insert(AuthItemRef(AGENT_HINT_CREATOR_PID, AuthValueOverlay(sizeof(pid_t), &creatorPid))); + audit_token_t creatorAuditToken = auth.creatorAuditToken().auditToken(); + environmentToClient.erase(AuthItemRef(AGENT_HINT_CREATOR_AUDIT_TOKEN)); + environmentToClient.insert(AuthItemRef(AGENT_HINT_CREATOR_AUDIT_TOKEN, AuthValueOverlay(sizeof(audit_token_t), &creatorAuditToken))); + Process &thisProcess = Server::process(); string bundlePath; if (SecStaticCodeRef clientCode = auth.creatorCode()) @@ -369,9 +415,12 @@ RuleImpl::setAgentHints(const AuthItemRef &inRight, const Rule &inTopLevelRule, environmentToClient.insert(processHints.begin(), processHints.end()); map defaultPrompts = inTopLevelRule->localizedPrompts(); + map defaultButtons = inTopLevelRule->localizedButtons(); if (defaultPrompts.empty()) defaultPrompts = localizedPrompts(); + if (defaultButtons.empty()) + defaultButtons = localizedButtons(); if (!defaultPrompts.empty()) { @@ -383,6 +432,16 @@ RuleImpl::setAgentHints(const AuthItemRef &inRight, const Rule &inTopLevelRule, environmentToClient.insert(AuthItemRef(key.c_str(), AuthValueOverlay(value))); } } + if (!defaultButtons.empty()) + { + map::const_iterator it; + for (it = defaultButtons.begin(); it != defaultButtons.end(); it++) + { + const string &key = it->first; + const string &value = it->second; + environmentToClient.insert(AuthItemRef(key.c_str(), AuthValueOverlay(value))); + } + } // add rulename as a hint string ruleName = name(); @@ -394,12 +453,12 @@ RuleImpl::setAgentHints(const AuthItemRef &inRight, const Rule &inTopLevelRule, // we'll run that and validate the credentials from there. // we fall back on a default configuration from the authenticate rule OSStatus -RuleImpl::evaluateAuthentication(const AuthItemRef &inRight, const Rule &inRule,AuthItemSet &environmentToClient, AuthorizationFlags flags, CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials, AuthorizationToken &auth) const +RuleImpl::evaluateAuthentication(const AuthItemRef &inRight, const Rule &inRule,AuthItemSet &environmentToClient, AuthorizationFlags flags, CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials, AuthorizationToken &auth, SecurityAgent::Reason &reason, bool savePassword) const { OSStatus status = errAuthorizationDenied; Credential hintCredential; - if (errAuthorizationSuccess == evaluateSessionOwner(inRight, inRule, environmentToClient, now, auth, hintCredential)) { + if (errAuthorizationSuccess == evaluateSessionOwner(inRight, inRule, environmentToClient, now, auth, hintCredential, reason)) { if (hintCredential->name().length()) environmentToClient.insert(AuthItemRef(AGENT_HINT_SUGGESTED_USER, AuthValueOverlay(hintCredential->name()))); if (hintCredential->realname().length()) @@ -410,13 +469,51 @@ RuleImpl::evaluateAuthentication(const AuthItemRef &inRight, const Rule &inRule, environmentToClient.insert(AuthItemRef(AGENT_HINT_REQUIRE_USER_IN_GROUP, AuthValueOverlay(mGroupName))); uint32 tries; - SecurityAgent::Reason reason = SecurityAgent::noReason; + reason = SecurityAgent::noReason; Process &cltProc = Server::process(); // Authorization preserves creator's UID in setuid processes + // (which is nice, but cltUid ends up being unused except by the debug + // message -- AgentMechanismEvaluator ignores it) uid_t cltUid = (cltProc.uid() != 0) ? cltProc.uid() : auth.creatorUid(); secdebug("AuthEvalMech", "Mechanism invocation by process %d (UID %d)", cltProc.pid(), cltUid); + // For auditing within AuthorizationMechEval, pass the right name. + size_t rightNameSize = inRight->name() ? strlen(inRight->name()) : 0; + AuthorizationString rightName = inRight->name() ? inRight->name() : ""; + // @@@ AuthValueRef's ctor ought to take a const void * + AuthValueRef rightValue(rightNameSize, const_cast(rightName)); + AuthValueVector authValueVector; + authValueVector.push_back(rightValue); + + RightAuthenticationLogger rightAuthLogger(auth.creatorAuditToken(), AUE_ssauthint); + rightAuthLogger.setRight(rightName); + + // Just succeed for a continuously active session owner. + if (auth.session().originatorUid() == auth.creatorUid() && auth.session().attributes() & AU_SESSION_FLAG_HAS_AUTHENTICATED) { + secdebug("AuthEvalMech", "We are an active session owner."); + aslmsg m = asl_new(ASL_TYPE_MSG); + asl_set(m, "com.apple.message.domain", "com.apple.securityd.UserActivity"); + asl_set(m, "com.apple.message.signature", "userIsActive"); + asl_set(m, "com.apple.message.signature2", rightName); + asl_set(m, "com.apple.message.result", "failure"); + asl_log(NULL, m, ASL_LEVEL_NOTICE, "We are an active session owner."); + asl_free(m); +// Credential rightCredential(rightName, auth.creatorUid(), mShared); +// credentials.erase(rightCredential); credentials.insert(rightCredential); +// return errAuthorizationSuccess; + } + else { + secdebug("AuthEvalMech", "We are not an active session owner."); + aslmsg m = asl_new(ASL_TYPE_MSG); + asl_set(m, "com.apple.message.domain", "com.apple.securityd.UserActivity"); + asl_set(m, "com.apple.message.signature", "userIsNotActive"); + asl_set(m, "com.apple.message.signature2", rightName); + asl_set(m, "com.apple.message.result", "success"); + asl_log(NULL, m, ASL_LEVEL_NOTICE, "We are not an active session owner."); + asl_free(m); + } + AgentMechanismEvaluator eval(cltUid, auth.session(), mEvalDef); for (tries = 0; tries < mTries; tries++) @@ -426,89 +523,93 @@ RuleImpl::evaluateAuthentication(const AuthItemRef &inRight, const Rule &inRule, AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(tries), &tries)); environmentToClient.erase(triesHint); environmentToClient.insert(triesHint); // replace - status = eval.run(AuthValueVector(), environmentToClient, auth); - - if ((status == errAuthorizationSuccess) || - (status == errAuthorizationCanceled)) // @@@ can only pass back sideband through context - { - secdebug("AuthEvalMech", "storing new context for authorization"); - auth.setInfoSet(eval.context()); - } - - // successfully ran mechanisms to obtain credential - if (status == errAuthorizationSuccess) - { - // deny is the default - status = errAuthorizationDenied; - - CredentialSet newCredentials = makeCredentials(auth); - // clear context after extracting credentials - auth.scrubInfoSet(); - - CommonCriteria::AuditRecord auditrec(auth.creatorAuditToken()); - for (CredentialSet::const_iterator it = newCredentials.begin(); it != newCredentials.end(); ++it) - { - const Credential& newCredential = *it; - - // @@@ we log the uid a process was running under when it created the authref, which is misleading in the case of loginwindow - if (newCredential->isValid()) { - Syslog::info("uid %lu succeeded authenticating as user %s (uid %lu) for right %s.", auth.creatorUid(), newCredential->name().c_str(), newCredential->uid(), inRight->name()); - auditrec.submit(AUE_ssauthint, CommonCriteria::errNone, inRight->name()); - } else { - // we can't be sure that the user actually exists so inhibit logging of uid - Syslog::error("uid %lu failed to authenticate as user %s for right %s.", auth.creatorUid(), newCredential->name().c_str(), inRight->name()); - auditrec.submit(AUE_ssauthint, CommonCriteria::errInvalidCredential, inRight->name()); - } - - if (!newCredential->isValid()) - { - reason = SecurityAgent::invalidPassphrase; //invalidPassphrase; - continue; - } - - // verify that this credential authorizes right - status = evaluateUserCredentialForRight(auth, inRight, inRule, environmentToClient, now, newCredential, true); - - if (status == errAuthorizationSuccess) - { - if (auth.operatesAsLeastPrivileged()) { - Credential rightCredential(inRight->name(), mShared); - credentials.erase(rightCredential); credentials.insert(rightCredential); - if (mShared) - credentials.insert(Credential(inRight->name(), false)); - } else { - // whack an equivalent credential, so it gets updated to a later achieved credential which must have been more stringent - credentials.erase(newCredential); credentials.insert(newCredential); - // just got a new credential - if it's shared also add a non-shared one that to stick in the authorizationref local cache - if (mShared) - credentials.insert(Credential(newCredential->uid(), newCredential->name(), newCredential->realname(), false)); - } - - // use valid credential to set context info - // XXX/cs keeping this for now, such that the uid is passed back - auth.setCredentialInfo(newCredential); - secdebug("SSevalMech", "added valid credential for user %s", newCredential->name().c_str()); - status = errAuthorizationSuccess; - break; - } - else - reason = SecurityAgent::userNotInGroup; //unacceptableUser; // userNotInGroup - } + status = eval.run(authValueVector, environmentToClient, auth); + + if ((status == errAuthorizationSuccess) || + (status == errAuthorizationCanceled)) // @@@ can only pass back sideband through context + { + secdebug("AuthEvalMech", "storing new context for authorization"); + auth.setInfoSet(eval.context(), savePassword); + } + + // successfully ran mechanisms to obtain credential + if (status == errAuthorizationSuccess) + { + // deny is the default + status = errAuthorizationDenied; + + CredentialSet newCredentials = makeCredentials(auth); + // clear context after extracting credentials + auth.scrubInfoSet(savePassword); + + for (CredentialSet::const_iterator it = newCredentials.begin(); it != newCredentials.end(); ++it) + { + const Credential& newCredential = *it; + + // @@@ we log the uid a process was running under when it created the authref, which is misleading in the case of loginwindow + if (newCredential->isValid()) { + Syslog::info("UID %u authenticated as user %s (UID %u) for right '%s'", auth.creatorUid(), newCredential->name().c_str(), newCredential->uid(), rightName); + rightAuthLogger.logSuccess(auth.creatorUid(), newCredential->uid(), newCredential->name().c_str()); + } else { + // we can't be sure that the user actually exists so inhibit logging of uid + Syslog::error("UID %u failed to authenticate as user '%s' for right '%s'", auth.creatorUid(), newCredential->name().c_str(), rightName); + rightAuthLogger.logFailure(auth.creatorUid(), newCredential->name().c_str()); + } + + if (!newCredential->isValid()) + { + reason = SecurityAgent::invalidPassphrase; + continue; + } + + // verify that this credential authorizes right + status = evaluateUserCredentialForRight(auth, inRight, inRule, environmentToClient, now, newCredential, true, reason); + + if (status == errAuthorizationSuccess) + { + if (auth.operatesAsLeastPrivileged()) { + Credential rightCredential(rightName, mShared); + credentials.erase(rightCredential); credentials.insert(rightCredential); + if (mShared) + credentials.insert(Credential(rightName, false)); + } + + // whack an equivalent credential, so it gets updated to a later achieved credential which must have been more stringent + credentials.erase(newCredential); credentials.insert(newCredential); + // just got a new credential - if it's shared also add a non-shared one that to stick in the authorizationref local cache + if (mShared) + credentials.insert(Credential(newCredential->uid(), newCredential->name(), newCredential->realname(), false)); + + // use valid credential to set context info + // XXX/cs keeping this for now, such that the uid is passed back + auth.setCredentialInfo(newCredential, savePassword); + secdebug("SSevalMech", "added valid credential for user %s", newCredential->name().c_str()); + // set the sessionHasAuthenticated + if (newCredential->uid() == auth.session().originatorUid()) { + secdebug("AuthEvalMech", "We authenticated as the session owner.\n"); + SessionAttributeBits flags = auth.session().attributes(); + flags |= AU_SESSION_FLAG_HAS_AUTHENTICATED; + auth.session().setAttributes(flags); + } + + status = errAuthorizationSuccess; + break; + } + } if (status == errAuthorizationSuccess) break; } else - if ((status == errAuthorizationCanceled) || - (status == errAuthorizationInternal)) + if ((status == errAuthorizationCanceled) || (status == errAuthorizationInternal)) { - auth.scrubInfoSet(); + auth.scrubInfoSet(false); break; } else // last mechanism is now authentication - fail if (status == errAuthorizationDenied) reason = SecurityAgent::invalidPassphrase; -} + } // If we fell out of the loop because of too many tries, notify user if (tries == mTries) @@ -518,12 +619,11 @@ RuleImpl::evaluateAuthentication(const AuthItemRef &inRight, const Rule &inRule, environmentToClient.erase(retryHint); environmentToClient.insert(retryHint); // replace AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(tries), &tries)); environmentToClient.erase(triesHint); environmentToClient.insert(triesHint); // replace - eval.run(AuthValueVector(), environmentToClient, auth); + eval.run(AuthValueVector(), environmentToClient, auth); // XXX/cs is this still necessary? - auth.scrubInfoSet(); + auth.scrubInfoSet(false); - CommonCriteria::AuditRecord auditrec(auth.creatorAuditToken()); - auditrec.submit(AUE_ssauthorize, CommonCriteria::errTooManyTries, inRight->name()); + rightAuthLogger.logFailure(NULL, CommonCriteria::errTooManyTries); } return status; @@ -565,36 +665,28 @@ RuleImpl::makeCredentials(const AuthorizationToken &auth) const // evaluate whether a good credential of the current session owner would authorize a right OSStatus -RuleImpl::evaluateSessionOwner(const AuthItemRef &inRight, const Rule &inRule, const AuthItemSet &environment, const CFAbsoluteTime now, const AuthorizationToken &auth, Credential &credential) const +RuleImpl::evaluateSessionOwner(const AuthItemRef &inRight, const Rule &inRule, const AuthItemSet &environment, const CFAbsoluteTime now, const AuthorizationToken &auth, Credential &credential, SecurityAgent::Reason &reason) const { // username hint is taken from the user who created the authorization, unless it's clearly ineligible // @@@ we have no access to current requester uid here and the process uid is only taken when the authorization is created // meaning that a process like loginwindow that drops privs later is screwed. - uid_t uid; - Session &session = auth.session(); Credential sessionCredential; - if (session.haveOriginatorUid()) { - // preflight session credential as if it were a fresh copy - const Credential &cred = session.originatorCredential(); - sessionCredential = Credential(cred->uid(), cred->name(), cred->realname(), mShared/*ignored*/); - } else { - uid = auth.creatorUid(); - Server::active().longTermActivity(); - struct passwd *pw = getpwuid(uid); - if (pw != NULL) { - // avoid hinting a locked account - 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 - secdebug("AuthEvalMech", "preflight credential from current user, result follows:"); - sessionCredential = Credential(pw->pw_uid, pw->pw_name, pw->pw_gecos, mShared/*ignored*/); - } //fi - endpwent(); - } + uid_t uid = auth.session().originatorUid(); + Server::active().longTermActivity(); + struct passwd *pw = getpwuid(uid); + if (pw != NULL) { + // avoid hinting a locked account + 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 + secdebug("AuthEvalMech", "preflight credential from current user, result follows:"); + sessionCredential = Credential(pw->pw_uid, pw->pw_name, pw->pw_gecos, mShared/*ignored*/); + } //fi + endpwent(); } - OSStatus status = evaluateUserCredentialForRight(auth, inRight, inRule, environment, now, sessionCredential, true); + OSStatus status = evaluateUserCredentialForRight(auth, inRight, inRule, environment, now, sessionCredential, true, reason); if (errAuthorizationSuccess == status) credential = sessionCredential; @@ -603,43 +695,66 @@ RuleImpl::evaluateSessionOwner(const AuthItemRef &inRight, const Rule &inRule, c OSStatus -RuleImpl::evaluateCredentialForRight(const AuthorizationToken &auth, const AuthItemRef &inRight, const Rule &inRule, const AuthItemSet &environment, CFAbsoluteTime now, const Credential &credential, bool ignoreShared) const +RuleImpl::evaluateCredentialForRight(const AuthorizationToken &auth, const AuthItemRef &inRight, const Rule &inRule, const AuthItemSet &environment, CFAbsoluteTime now, const Credential &credential, bool ignoreShared, SecurityAgent::Reason &reason) const { if (auth.operatesAsLeastPrivileged()) { - if (credential->isRight() && credential->isValid() && (inRight->name() == credential->name())) - return errAuthorizationSuccess; - else - return errAuthorizationDenied; + if (credential->isRight() && credential->isValid() && (inRight->name() == credential->name())) + { + if (!ignoreShared && !mShared && credential->isShared()) + { + // @@@ no proper SA::Reason + reason = SecurityAgent::unknownReason; + secdebug("autheval", "shared credential cannot be used, denying right %s", inRight->name()); + return errAuthorizationDenied; + } else { + return errAuthorizationSuccess; + } + } else { + // @@@ no proper SA::Reason + reason = SecurityAgent::unknownReason; + return errAuthorizationDenied; + } } else - return evaluateUserCredentialForRight(auth, inRight, inRule, environment, now, credential, false); + return evaluateUserCredentialForRight(auth, inRight, inRule, environment, now, credential, false, reason); } // Return errAuthorizationSuccess if this rule allows access based on the specified credential, // return errAuthorizationDenied otherwise. OSStatus -RuleImpl::evaluateUserCredentialForRight(const AuthorizationToken &auth, const AuthItemRef &inRight, const Rule &inRule, const AuthItemSet &environment, CFAbsoluteTime now, const Credential &credential, bool ignoreShared) const +RuleImpl::evaluateUserCredentialForRight(const AuthorizationToken &auth, const AuthItemRef &inRight, const Rule &inRule, const AuthItemSet &environment, CFAbsoluteTime now, const Credential &credential, bool ignoreShared, SecurityAgent::Reason &reason) const { assert(mType == kUser); + // Ideally we'd set the AGENT_HINT_RETRY_REASON hint in this method, but + // evaluateAuthentication() overwrites it before + // AgentMechanismEvaluator::run(). That's what led to passing "reason" + // everywhere, from RuleImpl::evaluate() on down. + // Get the username from the credential const char *user = credential->name().c_str(); - // If the credential is not valid or it's age is more than the allowed maximum age + // If the credential is not valid or its age is more than the allowed maximum age // for a credential, deny. if (!credential->isValid()) { + // @@@ it could be the username, not password, was invalid + reason = SecurityAgent::invalidPassphrase; secdebug("autheval", "credential for user %s is invalid, denying right %s", user, inRight->name()); return errAuthorizationDenied; } if (now - credential->creationTime() > mMaxCredentialAge) { + // @@@ no proper SA::Reason + reason = SecurityAgent::unknownReason; secdebug("autheval", "credential for user %s has expired, denying right %s", user, inRight->name()); return errAuthorizationDenied; } if (!ignoreShared && !mShared && credential->isShared()) { + // @@@ no proper SA::Reason + reason = SecurityAgent::unknownReason; secdebug("autheval", "shared credential for user %s cannot be used, denying right %s", user, inRight->name()); return errAuthorizationDenied; } @@ -654,18 +769,20 @@ RuleImpl::evaluateUserCredentialForRight(const AuthorizationToken &auth, const A if (mSessionOwner) { Session &session = auth.session(); - if (session.haveOriginatorUid()) - { - uid_t console_user = session.originatorUid(); + uid_t console_user = session.originatorUid(); - if (credential->uid() == console_user) - { - secdebug("autheval", "user %s is session-owner(uid: %d), granting right %s", user, console_user, inRight->name()); - return errAuthorizationSuccess; - } + if (credential->uid() == console_user) + { + secdebug("autheval", "user %s is session-owner(uid: %d), granting right %s", user, console_user, inRight->name()); + return errAuthorizationSuccess; } - else - secdebug("autheval", "session-owner check failed."); + // set "reason" in this case? not that a proper SA::Reason exists + } + else + { + // @@@ no proper SA::Reason + reason = SecurityAgent::unknownReason; + secdebug("autheval", "session-owner check failed."); } if (mGroupName.length()) @@ -680,12 +797,21 @@ RuleImpl::evaluateUserCredentialForRight(const AuthorizationToken &auth, const A { uuid_t group_uuid, user_uuid; int is_member; - + + // @@@ it'd be nice to have SA::Reason codes for the failures + // associated with the pre-check-membership mbr_*() functions, + // but userNotInGroup will do if (mbr_group_name_to_uuid(groupname, group_uuid)) break; if (mbr_uid_to_uuid(credential->uid(), user_uuid)) - break; + { + struct passwd *pwd; + if (NULL == (pwd = getpwnam(user))) + break; + if (mbr_uid_to_uuid(pwd->pw_uid, user_uuid)) + break; + } if (mbr_check_membership(user_uuid, group_uuid, &is_member)) break; @@ -699,10 +825,15 @@ RuleImpl::evaluateUserCredentialForRight(const AuthorizationToken &auth, const A } while (0); - + + reason = SecurityAgent::userNotInGroup; secdebug("autheval", "user %s is not a member of group %s, denying right %s", user, groupname, inRight->name()); } + else if (mSessionOwner) // rule asks only if user is the session owner + { + reason = SecurityAgent::unacceptableUser; + } return errAuthorizationDenied; } @@ -710,15 +841,17 @@ RuleImpl::evaluateUserCredentialForRight(const AuthorizationToken &auth, const A OSStatus -RuleImpl::evaluateUser(const AuthItemRef &inRight, const Rule &inRule, AuthItemSet &environmentToClient, AuthorizationFlags flags, CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials, AuthorizationToken &auth) const +RuleImpl::evaluateUser(const AuthItemRef &inRight, const Rule &inRule, AuthItemSet &environmentToClient, AuthorizationFlags flags, CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials, AuthorizationToken &auth, SecurityAgent::Reason &reason, bool savePassword) const { - // If we got here, this is a kUser type rule, let's start looking for a + // If we got here, this is a kUser 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) { + SECURITYD_AUTH_USER_ALLOWROOT(&auth); + secdebug("autheval", "creator of authorization has uid == 0 granting right %s", inRight->name()); return errAuthorizationSuccess; @@ -728,10 +861,13 @@ RuleImpl::evaluateUser(const AuthItemRef &inRight, const Rule &inRule, AuthItemS if (!mAuthenticateUser) { Credential hintCredential; - OSStatus status = evaluateSessionOwner(inRight, inRule, environmentToClient, now, auth, hintCredential); + OSStatus status = evaluateSessionOwner(inRight, inRule, environmentToClient, now, auth, hintCredential, reason); if (!status) + { + SECURITYD_AUTH_USER_ALLOWSESSIONOWNER(&auth); return errAuthorizationSuccess; + } return errAuthorizationDenied; } @@ -739,10 +875,10 @@ RuleImpl::evaluateUser(const AuthItemRef &inRight, const Rule &inRule, AuthItemS // 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) { - // Passed in user credentials are allowed for least privileged mode + // Passed-in user credentials are allowed for least-privileged mode if (auth.operatesAsLeastPrivileged() && !(*it)->isRight() && (*it)->isValid()) { - OSStatus status = evaluateUserCredentialForRight(auth, inRight, inRule, environmentToClient, now, *it, false); + OSStatus status = evaluateUserCredentialForRight(auth, inRight, inRule, environmentToClient, now, *it, false, reason); if (errAuthorizationSuccess == status) { Credential rightCredential(inRight->name(), mShared); credentials.erase(rightCredential); credentials.insert(rightCredential); @@ -753,11 +889,11 @@ RuleImpl::evaluateUser(const AuthItemRef &inRight, const Rule &inRule, AuthItemS } // if this is least privileged, this will function differently: match credential to requested right - OSStatus status = evaluateCredentialForRight(auth, inRight, inRule, environmentToClient, now, *it, false); + OSStatus status = evaluateCredentialForRight(auth, inRight, inRule, environmentToClient, now, *it, false, reason); if (status != errAuthorizationDenied) { // add credential to authinfo - auth.setCredentialInfo(*it); + auth.setCredentialInfo(*it, savePassword); return status; } @@ -769,7 +905,7 @@ RuleImpl::evaluateUser(const AuthItemRef &inRight, const Rule &inRule, AuthItemS for (CredentialSet::const_iterator it = inCredentials->begin(); it != inCredentials->end(); ++it) { // if this is least privileged, this will function differently: match credential to requested right - OSStatus status = evaluateCredentialForRight(auth, inRight, inRule, environmentToClient, now, *it, false); + OSStatus status = evaluateCredentialForRight(auth, inRight, inRule, environmentToClient, now, *it, false, reason); if (status == errAuthorizationSuccess) { @@ -777,7 +913,7 @@ RuleImpl::evaluateUser(const AuthItemRef &inRight, const Rule &inRule, AuthItemS // whack an equivalent credential, so it gets updated to a later achieved credential which must have been more stringent credentials.erase(*it); credentials.insert(*it); // add credential to authinfo - auth.setCredentialInfo(*it); + auth.setCredentialInfo(*it, savePassword); return status; } @@ -803,11 +939,11 @@ RuleImpl::evaluateUser(const AuthItemRef &inRight, const Rule &inRule, AuthItemS setAgentHints(inRight, inRule, environmentToClient, auth); - return evaluateAuthentication(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth); + return evaluateAuthentication(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth, reason, savePassword); } OSStatus -RuleImpl::evaluateMechanismOnly(const AuthItemRef &inRight, const Rule &inRule, AuthItemSet &environmentToClient, AuthorizationToken &auth, CredentialSet &outCredentials) const +RuleImpl::evaluateMechanismOnly(const AuthItemRef &inRight, const Rule &inRule, AuthItemSet &environmentToClient, AuthorizationToken &auth, CredentialSet &outCredentials, bool savePassword) const { uint32 tries = 0; OSStatus status; @@ -819,26 +955,61 @@ RuleImpl::evaluateMechanismOnly(const AuthItemRef &inRight, const Rule &inRule, { AgentMechanismEvaluator eval(cltUid, auth.session(), mEvalDef); - + // For auditing within AuthorizationMechEval, pass the right name. + size_t rightNameSize = inRight->name() ? strlen(inRight->name()) : 0; + AuthorizationString rightName = inRight->name() ? inRight->name() : ""; + // @@@ AuthValueRef's ctor ought to take a const void * + AuthValueRef rightValue(rightNameSize, const_cast(rightName)); + AuthValueVector authValueVector; + authValueVector.push_back(rightValue); + do { setAgentHints(inRight, inRule, environmentToClient, auth); AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(tries), &tries)); environmentToClient.erase(triesHint); environmentToClient.insert(triesHint); // replace - - status = eval.run(AuthValueVector(), environmentToClient, auth); - + + status = eval.run(authValueVector, environmentToClient, auth); if ((status == errAuthorizationSuccess) || (status == errAuthorizationCanceled)) // @@@ can only pass back sideband through context { secdebug("AuthEvalMech", "storing new context for authorization"); - auth.setInfoSet(eval.context()); + auth.setInfoSet(eval.context(), savePassword); if (status == errAuthorizationSuccess) { + // (try to) attach the authorizing UID to the least-priv cred if (auth.operatesAsLeastPrivileged()) - outCredentials.insert(Credential(inRight->name(), mShared)); - else - outCredentials = makeCredentials(auth); + { + outCredentials.insert(Credential(rightName, mShared)); + if (mShared) + outCredentials.insert(Credential(rightName, false)); + + RightAuthenticationLogger logger(auth.creatorAuditToken(), AUE_ssauthint); + logger.setRight(rightName); + + AuthItem *uidItem = eval.context().find(AGENT_CONTEXT_UID); + if (uidItem) + { + uid_t authorizedUid; + memcpy(&authorizedUid, uidItem->value().data, sizeof(authorizedUid)); + secdebug("AuthEvalMech", "generating least-privilege cred for '%s' authorized by UID %u", inRight->name(), authorizedUid); + logger.logLeastPrivilege(authorizedUid, true); + } + else // cltUid is better than nothing + { + secdebug("AuthEvalMech", "generating least-privilege cred for '%s' with process- or auth-UID %u", inRight->name(), cltUid); + logger.logLeastPrivilege(cltUid, false); + } + } + + if (0 == strcmp(rightName, "system.login.console") && NULL == eval.context().find(AGENT_CONTEXT_AUTO_LOGIN)) { + secdebug("AuthEvalMech", "We logged in as the session owner.\n"); + SessionAttributeBits flags = auth.session().attributes(); + flags |= AU_SESSION_FLAG_HAS_AUTHENTICATED; + auth.session().setAttributes(flags); + } + CredentialSet newCredentials = makeCredentials(auth); + outCredentials.insert(newCredentials.begin(), newCredentials.end()); } } @@ -851,8 +1022,10 @@ RuleImpl::evaluateMechanismOnly(const AuthItemRef &inRight, const Rule &inRule, } // HACK kill all hosts to free pages for low memory systems + // (XXX/gh there should be a #define for this right) if (name() == "system.login.done") { + // one case where we don't want to mark the agents as "busy" QueryInvokeMechanism query(securityAgent, auth.session()); query.terminateAgent(); QueryInvokeMechanism query2(privilegedAuthHost, auth.session()); @@ -863,7 +1036,7 @@ RuleImpl::evaluateMechanismOnly(const AuthItemRef &inRight, const Rule &inRule, } OSStatus -RuleImpl::evaluateRules(const AuthItemRef &inRight, const Rule &inRule, AuthItemSet &environmentToClient, AuthorizationFlags flags, CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials, AuthorizationToken &auth) const +RuleImpl::evaluateRules(const AuthItemRef &inRight, const Rule &inRule, AuthItemSet &environmentToClient, AuthorizationFlags flags, CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials, AuthorizationToken &auth, SecurityAgent::Reason &reason, bool savePassword) const { // line up the rules to try if (!mRuleDef.size()) @@ -880,7 +1053,7 @@ RuleImpl::evaluateRules(const AuthItemRef &inRight, const Rule &inRule, AuthItem return errAuthorizationSuccess; // get a rule and try it - status = (*it)->evaluate(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth); + status = (*it)->evaluate(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth, reason, savePassword); // if status is cancel/internal error abort if ((status == errAuthorizationCanceled) || (status == errAuthorizationInternal)) @@ -897,36 +1070,42 @@ RuleImpl::evaluateRules(const AuthItemRef &inRight, const Rule &inRule, AuthItem else count++; } + + if ((mType == kKofN) && (status == errAuthorizationSuccess) && (count < mKofN)) + status = errAuthorizationDenied; return status; // return the last failure } OSStatus -RuleImpl::evaluate(const AuthItemRef &inRight, const Rule &inRule, AuthItemSet &environmentToClient, AuthorizationFlags flags, CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials, AuthorizationToken &auth) const +RuleImpl::evaluate(const AuthItemRef &inRight, const Rule &inRule, AuthItemSet &environmentToClient, AuthorizationFlags flags, CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials, AuthorizationToken &auth, SecurityAgent::Reason &reason, bool savePassword) const { switch (mType) { case kAllow: - secdebug("autheval", "rule is always allow"); + SECURITYD_AUTH_ALLOW(&auth, (char *)name().c_str()); return errAuthorizationSuccess; case kDeny: - secdebug("autheval", "rule is always deny"); + SECURITYD_AUTH_DENY(&auth, (char *)name().c_str()); return errAuthorizationDenied; case kUser: - secdebug("autheval", "rule is user"); - return evaluateUser(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth); + SECURITYD_AUTH_USER(&auth, (char *)name().c_str()); + return evaluateUser(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth, reason, savePassword); case kRuleDelegation: - secdebug("autheval", "rule evaluates rules"); - return evaluateRules(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth); + SECURITYD_AUTH_RULES(&auth, (char *)name().c_str()); + return evaluateRules(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth, reason, savePassword); case kKofN: - secdebug("autheval", "rule evaluates k-of-n rules"); - return evaluateRules(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth); + SECURITYD_AUTH_KOFN(&auth, (char *)name().c_str()); + return evaluateRules(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth, reason, savePassword); case kEvaluateMechanisms: - secdebug("autheval", "rule evaluates mechanisms"); - return evaluateMechanismOnly(inRight, inRule, environmentToClient, auth, credentials); + SECURITYD_AUTH_MECHRULE(&auth, (char *)name().c_str()); + // if we had a SecurityAgent::Reason code for "mechanism denied," + // it would make sense to pass down "reason" + return evaluateMechanismOnly(inRight, inRule, environmentToClient, auth, credentials, savePassword); default: - MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule + Syslog::alert("Unrecognized rule type %d", mType); + MacOSError::throwMe(errAuthorizationInternal); // invalid rule } }