#include "server.h"
#include "session.h"
#include "process.h"
#include "authtoken.h"
#include "authdb.h"
#include "rule.h"
#include "authutilities.h"
#include "crc.h"
#include "mechanism.h"
#include "agent.h"
#include "authitems.h"
#include "debugging.h"
#include "engine.h"
#include "connection.h"
#include <bsm/libbsm.h>
#include <Security/Authorization.h>
#include <Security/AuthorizationPriv.h>
#include <Security/AuthorizationTagsPriv.h>
#include <Security/AuthorizationPlugin.h>
#include <xpc/private.h>
#include <dispatch/dispatch.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFXPCBridge.h>
#include <IOKit/IOMessage.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <IOKit/pwr_mgt/IOPMLibPrivate.h>
#define MAX_PROCESS_RIGHTS 100
static CFMutableDictionaryRef gProcessMap = NULL;
static CFMutableDictionaryRef gSessionMap = NULL;
static CFMutableDictionaryRef gAuthTokenMap = NULL;
static authdb_t gDatabase = NULL;
static dispatch_queue_t power_queue;
static bool gInDarkWake = false;
static IOPMConnection gIOPMconn = NULL;
static bool gXPCTransaction = false;
static dispatch_queue_t
get_server_dispatch_queue()
{
static dispatch_once_t onceToken;
static dispatch_queue_t server_queue = NULL;
dispatch_once(&onceToken, ^{
server_queue = dispatch_queue_create("com.apple.security.auth.server", DISPATCH_QUEUE_SERIAL);
check(server_queue != NULL);
});
return server_queue;
}
static Boolean _processEqualCallBack(const void *value1, const void *value2)
{
audit_info_s * info1 = (audit_info_s*)value1;
audit_info_s * info2 = (audit_info_s*)value2;
if (info1->pid == info2->pid) {
if (info1->tid == info2->tid) {
return true;
}
}
return false;
}
static CFHashCode _processHashCallBack(const void *value)
{
audit_info_s * info = (audit_info_s*)value;
uint64_t crc = crc64_init();
crc = crc64_update(crc, &info->pid, sizeof(info->pid));
crc = crc64_update(crc, &info->tid, sizeof(info->tid));
crc = crc64_final(crc);
return (CFHashCode)crc;
}
static const CFDictionaryKeyCallBacks kProcessMapKeyCallBacks = {
.version = 0,
.retain = NULL,
.release = NULL,
.copyDescription = NULL,
.equal = &_processEqualCallBack,
.hash = &_processHashCallBack
};
static Boolean _sessionEqualCallBack(const void *value1, const void *value2)
{
return (*(session_id_t*)value1) == (*(session_id_t*)value2);
}
static CFHashCode _sessionHashCallBack(const void *value)
{
return (CFHashCode)(*(session_id_t*)(value));
}
static const CFDictionaryKeyCallBacks kSessionMapKeyCallBacks = {
.version = 0,
.retain = NULL,
.release = NULL,
.copyDescription = NULL,
.equal = &_sessionEqualCallBack,
.hash = &_sessionHashCallBack
};
void server_cleanup()
{
CFRelease(gProcessMap);
CFRelease(gSessionMap);
CFRelease(gAuthTokenMap);
IOPMConnectionSetDispatchQueue(gIOPMconn, NULL);
IOPMConnectionRelease(gIOPMconn);
dispatch_queue_t queue = get_server_dispatch_queue();
if (queue) {
dispatch_release(queue);
}
dispatch_release(power_queue);
}
static void _IOMPCallBack(void * param AUTH_UNUSED, IOPMConnection connection, IOPMConnectionMessageToken token, IOPMSystemPowerStateCapabilities capabilities)
{
LOGV("server: IOMP powerstates %i", capabilities);
if (capabilities & kIOPMSystemPowerStateCapabilityDisk)
LOGV("server: disk");
if (capabilities & kIOPMSystemPowerStateCapabilityNetwork)
LOGV("server: net");
if (capabilities & kIOPMSystemPowerStateCapabilityAudio)
LOGV("server: audio");
if (capabilities & kIOPMSystemPowerStateCapabilityVideo)
LOGV("server: video");
LOGD("server: DarkWake check current=%i==%i", (capabilities & (kIOPMSystemPowerStateCapabilityCPU|kIOPMSystemPowerStateCapabilityVideo)), kIOPMSystemPowerStateCapabilityCPU);
if ((capabilities & (kIOPMSystemPowerStateCapabilityCPU|kIOPMSystemPowerStateCapabilityVideo)) == kIOPMSystemPowerStateCapabilityCPU) {
LOGV("server: enter DarkWake");
gInDarkWake = true;
} else if (gInDarkWake) {
LOGV("server: exit DarkWake");
gInDarkWake = false;
}
(void)IOPMConnectionAcknowledgeEvent(connection, token);
return;
}
static void
_setupDarkWake(void *__unused ctx)
{
IOReturn ret;
IOPMConnectionCreate(CFSTR("IOPowerWatcher"),
kIOPMSystemPowerStateCapabilityDisk
| kIOPMSystemPowerStateCapabilityNetwork
| kIOPMSystemPowerStateCapabilityAudio
| kIOPMSystemPowerStateCapabilityVideo,
&gIOPMconn);
ret = IOPMConnectionSetNotification(gIOPMconn, NULL, _IOMPCallBack);
if (ret != kIOReturnSuccess)
return;
IOPMConnectionSetDispatchQueue(gIOPMconn, power_queue);
IOPMScheduleUserActiveChangedNotification(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(bool active) {
if (active) {
gInDarkWake = false;
}
});
}
bool server_in_dark_wake()
{
return gInDarkWake;
}
authdb_t server_get_database()
{
return gDatabase;
}
static void _setupAuditSessionMonitor()
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
au_sdev_handle_t *dev = au_sdev_open(AU_SDEVF_ALLSESSIONS);
int event;
auditinfo_addr_t aia;
if (NULL == dev) {
LOGE("server: could not open %s %d", AUDIT_SDEV_PATH, errno);
return;
}
for (;;) {
if (0 != au_sdev_read_aia(dev, &event, &aia)) {
LOGE("server: au_sdev_read_aia failed: %d", errno);
continue;
}
LOGD("server: au_sdev_handle_t event=%i, session=%i", event, aia.ai_asid);
if (event == AUE_SESSION_END) {
dispatch_async(get_server_dispatch_queue(), ^{
LOGV("server: session %i destroyed", aia.ai_asid);
CFDictionaryRemoveValue(gSessionMap, &aia.ai_asid);
});
}
}
});
}
static void _setupSignalHandlers()
{
signal(SIGTERM, SIG_IGN);
static dispatch_source_t sigtermHandler;
sigtermHandler = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGTERM, 0, get_server_dispatch_queue());
if (sigtermHandler) {
dispatch_source_set_event_handler(sigtermHandler, ^{
exit(EXIT_SUCCESS);
});
dispatch_resume(sigtermHandler);
}
}
OSStatus server_init(void)
{
OSStatus status = errAuthorizationSuccess;
auditinfo_addr_t info;
memset(&info, 0, sizeof(info));
getaudit_addr(&info, sizeof(info));
LOGV("server: uid=%i, sid=%i", info.ai_auid, info.ai_asid);
require_action(get_server_dispatch_queue() != NULL, done, status = errAuthorizationInternal);
gProcessMap = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kProcessMapKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
require_action(gProcessMap != NULL, done, status = errAuthorizationInternal);
gSessionMap = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kSessionMapKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
require_action(gSessionMap != NULL, done, status = errAuthorizationInternal);
gAuthTokenMap = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kAuthTokenKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
require_action(gAuthTokenMap != NULL, done, status = errAuthorizationInternal);
gDatabase = authdb_create();
require_action(gDatabase != NULL, done, status = errAuthorizationInternal);
authdb_connection_t dbconn = authdb_connection_acquire(gDatabase);
authdb_maintenance(dbconn);
authdb_connection_release(&dbconn);
power_queue = dispatch_queue_create("com.apple.security.auth.power", DISPATCH_QUEUE_SERIAL);
require_action(power_queue != NULL, done, status = errAuthorizationInternal);
dispatch_async_f(power_queue, NULL, _setupDarkWake);
_setupAuditSessionMonitor();
_setupSignalHandlers();
done:
return status;
}
static void _server_parse_audit_token(audit_token_t * token, audit_info_s * info)
{
if (token && info) {
memset(info, 0, sizeof(*info));
au_tid_t tid;
memset(&tid, 0, sizeof(tid));
audit_token_to_au32(*token, &info->auid, &info->euid,
&info->egid, &info->ruid, &info->rgid,
&info->pid, &info->asid, &tid);
info->tid = tid.port;
info->opaqueToken = *token;
}
}
connection_t
server_register_connection(xpc_connection_t connection)
{
__block connection_t conn = NULL;
__block session_t session = NULL;
__block process_t proc = NULL;
__block CFIndex conn_count = 0;
require(connection != NULL, done);
audit_token_t auditToken;
audit_info_s info;
xpc_connection_get_audit_token(connection, &auditToken);
_server_parse_audit_token(&auditToken, &info);
dispatch_sync(get_server_dispatch_queue(), ^{
session = (session_t)CFDictionaryGetValue(gSessionMap, &info.asid);
if (session) {
CFRetain(session);
} else {
session = session_create(info.asid);
CFDictionarySetValue(gSessionMap, session_get_key(session), session);
}
proc = (process_t)CFDictionaryGetValue(gProcessMap, &info);
if (proc) {
CFRetain(proc);
}
if (proc) {
conn = connection_create(proc);
conn_count = process_add_connection(proc, conn);
} else {
proc = process_create(&info, session);
if (proc) {
conn = connection_create(proc);
conn_count = process_add_connection(proc, conn);
session_add_process(session, proc);
CFDictionarySetValue(gProcessMap, process_get_key(proc), proc);
}
}
if (!gXPCTransaction) {
xpc_transaction_begin();
gXPCTransaction = true;
}
});
LOGV("server[%i]: registered connection (total=%li)", info.pid, conn_count);
done:
CFReleaseSafe(session);
CFReleaseSafe(proc);
return conn;
}
void
server_unregister_connection(connection_t conn)
{
assert(conn); process_t proc = connection_get_process(conn);
dispatch_sync(get_server_dispatch_queue(), ^{
CFIndex connectionCount = process_get_connection_count(proc);
LOGV("server[%i]: unregistered connection (total=%li)", process_get_pid(proc), connectionCount);
if (connectionCount == 1) {
CFDictionaryRemoveValue(gProcessMap, process_get_key(proc));
}
if (CFDictionaryGetCount(gProcessMap) == 0) {
xpc_transaction_end();
gXPCTransaction = false;
}
});
CFRelease(conn);
}
void
server_register_auth_token(auth_token_t auth)
{
assert(auth); dispatch_sync(get_server_dispatch_queue(), ^{
LOGV("server: registering auth %p", auth);
CFDictionarySetValue(gAuthTokenMap, auth_token_get_key(auth), auth);
auth_token_set_state(auth, auth_token_state_registered);
});
}
void
server_unregister_auth_token(auth_token_t auth)
{
assert(auth);
AuthorizationBlob blob = *(AuthorizationBlob*)auth_token_get_key(auth);
dispatch_async(get_server_dispatch_queue(), ^{
LOGV("server: unregistering auth %p", auth);
CFDictionaryRemoveValue(gAuthTokenMap, &blob);
});
}
auth_token_t
server_find_copy_auth_token(AuthorizationBlob * blob)
{
assert(blob); __block auth_token_t auth = NULL;
dispatch_sync(get_server_dispatch_queue(), ^{
auth = (auth_token_t)CFDictionaryGetValue(gAuthTokenMap, blob);
if (auth) {
CFRetain(auth);
}
});
return auth;
}
session_t
server_find_copy_session(session_id_t sid, bool create)
{
__block session_t session = NULL;
dispatch_sync(get_server_dispatch_queue(), ^{
session = (session_t)CFDictionaryGetValue(gSessionMap, &sid);
if (session) {
CFRetain(session);
} else if (create) {
session = session_create(sid);
if (session) {
CFDictionarySetValue(gSessionMap, session_get_key(session), session);
}
}
});
return session;
}
#pragma mark -
#pragma mark API
static OSStatus
_process_find_copy_auth_token_from_xpc(process_t proc, xpc_object_t message, auth_token_t * auth_out)
{
OSStatus status = errAuthorizationSuccess;
require_action(auth_out != NULL, done, status = errAuthorizationInternal);
size_t len;
AuthorizationBlob * blob = (AuthorizationBlob *)xpc_dictionary_get_data(message, AUTH_XPC_BLOB, &len);
require_action(blob != NULL, done, status = errAuthorizationInvalidRef);
require_action(len == sizeof(AuthorizationBlob), done, status = errAuthorizationInvalidRef);
auth_token_t auth = process_find_copy_auth_token(proc, blob);
require_action(auth != NULL, done, status = errAuthorizationInvalidRef);
#if DEBUG
LOGV("server[%i]: authtoken lookup %#x%x %p", process_get_pid(proc), blob->data[1],blob->data[0], auth);
#else
LOGV("server[%i]: authtoken lookup %p", process_get_pid(proc), auth);
#endif
*auth_out = auth;
done:
return status;
}
static OSStatus _server_authorize(connection_t conn, auth_token_t auth, AuthorizationFlags flags, auth_rights_t rights, auth_items_t environment, engine_t * engine_out)
{
__block OSStatus status = errAuthorizationDenied;
engine_t engine = NULL;
require_action(conn, done, status = errAuthorizationInternal);
engine = engine_create(conn, auth);
require_action(engine, done, status = errAuthorizationInternal);
if (flags & kAuthorizationFlagInteractionAllowed) {
dispatch_sync(connection_get_dispatch_queue(conn), ^{
connection_set_engine(conn, engine);
status = engine_authorize(engine, rights, environment, flags);
connection_set_engine(conn, NULL);
});
} else {
status = engine_authorize(engine, rights, environment, flags);
}
done:
if (engine) {
if (engine_out) {
*engine_out = engine;
} else {
CFRelease(engine);
}
}
return status;
}
OSStatus
authorization_create(connection_t conn, xpc_object_t message, xpc_object_t reply)
{
OSStatus status = errAuthorizationDenied;
process_t proc = connection_get_process(conn);
auth_rights_t rights = auth_rights_create_with_xpc(xpc_dictionary_get_value(message, AUTH_XPC_RIGHTS));
auth_items_t environment = auth_items_create_with_xpc(xpc_dictionary_get_value(message, AUTH_XPC_ENVIRONMENT));
AuthorizationFlags flags = (AuthorizationFlags)xpc_dictionary_get_uint64(message, AUTH_XPC_FLAGS);
auth_token_t auth = auth_token_create(proc, flags & kAuthorizationFlagLeastPrivileged);
require_action(auth != NULL, done, status = errAuthorizationInternal);
if (!(flags & kAuthorizationFlagNoData)) {
process_add_auth_token(proc,auth);
}
status = _server_authorize(conn, auth, flags, rights, environment, NULL);
require_noerr(status, done);
xpc_dictionary_set_data(reply, AUTH_XPC_BLOB, auth_token_get_blob(auth), sizeof(AuthorizationBlob));
done:
CFReleaseSafe(rights);
CFReleaseSafe(environment);
CFReleaseSafe(auth);
return status;
}
OSStatus authorization_create_with_audit_token(connection_t conn, xpc_object_t message, xpc_object_t reply)
{
OSStatus status = errAuthorizationDenied;
auth_token_t auth = NULL;
process_t proc = connection_get_process(conn);
require(process_get_uid(proc) == 0, done);
size_t len = 0;
const char * data = xpc_dictionary_get_data(message, AUTH_XPC_DATA, &len);
require(data != NULL, done);
require(len == sizeof(audit_token_t), done);
AuthorizationFlags flags = (AuthorizationFlags)xpc_dictionary_get_uint64(message, AUTH_XPC_FLAGS);
audit_info_s auditInfo;
_server_parse_audit_token((audit_token_t*)data, &auditInfo);
auth = auth_token_create(proc, flags & kAuthorizationFlagLeastPrivileged);
require_action(auth != NULL, done, status = errAuthorizationInternal);
process_add_auth_token(proc,auth);
xpc_dictionary_set_data(reply, AUTH_XPC_BLOB, auth_token_get_blob(auth), sizeof(AuthorizationBlob));
done:
CFReleaseSafe(auth);
return status;
}
OSStatus
authorization_free(connection_t conn, xpc_object_t message, xpc_object_t reply AUTH_UNUSED)
{
OSStatus status = errAuthorizationSuccess;
AuthorizationFlags flags = 0;
process_t proc = connection_get_process(conn);
auth_token_t auth = NULL;
status = _process_find_copy_auth_token_from_xpc(proc, message, &auth);
require_noerr(status, done);
flags = (AuthorizationFlags)xpc_dictionary_get_uint64(message, AUTH_XPC_FLAGS);
if (flags & kAuthorizationFlagDestroyRights) {
auth_token_credentials_iterate(auth, ^bool(credential_t cred) {
credential_invalidate(cred);
LOGV("engine[%i]: invalidating %scredential %s (%i) from authorization (%p)", connection_get_pid(conn), credential_get_shared(cred) ? "shared " : "", credential_get_name(cred), credential_get_uid(cred), auth);
return true;
});
session_credentials_purge(auth_token_get_session(auth));
}
process_remove_auth_token(proc, auth, flags);
done:
CFReleaseSafe(auth);
LOGV("server[%i]: AuthorizationFree %d (flags:%x)", connection_get_pid(conn), (int)status, (unsigned int)flags);
return status;
}
OSStatus
authorization_copy_rights(connection_t conn, xpc_object_t message, xpc_object_t reply)
{
OSStatus status = errAuthorizationDenied;
engine_t engine = NULL;
process_t proc = connection_get_process(conn);
auth_rights_t rights = auth_rights_create_with_xpc(xpc_dictionary_get_value(message, AUTH_XPC_RIGHTS));
auth_items_t environment = auth_items_create_with_xpc(xpc_dictionary_get_value(message, AUTH_XPC_ENVIRONMENT));
AuthorizationFlags flags = (AuthorizationFlags)xpc_dictionary_get_uint64(message, AUTH_XPC_FLAGS);
auth_token_t auth = NULL;
status = _process_find_copy_auth_token_from_xpc(proc, message, &auth);
require_noerr(status, done);
require_noerr_action_quiet(status, done, LOGE("copy_rights: no auth token"));
status = _server_authorize(conn, auth, flags, rights, environment, &engine);
require_noerr_action_quiet(status, done, LOGE("copy_rights: _server_authorize failed"));
xpc_object_t outItems = auth_rights_export_xpc(engine_get_granted_rights(engine));
xpc_dictionary_set_value(reply, AUTH_XPC_OUT_ITEMS, outItems);
xpc_release_safe(outItems);
done:
CFReleaseSafe(rights);
CFReleaseSafe(environment);
CFReleaseSafe(auth);
CFReleaseSafe(engine);
return status;
}
OSStatus
authorization_copy_info(connection_t conn, xpc_object_t message, xpc_object_t reply)
{
OSStatus status = errAuthorizationSuccess;
auth_items_t items = NULL;
const char * tag = NULL;
process_t proc = connection_get_process(conn);
auth_token_t auth = NULL;
status = _process_find_copy_auth_token_from_xpc(proc, message, &auth);
require_noerr(status, done);
items = auth_items_create();
tag = xpc_dictionary_get_string(message, AUTH_XPC_TAG);
LOGV("server[%i]: requested tag: %s", connection_get_pid(conn), tag ? tag : "(all)");
if (tag) {
size_t len;
const void * data = auth_items_get_data_with_flags(auth_token_get_context(auth), tag, &len, kAuthorizationContextFlagExtractable);
if (data)
auth_items_set_data(items, tag, data, len);
} else {
auth_items_copy_with_flags(items, auth_token_get_context(auth), kAuthorizationContextFlagExtractable);
}
#if DEBUG
LOGV("server[%i]: Dumping requested AuthRef items", connection_get_pid(conn));
_show_cf(items);
#endif
xpc_object_t outItems = auth_items_export_xpc(items);
xpc_dictionary_set_value(reply, AUTH_XPC_OUT_ITEMS, outItems);
xpc_release_safe(outItems);
done:
CFReleaseSafe(items);
CFReleaseSafe(auth);
LOGV("server[%i]: AuthorizationCopyInfo %i", connection_get_pid(conn), (int) status);
return status;
}
OSStatus
authorization_make_external_form(connection_t conn, xpc_object_t message, xpc_object_t reply)
{
OSStatus status = errAuthorizationSuccess;
process_t proc = connection_get_process(conn);
auth_token_t auth = NULL;
status = _process_find_copy_auth_token_from_xpc(proc, message, &auth);
require_noerr(status, done);
AuthorizationExternalForm exForm;
AuthorizationExternalBlob * exBlob = (AuthorizationExternalBlob *)&exForm;
memset(&exForm, 0, sizeof(exForm));
exBlob->blob = *auth_token_get_blob(auth);
exBlob->session = process_get_session_id(proc);
xpc_dictionary_set_data(reply, AUTH_XPC_EXTERNAL, &exForm, sizeof(exForm));
server_register_auth_token(auth);
done:
CFReleaseSafe(auth);
LOGV("server[%i]: AuthorizationMakeExternalForm %d", connection_get_pid(conn), (int)status);
return status;
}
OSStatus
authorization_create_from_external_form(connection_t conn, xpc_object_t message, xpc_object_t reply)
{
OSStatus status = errAuthorizationSuccess;
auth_token_t auth = NULL;
process_t proc = connection_get_process(conn);
size_t len;
AuthorizationExternalForm * exForm = (AuthorizationExternalForm *)xpc_dictionary_get_data(message, AUTH_XPC_EXTERNAL, &len);
require_action(exForm != NULL, done, status = errAuthorizationInternal);
require_action(len == sizeof(AuthorizationExternalForm), done, status = errAuthorizationInvalidRef);
AuthorizationExternalBlob * exBlob = (AuthorizationExternalBlob *)exForm;
auth = server_find_copy_auth_token(&exBlob->blob);
require_action(auth != NULL, done, status = errAuthorizationDenied);
process_add_auth_token(proc, auth);
xpc_dictionary_set_data(reply, AUTH_XPC_BLOB, auth_token_get_blob(auth), sizeof(AuthorizationBlob));
done:
CFReleaseSafe(auth);
LOGV("server[%i]: AuthorizationCreateFromExternalForm %d", connection_get_pid(conn), (int)status);
return status;
}
OSStatus
authorization_right_get(connection_t conn AUTH_UNUSED, xpc_object_t message, xpc_object_t reply)
{
OSStatus status = errAuthorizationDenied;
rule_t rule = NULL;
CFTypeRef cfdict = NULL;
xpc_object_t xpcdict = NULL;
authdb_connection_t dbconn = authdb_connection_acquire(server_get_database());
rule = rule_create_with_string(xpc_dictionary_get_string(message, AUTH_XPC_RIGHT_NAME), dbconn);
require(rule != NULL, done);
require(rule_get_id(rule) != 0, done);
cfdict = rule_copy_to_cfobject(rule, dbconn);
require(cfdict != NULL, done);
xpcdict = _CFXPCCreateXPCObjectFromCFObject(cfdict);
require(xpcdict != NULL, done);
xpc_dictionary_set_value(reply, AUTH_XPC_DATA, xpcdict);
status = errAuthorizationSuccess;
done:
authdb_connection_release(&dbconn);
CFReleaseSafe(cfdict);
xpc_release_safe(xpcdict);
CFReleaseSafe(rule);
LOGV("server[%i]: AuthorizationRightGet %d", connection_get_pid(conn), (int)status);
return status;
}
static bool _prompt_for_modifications(process_t __unused proc, rule_t __unused rule)
{
return true;
}
static CFIndex _get_mechanism_index(CFArrayRef mechanisms, CFStringRef m_name)
{
CFIndex index = -1;
require(mechanisms, done);
CFIndex c = CFArrayGetCount(mechanisms);
CFStringRef i_name = NULL;
for (CFIndex i = 0; i < c; ++i)
{
i_name = CFArrayGetValueAtIndex(mechanisms, i);
if (i_name && (CFGetTypeID(m_name) == CFStringGetTypeID())) {
if (CFStringCompare(i_name, m_name, kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
index = i;
break;
}
}
}
done:
return index;
}
static bool _update_rule_mechanism(authdb_connection_t dbconn, const char * rule_name, CFStringRef mechanism_name, CFStringRef insert_after_name, bool remove)
{
bool updated = false;
rule_t rule = NULL;
rule_t update_rule = NULL;
CFMutableDictionaryRef cfdict = NULL;
CFStringRef update_name = NULL;
require(mechanism_name, done);
rule = rule_create_with_string(rule_name, dbconn);
require(rule_get_id(rule) != 0, done);
cfdict = rule_copy_to_cfobject(rule, dbconn);
require(cfdict != NULL, done);
CFMutableArrayRef mechanisms = NULL;
bool res = CFDictionaryGetValueIfPresent(cfdict, CFSTR(kAuthorizationRuleParameterMechanisms), (void*)&mechanisms);
require(res == true, done);
CFIndex index = -1;
if (remove) {
index = _get_mechanism_index(mechanisms, mechanism_name);
} else {
if (insert_after_name) {
if ((index = _get_mechanism_index(mechanisms, insert_after_name)) != -1) {
index++;
} else {
index = 0; }
} else {
index = 0;
}
}
if (index != -1) {
if(remove) {
CFArrayRemoveValueAtIndex(mechanisms, index);
} else {
if (index < CFArrayGetCount(mechanisms)) {
require_action(CFStringCompare(CFArrayGetValueAtIndex(mechanisms, index), mechanism_name, kCFCompareCaseInsensitive) != kCFCompareEqualTo, done, updated = true);
}
CFArrayInsertValueAtIndex(mechanisms, index, mechanism_name);
}
CFDictionarySetValue(cfdict, CFSTR(kAuthorizationRuleParameterMechanisms), mechanisms);
update_name = CFStringCreateWithCString(kCFAllocatorDefault, rule_name, kCFStringEncodingUTF8);
require(update_name, done);
update_rule = rule_create_with_plist(rule_get_type(rule), update_name, cfdict, dbconn);
require(update_rule, done);
require(rule_sql_commit(update_rule, dbconn, CFAbsoluteTimeGetCurrent(), NULL), done);
}
updated = true;
done:
CFReleaseSafe(rule);
CFReleaseSafe(update_rule);
CFReleaseSafe(cfdict);
CFReleaseSafe(update_name);
return updated;
}
OSStatus
authorization_enable_smartcard(connection_t conn, xpc_object_t message, xpc_object_t reply AUTH_UNUSED)
{
const CFStringRef SMARTCARD_LINE = CFSTR("builtin:smartcard-sniffer,privileged");
const CFStringRef BUILTIN_LINE = CFSTR("builtin:policy-banner");
const char* SYSTEM_LOGIN_CONSOLE = "system.login.console";
const char* AUTHENTICATE = "authenticate";
__block OSStatus status = errAuthorizationSuccess;
bool enable_smartcard = false;
authdb_connection_t dbconn = NULL;
auth_token_t auth = NULL;
auth_rights_t checkRight = NULL;
process_t proc = connection_get_process(conn);
status = _process_find_copy_auth_token_from_xpc(proc, message, &auth);
require_noerr(status, done);
checkRight = auth_rights_create();
auth_rights_add(checkRight, "config.modify.smartcard");
status = _server_authorize(conn, auth, kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights, checkRight, NULL, NULL);
require_noerr(status, done);
enable_smartcard = xpc_dictionary_get_bool(message, AUTH_XPC_DATA);
dbconn = authdb_connection_acquire(server_get_database());
if (!_update_rule_mechanism(dbconn, SYSTEM_LOGIN_CONSOLE, SMARTCARD_LINE, BUILTIN_LINE, enable_smartcard ? false : true)) {
status = errAuthorizationInternal;
LOGE("server[%i]: smartcard: enable(%i) failed to update %s", connection_get_pid(conn), enable_smartcard, SYSTEM_LOGIN_CONSOLE);
}
if (!_update_rule_mechanism(dbconn, AUTHENTICATE, SMARTCARD_LINE, NULL, enable_smartcard ? false : true)) {
status = errAuthorizationInternal;
LOGE("server[%i]: smartcard: enable(%i) failed to update %s", connection_get_pid(conn), enable_smartcard, AUTHENTICATE);
}
authdb_checkpoint(dbconn);
done:
authdb_connection_release(&dbconn);
CFReleaseSafe(checkRight);
CFReleaseSafe(auth);
return status;
}
static int64_t _process_get_identifier_count(process_t proc, authdb_connection_t conn)
{
__block int64_t result = 0;
authdb_step(conn, "SELECT COUNT(*) AS cnt FROM rules WHERE identifier = ? ", ^(sqlite3_stmt *stmt) {
sqlite3_bind_text(stmt, 1, process_get_identifier(proc), -1, NULL);
}, ^bool(auth_items_t data) {
result = auth_items_get_int64(data, "cnt");
return true;
});
return result;
}
static int64_t _get_max_process_rights()
{
static dispatch_once_t onceToken;
static int64_t max_rights = MAX_PROCESS_RIGHTS;
dispatch_once(&onceToken, ^{
CFTypeRef max = (CFNumberRef)CFPreferencesCopyValue(CFSTR("max_process_rights"), CFSTR(SECURITY_AUTH_NAME), kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
if (max && CFGetTypeID(max) == CFNumberGetTypeID()) {
CFNumberGetValue(max, kCFNumberSInt64Type, &max_rights);
}
CFReleaseSafe(max);
});
return max_rights;
}
OSStatus
authorization_right_set(connection_t conn, xpc_object_t message, xpc_object_t reply AUTH_UNUSED)
{
__block OSStatus status = errAuthorizationDenied;
__block engine_t engine = NULL;
CFStringRef cf_rule_name = NULL;
CFDictionaryRef cf_rule_dict = NULL;
rule_t rule = NULL;
rule_t existingRule = NULL;
authdb_connection_t dbconn = NULL;
auth_token_t auth = NULL;
bool force_modify = false;
RuleType rule_type = RT_RIGHT;
const char * rule_name = NULL;
bool auth_rule = false;
process_t proc = connection_get_process(conn);
status = _process_find_copy_auth_token_from_xpc(proc, message, &auth);
require_noerr(status, done);
require_action(xpc_dictionary_get_string(message, AUTH_XPC_RIGHT_NAME) != NULL, done, status = errAuthorizationInternal);
require_action(xpc_dictionary_get_value(message, AUTH_XPC_DATA) != NULL, done, status = errAuthorizationInternal);
rule_name = xpc_dictionary_get_string(message, AUTH_XPC_RIGHT_NAME);
require(rule_name != NULL, done);
if (_compare_string(rule_name, "authenticate")) {
rule_type = RT_RULE;
auth_rule = true;
}
cf_rule_name = CFStringCreateWithCString(kCFAllocatorDefault, rule_name, kCFStringEncodingUTF8);
require(cf_rule_name != NULL, done);
cf_rule_dict = _CFXPCCreateCFObjectFromXPCObject(xpc_dictionary_get_value(message, AUTH_XPC_DATA));
require(cf_rule_dict != NULL, done);
dbconn = authdb_connection_acquire(server_get_database());
rule = rule_create_with_plist(rule_type, cf_rule_name, cf_rule_dict, dbconn);
if (process_get_uid(proc) != 0) {
require_action(rule_get_extract_password(rule) == false, done, status = errAuthorizationDenied; LOGE("server[%i]: AuthorizationRightSet not allowed to set extract-password. (denied)", connection_get_pid(conn)));
}
if (rule_get_id(rule) == 0) {
if (process_get_identifier(proc) == NULL) {
LOGE("server[%i]: AuthorizationRightSet required for process %s (missing code signature). To add rights to the Authorization database, your process must have a code signature.", connection_get_pid(conn), process_get_code_url(proc));
force_modify = true;
} else {
int64_t process_rule_count = _process_get_identifier_count(proc, dbconn);
if ((process_rule_count >= _get_max_process_rights())) {
if (!connection_get_syslog_warn(conn)) {
LOGE("server[%i]: AuthorizationRightSet Denied API abuse process %s already contains %lli rights.", connection_get_pid(conn), process_get_code_url(proc), _get_max_process_rights());
connection_set_syslog_warn(conn);
}
status = errAuthorizationDenied;
goto done;
}
}
} else {
if (auth_rule) {
if (process_get_uid(proc) != 0) {
LOGE("server[%i]: AuthorizationRightSet denied, root required to update the 'authenticate' rule", connection_get_pid(conn));
status = errAuthorizationDenied;
goto done;
}
} else {
existingRule = rule_create_with_string(rule_get_name(rule), dbconn);
if (rule_get_type(existingRule) == RT_RULE) {
LOGE("server[%i]: AuthorizationRightSet Denied updating '%s' rule is prohibited", connection_get_pid(conn), rule_get_name(existingRule));
status = errAuthorizationDenied;
goto done;
}
}
}
if (_prompt_for_modifications(proc,rule)) {
authdb_connection_release(&dbconn);
dispatch_sync(connection_get_dispatch_queue(conn), ^{
engine = engine_create(conn, auth);
connection_set_engine(conn, engine);
status = engine_verify_modification(engine, rule, false, force_modify);
connection_set_engine(conn, NULL);
});
require_noerr(status, done);
dbconn = authdb_connection_acquire(server_get_database());
}
if (rule_sql_commit(rule, dbconn, engine ? engine_get_time(engine) : CFAbsoluteTimeGetCurrent(), proc)) {
LOGV("server[%i]: Successfully updated rule %s", connection_get_pid(conn), rule_get_name(rule));
authdb_checkpoint(dbconn);
status = errAuthorizationSuccess;
} else {
LOGE("server[%i]: Failed to update rule %s", connection_get_pid(conn), rule_get_name(rule));
status = errAuthorizationDenied;
}
done:
authdb_connection_release(&dbconn);
CFReleaseSafe(existingRule);
CFReleaseSafe(cf_rule_name);
CFReleaseSafe(cf_rule_dict);
CFReleaseSafe(auth);
CFReleaseSafe(rule);
CFReleaseSafe(engine);
return status;
}
OSStatus
authorization_right_remove(connection_t conn, xpc_object_t message, xpc_object_t reply AUTH_UNUSED)
{
__block OSStatus status = errAuthorizationDenied;
__block engine_t engine = NULL;
rule_t rule = NULL;
authdb_connection_t dbconn = NULL;
process_t proc = connection_get_process(conn);
auth_token_t auth = NULL;
status = _process_find_copy_auth_token_from_xpc(proc, message, &auth);
require_noerr(status, done);
dbconn = authdb_connection_acquire(server_get_database());
rule = rule_create_with_string(xpc_dictionary_get_string(message, AUTH_XPC_RIGHT_NAME), dbconn);
require(rule != NULL, done);
if (_prompt_for_modifications(proc,rule)) {
authdb_connection_release(&dbconn);
dispatch_sync(connection_get_dispatch_queue(conn), ^{
engine = engine_create(conn, auth);
connection_set_engine(conn, engine);
status = engine_verify_modification(engine, rule, true, false);
connection_set_engine(conn, NULL);
});
require_noerr(status, done);
dbconn = authdb_connection_acquire(server_get_database());
}
if (rule_get_id(rule) != 0) {
rule_sql_remove(rule, dbconn);
}
done:
authdb_connection_release(&dbconn);
CFReleaseSafe(auth);
CFReleaseSafe(rule);
CFReleaseSafe(engine);
LOGV("server[%i]: AuthorizationRightRemove %d", connection_get_pid(conn), (int)status);
return status;
}
#pragma mark -
#pragma mark test code
OSStatus
session_set_user_preferences(connection_t conn, xpc_object_t message, xpc_object_t reply)
{
(void)conn;
(void)message;
(void)reply;
return errAuthorizationSuccess;
}
void
server_dev() {
}