X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/authd/engine.c diff --git a/Security/authd/engine.c b/Security/authd/engine.c new file mode 100644 index 00000000..e9247196 --- /dev/null +++ b/Security/authd/engine.c @@ -0,0 +1,1329 @@ +/* Copyright (c) 2012-2014 Apple Inc. All Rights Reserved. */ + +#include "engine.h" +#include "rule.h" +#include "authitems.h" +#include "authtoken.h" +#include "agent.h" +#include "process.h" +#include "debugging.h" +#include "server.h" +#include "credential.h" +#include "session.h" +#include "mechanism.h" +#include "authutilities.h" +#include "ccaudit.h" +#include "connection.h" + +#include +#include +int checkpw_internal( const struct passwd *pw, const char* password ); + +#include +#include +#include +#include + +static void _set_process_hints(auth_items_t, process_t); +static void _set_process_immutable_hints(auth_items_t, process_t); +static void _set_auth_token_hints(auth_items_t, auth_token_t); +static OSStatus _evaluate_user_credential_for_rule(engine_t, credential_t, rule_t, bool, bool, enum Reason *); +static void _engine_set_credential(engine_t, credential_t, bool); +static OSStatus _evaluate_rule(engine_t, rule_t, bool *); + +enum { + kEngineHintsFlagTemporary = (1 << 30) +}; + +#pragma mark - +#pragma mark engine creation + +struct _engine_s { + __AUTH_BASE_STRUCT_HEADER__; + + connection_t conn; + process_t proc; + auth_token_t auth; + + AuthorizationFlags flags; + auth_items_t hints; + auth_items_t context; + auth_items_t sticky_context; + auth_items_t immutable_hints; + + auth_rights_t grantedRights; + + enum Reason reason; + int32_t tries; + + CFAbsoluteTime now; + + credential_t sessionCredential; + CFMutableSetRef credentials; + CFMutableSetRef effectiveCredentials; + + CFMutableDictionaryRef mechanism_agents; + + // set only in engine_authorize + const char * currentRightName; // weak ref + rule_t currentRule; // weak ref + + rule_t authenticateRule; + + bool dismissed; +}; + +static void +_engine_finalizer(CFTypeRef value) +{ + engine_t engine = (engine_t)value; + + CFReleaseSafe(engine->mechanism_agents); + CFReleaseSafe(engine->conn); + CFReleaseSafe(engine->auth); + CFReleaseSafe(engine->hints); + CFReleaseSafe(engine->context); + CFReleaseSafe(engine->immutable_hints); + CFReleaseSafe(engine->sticky_context); + CFReleaseSafe(engine->grantedRights); + CFReleaseSafe(engine->sessionCredential); + CFReleaseSafe(engine->credentials); + CFReleaseSafe(engine->effectiveCredentials); + CFReleaseSafe(engine->authenticateRule); +} + +AUTH_TYPE_INSTANCE(engine, + .init = NULL, + .copy = NULL, + .finalize = _engine_finalizer, + .equal = NULL, + .hash = NULL, + .copyFormattingDesc = NULL, + .copyDebugDesc = NULL + ); + +static CFTypeID engine_get_type_id() { + static CFTypeID type_id = _kCFRuntimeNotATypeID; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + type_id = _CFRuntimeRegisterClass(&_auth_type_engine); + }); + + return type_id; +} + +engine_t +engine_create(connection_t conn, auth_token_t auth) +{ + engine_t engine = NULL; + require(conn != NULL, done); + require(auth != NULL, done); + + engine = (engine_t)_CFRuntimeCreateInstance(kCFAllocatorDefault, engine_get_type_id(), AUTH_CLASS_SIZE(engine), NULL); + require(engine != NULL, done); + + engine->conn = (connection_t)CFRetain(conn); + engine->proc = connection_get_process(conn); + engine->auth = (auth_token_t)CFRetain(auth); + + engine->hints = auth_items_create(); + engine->context = auth_items_create(); + engine->immutable_hints = auth_items_create(); + engine->sticky_context = auth_items_create(); + _set_process_hints(engine->hints, engine->proc); + _set_process_immutable_hints(engine->immutable_hints, engine->proc); + _set_auth_token_hints(engine->hints, auth); + + engine->grantedRights = auth_rights_create(); + + engine->reason = noReason; + + engine->now = CFAbsoluteTimeGetCurrent(); + + session_update(auth_token_get_session(engine->auth)); + engine->sessionCredential = credential_create(session_get_uid(auth_token_get_session(engine->auth))); + engine->credentials = CFSetCreateMutable(kCFAllocatorDefault, 0, &kCFTypeSetCallBacks); + engine->effectiveCredentials = CFSetCreateMutable(kCFAllocatorDefault, 0, &kCFTypeSetCallBacks); + + session_credentials_iterate(auth_token_get_session(engine->auth), ^bool(credential_t cred) { + CFSetAddValue(engine->effectiveCredentials, cred); + return true; + }); + + auth_token_credentials_iterate(engine->auth, ^bool(credential_t cred) { + // we added all session credentials already now just add all previously acquired credentials + if (!credential_get_shared(cred)) { + CFSetAddValue(engine->credentials, cred); + } + return true; + }); + + engine->mechanism_agents = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + +done: + return engine; +} + +#pragma mark - +#pragma mark agent hints + +void +_set_process_hints(auth_items_t hints, process_t proc) +{ + // process information + RequestorType type = bundle; + auth_items_set_data(hints, AGENT_HINT_CLIENT_TYPE, &type, sizeof(type)); + auth_items_set_int(hints, AGENT_HINT_CLIENT_PID, process_get_pid(proc)); + auth_items_set_uint(hints, AGENT_HINT_CLIENT_UID, process_get_uid(proc)); +} + +void +_set_process_immutable_hints(auth_items_t immutable_hints, process_t proc) +{ + // process information - immutable + auth_items_set_bool(immutable_hints, AGENT_HINT_PROCESS_SIGNED, process_apple_signed(proc)); +} + +void +_set_auth_token_hints(auth_items_t hints, auth_token_t auth) +{ + auth_items_set_string(hints, AGENT_HINT_CLIENT_PATH, auth_token_get_code_url(auth)); + auth_items_set_int(hints, AGENT_HINT_CREATOR_PID, auth_token_get_pid(auth)); + const audit_info_s * info = auth_token_get_audit_info(auth); + auth_items_set_data(hints, AGENT_HINT_CREATOR_AUDIT_TOKEN, &info->opaqueToken, sizeof(info->opaqueToken)); +} + +static void +_set_right_hints(auth_items_t hints, const char * right) +{ + auth_items_set_string(hints, AGENT_HINT_AUTHORIZE_RIGHT, right); +} + +static void +_set_rule_hints(auth_items_t hints, rule_t rule) +{ + auth_items_set_string(hints, AGENT_HINT_AUTHORIZE_RULE, rule_get_name(rule)); + const char * group = rule_get_group(rule); + if (rule_get_class(rule) == RC_USER && group != NULL) { + auth_items_set_string(hints, AGENT_HINT_REQUIRE_USER_IN_GROUP, group); + } else { + auth_items_remove(hints, AGENT_HINT_REQUIRE_USER_IN_GROUP); + } +} + +static void +_set_localization_hints(authdb_connection_t dbconn, auth_items_t hints, rule_t rule) +{ + char * key = calloc(1u, 128); + + authdb_step(dbconn, "SELECT lang,value FROM prompts WHERE r_id = ?", ^(sqlite3_stmt *stmt) { + sqlite3_bind_int64(stmt, 1, rule_get_id(rule)); + }, ^bool(auth_items_t data) { + snprintf(key, 128, "%s%s", kAuthorizationRuleParameterDescription, auth_items_get_string(data, "lang")); + auth_items_set_string(hints, key, auth_items_get_string(data, "value")); + auth_items_set_flags(hints, key, kEngineHintsFlagTemporary); + return true; + }); + + authdb_step(dbconn, "SELECT lang,value FROM buttons WHERE r_id = ?", ^(sqlite3_stmt *stmt) { + sqlite3_bind_int64(stmt, 1, rule_get_id(rule)); + }, ^bool(auth_items_t data) { + snprintf(key, 128, "%s%s", kAuthorizationRuleParameterButton, auth_items_get_string(data, "lang")); + auth_items_set_string(hints, key, auth_items_get_string(data, "value")); + auth_items_set_flags(hints, key, kEngineHintsFlagTemporary); + return true; + }); + + free_safe(key); +} + +static void +_set_session_hints(engine_t engine, rule_t rule) +{ + LOGV("engine[%i]: ** prepare agent hints for rule %s", connection_get_pid(engine->conn), rule_get_name(rule)); + if (_evaluate_user_credential_for_rule(engine, engine->sessionCredential, rule, true, true, NULL) == errAuthorizationSuccess) { + const char * tmp = credential_get_name(engine->sessionCredential); + if (tmp != NULL) { + auth_items_set_string(engine->hints, AGENT_HINT_SUGGESTED_USER, tmp); + } + tmp = credential_get_realname(engine->sessionCredential); + if (tmp != NULL) { + auth_items_set_string(engine->hints, AGENT_HINT_SUGGESTED_USER_LONG, tmp); + } + } else { + auth_items_remove(engine->hints, AGENT_HINT_SUGGESTED_USER); + auth_items_remove(engine->hints, AGENT_HINT_SUGGESTED_USER_LONG); + } +} + +#pragma mark - +#pragma mark right processing + +static OSStatus +_evaluate_credential_for_rule(engine_t engine, credential_t cred, rule_t rule, bool ignoreShared, bool sessionOwner, enum Reason * reason) +{ + if (auth_token_least_privileged(engine->auth)) { + if (credential_is_right(cred) && credential_get_valid(cred) && _compare_string(engine->currentRightName, credential_get_name(cred))) { + if (!ignoreShared) { + if (!rule_get_shared(rule) && credential_get_shared(cred)) { + LOGV("engine[%i]: - shared right %s (does NOT satisfy rule)", connection_get_pid(engine->conn), credential_get_name(cred)); + if (reason) { *reason = unknownReason; } + return errAuthorizationDenied; + } + } + + return errAuthorizationSuccess; + } else { + if (reason) { *reason = unknownReason; } + return errAuthorizationDenied; + } + } else { + return _evaluate_user_credential_for_rule(engine,cred,rule,ignoreShared,sessionOwner, reason); + } +} + +static OSStatus +_evaluate_user_credential_for_rule(engine_t engine, credential_t cred, rule_t rule, bool ignoreShared, bool sessionOwner, enum Reason * reason) +{ + const char * cred_label = sessionOwner ? "session owner" : "credential"; + LOGV("engine[%i]: - validating %s%s %s (%i) for %s", connection_get_pid(engine->conn), + credential_get_shared(cred) ? "shared " : "", + cred_label, + credential_get_name(cred), + credential_get_uid(cred), + rule_get_name(rule)); + + if (rule_get_class(rule) != RC_USER) { + LOGV("engine[%i]: - invalid rule class %i (denied)", connection_get_pid(engine->conn), rule_get_class(rule)); + return errAuthorizationDenied; + } + + if (credential_get_valid(cred) != true) { + LOGV("engine[%i]: - %s %i invalid (does NOT satisfy rule)", connection_get_pid(engine->conn), cred_label, credential_get_uid(cred)); + if (reason) { *reason = invalidPassphrase; } + return errAuthorizationDenied; + } + + if (engine->now - credential_get_creation_time(cred) > rule_get_timeout(rule)) { + LOGV("engine[%i]: - %s %i expired '%f > %lli' (does NOT satisfy rule)", connection_get_pid(engine->conn), cred_label, credential_get_uid(cred), + (engine->now - credential_get_creation_time(cred)), rule_get_timeout(rule)); + if (reason) { *reason = unknownReason; } + return errAuthorizationDenied; + } + + + if (!ignoreShared) { + if (!rule_get_shared(rule) && credential_get_shared(cred)) { + LOGV("engine[%i]: - shared %s %i (does NOT satisfy rule)", connection_get_pid(engine->conn), cred_label, credential_get_uid(cred)); + if (reason) { *reason = unknownReason; } + return errAuthorizationDenied; + } + } + + if (credential_get_uid(cred) == 0) { + LOGV("engine[%i]: - %s %i has uid 0 (does satisfy rule)", connection_get_pid(engine->conn), cred_label, credential_get_uid(cred)); + return errAuthorizationSuccess; + } + + if (rule_get_session_owner(rule)) { + if (credential_get_uid(cred) == session_get_uid(auth_token_get_session(engine->auth))) { + LOGV("engine[%i]: - %s %i is session owner (does satisfy rule)", connection_get_pid(engine->conn), cred_label, credential_get_uid(cred)); + return errAuthorizationSuccess; + } + } + + if (rule_get_group(rule) != NULL) { + do + { + // This allows testing a group modifier without prompting the user + // When (authenticate-user = false) we are just testing the creator uid. + // If a group modifier is enabled (RuleFlagEntitledAndGroup | RuleFlagVPNEntitledAndGroup) + // we want to skip the creator uid group check. + // group modifiers are checked early during the evaluation in _check_entitlement_for_rule + if (!rule_get_authenticate_user(rule)) { + if (rule_check_flags(rule, RuleFlagEntitledAndGroup | RuleFlagVPNEntitledAndGroup)) { + break; + } + } + + if (credential_check_membership(cred, rule_get_group(rule))) { + LOGV("engine[%i]: - %s %i is member of group %s (does satisfy rule)", connection_get_pid(engine->conn), cred_label, credential_get_uid(cred), rule_get_group(rule)); + return errAuthorizationSuccess; + } else { + if (reason) { *reason = userNotInGroup; } + } + } while (0); + } else if (rule_get_session_owner(rule)) { // rule asks only if user is the session owner + if (reason) { *reason = unacceptableUser; } + } + + LOGV("engine[%i]: - %s %i (does NOT satisfy rule)", connection_get_pid(engine->conn), cred_label, credential_get_uid(cred)); + return errAuthorizationDenied; +} + +static agent_t +_get_agent(engine_t engine, mechanism_t mech, bool create, bool firstMech) +{ + agent_t agent = (agent_t)CFDictionaryGetValue(engine->mechanism_agents, mech); + if (create && !agent) { + agent = agent_create(engine, mech, engine->auth, engine->proc, firstMech); + if (agent) { + CFDictionaryAddValue(engine->mechanism_agents, mech, agent); + CFReleaseSafe(agent); + } + } + return agent; +} + +static uint64_t +_evaluate_builtin_mechanism(engine_t engine, mechanism_t mech) +{ + uint64_t result = kAuthorizationResultDeny; + + switch (mechanism_get_type(mech)) { + case kMechanismTypeEntitled: + if (auth_token_has_entitlement_for_right(engine->auth, engine->currentRightName)) { + result = kAuthorizationResultAllow; + } + break; + default: + break; + } + + return result; +} + + +static OSStatus +_evaluate_mechanisms(engine_t engine, CFArrayRef mechanisms) +{ + uint64_t result = kAuthorizationResultAllow; + ccaudit_t ccaudit = ccaudit_create(engine->proc, engine->auth, AUE_ssauthmech); + auth_items_t context = auth_items_create(); + auth_items_t hints = auth_items_create(); + + auth_items_copy(context, engine->context); + auth_items_copy(hints, engine->hints); + auth_items_copy(context, engine->sticky_context); + + CFIndex count = CFArrayGetCount(mechanisms); + for (CFIndex i = 0; i < count; i++) { + mechanism_t mech = (mechanism_t)CFArrayGetValueAtIndex(mechanisms, i); + + if (mechanism_get_type(mech)) { + LOGV("engine[%i]: running builtin mechanism %s (%li of %li)", connection_get_pid(engine->conn), mechanism_get_string(mech), i+1, count); + result = _evaluate_builtin_mechanism(engine, mech); + } else { + agent_t agent = _get_agent(engine, mech, true, i == 0); + require_action(agent != NULL, done, result = kAuthorizationResultUndefined; LOGE("engine[%i]: error creating mechanism agent", connection_get_pid(engine->conn))); + + // check if any agent has been interrupted (it necessary if interrupt will come during creation) + CFIndex j; + agent_t agent1; + for (j = 0; j < i; j++) { + agent1 = _get_agent(engine, (mechanism_t)CFArrayGetValueAtIndex(mechanisms, j), false, j == 0); + if(agent_get_state(agent1) == interrupting) { + break; + } + } + if (j < i) { + LOGV("engine[%i]: mechanisms interrupted", connection_get_pid(engine->conn)); + char * buf = NULL; + asprintf(&buf, "evaluation interrupted by %s; restarting evaluation there", mechanism_get_string(agent_get_mechanism(agent1))); + ccaudit_log_mechanism(ccaudit, engine->currentRightName, mechanism_get_string(agent_get_mechanism(agent1)), kAuthorizationResultAllow, buf); + free_safe(buf); + ccaudit_log_mechanism(ccaudit, engine->currentRightName, mechanism_get_string(mech), kAuthorizationResultAllow, NULL); + const char * token_name = auth_items_get_string(hints, AGENT_HINT_TOKEN_NAME); + if (token_name && strlen(token_name) == 0) { + auth_items_remove(hints, AGENT_HINT_TOKEN_NAME); + } + auth_items_copy(context, agent_get_context(agent1)); + auth_items_copy(hints, agent_get_hints(agent1)); + + i = j - 1; + + continue; + } + + LOGV("engine[%i]: running mechanism %s (%li of %li)", connection_get_pid(engine->conn), mechanism_get_string(agent_get_mechanism(agent)), i+1, count); + result = agent_run(agent, hints, context, engine->immutable_hints); + + auth_items_copy(context, agent_get_context(agent)); + auth_items_copy(hints, agent_get_hints(agent)); + + bool interrupted = false; + for (CFIndex i2 = 0; i2 != i; i2++) { + agent_t agent2 = _get_agent(engine, (mechanism_t)CFArrayGetValueAtIndex(mechanisms, i2), false, i == 0); + if (agent_get_state(agent2) == interrupting) { + agent_deactivate(agent); + interrupted = true; + i = i2 - 1; + char * buf = NULL; + asprintf(&buf, "evaluation interrupted by %s; restarting evaluation there", mechanism_get_string(agent_get_mechanism(agent2))); + ccaudit_log_mechanism(ccaudit, engine->currentRightName, mechanism_get_string(agent_get_mechanism(agent2)), kAuthorizationResultAllow, buf); + free_safe(buf); + auth_items_copy(context, agent_get_context(agent2)); + auth_items_copy(hints, agent_get_hints(agent2)); + break; + } + } + + // Empty token name means that token doesn't exist (e.g. SC was removed). + // Remove empty token name from hints for UI drawing logic. + const char * token_name = auth_items_get_string(hints, AGENT_HINT_TOKEN_NAME); + if (token_name && strlen(token_name) == 0) { + auth_items_remove(hints, AGENT_HINT_TOKEN_NAME); + } + + if (interrupted) { + LOGV("engine[%i]: mechanisms interrupted", connection_get_pid(engine->conn)); + enum Reason reason = worldChanged; + auth_items_set_data(hints, AGENT_HINT_RETRY_REASON, &reason, sizeof(reason)); + result = kAuthorizationResultAllow; + _cf_dictionary_iterate(engine->mechanism_agents, ^bool(CFTypeRef key __attribute__((__unused__)), CFTypeRef value) { + agent_t tempagent = (agent_t)value; + agent_clear_interrupt(tempagent); + return true; + }); + } + } + + if (result == kAuthorizationResultAllow) { + ccaudit_log_mechanism(ccaudit, engine->currentRightName, mechanism_get_string(mech), kAuthorizationResultAllow, NULL); + } else { + ccaudit_log_mechanism(ccaudit, engine->currentRightName, mechanism_get_string(mech), (uint32_t)result, NULL); + break; + } + } + +done: + if ((result == kAuthorizationResultUserCanceled) || (result == kAuthorizationResultAllow)) { + // only make non-sticky context values available externally + auth_items_set_flags(context, kAuthorizationEnvironmentPassword, kAuthorizationContextFlagVolatile); + // Takauthorizationenvironmentusername should always be extractable + auth_items_set_flags(context, kAuthorizationEnvironmentUsername, kAuthorizationContextFlagExtractable); + auth_items_copy_with_flags(engine->context, context, kAuthorizationContextFlagExtractable | kAuthorizationContextFlagVolatile); + } else if (result == kAuthorizationResultDeny) { + auth_items_clear(engine->sticky_context); + // save off sticky values in context + auth_items_copy_with_flags(engine->sticky_context, context, kAuthorizationContextFlagSticky); + } + + CFReleaseSafe(ccaudit); + CFReleaseSafe(context); + CFReleaseSafe(hints); + + switch(result) + { + case kAuthorizationResultDeny: + return errAuthorizationDenied; + case kAuthorizationResultUserCanceled: + return errAuthorizationCanceled; + case kAuthorizationResultAllow: + return errAuthorizationSuccess; + case kAuthorizationResultUndefined: + return errAuthorizationInternal; + default: + { + LOGV("engine[%i]: unexpected error result", connection_get_pid(engine->conn)); + return errAuthorizationInternal; + } + } +} + +static OSStatus +_evaluate_authentication(engine_t engine, rule_t rule) +{ + OSStatus status = errAuthorizationDenied; + ccaudit_t ccaudit = ccaudit_create(engine->proc, engine->auth, AUE_ssauthint); + LOGV("engine[%i]: evaluate authentication", connection_get_pid(engine->conn)); + _set_rule_hints(engine->hints, rule); + _set_session_hints(engine, rule); + + CFArrayRef mechanisms = rule_get_mechanisms(rule); + if (!(CFArrayGetCount(mechanisms) > 0)) { + mechanisms = rule_get_mechanisms(engine->authenticateRule); + } + require_action(CFArrayGetCount(mechanisms) > 0, done, LOGV("engine[%i]: error no mechanisms found", connection_get_pid(engine->conn))); + + int64_t ruleTries = rule_get_tries(rule); + for (engine->tries = 0; engine->tries < ruleTries; engine->tries++) { + + auth_items_set_data(engine->hints, AGENT_HINT_RETRY_REASON, &engine->reason, sizeof(engine->reason)); + auth_items_set_int(engine->hints, AGENT_HINT_TRIES, engine->tries); + + status = _evaluate_mechanisms(engine, mechanisms); + + LOGV("engine[%i]: evaluate mechanisms result %i", connection_get_pid(engine->conn), status); + + // successfully ran mechanisms to obtain credential + if (status == errAuthorizationSuccess) { + // deny is the default + status = errAuthorizationDenied; + + credential_t newCred = NULL; + if (auth_items_exist(engine->context, "uid")) { + newCred = credential_create(auth_items_get_uint(engine->context, "uid")); + } else { + LOGV("engine[%i]: mechanism failed to return a valid uid", connection_get_pid(engine->conn)); + } + + if (newCred) { + if (credential_get_valid(newCred)) { + LOG("UID %u authenticated as user %s (UID %u) for right '%s'", auth_token_get_uid(engine->auth), credential_get_name(newCred), credential_get_uid(newCred), engine->currentRightName); + ccaudit_log_success(ccaudit, newCred, engine->currentRightName); + } else { + LOG("UID %u failed to authenticate as user '%s' for right '%s'", auth_token_get_uid(engine->auth), auth_items_get_string(engine->context, "username"), engine->currentRightName); + ccaudit_log_failure(ccaudit, auth_items_get_string(engine->context, "username"), engine->currentRightName); + } + + status = _evaluate_user_credential_for_rule(engine, newCred, rule, true, false, &engine->reason); + + if (status == errAuthorizationSuccess) { + _engine_set_credential(engine, newCred, rule_get_shared(rule)); + CFReleaseSafe(newCred); + + if (auth_token_least_privileged(engine->auth)) { + credential_t rightCred = credential_create_with_right(engine->currentRightName); + _engine_set_credential(engine, rightCred, rule_get_shared(rule)); + CFReleaseSafe(rightCred); + } + + session_t session = auth_token_get_session(engine->auth); + if (credential_get_uid(newCred) == session_get_uid(session)) { + LOGV("engine[%i]: authenticated as the session owner", connection_get_pid(engine->conn)); + session_set_attributes(auth_token_get_session(engine->auth), AU_SESSION_FLAG_HAS_AUTHENTICATED); + } + + break; + } + + CFReleaseSafe(newCred); + } + + } else if (status == errAuthorizationCanceled || status == errAuthorizationInternal) { + break; + } else if (status == errAuthorizationDenied) { + engine->reason = invalidPassphrase; + } + } + + if (engine->tries == ruleTries) { + engine->reason = tooManyTries; + auth_items_set_data(engine->hints, AGENT_HINT_RETRY_REASON, &engine->reason, sizeof(engine->reason)); + auth_items_set_int(engine->hints, AGENT_HINT_TRIES, engine->tries); + _evaluate_mechanisms(engine, mechanisms); + ccaudit_log(ccaudit, engine->currentRightName, NULL, 1113); + } + +done: + CFReleaseSafe(ccaudit); + + return status; +} + +static bool +_check_entitlement_for_rule(engine_t engine, rule_t rule) +{ + bool entitled = false; + CFTypeRef value = NULL; + + if (rule_check_flags(rule, RuleFlagEntitledAndGroup)) { + if (auth_token_has_entitlement_for_right(engine->auth, engine->currentRightName)) { + if (credential_check_membership(auth_token_get_credential(engine->auth), rule_get_group(rule))) { + LOGV("engine[%i]: creator of authorization has entitlement for right %s and is member of group '%s'", connection_get_pid(engine->conn), engine->currentRightName, rule_get_group(rule)); + entitled = true; + goto done; + } + } + } + + if (rule_check_flags(rule, RuleFlagVPNEntitledAndGroup)) { + // com.apple.networking.vpn.configuration is an array we only check for it's existence + value = auth_token_copy_entitlement_value(engine->auth, "com.apple.networking.vpn.configuration"); + if (value) { + if (credential_check_membership(auth_token_get_credential(engine->auth), rule_get_group(rule))) { + LOGV("engine[%i]: creator of authorization has VPN entitlement and is member of group '%s'", connection_get_pid(engine->conn), rule_get_group(rule)); + entitled = true; + goto done; + } + } + } + +done: + CFReleaseSafe(value); + return entitled; +} + +static OSStatus +_evaluate_class_user(engine_t engine, rule_t rule) +{ + __block OSStatus status = errAuthorizationDenied; + + if (_check_entitlement_for_rule(engine,rule)) { + return errAuthorizationSuccess; + } + + if (rule_get_allow_root(rule) && auth_token_get_uid(engine->auth) == 0) { + LOGV("engine[%i]: creator of authorization has uid == 0 granting right %s", connection_get_pid(engine->conn), engine->currentRightName); + return errAuthorizationSuccess; + } + + if (!rule_get_authenticate_user(rule)) { + status = _evaluate_user_credential_for_rule(engine, engine->sessionCredential, rule, true, true, NULL); + + if (status == errAuthorizationSuccess) { + return errAuthorizationSuccess; + } + + return errAuthorizationDenied; + } + + // First -- check all the credentials we have either acquired or currently have + _cf_set_iterate(engine->credentials, ^bool(CFTypeRef value) { + credential_t cred = (credential_t)value; + // Passed-in user credentials are allowed for least-privileged mode + if (auth_token_least_privileged(engine->auth) && !credential_is_right(cred) && credential_get_valid(cred)) { + status = _evaluate_user_credential_for_rule(engine, cred, rule, false, false, NULL); + if (errAuthorizationSuccess == status) { + credential_t rightCred = credential_create_with_right(engine->currentRightName); + _engine_set_credential(engine,rightCred,rule_get_shared(rule)); + CFReleaseSafe(rightCred); + return false; // exit loop + } + } + + status = _evaluate_credential_for_rule(engine, cred, rule, false, false, NULL); + if (status == errAuthorizationSuccess) { + return false; // exit loop + } + return true; + }); + + if (status == errAuthorizationSuccess) { + return status; + } + + // Second -- go through the credentials associated to the authorization token session/auth token + _cf_set_iterate(engine->effectiveCredentials, ^bool(CFTypeRef value) { + credential_t cred = (credential_t)value; + status = _evaluate_credential_for_rule(engine, cred, rule, false, false, NULL); + if (status == errAuthorizationSuccess) { + // Add the credential we used to the output set. + _engine_set_credential(engine, cred, false); + return false; // exit loop + } + return true; + }); + + if (status == errAuthorizationSuccess) { + return status; + } + + // Finally - we didn't find a credential. Obtain a new credential if our flags let us do so. + if (!(engine->flags & kAuthorizationFlagExtendRights)) { + LOGV("engine[%i]: authorization denied (kAuthorizationFlagExtendRights not set)", connection_get_pid(engine->conn)); + return errAuthorizationDenied; + } + + // authorization that timeout immediately cannot be preauthorized + if (engine->flags & kAuthorizationFlagPreAuthorize && rule_get_timeout(rule) == 0) { + return errAuthorizationSuccess; + } + + if (!(engine->flags & kAuthorizationFlagInteractionAllowed)) { + LOGV("engine[%i]: Interaction not allowed (kAuthorizationFlagInteractionAllowed not set)", connection_get_pid(engine->conn)); + return errAuthorizationInteractionNotAllowed; + } + + if (!(session_get_attributes(auth_token_get_session(engine->auth)) & AU_SESSION_FLAG_HAS_GRAPHIC_ACCESS)) { + LOGV("engine[%i]: Interaction not allowed (session has no ui access)", connection_get_pid(engine->conn)); + return errAuthorizationInteractionNotAllowed; + } + + if (server_in_dark_wake()) { + LOGV("engine[%i]: authorization denied (in DarkWake)", connection_get_pid(engine->conn)); + return errAuthorizationDenied; + } + + return _evaluate_authentication(engine, rule); +} + +static OSStatus +_evaluate_class_rule(engine_t engine, rule_t rule, bool *save_pwd) +{ + __block OSStatus status = errAuthorizationDenied; + int64_t kofn = rule_get_kofn(rule); + + uint32_t total = (uint32_t)rule_get_delegates_count(rule); + __block uint32_t success_count = 0; + __block uint32_t count = 0; + LOGV("engine[%i]: ** rule %s has %zi delegates kofn = %lli", connection_get_pid(engine->conn), rule_get_name(rule), total, kofn); + rule_delegates_iterator(rule, ^bool(rule_t delegate) { + count++; + + if (kofn != 0 && success_count == kofn) { + status = errAuthorizationSuccess; + return false; + } + + LOGV("engine[%i]: * evaluate rule %s (%i)", connection_get_pid(engine->conn), rule_get_name(delegate), count); + status = _evaluate_rule(engine, delegate, save_pwd); + + // if status is cancel/internal error abort + if ((status == errAuthorizationCanceled) || (status == errAuthorizationInternal)) + return false; + + if (status != errAuthorizationSuccess) { + if (kofn != 0) { + // if remaining is less than required abort + if ((total - count) < (kofn - success_count)) { + LOGD("engine[%i]: rule evaluation remaining: %i, required: %lli", connection_get_pid(engine->conn), (total - count), (kofn - success_count)); + return false; + } + return true; + } + return false; + } else { + success_count++; + return true; + } + }); + + return status; +} + +static OSStatus +_evaluate_class_mechanism(engine_t engine, rule_t rule) +{ + OSStatus status = errAuthorizationDenied; + CFArrayRef mechanisms = NULL; + + require_action(rule_get_mechanisms_count(rule) > 0, done, status = errAuthorizationSuccess; LOGV("engine[%i]: no mechanisms specified", connection_get_pid(engine->conn))); + + mechanisms = rule_get_mechanisms(rule); + + if (server_in_dark_wake()) { + CFIndex count = CFArrayGetCount(mechanisms); + for (CFIndex i = 0; i < count; i++) { + if (!mechanism_is_privileged((mechanism_t)CFArrayGetValueAtIndex(mechanisms, i))) { + LOGV("engine[%i]: authorization denied (in DarkWake)", connection_get_pid(engine->conn)); + goto done; + } + } + } + + int64_t ruleTries = rule_get_tries(rule); + engine->tries = 0; + do { + auth_items_set_data(engine->hints, AGENT_HINT_RETRY_REASON, &engine->reason, sizeof(engine->reason)); + auth_items_set_int(engine->hints, AGENT_HINT_TRIES, engine->tries); + + status = _evaluate_mechanisms(engine, mechanisms); + LOGV("engine[%i]: evaluate mechanisms result %i", connection_get_pid(engine->conn), status); + + if (status == errAuthorizationSuccess) { + credential_t newCred = NULL; + if (auth_items_exist(engine->context, "uid")) { + newCred = credential_create(auth_items_get_uint(engine->context, "uid")); + } else { + LOGV("engine[%i]: mechanism did not return a uid", connection_get_pid(engine->conn)); + } + + if (newCred) { + _engine_set_credential(engine, newCred, rule_get_shared(rule)); + + if (auth_token_least_privileged(engine->auth)) { + credential_t rightCred = credential_create_with_right(engine->currentRightName); + _engine_set_credential(engine, rightCred, rule_get_shared(rule)); + CFReleaseSafe(rightCred); + } + + if (strcmp(engine->currentRightName, "system.login.console") == 0 && !auth_items_exist(engine->context, AGENT_CONTEXT_AUTO_LOGIN)) { + session_set_attributes(auth_token_get_session(engine->auth), AU_SESSION_FLAG_HAS_AUTHENTICATED); + } + + CFReleaseSafe(newCred); + } + } + + engine->tries++; + + } while ( (status == errAuthorizationDenied) // only if we have an expected faulure we continue + && ((ruleTries == 0) || ((ruleTries > 0) && engine->tries < ruleTries))); // ruleTries == 0 means we try forever + // ruleTires > 0 means we try upto ruleTries times +done: + return status; +} + +static OSStatus +_evaluate_rule(engine_t engine, rule_t rule, bool *save_pwd) +{ + if (rule_check_flags(rule, RuleFlagEntitled)) { + if (auth_token_has_entitlement_for_right(engine->auth, engine->currentRightName)) { + LOGV("engine[%i]: rule allow, creator of authorization has entitlement for right %s", connection_get_pid(engine->conn), engine->currentRightName); + return errAuthorizationSuccess; + } + } + + if (rule_check_flags(rule, RuleFlagRequireAppleSigned)) { + if (!auth_token_apple_signed(engine->auth)) { + LOGV("engine[%i]: rule deny, creator of authorization is not signed by apple", connection_get_pid(engine->conn)); + return errAuthorizationDenied; + } + } + + *save_pwd |= rule_get_extract_password(rule); + + switch (rule_get_class(rule)) { + case RC_ALLOW: + LOGV("engine[%i]: rule set to allow", connection_get_pid(engine->conn)); + return errAuthorizationSuccess; + case RC_DENY: + LOGV("engine[%i]: rule set to deny", connection_get_pid(engine->conn)); + return errAuthorizationDenied; + case RC_USER: + return _evaluate_class_user(engine, rule); + case RC_RULE: + return _evaluate_class_rule(engine, rule, save_pwd); + case RC_MECHANISM: + return _evaluate_class_mechanism(engine, rule); + default: + LOGV("engine[%i]: invalid class for rule or rule not found", connection_get_pid(engine->conn)); + return errAuthorizationInternal; + } +} + +static rule_t +_find_rule(engine_t engine, authdb_connection_t dbconn, const char * string) +{ + rule_t r = NULL; + size_t sLen = strlen(string); + + char * buf = calloc(1u, sLen + 1); + strlcpy(buf, string, sLen + 1); + char * ptr = buf + sLen; + __block int64_t count = 0; + + for (;;) { + + // lookup rule + authdb_step(dbconn, "SELECT COUNT(name) AS cnt FROM rules WHERE name = ? AND type = 1", + ^(sqlite3_stmt *stmt) { + sqlite3_bind_text(stmt, 1, buf, -1, NULL); + }, ^bool(auth_items_t data) { + count = auth_items_get_int64(data, "cnt"); + return false; + }); + + if (count > 0) { + r = rule_create_with_string(buf, dbconn); + goto done; + } + + // if buf ends with a . and we didn't find a rule remove . + if (*ptr == '.') { + *ptr = '\0'; + } + // find any remaining . and truncate the string + ptr = strrchr(buf, '.'); + if (ptr) { + *(ptr+1) = '\0'; + } else { + break; + } + } + +done: + free_safe(buf); + + // set default if we didn't find a rule + if (r == NULL) { + r = rule_create_with_string("", dbconn); + if (rule_get_id(r) == 0) { + CFReleaseNull(r); + LOGE("engine[%i]: default rule lookup error (missing), using builtin defaults", connection_get_pid(engine->conn)); + r = rule_create_default(); + } + } + return r; +} + +static void _parse_enviroment(engine_t engine, auth_items_t enviroment) +{ + require(enviroment != NULL, done); + +#if DEBUG + LOGV("engine[%i]: Dumping Enviroment", connection_get_pid(engine->conn)); + _show_cf(enviroment); +#endif + + // Check if a credential was passed into the environment and we were asked to extend the rights + if (engine->flags & kAuthorizationFlagExtendRights) { + const char * user = auth_items_get_string(enviroment, kAuthorizationEnvironmentUsername); + const char * pass = auth_items_get_string(enviroment, kAuthorizationEnvironmentPassword); + bool shared = auth_items_exist(enviroment, kAuthorizationEnvironmentShared); + require(user != NULL, done); + + struct passwd *pw = getpwnam(user); + require_action(pw != NULL, done, LOGE("engine[%i]: user not found %s", connection_get_pid(engine->conn), user)); + + int checkpw_status = checkpw_internal(pw, pass ? pass : ""); + require_action(checkpw_status == CHECKPW_SUCCESS, done, LOGE("engine[%i]: checkpw() returned %d; failed to authenticate user %s (uid %u).", connection_get_pid(engine->conn), checkpw_status, pw->pw_name, pw->pw_uid)); + + credential_t cred = credential_create(pw->pw_uid); + if (credential_get_valid(cred)) { + LOG("engine[%i]: checkpw() succeeded, creating credential for user %s", connection_get_pid(engine->conn), user); + _engine_set_credential(engine, cred, shared); + + auth_items_set_string(engine->context, kAuthorizationEnvironmentUsername, user); + auth_items_set_string(engine->context, kAuthorizationEnvironmentPassword, pass ? pass : ""); + } + CFReleaseSafe(cred); + } + +done: + endpwent(); + return; +} + +static bool _verify_sandbox(engine_t engine, const char * right) +{ + pid_t pid = process_get_pid(engine->proc); + if (sandbox_check(pid, "authorization-right-obtain", SANDBOX_FILTER_RIGHT_NAME, right)) { + LOGE("Sandbox denied authorizing right '%s' by client '%s' [%d]", right, process_get_code_url(engine->proc), pid); + return false; + } + + pid = auth_token_get_pid(engine->auth); + if (auth_token_get_sandboxed(engine->auth) && sandbox_check(pid, "authorization-right-obtain", SANDBOX_FILTER_RIGHT_NAME, right)) { + LOGE("Sandbox denied authorizing right '%s' for authorization created by '%s' [%d]", right, auth_token_get_code_url(engine->auth), pid); + return false; + } + + return true; +} + +#pragma mark - +#pragma mark engine methods + +OSStatus engine_authorize(engine_t engine, auth_rights_t rights, auth_items_t enviroment, AuthorizationFlags flags) +{ + __block OSStatus status = errAuthorizationSuccess; + __block bool savePassword = false; + ccaudit_t ccaudit = NULL; + + require(rights != NULL, done); + + ccaudit = ccaudit_create(engine->proc, engine->auth, AUE_ssauthorize); + if (auth_rights_get_count(rights) > 0) { + ccaudit_log(ccaudit, "begin evaluation", NULL, 0); + } + + engine->flags = flags; + + if (enviroment) { + _parse_enviroment(engine, enviroment); + auth_items_copy(engine->hints, enviroment); + } + + auth_items_copy(engine->context, auth_token_get_context(engine->auth)); + + engine->dismissed = false; + auth_rights_clear(engine->grantedRights); + + auth_rights_iterate(rights, ^bool(const char *key) { + if (!key) + return true; + + + if (!_verify_sandbox(engine, key)) { + status = errAuthorizationDenied; + return false; + } + + authdb_connection_t dbconn = authdb_connection_acquire(server_get_database()); // get db handle + + LOGV("engine[%i]: evaluate right %s", connection_get_pid(engine->conn), key); + rule_t rule = _find_rule(engine, dbconn, key); + const char * rule_name = rule_get_name(rule); + if (rule_name && (strcasecmp(rule_name, "") == 0)) { + rule_name = "default (not defined)"; + } + LOGV("engine[%i]: using rule %s", connection_get_pid(engine->conn), rule_name); + + // only need the hints & mechanisms if we are going to show ui + if (engine->flags & kAuthorizationFlagInteractionAllowed) { + _set_right_hints(engine->hints, key); + _set_localization_hints(dbconn, engine->hints, rule); + if (!engine->authenticateRule) { + engine->authenticateRule = rule_create_with_string("authenticate", dbconn); + } + } + + authdb_connection_release(&dbconn); // release db handle + + engine->currentRightName = key; + engine->currentRule = rule; + + ccaudit_log(ccaudit, key, rule_name, 0); + + status = _evaluate_rule(engine, engine->currentRule, &savePassword); + switch (status) { + case errAuthorizationSuccess: + auth_rights_add(engine->grantedRights, key); + auth_rights_set_flags(engine->grantedRights, key, auth_rights_get_flags(rights,key)); + + if ((engine->flags & kAuthorizationFlagPreAuthorize) && + (rule_get_class(engine->currentRule) == RC_USER) && + (rule_get_timeout(engine->currentRule) == 0)) { + // FIXME: kAuthorizationFlagPreAuthorize => kAuthorizationFlagCanNotPreAuthorize ??? + auth_rights_set_flags(engine->grantedRights, engine->currentRightName, kAuthorizationFlagPreAuthorize); + } + + LOG("Succeeded authorizing right '%s' by client '%s' [%d] for authorization created by '%s' [%d] (%X,%d)", + key, process_get_code_url(engine->proc), process_get_pid(engine->proc), + auth_token_get_code_url(engine->auth), auth_token_get_pid(engine->auth), engine->flags, auth_token_least_privileged(engine->auth)); + break; + case errAuthorizationDenied: + case errAuthorizationInteractionNotAllowed: + case errAuthorizationCanceled: + if (engine->flags & kAuthorizationFlagInteractionAllowed) { + LOG("Failed to authorize right '%s' by client '%s' [%d] for authorization created by '%s' [%d] (%X,%d) (%i)", + key, process_get_code_url(engine->proc), process_get_pid(engine->proc), + auth_token_get_code_url(engine->auth), auth_token_get_pid(engine->auth), engine->flags, auth_token_least_privileged(engine->auth), status); + } else { + LOGV("Failed to authorize right '%s' by client '%s' [%d] for authorization created by '%s' [%d] (%X,%d) (%i)", + key, process_get_code_url(engine->proc), process_get_pid(engine->proc), + auth_token_get_code_url(engine->auth), auth_token_get_pid(engine->auth), engine->flags, auth_token_least_privileged(engine->auth), status); + } + break; + default: + LOGE("engine[%i]: evaluate returned %i returning errAuthorizationInternal", connection_get_pid(engine->conn), status); + status = errAuthorizationInternal; + break; + } + + ccaudit_log_authorization(ccaudit, engine->currentRightName, status); + + CFReleaseSafe(rule); + engine->currentRightName = NULL; + engine->currentRule = NULL; + + auth_items_remove_with_flags(engine->hints, kEngineHintsFlagTemporary); + + if (!(engine->flags & kAuthorizationFlagPartialRights) && (status != errAuthorizationSuccess)) { + return false; + } + + return true; + }); + + if ((engine->flags & kAuthorizationFlagPartialRights) && (auth_rights_get_count(engine->grantedRights) > 0)) { + status = errAuthorizationSuccess; + } + + if (engine->dismissed) { + status = errAuthorizationDenied; + } + + LOGV("engine[%i]: authorize result: %i", connection_get_pid(engine->conn), status); + + if ((engine->flags & kAuthorizationFlagExtendRights) && !(engine->flags & kAuthorizationFlagDestroyRights)) { + _cf_set_iterate(engine->credentials, ^bool(CFTypeRef value) { + credential_t cred = (credential_t)value; + // skip all uid credentials when running in least privileged + if (auth_token_least_privileged(engine->auth) && !credential_is_right(cred)) + return true; + + session_t session = auth_token_get_session(engine->auth); + auth_token_set_credential(engine->auth, cred); + if (credential_get_shared(cred)) { + session_set_credential(session, cred); + } + if (credential_is_right(cred)) { + LOGV("engine[%i]: adding least privileged %scredential %s to authorization", connection_get_pid(engine->conn), credential_get_shared(cred) ? "shared " : "", credential_get_name(cred)); + } else { + LOGV("engine[%i]: adding %scredential %s (%i) to authorization", connection_get_pid(engine->conn), credential_get_shared(cred) ? "shared " : "", credential_get_name(cred), credential_get_uid(cred)); + } + return true; + }); + } + + if (status == errAuthorizationSuccess && savePassword) { + auth_items_set_flags(engine->context, kAuthorizationEnvironmentPassword, kAuthorizationContextFlagExtractable); + } + + if ((status == errAuthorizationSuccess) || (status == errAuthorizationCanceled)) { + auth_items_copy_with_flags(auth_token_get_context(engine->auth), engine->context, kAuthorizationContextFlagExtractable); + } + + if (auth_rights_get_count(rights) > 0) { + ccaudit_log(ccaudit, "end evaluation", NULL, status); + } + +#if DEBUG + LOGV("engine[%i]: ********** Dumping auth->credentials **********", connection_get_pid(engine->conn)); + auth_token_credentials_iterate(engine->auth, ^bool(credential_t cred) { + _show_cf(cred); + return true; + }); + LOGV("engine[%i]: ********** Dumping session->credentials **********", connection_get_pid(engine->conn)); + session_credentials_iterate(auth_token_get_session(engine->auth), ^bool(credential_t cred) { + _show_cf(cred); + return true; + }); + LOGV("engine[%i]: ********** Dumping engine->context **********", connection_get_pid(engine->conn)); + _show_cf(engine->context); + LOGV("engine[%i]: ********** Dumping auth->context **********", connection_get_pid(engine->conn)); + _show_cf(auth_token_get_context(engine->auth)); + LOGV("engine[%i]: ********** Dumping granted rights **********", connection_get_pid(engine->conn)); + _show_cf(engine->grantedRights); +#endif + +done: + auth_items_clear(engine->context); + auth_items_clear(engine->sticky_context); + CFReleaseSafe(ccaudit); + CFDictionaryRemoveAllValues(engine->mechanism_agents); + + return status; +} + +static bool +_wildcard_right_exists(engine_t engine, const char * right) +{ + // checks if a wild card right exists + // ex: com.apple. system. + bool exists = false; + rule_t rule = NULL; + authdb_connection_t dbconn = authdb_connection_acquire(server_get_database()); // get db handle + require(dbconn != NULL, done); + + rule = _find_rule(engine, dbconn, right); + require(rule != NULL, done); + + const char * ruleName = rule_get_name(rule); + require(ruleName != NULL, done); + size_t len = strlen(ruleName); + require(len != 0, done); + + if (ruleName[len-1] == '.') { + exists = true; + goto done; + } + +done: + authdb_connection_release(&dbconn); + CFReleaseSafe(rule); + + return exists; +} + +// Validate db right modification + +// meta rights are constructed as follows: +// we don't allow setting of wildcard rights, so you can only be more specific +// note that you should never restrict things with a wildcard right without disallowing +// changes to the entire domain. ie. +// system.privilege. -> never +// config.add.system.privilege. -> never +// config.modify.system.privilege. -> never +// config.delete.system.privilege. -> never +// For now we don't allow any configuration of configuration rules +// config.config. -> never + +OSStatus engine_verify_modification(engine_t engine, rule_t rule, bool remove, bool force_modify) +{ + OSStatus status = errAuthorizationDenied; + auth_rights_t checkRight = NULL; + char buf[BUFSIZ]; + memset(buf, 0, sizeof(buf)); + + const char * right = rule_get_name(rule); + require(right != NULL, done); + size_t len = strlen(right); + require(len != 0, done); + + require_action(right[len-1] != '.', done, LOGE("engine[%i]: not allowed to set wild card rules", connection_get_pid(engine->conn))); + + if (strncasecmp(right, kConfigRight, strlen(kConfigRight)) == 0) { + // special handling of meta right change: + // config.add. config.modify. config.remove. config.{}. + // check for config. (which always starts with config.config.) + strlcat(buf, kConfigRight, sizeof(buf)); + } else { + bool existing = (rule_get_id(rule) != 0) ? true : _wildcard_right_exists(engine, right); + if (!remove) { + if (existing || force_modify) { + strlcat(buf, kAuthorizationConfigRightModify,sizeof(buf)); + } else { + strlcat(buf, kAuthorizationConfigRightAdd, sizeof(buf)); + } + } else { + if (existing) { + strlcat(buf, kAuthorizationConfigRightRemove, sizeof(buf)); + } else { + status = errAuthorizationSuccess; + goto done; + } + } + } + + strlcat(buf, right, sizeof(buf)); + + checkRight = auth_rights_create(); + auth_rights_add(checkRight, buf); + status = engine_authorize(engine, checkRight, NULL, kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights); + +done: + LOGV("engine[%i]: authorizing %s for db modification: %i", connection_get_pid(engine->conn), right, status); + CFReleaseSafe(checkRight); + return status; +} + +void +_engine_set_credential(engine_t engine, credential_t cred, bool shared) +{ + LOGV("engine[%i]: adding %scredential %s (%i) to engine shared: %i", connection_get_pid(engine->conn), credential_get_shared(cred) ? "shared " : "", credential_get_name(cred), credential_get_uid(cred), shared); + CFSetSetValue(engine->credentials, cred); + if (shared) { + credential_t sharedCred = credential_create_with_credential(cred, true); + CFSetSetValue(engine->credentials, sharedCred); + CFReleaseSafe(sharedCred); + } +} + +auth_rights_t +engine_get_granted_rights(engine_t engine) +{ + return engine->grantedRights; +} + +CFAbsoluteTime engine_get_time(engine_t engine) +{ + return engine->now; +} + +void engine_destroy_agents(engine_t engine) +{ + engine->dismissed = true; + + _cf_dictionary_iterate(engine->mechanism_agents, ^bool(CFTypeRef key __attribute__((__unused__)), CFTypeRef value) { + LOGD("engine[%i]: Destroying %s", connection_get_pid(engine->conn), mechanism_get_string((mechanism_t)key)); + agent_t agent = (agent_t)value; + agent_destroy(agent); + + return true; + }); +} + +void engine_interrupt_agent(engine_t engine) +{ + _cf_dictionary_iterate(engine->mechanism_agents, ^bool(CFTypeRef key __attribute__((__unused__)), CFTypeRef value) { + agent_t agent = (agent_t)value; + agent_notify_interrupt(agent); + return true; + }); +}