#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, "ODSession", NULL, NULL, __ODSessionFinalize, NULL, NULL, NULL, __ODSessionCopyDebugDesc, NULL, #if CF_REFCOUNT_AVAILABLE
NULL, #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);
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;
if ((tmp = CFDictionaryGetValue(options, kODSessionLocalPath))) {
struct stat localDirStat;
struct stat statResult;
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);
if (lstat(newPath, &statResult) == 0 && lstat("/var/db/dslocal/nodes/Default", &localDirStat) == 0) {
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 {
keys[n] = kODSessionLocalPath;
values[n] = workPath;
n++;
}
}
}
} else {
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++;
}
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) {
(*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;
}
bool
ODSessionNodeNameIsLocal(ODSessionRef session __unused, CFStringRef nodename)
{
if (CFEqual(nodename, CFSTR("/BSD/local"))) {
return true;
}
if (CFStringHasPrefix(nodename, CFSTR("/Local/"))) {
return true;
}
return false;
}
ODSessionRef
ODSessionCreateWithDSRef(CFAllocatorRef inAllocator, tDirReference inDirRef, bool inCloseOnRelease)
{
static dispatch_once_t once;
static ODSessionRef(*dsCopyDataFromDirRef)(tDirReference inNodeRef);
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);
dispatch_once(&once,
^(void) {
CFBundleRef bundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.DirectoryService.Framework"));
if (bundle != NULL) {
dsCreateDataDirRefData = CFBundleGetFunctionPointerForName(bundle, CFSTR("dsCreateDataDirRefData"));
}
});
if (dsCreateDataDirRefData != NULL) {
return dsCreateDataDirRefData(inSessionRef);
} else {
OD_CRASH("DirectoryService.framework missing function: dsCreateDataDirRefData");
}
return 0;
}