#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFBridgingPriv.h>
#include <dispatch/dispatch.h>
#include <assumes.h>
#include <opendirectory/odutils.h>
#include <membership.h>
#include <membershipPriv.h>
#include "CFODRecord.h"
#include "CFODNode.h"
#include "CFOpenDirectoryPriv.h"
#include "internal.h"
#include "format.h"
#include "transaction.h"
#include "extauth.h"
#include "record_internal.h"
#include "context_internal.h"
static CFTypeID __kODRecordTypeID = _kCFRuntimeNotATypeID;
static void
__ODRecordFinalize(CFTypeRef cf)
{
ODRecordRef record = (ODRecordRef)cf;
safe_cfrelease_null(record->_session);
safe_cfrelease_null(record->_realnode);
safe_cfrelease_null(record->_orignode);
safe_cfrelease_null(record->_attrset);
safe_cfrelease_null(record->_attributes);
safe_dispatch_release(record->_attrq);
safe_cfrelease_null(record->_type);
safe_cfrelease_null(record->_name);
safe_cfrelease_null(record->_metaname);
}
static CFStringRef
__ODRecordCopyDebugDesc(CFTypeRef cf)
{
ODRecordRef record = (ODRecordRef)cf;
CFStringRef attr, output;
attr = format_small(record->_attributes);
output = CFStringCreateWithFormat(NULL, NULL, CFSTR("<ODRecord %p [attributes %@]>"), record, attr);
CFRelease(attr);
return output;
}
static const CFRuntimeClass __ODRecordClass = {
0, "ODRecord", NULL, NULL, __ODRecordFinalize, NULL, NULL, NULL, __ODRecordCopyDebugDesc, NULL, #if CF_REFCOUNT_AVAILABLE
NULL, #endif
};
CFTypeID
ODRecordGetTypeID(void)
{
static dispatch_once_t once;
dispatch_once(&once, ^ {
__kODRecordTypeID = _CFRuntimeRegisterClass(&__ODRecordClass);
if (__kODRecordTypeID != _kCFRuntimeNotATypeID) {
_CFRuntimeBridgeClasses(__kODRecordTypeID, "NSODRecord");
}
});
return __kODRecordTypeID;
}
ODNodeRef
_ODRecordGetNode(ODRecordRef record)
{
if (record->_realnode == NULL) {
CFArrayRef metanode = CFDictionaryGetValue(record->_attributes, kODAttributeTypeMetaNodeLocation);
if (metanode && CFArrayGetCount(metanode) > 0) {
record->_realnode = ODNodeCreateWithName(NULL, record->_session, CFArrayGetValueAtIndex(metanode, 0), NULL);
}
if (record->_orignode != NULL) {
(void)_ODNodeSetCredentialsFromOtherNode(record->_realnode, record->_orignode, NULL);
safe_cfrelease_null(record->_orignode);
}
}
(void)osx_assumes(record->_realnode != NULL);
return record->_realnode;
}
static void
__update_name(ODRecordRef record)
{
CFArrayRef tmpval;
tmpval = CFDictionaryGetValue(record->_attributes, kODAttributeTypeRecordName);
if (tmpval && CFArrayGetCount(tmpval) != 0) {
CFStringRef new_name = CFArrayGetValueAtIndex(tmpval, 0);
if (record->_name == NULL || CFStringCompare(record->_name, new_name, 0) != kCFCompareEqualTo) {
safe_cfrelease_null(record->_name);
record->_name = CFStringCreateCopy(NULL, new_name);
}
}
tmpval = CFDictionaryGetValue(record->_attributes, kODAttributeTypeMetaRecordName);
if (tmpval && CFArrayGetCount(tmpval) != 0) {
safe_cfrelease_null(record->_metaname);
record->_metaname = CFStringCreateCopy(NULL, CFArrayGetValueAtIndex(tmpval, 0));
}
}
static void
_RecordUpdate(ODRecordRef record, CFArrayRef more_attrs, bool force_update, void (^callback)(CFDictionaryRef, CFErrorRef))
{
dispatch_sync(record->_attrq, ^ {
CFArrayRef attrs = NULL;
CFArrayRef tmpval;
if (force_update) {
attrs = CFArrayCreateWithSet(record->_attrset);
} else if (more_attrs) {
CFMutableSetRef newattrs;
bool must_update = false;
newattrs = CFSetCreateMutableCopy(NULL, 0, record->_attrset);
CFIndex i;
CFIndex count = CFArrayGetCount(more_attrs);
CFStringRef attr;
for (i = 0; i < count; i++) {
attr = CFArrayGetValueAtIndex(more_attrs, i);
if (!attrset_contains_attribute(record->_attrset, attr)) {
must_update = true;
CFSetAddValue(newattrs, attr);
}
}
if (must_update) {
CFRelease(record->_attrset);
record->_attrset = attrset_create_minimized_copy(newattrs);
attrs = CFArrayCreateWithSet(record->_attrset);
}
CFRelease(newattrs);
}
if (attrs != NULL) {
ODRecordRef tmprecord;
tmprecord = ODNodeCopyRecord(_ODRecordGetNode(record), record->_type, record->_name, attrs, NULL);
if (tmprecord != NULL) {
CFRelease(record->_attributes);
record->_attributes = (CFMutableDictionaryRef)CFRetain(tmprecord->_attributes);
CFRelease(tmprecord);
}
CFRelease(attrs);
}
tmpval = CFDictionaryGetValue(record->_attributes, kODAttributeTypeRecordType);
if (tmpval && CFArrayGetCount(tmpval) != 0) {
if (record->_type == NULL) {
record->_type = CFStringCreateCopy(NULL, CFArrayGetValueAtIndex(tmpval, 0));
}
}
__update_name(record);
if (callback) {
callback(record->_attributes, NULL);
}
});
}
ODRecordRef
_RecordCreate(ODNodeRef node, CFSetRef fetched, CFDictionaryRef attributes)
{
ODRecordRef result;
char qname[256];
result = (ODRecordRef)_CFRuntimeCreateInstance(NULL, ODRecordGetTypeID(), sizeof(struct __ODRecord) - sizeof(CFRuntimeBase), NULL);
result->_session = (ODSessionRef)safe_cfretain(_NodeGetSession(node));
if (attributes != NULL) {
CFArrayRef metanode = CFDictionaryGetValue(attributes, kODAttributeTypeMetaNodeLocation);
if (metanode != NULL && CFArrayGetCount(metanode) > 0) {
if (CFEqual(CFArrayGetValueAtIndex(metanode, 0), ODNodeGetName(node)) == true) {
result->_realnode = (ODNodeRef)CFRetain(node);
}
}
}
if (result->_realnode == NULL) {
result->_orignode = (ODNodeRef)CFRetain(node);
}
snprintf(qname, sizeof(qname), "com.apple.OpenDirectory.ODRecord.%p", result);
result->_attrq = dispatch_queue_create(qname, NULL);
result->_attrset = fetched ? CFRetain(fetched) : CFSetCreate(NULL, NULL, 0, &kCFTypeSetCallBacks);
result->_attributes = (attributes != NULL ? CFDictionaryCreateMutableCopy(NULL, 0, attributes) : CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
_RecordUpdate(result, NULL, false, NULL);
return result;
}
bool
ODRecordSetNodeCredentials(ODRecordRef record, CFStringRef username, CFStringRef password, CFErrorRef *error)
{
CLEAR_ERROR(error);
if (!_validate_nonnull(error, CFSTR("record"), record)) {
return false;
}
if (!_validate_nonnull(error, CFSTR("username"), username)) {
return false;
}
if (!_validate_nonnull(error, CFSTR("password"), password)) {
return false;
}
CF_OBJC_FUNCDISPATCH(__kODRecordTypeID, bool, record, "setNodeCredentials:password:error:", username, password, error);
bool success = false;
ODNodeRef node;
node = ODNodeCreateCopy(CFGetAllocator(record), _ODRecordGetNode(record), error);
if (node != NULL) {
success = ODNodeSetCredentials(node, kODRecordTypeUsers, username, password, error);
if (success) {
CFRelease(record->_realnode);
record->_realnode = node;
} else {
CFRelease(node);
}
}
return success;
}
bool
ODRecordSetNodeCredentialsExtended(ODRecordRef record, ODRecordType recordType, ODAuthenticationType authType, CFArrayRef authItems, CFArrayRef *outAuthItems, ODContextRef *outContext, CFErrorRef *error)
{
CLEAR_ERROR(error);
if (!_validate_nonnull(error, CFSTR("record"), record)) {
return false;
}
if (!_validate_nonnull(error, CFSTR("record type"), recordType)) {
return false;
}
if (!_validate_nonnull(error, CFSTR("authentication type"), authType)) {
return false;
}
if (!_validate_nonnull(error, CFSTR("authentication items"), authItems)) {
return false;
}
bool success = false;
ODNodeRef node;
node = ODNodeCreateCopy(CFGetAllocator(record), _ODRecordGetNode(record), error);
if (node) {
success = ODNodeSetCredentialsExtended(node, recordType, authType, authItems, outAuthItems, outContext, error);
if (success) {
CFRelease(record->_realnode);
record->_realnode = node;
} else {
CFRelease(node);
}
}
return success;
}
bool
ODRecordSetNodeCredentialsUsingKerberosCache(ODRecordRef record, CFStringRef cacheName, CFErrorRef *error)
{
if (error) {
*error = CFErrorCreate(kCFAllocatorDefault, kODErrorDomainFramework, kODErrorCredentialsMethodNotSupported, NULL);
}
return false;
}
CFDictionaryRef
ODRecordCopyPasswordPolicy(CFAllocatorRef allocator, ODRecordRef record, CFErrorRef *error)
{
CLEAR_ERROR(error);
if (!_validate_nonnull(error, CFSTR("record"), record)) {
return NULL;
}
if (CF_IS_OBJC(__kODRecordTypeID, record)) {
CFDictionaryRef policy;
CF_OBJC_CALL(CFDictionaryRef, policy, record, "passwordPolicyAndReturnError:", error);
return policy ? CFRetain(policy) : NULL;
}
__block CFDictionaryRef policy = NULL;
CFArrayRef response;
uint32_t code;
response = transaction_simple(&code, record->_session, _ODRecordGetNode(record), CFSTR("ODRecordCopyPasswordPolicy"), 3, record->_type, record->_name, record->_metaname);
transaction_simple_response(response, code, 1, error, ^ {
policy = schema_get_value_at_index(response, 0);
if (policy) CFRetain(policy);
});
return policy;
}
bool
ODRecordVerifyPassword(ODRecordRef record, CFStringRef password, CFErrorRef *error)
{
CLEAR_ERROR(error);
if (!_validate_nonnull(error, CFSTR("record"), record)) {
return false;
}
if (!_validate_nonnull(error, CFSTR("password"), password)) {
return false;
}
CF_OBJC_FUNCDISPATCH(__kODRecordTypeID, bool, record, "verifyPassword:error:", password, error);
__block bool success = false;
CFArrayRef response;
uint32_t code;
response = transaction_simple(&code, record->_session, _ODRecordGetNode(record), CFSTR("ODRecordVerifyPassword"), 4, record->_type, record->_name, record->_metaname, password);
transaction_simple_response(response, code, 1, error, ^ {
success = true;
});
return success;
}
bool
ODRecordVerifyPasswordExtended(ODRecordRef record, ODAuthenticationType authType, CFArrayRef authItems, CFArrayRef *authItemsOut, ODContextRef *context, CFErrorRef *error)
{
CLEAR_ERROR(error);
if (!_validate_nonnull(error, CFSTR("record"), record)) {
return false;
}
if (!_validate_nonnull(error, CFSTR("authentication type"), authType)) {
return false;
}
if (!_validate_nonnull(error, CFSTR("authentication items"), authItems)) {
return false;
}
CF_OBJC_FUNCDISPATCH(__kODRecordTypeID, bool, record,
"verifyExtendedWithAuthenticationType:authenticationItems:continueItems:context:error:",
authType, authItems, authItemsOut, context, error);
__block bool success = false;
__block CFArrayRef local_items = NULL;
CFArrayRef response;
uint32_t code;
uint32_t mapped_type;
if ((mapped_type = extauth_map_type(authType)) != 0) {
success = extauth_record_verify(record, mapped_type, authItems, &local_items, context, error);
} else {
response = transaction_simple(&code, record->_session, _ODRecordGetNode(record), CFSTR("ODRecordVerifyPasswordExtended"), 6, record->_type, record->_name, record->_metaname, authType, authItems, context ? _ODContextGetUUID(*context) : NULL);
transaction_simple_response(response, code, 3, error, ^ {
success = true;
local_items = safe_cfretain(schema_get_value_at_index(response, 1));
if (context) {
*context = _ODContextCreateWithNodeAndUUID(NULL, _ODRecordGetNode(record), schema_get_value_at_index(response, 2));
}
});
}
if (authItemsOut) {
*authItemsOut = local_items ? local_items : CFArrayCreate(kCFAllocatorDefault, NULL, 0, &kCFTypeArrayCallBacks);
} else if (local_items) {
CFRelease(local_items);
}
return success;
}
bool
ODRecordChangePassword(ODRecordRef record, CFStringRef oldPassword, CFStringRef newPassword, CFErrorRef *error)
{
CLEAR_ERROR(error);
if (!_validate_nonnull(error, CFSTR("record"), record)) {
return false;
}
if (!_validate_nonnull(error, CFSTR("new password"), newPassword)) {
return false;
}
CF_OBJC_FUNCDISPATCH(__kODRecordTypeID, bool, record, "changePassword:toPassword:error:", oldPassword, newPassword, error);
__block bool success = false;
CFArrayRef response;
uint32_t code;
response = transaction_simple(&code, record->_session, _ODRecordGetNode(record), CFSTR("ODRecordChangePassword"), 5, record->_type, record->_name, record->_metaname, oldPassword, newPassword);
transaction_simple_response(response, code, 1, error, ^ {
success = true;
});
return success;
}
CFStringRef
ODRecordGetRecordType(ODRecordRef record)
{
if (!record) {
return NULL;
}
return record->_type;
}
CFStringRef
ODRecordGetRecordName(ODRecordRef record)
{
if (!record) {
return NULL;
}
return record->_name;
}
CFArrayRef
ODRecordCopyValues(ODRecordRef record, ODAttributeType attribute, CFErrorRef *error)
{
CLEAR_ERROR(error);
if (!_validate_nonnull(error, CFSTR("record"), record)) {
return NULL;
}
if (!_validate_nonnull(error, CFSTR("attribute"), attribute)) {
return NULL;
}
if (CF_IS_OBJC(__kODRecordTypeID, record)) {
CFArrayRef values;
CF_OBJC_CALL(CFArrayRef, values, record, "valuesForAttribute:error:", attribute, error);
return values ? CFRetain(values) : NULL;
}
const void *attrvals[1];
CFArrayRef attrs;
__block CFArrayRef values = NULL;
attrvals[0] = attribute;
attrs = CFArrayCreate(NULL, attrvals, 1, &kCFTypeArrayCallBacks);
_RecordUpdate(record, attrs, false, ^(CFDictionaryRef a, CFErrorRef e) {
values = CFDictionaryGetValue(a, attribute);
if (values) {
values = CFArrayCreateCopy(kCFAllocatorDefault, values);
}
});
CFRelease(attrs);
return values;
}
static bool
_attribute_is_immutable(ODAttributeType attribute)
{
bool immutable = false;
if (CFStringCompare(attribute, kODAttributeTypeMetaNodeLocation, 0) == kCFCompareEqualTo) {
immutable = true;
}
return immutable;
}
bool
ODRecordSetValue(ODRecordRef record, ODAttributeType attribute, CFTypeRef value, CFErrorRef *error)
{
CLEAR_ERROR(error);
if (!_validate_nonnull(error, CFSTR("record"), record)) {
return false;
}
if (!_validate_nonnull(error, CFSTR("attribute"), attribute)) {
return false;
}
if (!_validate_nonnull(error, CFSTR("value"), value)) {
return false;
}
CF_OBJC_FUNCDISPATCH(__kODRecordTypeID, bool, record, "setValue:forAttribute:error:", value, attribute, error);
__block bool success = false;
CFArrayRef value_array;
CFArrayRef response;
uint32_t code;
bool is_delete;
if (_attribute_is_immutable(attribute)) {
success = true;
} else {
if (CFGetTypeID(value) == CFArrayGetTypeID()) {
value_array = CFPropertyListCreateDeepCopy(kCFAllocatorDefault, value, kCFPropertyListMutableContainers);
} else {
CFTypeRef valcopy = CFPropertyListCreateDeepCopy(kCFAllocatorDefault, value, kCFPropertyListMutableContainers);
value_array = CFArrayCreate(NULL, (const void **)&valcopy, 1, &kCFTypeArrayCallBacks);
CFRelease(valcopy);
}
is_delete = (CFArrayGetCount(value_array) == 0);
response = transaction_simple(&code, record->_session, _ODRecordGetNode(record), CFSTR("ODRecordSetValue"), 5, record->_type, record->_name, record->_metaname, attribute, value_array);
if (is_delete && code == kODErrorRecordAttributeUnknownType) {
code = 0;
}
transaction_simple_response(response, code, 1, error, ^ {
__block bool issue_update = false;
success = true;
dispatch_sync(record->_attrq, ^ {
CFStringRef attrcopy;
if (is_delete) {
CFDictionaryRemoveValue(record->_attributes, attribute);
} else {
attrcopy = CFStringCreateCopy(kCFAllocatorDefault, attribute);
CFDictionarySetValue(record->_attributes, attrcopy, value_array);
CFRelease(attrcopy);
if (CFStringCompare(attribute, kODAttributeTypeRecordName, 0) == kCFCompareEqualTo) {
__update_name(record);
issue_update = true;
}
}
});
if (issue_update == true) {
_RecordUpdate(record, NULL, true, NULL); }
});
CFRelease(value_array);
}
return success;
}
bool
ODRecordAddValue(ODRecordRef record, ODAttributeType attribute, CFTypeRef value, CFErrorRef *error)
{
CLEAR_ERROR(error);
if (!_validate_nonnull(error, CFSTR("record"), record)) {
return false;
}
if (!_validate_nonnull(error, CFSTR("attribute"), attribute)) {
return false;
}
if (!_validate_nonnull(error, CFSTR("value"), value)) {
return false;
}
CF_OBJC_FUNCDISPATCH(__kODRecordTypeID, bool, record, "addValue:toAttribute:error:", value, attribute, error);
__block bool success = false;
CFArrayRef response;
uint32_t code;
if (_attribute_is_immutable(attribute)) {
success = true;
} else {
response = transaction_simple(&code, record->_session, _ODRecordGetNode(record), CFSTR("ODRecordAddValue"), 5, record->_type, record->_name, record->_metaname, attribute, value);
transaction_simple_response(response, code, 1, error, ^ {
success = true;
dispatch_sync(record->_attrq, ^ {
CFMutableArrayRef new;
CFArrayRef values = CFDictionaryGetValue(record->_attributes, attribute);
CFStringRef attrcopy, valcopy;
if (values != NULL) {
new = CFArrayCreateMutableCopy(NULL, 0, values);
} else {
new = CFArrayCreateMutable(NULL, 1, &kCFTypeArrayCallBacks);
}
valcopy = CFPropertyListCreateDeepCopy(kCFAllocatorDefault, value, kCFPropertyListMutableContainers);
CFArrayAppendValue(new, valcopy);
CFRelease(valcopy);
attrcopy = CFStringCreateCopy(kCFAllocatorDefault, attribute);
CFDictionarySetValue(record->_attributes, attrcopy, new);
CFRelease(attrcopy);
CFRelease(new);
if (CFStringCompare(attribute, kODAttributeTypeRecordName, 0) == kCFCompareEqualTo) {
__update_name(record);
}
});
});
}
return success;
}
bool
ODRecordRemoveValue(ODRecordRef record, ODAttributeType attribute, CFTypeRef value, CFErrorRef *error)
{
CLEAR_ERROR(error);
if (!_validate_nonnull(error, CFSTR("record"), record)) {
return false;
}
if (!_validate_nonnull(error, CFSTR("attribute"), attribute)) {
return false;
}
if (!_validate_nonnull(error, CFSTR("value"), value)) {
return false;
}
CF_OBJC_FUNCDISPATCH(__kODRecordTypeID, bool, record, "removeValue:fromAttribute:error:", value, attribute, error);
__block bool success = false;
CFArrayRef response;
uint32_t code;
if (_attribute_is_immutable(attribute)) {
success = true;
} else {
response = transaction_simple(&code, record->_session, _ODRecordGetNode(record), CFSTR("ODRecordRemoveValue"), 5, record->_type, record->_name, record->_metaname, attribute, value);
transaction_simple_response(response, code, 1, error, ^ {
success = true;
dispatch_sync(record->_attrq, ^ {
CFMutableArrayRef new;
CFIndex idx;
CFArrayRef values = CFDictionaryGetValue(record->_attributes, attribute);
CFStringRef attrcopy;
if (values != NULL) {
new = CFArrayCreateMutableCopy(NULL, 0, values);
} else {
new = CFArrayCreateMutable(NULL, 1, &kCFTypeArrayCallBacks);
}
idx = CFArrayGetFirstIndexOfValue(new, CFRangeMake(0, CFArrayGetCount(new)), value);
if (idx != kCFNotFound) {
CFArrayRemoveValueAtIndex(new, idx);
attrcopy = CFStringCreateCopy(kCFAllocatorDefault, attribute);
CFDictionarySetValue(record->_attributes, attrcopy, new);
CFRelease(attrcopy);
}
CFRelease(new);
if (CFStringCompare(attribute, kODAttributeTypeRecordName, 0) == kCFCompareEqualTo) {
__update_name(record);
}
});
});
}
return success;
}
CFDictionaryRef
ODRecordCopyDetails(ODRecordRef record, CFArrayRef attributes, CFErrorRef *error)
{
CLEAR_ERROR(error);
if (!_validate_nonnull(error, CFSTR("record"), record)) {
return NULL;
}
if (CF_IS_OBJC(__kODRecordTypeID, record)) {
CFDictionaryRef details;
CF_OBJC_CALL(CFDictionaryRef, details, record, "recordDetailsForAttributes:error:", attributes, error);
return details ? CFRetain(details) : NULL;
}
__block CFMutableDictionaryRef details = NULL;
if (attributes) {
_RecordUpdate(record, attributes, false, ^(CFDictionaryRef a, CFErrorRef e) {
CFIndex count = CFArrayGetCount(attributes);
CFRange range = CFRangeMake(0, count);
if (CFArrayContainsValue(attributes, range, kODAttributeTypeAllAttributes)) {
details = (CFMutableDictionaryRef) CFDictionaryCreateCopy(NULL, a);
} else {
bool std = false;
bool native = false;
details = CFDictionaryCreateMutable(NULL, count, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
for (CFIndex i = 0; i < count; i++) {
CFStringRef attr;
CFArrayRef values;
attr = CFArrayGetValueAtIndex(attributes, i);
values = CFDictionaryGetValue(a, attr);
if (values) {
CFTypeRef temp = CFArrayCreateCopy(NULL, values);
CFDictionarySetValue(details, attr, temp);
CFRelease(temp);
} else if (std == false) {
std = CFEqual(attr, kODAttributeTypeStandardOnly);
} else if (native == false) {
native = CFEqual(attr, kODAttributeTypeNativeOnly);
}
}
if (std || native) {
CFIndex rec_count = CFDictionaryGetCount(a);
CFTypeRef *keys = (CFTypeRef *) alloca(sizeof(CFTypeRef) * rec_count);
CFTypeRef *values = (CFTypeRef *) alloca(sizeof(CFTypeRef) * rec_count);
CFDictionaryGetKeysAndValues(a, keys, values);
for (CFIndex i = 0; i < rec_count; i++) {
CFStringRef key = keys[i];
if ((std && CFStringHasPrefix(key, CFSTR("dsAttrTypeStandard:")))
|| (native && CFStringHasPrefix(key, CFSTR("dsAttrTypeNative:")))) {
CFTypeRef temp = CFArrayCreateCopy(NULL, values[i]);
CFDictionarySetValue(details, key, temp);
CFRelease(temp);
}
}
}
}
});
} else {
dispatch_sync(record->_attrq, ^ {
details = (CFMutableDictionaryRef) CFDictionaryCreateCopy(NULL, record->_attributes);
});
}
return details;
}
bool
ODRecordSynchronize(ODRecordRef record, CFErrorRef *error)
{
CLEAR_ERROR(error);
if (!_validate_nonnull(error, CFSTR("record"), record)) {
return false;
}
CF_OBJC_FUNCDISPATCH(__kODRecordTypeID, bool, record, "synchronizeAndReturnError:", error);
_RecordUpdate(record, NULL, true, ^(CFDictionaryRef a, CFErrorRef e) {
});
return true;
}
bool
ODRecordDelete(ODRecordRef record, CFErrorRef *error)
{
CLEAR_ERROR(error);
if (!_validate_nonnull(error, CFSTR("record"), record)) {
return false;
}
CF_OBJC_FUNCDISPATCH(__kODRecordTypeID, bool, record, "deleteRecordAndReturnError:", error);
__block bool success = false;
CFArrayRef response;
uint32_t code;
response = transaction_simple(&code, record->_session, _ODRecordGetNode(record), CFSTR("ODRecordDelete"), 3, record->_type, record->_name, record->_metaname);
transaction_simple_response(response, code, 1, error, ^ {
success = true;
});
return success;
}
#pragma mark -
#pragma Membership APIs
enum {
kODMemberUseUUID = 0x00000001,
kODMemberUseName = 0x00000002,
kODMemberNested = 0x00000010,
kODMemberUser = 0x00000100,
kODMemberComputer = 0x00000200,
kODMemberGroup = 0x00000400,
kODMemberOtherGroup = 0x00000800
};
static uint32_t
__ODRecordMapTypes(ODRecordRef group, ODRecordRef member)
{
CFStringRef grouptype = ODRecordGetRecordType(group);
CFStringRef membertype = ODRecordGetRecordType(member);
uint32_t flags = 0;
if (group == NULL || member == NULL) {
return 0;
}
if (CFStringCompare(grouptype, kODRecordTypeGroups, 0) == kCFCompareEqualTo) {
if (CFStringCompare(membertype, kODRecordTypeUsers, 0) == kCFCompareEqualTo) {
flags |= kODMemberUser | kODMemberUseUUID | kODMemberUseName;
} else if (CFStringCompare(membertype, kODRecordTypeComputers, 0) == kCFCompareEqualTo) {
flags |= kODMemberComputer | kODMemberUseUUID | kODMemberUseName;
} else if (CFStringCompare(membertype, kODRecordTypeGroups, 0) == kCFCompareEqualTo) {
flags |= kODMemberGroup | kODMemberUseUUID | kODMemberNested;
} else if (CFStringCompare(membertype, kODRecordTypeComputerGroups, 0) == kCFCompareEqualTo) {
flags |= kODMemberOtherGroup | kODMemberUseUUID | kODMemberNested;
}
} else if (CFStringCompare(grouptype, kODRecordTypeComputerGroups, 0) == kCFCompareEqualTo) {
if (CFStringCompare(membertype, kODRecordTypeComputers, 0) == kCFCompareEqualTo) {
flags |= kODMemberComputer | kODMemberUseUUID | kODMemberUseName;
} else if (CFStringCompare(membertype, kODRecordTypeComputerGroups, 0) == kCFCompareEqualTo) {
flags |= kODMemberOtherGroup | kODMemberUseUUID | kODMemberNested;
}
} else if (CFStringCompare(grouptype, kODRecordTypeComputerLists, 0) == kCFCompareEqualTo) {
if (CFStringCompare(membertype, kODRecordTypeComputers, 0) == kCFCompareEqualTo) {
flags |= kODMemberComputer | kODMemberUseName;
} else if (CFStringCompare(membertype, kODRecordTypeComputerLists, 0) == kCFCompareEqualTo) {
flags |= kODMemberOtherGroup | kODMemberUseName | kODMemberNested;
}
}
return flags;
}
static CFStringRef
_CopyTempUUIDForRecord(ODRecordRef record, uint32_t flags)
{
CFStringRef returnUUID = NULL;
CFStringRef(^getUUID)(ODAttributeType, int ( *)(uid_t, uuid_t));
getUUID = ^(ODAttributeType attrib, int (*mbr_cb)(uid_t, uuid_t)) {
CFStringRef cfUUID = NULL;
CFArrayRef idArray = ODRecordCopyValues(record, attrib, NULL);
if (idArray != NULL && CFArrayGetCount(idArray) > 0) {
CFStringRef cfID = (CFStringRef) CFArrayGetValueAtIndex(idArray, 0);
if (cfID != NULL && CFGetTypeID(cfID) == CFStringGetTypeID()) {
uuid_t uuid;
uuid_string_t uuidStr;
if (mbr_cb(CFStringGetIntValue(cfID), uuid) == 0) {
uuid_unparse_upper(uuid, uuidStr);
cfUUID = CFStringCreateWithCString(kCFAllocatorDefault, uuidStr, kCFStringEncodingUTF8);
}
}
}
if (idArray) {
CFRelease(idArray);
}
return cfUUID;
};
if ((flags & kODMemberUser) != 0) {
returnUUID = getUUID(kODAttributeTypeUniqueID, mbr_uid_to_uuid);
} else if ((flags & kODMemberGroup) != 0) {
returnUUID = getUUID(kODAttributeTypePrimaryGroupID, mbr_gid_to_uuid);
}
return returnUUID;
}
static CFErrorRef
__MembershipApplyBlock(ODRecordRef group, ODRecordRef member, uint32_t flags, CFErrorRef(^block)(ODAttributeType attr, CFTypeRef value))
{
CFErrorRef error = NULL;
bool valid = false;
if ((flags & kODMemberUseUUID) != 0) {
CFArrayRef memberUUIDList = ODRecordCopyValues(member, kODAttributeTypeGUID, NULL);
CFStringRef memberUUID = NULL;
CFStringRef tempUUID = NULL;
if (NULL != memberUUIDList && 0 != CFArrayGetCount(memberUUIDList)) {
memberUUID = (CFStringRef) CFArrayGetValueAtIndex(memberUUIDList, 0);
}
if (NULL == memberUUID) {
memberUUID = tempUUID = _CopyTempUUIDForRecord(member, flags);
}
if (memberUUID != NULL) {
if ((flags & (kODMemberComputer | kODMemberUser)) != 0) {
error = block(kODAttributeTypeGroupMembers, memberUUID);
valid = true;
} else if ((flags & kODMemberNested) != 0) {
error = block(kODAttributeTypeNestedGroups, memberUUID);
valid = true;
}
if (error != NULL && CFErrorGetCode(error) == kODErrorRecordAttributeUnknownType) {
CFRelease(error);
error = NULL;
valid = false;
}
} else if ((flags & kODMemberNested) != 0) {
_ODErrorSet(&error, kODErrorRecordAttributeNotFound, NULL);
}
if (tempUUID != NULL) {
CFRelease(tempUUID);
tempUUID = NULL;
}
if (memberUUIDList) {
CFRelease(memberUUIDList);
}
}
if (error == NULL && (flags & kODMemberUseName) != 0) {
CFStringRef memberName = ODRecordGetRecordName(member);
if (memberName != NULL) {
if ((flags & kODMemberUser) != 0) {
error = block(kODAttributeTypeGroupMembership, memberName);
valid = true;
} else if ((flags & kODMemberComputer) != 0) {
if ((flags & kODMemberUseUUID) != 0) {
error = block(kODAttributeTypeGroupMembership, memberName);
} else {
error = block(kODAttributeTypeComputers, memberName);
}
valid = true;
} else if ((flags & kODMemberNested) != 0) {
error = block(kODAttributeTypeGroup, memberName);
valid = true;
}
if (error != NULL && CFErrorGetCode(error) == kODErrorRecordAttributeUnknownType) {
CFRelease(error);
error = NULL;
valid = false;
}
} else {
_ODErrorSet(&error, kODErrorRecordAttributeNotFound, NULL);
}
}
if (error == NULL && valid == false) {
_ODErrorSet(&error, kODErrorRecordAttributeUnknownType, NULL);
}
return error;
}
bool
ODRecordAddMember(ODRecordRef group, ODRecordRef member, CFErrorRef *error)
{
bool success = false;
uint32_t flags;
CLEAR_ERROR(error);
if (!_validate_nonnull(error, CFSTR("group"), group)) {
return false;
}
if (!_validate_nonnull(error, CFSTR("member"), member)) {
return false;
}
CF_OBJC_FUNCDISPATCH(__kODRecordTypeID, bool, group, "addMemberRecord:error:", member, error);
flags = __ODRecordMapTypes(group, member);
if ((flags & (kODMemberUseUUID | kODMemberUseName)) != 0 && (flags & kODMemberUser) != 0) {
CFArrayRef names = ODRecordCopyValues(group, kODAttributeTypeGroupMembership, NULL);
CFArrayRef uuids = ODRecordCopyValues(group, kODAttributeTypeGroupMembers, NULL);
if (uuids == 0 && names > 0) {
flags &= ~kODMemberUseUUID;
}
if (uuids > 0 && names == 0) {
flags &= ~kODMemberUseName;
}
safe_cfrelease(names);
safe_cfrelease(uuids);
}
if ((flags & (kODMemberUseUUID | kODMemberUseName)) != 0) {
CFErrorRef cfTempError = __MembershipApplyBlock(group, member, flags, ^(ODAttributeType attr, CFTypeRef value) {
CFErrorRef localerror = NULL;
CFArrayRef cfValues = (CFArrayRef) ODRecordCopyValues(group, attr, NULL);
if (cfValues == NULL ||
CFArrayContainsValue(cfValues, CFRangeMake(0, CFArrayGetCount(cfValues)), value) == false) {
ODRecordAddValue(group, attr, value, &localerror);
}
if (cfValues != NULL) {
CFRelease(cfValues);
}
return localerror;
});
if (cfTempError == NULL) {
success = true;
} else {
if (error != NULL) {
(*error) = cfTempError;
} else if (cfTempError != NULL) {
CFRelease(cfTempError);
cfTempError = NULL;
}
}
} else {
_ODErrorSet(error, kODErrorRecordInvalidType, NULL);
}
return success;
}
bool
ODRecordRemoveMember(ODRecordRef group, ODRecordRef member, CFErrorRef *error)
{
bool success = false;
uint32_t flags;
CLEAR_ERROR(error);
if (!_validate_nonnull(error, CFSTR("group"), group)) {
return false;
}
if (!_validate_nonnull(error, CFSTR("member"), member)) {
return false;
}
CF_OBJC_FUNCDISPATCH(__kODRecordTypeID, bool, group, "removeRecordMember:error:", member, error);
flags = __ODRecordMapTypes(group, member);
if ((flags & (kODMemberUseUUID | kODMemberUseName)) != 0) {
CFErrorRef cfTempError = __MembershipApplyBlock(group, member, flags, ^(ODAttributeType attr, CFTypeRef value) {
CFErrorRef localerr = NULL;
bool bTemp = ODRecordRemoveValue(group, attr, value, &localerr);
if (bTemp == false) {
CFIndex code = CFErrorGetCode(localerr);
if (kODErrorRecordAttributeValueNotFound == code || kODErrorRecordAttributeNotFound == code) {
CFRelease(localerr);
localerr = NULL;
}
}
return localerr;
});
if (cfTempError == NULL) {
success = true;
} else {
if (error != NULL) {
(*error) = cfTempError;
} else if (cfTempError != NULL) {
CFRelease(cfTempError);
cfTempError = NULL;
}
}
} else {
_ODErrorSet(error, kODErrorRecordInvalidType, NULL);
}
return success;
}
static bool
_record_contains(ODRecordRef group, ODRecordRef member, bool refresh, CFErrorRef *error)
{
int isMember = 0;
uint32_t flags;
flags = __ODRecordMapTypes(group, member);
if ((flags & kODMemberNested) == 0) {
uuid_t uuid_group;
uuid_t uuid_member;
bool (^getUUID)(ODRecordRef, uuid_t);
getUUID = ^(ODRecordRef inRecord, uuid_t inUUID) {
CFArrayRef recordUUIDList = ODRecordCopyValues(inRecord, kODAttributeTypeGUID, NULL);
CFStringRef recordUUID = NULL;
CFStringRef tempUUID = NULL;
bool bFound = false;
if (NULL != recordUUIDList && 0 != CFArrayGetCount(recordUUIDList)) {
recordUUID = (CFStringRef) CFArrayGetValueAtIndex(recordUUIDList, 0);
}
if (NULL == recordUUID) {
recordUUID = tempUUID = _CopyTempUUIDForRecord(member, flags);
}
if (recordUUID != NULL) {
uuid_string_t memberUUID;
if (CFStringGetCString(recordUUID, memberUUID, sizeof(memberUUID), kCFStringEncodingASCII) == true) {
uuid_parse(memberUUID, inUUID);
bFound = true;
}
}
if (tempUUID != NULL) {
CFRelease(tempUUID);
tempUUID = NULL;
}
if (recordUUIDList != NULL) {
CFRelease(recordUUIDList);
recordUUIDList = NULL;
}
return bFound;
};
if (getUUID(group, uuid_group) == true && getUUID(member, uuid_member) == true) {
if (refresh == false) {
mbr_check_membership(uuid_member, uuid_group, &isMember);
} else {
mbr_check_membership_refresh(uuid_member, uuid_group, &isMember);
}
}
} else {
_ODErrorSet(error, kODErrorRecordInvalidType, NULL);
}
return (isMember == 1 ? true : false);
}
bool
ODRecordContainsMember(ODRecordRef group, ODRecordRef member, CFErrorRef *error)
{
CLEAR_ERROR(error);
if (!_validate_nonnull(error, CFSTR("group record"), group)) {
return false;
}
if (!_validate_nonnull(error, CFSTR("member record"), member)) {
return false;
}
CF_OBJC_FUNCDISPATCH(__kODRecordTypeID, bool, group, "isMemberRecord:error:", member, error);
return _record_contains(group, member, false, error);
}
bool
ODRecordContainsMemberRefresh(ODRecordRef group, ODRecordRef member, CFErrorRef *error)
{
CLEAR_ERROR(error);
if (!_validate_nonnull(error, CFSTR("group record"), group)) {
return false;
}
if (!_validate_nonnull(error, CFSTR("member record"), member)) {
return false;
}
CF_OBJC_FUNCDISPATCH(__kODRecordTypeID, bool, group, "isMemberRecordRefresh:error:", member, error);
return _record_contains(group, member, true, error);
}