mirror of
https://github.com/darlinghq/darling-opendirectory.git
synced 2024-11-30 07:40:48 +00:00
460 lines
12 KiB
C
460 lines
12 KiB
C
/*
|
|
* Copyright (c) 2009 Apple Inc. All rights reserved.
|
|
*
|
|
* @APPLE_LICENSE_HEADER_START@
|
|
*
|
|
* This file contains Original Code and/or Modifications of Original Code
|
|
* as defined in and that are subject to the Apple Public Source License
|
|
* Version 2.0 (the 'License'). You may not use this file except in
|
|
* compliance with the License. Please obtain a copy of the License at
|
|
* http://www.opensource.apple.com/apsl/ and read it before using this
|
|
* file.
|
|
*
|
|
* The Original Code and all software distributed under the License are
|
|
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
|
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
|
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
|
* Please see the License for the specific language governing rights and
|
|
* limitations under the License.
|
|
*
|
|
* @APPLE_LICENSE_HEADER_END@
|
|
*/
|
|
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
#include <CoreFoundation/CFBridgingPriv.h>
|
|
#include <uuid/uuid.h>
|
|
#include <ifaddrs.h>
|
|
#include <netdb.h>
|
|
|
|
#include "CFOpenDirectoryPriv.h"
|
|
|
|
#include <opendirectory/odutils.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "CFODSession.h"
|
|
#include "transaction.h"
|
|
#include "internal.h"
|
|
|
|
const CFStringRef kODSessionLocalPath = CFSTR("LocalPath");
|
|
const CFStringRef kODSessionProxyAddress = CFSTR("ProxyAddress");
|
|
const CFStringRef kODSessionProxyPort = CFSTR("ProxyPort");
|
|
const CFStringRef kODSessionProxyUsername = CFSTR("ProxyUsername");
|
|
const CFStringRef kODSessionProxyPassword = CFSTR("ProxyPassword");
|
|
|
|
ODSessionRef kODSessionDefault;
|
|
|
|
struct __ODSession {
|
|
CFRuntimeBase _base;
|
|
uuid_t _uuid;
|
|
CFDictionaryRef _info;
|
|
};
|
|
|
|
static CFTypeID __kODSessionTypeID = _kCFRuntimeNotATypeID;
|
|
|
|
static void
|
|
__ODSessionFinalize(CFTypeRef cf)
|
|
{
|
|
ODSessionRef session = (ODSessionRef)cf;
|
|
CFArrayRef response;
|
|
uint32_t code;
|
|
|
|
if (!uuid_is_null(session->_uuid)) {
|
|
response = transaction_simple(&code, session, NULL, CFSTR("ODSessionRelease"), 0);
|
|
safe_cfrelease_null(response);
|
|
}
|
|
|
|
if (session->_info) {
|
|
CFRelease(session->_info);
|
|
}
|
|
}
|
|
|
|
static CFStringRef
|
|
__ODSessionCopyDebugDesc(CFTypeRef cf)
|
|
{
|
|
ODSessionRef session = (ODSessionRef)cf;
|
|
CFDictionaryRef info = session->_info;
|
|
CFIndex count = info ? CFDictionaryGetCount(info) : 0;
|
|
uuid_string_t uuid;
|
|
CFStringRef urlstr, output;
|
|
if (count == 1) {
|
|
urlstr = CFStringCreateWithFormat(NULL, NULL, CFSTR(" [%@]"), CFDictionaryGetValue(info, kODSessionLocalPath));
|
|
} else if (count >= 3) {
|
|
urlstr = CFStringCreateWithFormat(NULL, NULL, CFSTR(" [%@@%@:%@]"), CFDictionaryGetValue(info, kODSessionProxyUsername), CFDictionaryGetValue(info, kODSessionProxyAddress), CFDictionaryGetValue(info, kODSessionProxyPort));
|
|
} else {
|
|
urlstr = CFSTR("");
|
|
}
|
|
uuid_unparse_upper(session->_uuid, uuid);
|
|
output = CFStringCreateWithFormat(NULL, NULL, CFSTR("<ODSession %p [uuid: %s]%@>"), session, uuid, urlstr);
|
|
CFRelease(urlstr);
|
|
return output;
|
|
}
|
|
|
|
static const CFRuntimeClass __ODSessionClass = {
|
|
0, // version
|
|
"ODSession", // className
|
|
NULL, // init
|
|
NULL, // copy
|
|
__ODSessionFinalize, // finalize
|
|
NULL, // equal
|
|
NULL, // hash
|
|
NULL, // copyFormattingDesc
|
|
__ODSessionCopyDebugDesc, // copyDebugDesc
|
|
NULL, // reclaim
|
|
#if CF_REFCOUNT_AVAILABLE
|
|
NULL, // refcount
|
|
#endif
|
|
};
|
|
|
|
#pragma mark Helper Functions
|
|
|
|
static bool
|
|
is_local(CFStringRef address)
|
|
{
|
|
char hostname[NI_MAXHOST];
|
|
struct addrinfo hints, *res, *res0;
|
|
int error;
|
|
struct ifaddrs *ifa, *ifap;
|
|
struct sockaddr_in *sin4, *sin4_if;
|
|
struct sockaddr_in6 *sin6, *sin6_if;
|
|
bool local;
|
|
|
|
local = false;
|
|
|
|
if (!CFStringGetCString(address, hostname, sizeof(hostname), kCFStringEncodingUTF8)) {
|
|
return false;
|
|
}
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
error = getaddrinfo(hostname, NULL, &hints, &res0);
|
|
if (error) {
|
|
return false;
|
|
}
|
|
|
|
(void)getifaddrs(&ifap); // ok if this fails
|
|
|
|
for (res = res0; res && !local; res = res->ai_next) {
|
|
switch (res->ai_family) {
|
|
case AF_INET:
|
|
sin4 = (struct sockaddr_in *)res->ai_addr;
|
|
if (sin4->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) {
|
|
local = true;
|
|
}
|
|
for (ifa = ifap; ifa && !local; ifa = ifa->ifa_next) {
|
|
if (ifa->ifa_addr->sa_family == AF_INET) {
|
|
sin4_if = (struct sockaddr_in *)ifa->ifa_addr;
|
|
if (sin4->sin_addr.s_addr == sin4_if->sin_addr.s_addr) {
|
|
local = true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case AF_INET6:
|
|
sin6 = (struct sockaddr_in6 *)res->ai_addr;
|
|
if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) {
|
|
local = true;
|
|
}
|
|
for (ifa = ifap; ifa && !local; ifa = ifa->ifa_next) {
|
|
if (ifa->ifa_addr->sa_family == AF_INET6) {
|
|
sin6_if = (struct sockaddr_in6 *)ifa->ifa_addr;
|
|
if (IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, &sin6_if->sin6_addr)) {
|
|
local = true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case AF_UNIX:
|
|
local = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
freeifaddrs(ifap);
|
|
freeaddrinfo(res0);
|
|
|
|
return local;
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
void
|
|
_ODInitialize(void)
|
|
{
|
|
static dispatch_once_t once;
|
|
|
|
dispatch_once(&once, ^(void) {
|
|
ODSessionGetTypeID();
|
|
ODNodeGetTypeID();
|
|
ODQueryGetTypeID();
|
|
ODRecordGetTypeID();
|
|
});
|
|
}
|
|
|
|
void
|
|
uuid_copy_session(uuid_t dst, ODSessionRef session)
|
|
{
|
|
if (session) {
|
|
uuid_copy(dst, session->_uuid);
|
|
} else {
|
|
uuid_clear(dst);
|
|
}
|
|
}
|
|
|
|
ODSessionRef
|
|
_ODSessionGetShared()
|
|
{
|
|
static dispatch_once_t once;
|
|
|
|
dispatch_once(&once, ^ {
|
|
kODSessionDefault = ODSessionCreate(NULL, NULL, NULL);
|
|
});
|
|
|
|
return kODSessionDefault;
|
|
}
|
|
|
|
CFTypeID
|
|
ODSessionGetTypeID(void)
|
|
{
|
|
static dispatch_once_t once;
|
|
|
|
dispatch_once(&once, ^ {
|
|
__kODSessionTypeID = _CFRuntimeRegisterClass(&__ODSessionClass);
|
|
if (__kODSessionTypeID != _kCFRuntimeNotATypeID) {
|
|
_CFRuntimeBridgeClasses(__kODSessionTypeID, "NSODSession");
|
|
}
|
|
});
|
|
|
|
return __kODSessionTypeID;
|
|
}
|
|
|
|
void
|
|
_ODSessionInit(ODSessionRef session, CFDictionaryRef options, CFErrorRef *error)
|
|
{
|
|
const void *keys[4];
|
|
const void *values[4];
|
|
CFStringRef tmp;
|
|
CFIndex n = 0;
|
|
CFArrayRef response;
|
|
uint32_t code;
|
|
bool local = false;
|
|
|
|
CLEAR_ERROR(error);
|
|
|
|
if (options != NULL && CFDictionaryGetCount(options) > 0) {
|
|
int proxyargs = 0;
|
|
CFMutableStringRef workPath = NULL;
|
|
|
|
/* Check for LocalPath first. */
|
|
if ((tmp = CFDictionaryGetValue(options, kODSessionLocalPath))) {
|
|
struct stat localDirStat;
|
|
struct stat statResult;
|
|
|
|
// see if we need to set a default path
|
|
workPath = CFStringCreateMutableCopy(kCFAllocatorDefault, PATH_MAX, tmp);
|
|
if (CFEqual(workPath, CFSTR("Default")) == false) {
|
|
if (CFStringGetLength(workPath) <= PATH_MAX) {
|
|
char newPath[PATH_MAX];
|
|
|
|
if (CFStringHasSuffix(workPath, CFSTR("/")) == false) {
|
|
CFStringAppend(workPath, CFSTR("/"));
|
|
}
|
|
|
|
if (CFStringHasSuffix(workPath, CFSTR("/dslocal/nodes/")) == true) {
|
|
CFStringAppend(workPath, CFSTR("Default/"));
|
|
}
|
|
|
|
CFStringGetCString(workPath, newPath, sizeof(newPath), kCFStringEncodingUTF8);
|
|
|
|
// let's see if we are trying to get to the same place
|
|
if (lstat(newPath, &statResult) == 0 && lstat("/var/db/dslocal/nodes/Default", &localDirStat) == 0) {
|
|
// if these are not the same files
|
|
if (statResult.st_ino != localDirStat.st_ino || statResult.st_dev != localDirStat.st_dev || statResult.st_gen != localDirStat.st_gen) {
|
|
keys[n] = kODSessionLocalPath;
|
|
values[n] = workPath;
|
|
n++;
|
|
}
|
|
} else {
|
|
// cannot stat, let opendirectoryd try
|
|
keys[n] = kODSessionLocalPath;
|
|
values[n] = workPath;
|
|
n++;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
/* Otherwise, grab proxy information. */
|
|
|
|
if ((tmp = CFDictionaryGetValue(options, kODSessionProxyAddress))) {
|
|
local = is_local(tmp);
|
|
|
|
keys[n] = kODSessionProxyAddress;
|
|
values[n] = tmp;
|
|
n++;
|
|
proxyargs++;
|
|
}
|
|
if ((tmp = CFDictionaryGetValue(options, kODSessionProxyPort))) {
|
|
keys[n] = kODSessionProxyPort;
|
|
values[n] = tmp;
|
|
n++;
|
|
}
|
|
if ((tmp = CFDictionaryGetValue(options, kODSessionProxyUsername))) {
|
|
keys[n] = kODSessionProxyUsername;
|
|
values[n] = tmp;
|
|
n++;
|
|
proxyargs++;
|
|
}
|
|
if ((tmp = CFDictionaryGetValue(options, kODSessionProxyPassword))) {
|
|
keys[n] = kODSessionProxyPassword;
|
|
values[n] = tmp;
|
|
n++;
|
|
proxyargs++;
|
|
}
|
|
|
|
/* Verify that all necessary proxy information was provided. */
|
|
if (proxyargs != 3) {
|
|
_ODErrorSet(error, kODErrorRecordParameterError, CFSTR("Invalid proxy arguments."));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (n > 0 && local == false) {
|
|
session->_info = CFDictionaryCreate(NULL, keys, values, n, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
|
|
|
response = transaction_simple(&code, NULL, NULL, CFSTR("ODSessionCreate"), 1, session->_info);
|
|
transaction_simple_response(response, code, 1, error, ^ {
|
|
uuid_copy(session->_uuid, CFDataGetBytePtr(schema_get_value_at_index(response, 0)));
|
|
});
|
|
}
|
|
|
|
safe_cfrelease_null(workPath);
|
|
}
|
|
}
|
|
|
|
ODSessionRef
|
|
_ODSessionCreate(CFAllocatorRef allocator)
|
|
{
|
|
return (ODSessionRef)_CFRuntimeCreateInstance(allocator, ODSessionGetTypeID(), sizeof(struct __ODSession) - sizeof(CFRuntimeBase), NULL);
|
|
}
|
|
|
|
ODSessionRef
|
|
ODSessionCreate(CFAllocatorRef allocator, CFDictionaryRef options, CFErrorRef *error)
|
|
{
|
|
CLEAR_ERROR(error);
|
|
|
|
CFErrorRef local_error = NULL;
|
|
ODSessionRef session = _ODSessionCreate(allocator);
|
|
|
|
if (session != NULL) {
|
|
_ODSessionInit(session, options, &local_error);
|
|
} else {
|
|
if (error != NULL) {
|
|
// TODO: do we have general allocation error?
|
|
(*error) = CFErrorCreate(kCFAllocatorDefault, kODErrorDomainFramework, kODErrorSessionFailed, NULL);
|
|
}
|
|
}
|
|
|
|
if (local_error != NULL) {
|
|
CFRelease(session);
|
|
session = NULL;
|
|
|
|
if (error != NULL) {
|
|
(*error) = local_error;
|
|
} else {
|
|
CFRelease(local_error);
|
|
}
|
|
}
|
|
|
|
return session;
|
|
}
|
|
|
|
CFArrayRef
|
|
ODSessionCopyNodeNames(CFAllocatorRef allocator, ODSessionRef session, CFErrorRef *error)
|
|
{
|
|
CLEAR_ERROR(error);
|
|
|
|
if (session && CF_IS_OBJC(__kODSessionTypeID, session)) {
|
|
CFArrayRef nodes = NULL;
|
|
CF_OBJC_CALL(CFArrayRef, nodes, session, "nodeNamesAndReturnError:", error);
|
|
return nodes ? CFRetain(nodes) : NULL;
|
|
}
|
|
|
|
__block CFArrayRef nodes = NULL;
|
|
CFArrayRef response;
|
|
uint32_t code;
|
|
|
|
response = transaction_simple(&code, session, NULL, CFSTR("ODSessionCopyNodeNames"), 0);
|
|
transaction_simple_response(response, code, 1, error, ^ {
|
|
nodes = schema_get_value_at_index(response, 0);
|
|
if (nodes) CFRetain(nodes);
|
|
});
|
|
|
|
return nodes;
|
|
}
|
|
|
|
// 7558703
|
|
bool
|
|
ODSessionNodeNameIsLocal(ODSessionRef session __unused, CFStringRef nodename)
|
|
{
|
|
if (CFEqual(nodename, CFSTR("/BSD/local"))) {
|
|
return true;
|
|
}
|
|
if (CFStringHasPrefix(nodename, CFSTR("/Local/"))) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// TODO: could simplify this by looking up all functions at once, but doesn't save much since bundle is cached
|
|
|
|
ODSessionRef
|
|
ODSessionCreateWithDSRef(CFAllocatorRef inAllocator, tDirReference inDirRef, bool inCloseOnRelease)
|
|
{
|
|
static dispatch_once_t once;
|
|
static ODSessionRef(*dsCopyDataFromDirRef)(tDirReference inNodeRef);
|
|
|
|
// We have to load this dynamically because otherwise we create a circular dependency
|
|
dispatch_once(&once,
|
|
^(void) {
|
|
CFBundleRef bundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.DirectoryService.Framework"));
|
|
if (bundle != NULL) {
|
|
dsCopyDataFromDirRef = CFBundleGetFunctionPointerForName(bundle, CFSTR("dsCopyDataFromDirRef"));
|
|
}
|
|
});
|
|
|
|
if (dsCopyDataFromDirRef != NULL) {
|
|
return dsCopyDataFromDirRef(inDirRef);
|
|
} else {
|
|
OD_CRASH("DirectoryService.framework missing function: dsCopyDataFromDirRef");
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
tDirReference
|
|
ODSessionGetDSRef(ODSessionRef inSessionRef)
|
|
{
|
|
static dispatch_once_t once;
|
|
static tDirReference(*dsCreateDataDirRefData)(CFTypeRef data);
|
|
|
|
// We have to load this dynamically because otherwise we create a circular dependency
|
|
dispatch_once(&once,
|
|
^(void) {
|
|
CFBundleRef bundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.DirectoryService.Framework"));
|
|
if (bundle != NULL) {
|
|
dsCreateDataDirRefData = CFBundleGetFunctionPointerForName(bundle, CFSTR("dsCreateDataDirRefData"));
|
|
}
|
|
});
|
|
|
|
if (dsCreateDataDirRefData != NULL) {
|
|
// TODO: need to track the reference we create and close it accordingly (just call dsCloseDirService)
|
|
return dsCreateDataDirRefData(inSessionRef);
|
|
} else {
|
|
OD_CRASH("DirectoryService.framework missing function: dsCreateDataDirRefData");
|
|
}
|
|
|
|
return 0;
|
|
}
|