#include "hdb_locl.h"
#include <heimbase.h>
#include <hx509.h>
#include <base64.h>
#include <rfc2459_asn1.h>
#include <ifaddrs.h>
#include <heimbase.h>
#include <roken.h>
#ifdef HAVE_OPENDIRECTORY
#include <CoreFoundation/CoreFoundation.h>
#include <OpenDirectory/OpenDirectory.h>
#include <opendirectory/odconstants.h>
#ifdef __APPLE_PRIVATE__
#include <OpenDirectory/OpenDirectoryPriv.h>
#include <DirectoryServer/DirectoryServer.h>
#include <dlfcn.h>
#endif
#include <SystemConfiguration/SCPreferences.h>
struct iter_ctx {
CFIndex idx;
CFArrayRef odRecordArray;
} iter;
struct hdb_od;
typedef krb5_error_code (*hod_locate_record)(krb5_context, struct hdb_od *, krb5_principal, unsigned, int, int *, ODRecordRef *);
typedef struct hdb_od {
CFStringRef rootName;
ODNodeRef rootNode;
CFArrayRef inNodeAttributes;
CFArrayRef inKDCAttributes;
CFArrayRef inComputerOrUsers;
CFStringRef restoreRoot;
char *LKDCRealm;
SCDynamicStoreRef *store;
char *ntlmDomain;
struct iter_ctx iter;
hod_locate_record locate_record;
} *hdb_od;
static CFStringRef kRealName = CFSTR("dsAttrTypeStandard:RealName");
static const char wellknown_lkdc[] = "WELLKNOWN:COM.APPLE.LKDC";
typedef struct hdb_entry_ex_ctx {
krb5_principal principal;
ODRecordRef record;
} hdb_entry_ex_ctx;
static void
free_hod_ctx(krb5_context context, hdb_entry_ex *entry)
{
hdb_entry_ex_ctx *ctx = entry->ctx;
if (ctx) {
if (ctx->record)
CFRelease(ctx->record);
free(ctx);
}
entry->ctx = NULL;
}
static hdb_entry_ex_ctx *
get_ex_ctx(hdb_entry_ex *entry)
{
if (entry->ctx == NULL) {
entry->ctx = calloc(1, sizeof(*entry->ctx));
if (entry->ctx == NULL)
return NULL;
entry->free_entry = free_hod_ctx;
}
return entry->ctx;
}
static int
is_lkdc(hdb_od d, const char *realm)
{
return d->LKDCRealm != NULL && krb5_realm_is_lkdc(realm);
}
static krb5_error_code
map_service(krb5_context context, krb5_const_principal principal, krb5_principal *eprinc)
{
const char *service;
krb5_error_code ret;
ret = krb5_copy_principal(context, principal, eprinc);
if (ret)
return ret;
service = krb5_principal_get_comp_string(context, principal, 0);
if (strcasecmp(service, "afpserver") == 0 ||
strcasecmp(service, "cifs") == 0 ||
strcasecmp(service, "smb") == 0 ||
strcasecmp(service, "vnc") == 0) {
free((*eprinc)->name.name_string.val[0]);
(*eprinc)->name.name_string.val[0] = strdup("host");
}
return 0;
}
static bool
tryAgainP(krb5_context context, int *tryAgain, CFErrorRef *error)
{
if (*tryAgain <= 1)
return 0;
if (error && *error) {
CFRelease(*error);
*error = NULL;
}
(*tryAgain)--;
krb5_warnx(context, "try fetching result again");
return 1;
}
#define MAX_TRIES 3
static ODRecordRef
HODODNodeCopyLinkageRecordFromAuthenticationData(krb5_context context, ODNodeRef node, ODRecordRef record)
{
CFMutableArrayRef types = NULL;
CFArrayRef linkageArray = NULL, resultArray = NULL;
CFTypeRef userLinkage;
ODQueryRef query = NULL;
CFErrorRef error = NULL;
ODRecordRef res;
int tryAgain = MAX_TRIES;
types = CFArrayCreateMutable(NULL, 2, &kCFTypeArrayCallBacks);
if (types == NULL)
goto out;
CFArrayAppendValue(types, kODRecordTypeUsers);
CFArrayAppendValue(types, kODRecordTypeComputers);
linkageArray = ODRecordCopyValues(record, CFSTR("dsAttrTypeNative:userLinkage"), &error);
if (linkageArray == NULL)
goto out;
if (CFArrayGetCount(linkageArray) == 0)
goto out;
userLinkage = CFArrayGetValueAtIndex(linkageArray, 0);
if (userLinkage == NULL)
goto out;
do {
query = ODQueryCreateWithNode(NULL, node, types,
CFSTR("dsAttrTypeNative:entryUUID"),
kODMatchEqualTo,
userLinkage, NULL, 1, &error);
if (query == NULL)
goto out;
resultArray = ODQueryCopyResults(query, FALSE, &error);
CFRelease(query);
if (resultArray == NULL && error == NULL)
tryAgain = 0;
} while(resultArray == NULL && tryAgainP(context, &tryAgain, &error));
if (resultArray == NULL)
goto out;
if (CFArrayGetCount(resultArray) == 0)
goto out;
res = (ODRecordRef)CFArrayGetValueAtIndex(resultArray, 0);
if (res)
record = res;
out:
CFRetain(record);
if (error)
CFRelease(error);
if (linkageArray)
CFRelease(linkageArray);
if (resultArray)
CFRelease(resultArray);
if (types)
CFRelease(types);
return record;
}
static krb5_error_code
od2hdb_usercert(krb5_context context, hdb_od d,
CFArrayRef data, unsigned flags, hdb_entry_ex *entry)
{
HDB_extension ext;
void *ptr;
krb5_error_code ret;
CFIndex i;
memset(&ext, 0, sizeof(ext));
ext.data.element = choice_HDB_extension_data_pkinit_cert;
for (i = 0; i < CFArrayGetCount(data); i++) {
CFDataRef c = (CFDataRef) CFArrayGetValueAtIndex(data, i);
krb5_data cd;
if (CFGetTypeID((CFTypeRef)c) != CFDataGetTypeID())
continue;
ret = krb5_data_copy(&cd, CFDataGetBytePtr(c),
CFDataGetLength(c));
if (ret) {
free_HDB_extension(&ext);
return ENOMEM;
}
ext.data.u.pkinit_cert.len += 1;
ptr = realloc(ext.data.u.pkinit_cert.val,
sizeof(ext.data.u.pkinit_cert.val[0]) * ext.data.u.pkinit_cert.len);
if (ptr == NULL) {
krb5_data_free(&cd);
free_HDB_extension(&ext);
return ENOMEM;
}
ext.data.u.pkinit_cert.val = ptr;
memset(&ext.data.u.pkinit_cert.val[ext.data.u.pkinit_cert.len - 1],
0, sizeof(ext.data.u.pkinit_cert.val[0]));
ext.data.u.pkinit_cert.val[ext.data.u.pkinit_cert.len - 1].cert = cd;
}
ret = hdb_replace_extension(context, &entry->entry, &ext);
free_HDB_extension(&ext);
return ret;
}
static krb5_error_code
nodeCreateWithName(krb5_context context, hdb_od d, ODNodeRef *node)
{
ODSessionRef session = kODSessionDefault;
*node = NULL;
#ifdef __APPLE_PRIVATE__
if (d->restoreRoot) {
CFMutableDictionaryRef options;
options = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (options == NULL)
return ENOMEM;
CFDictionaryAddValue(options, kODSessionLocalPath, d->restoreRoot);
session = ODSessionCreate(kCFAllocatorDefault, options, NULL);
CFRelease(options);
if (session == NULL) {
krb5_set_error_message(context, HDB_ERR_DB_INUSE, "failed to create session");
return HDB_ERR_DB_INUSE;
}
}
#endif
*node = ODNodeCreateWithName(kCFAllocatorDefault,
session,
d->rootName,
NULL);
if (session)
CFRelease(session);
if (*node == NULL) {
char *restoreRoot = NULL, *path = NULL;
if (d->restoreRoot)
restoreRoot = rk_cfstring2cstring(d->restoreRoot);
path = rk_cfstring2cstring(d->rootName);
krb5_set_error_message(context, HDB_ERR_DB_INUSE, "failed to create root node: %s %s",
path ? path : "<nopath>",
restoreRoot ? restoreRoot : "");
free(restoreRoot);
free(path);
return HDB_ERR_DB_INUSE;
}
return 0;
}
static krb5_error_code
od2hdb_null(krb5_context context, hdb_od d,
CFArrayRef data, unsigned flags, hdb_entry_ex *entry)
{
return 0;
}
static krb5_error_code
map_lkdc_principal(krb5_context context, krb5_principal principal, const char *from, const char *to)
{
size_t num = krb5_principal_get_num_comp(context, principal);
krb5_error_code ret;
if (strcasecmp(principal->realm, from) == 0) {
ret = krb5_principal_set_realm(context, principal, to);
if (ret)
return ret;
}
if (num == 2 && strcasecmp(principal->name.name_string.val[1], from) == 0) {
free(principal->name.name_string.val[1]);
principal->name.name_string.val[1] = strdup(to);
if (principal->name.name_string.val[1] == NULL)
return ENOMEM;
}
return 0;
}
static krb5_error_code
od2hdb_principal(krb5_context context, hdb_od d,
CFArrayRef data, unsigned flags, hdb_entry_ex *entry)
{
hdb_entry_ex_ctx *ctx = entry->ctx;
krb5_error_code ret;
char *str;
if (ctx && ctx->principal)
return 0;
if ((flags & HDB_F_CANON) == 0)
return 0;
if (entry->entry.principal) {
krb5_free_principal(context, entry->entry.principal);
entry->entry.principal = NULL;
}
str = rk_cfstring2cstring((CFStringRef)CFArrayGetValueAtIndex(data, 0));
if (str == NULL)
return ENOMEM;
ret = krb5_parse_name(context, str, &entry->entry.principal);
free(str);
if (ret)
return ret;
if (d->LKDCRealm) {
ret = map_lkdc_principal(context, entry->entry.principal, wellknown_lkdc, d->LKDCRealm);
if (ret)
return ret;
}
return 0;
}
static krb5_error_code
od2hdb_def_principal(krb5_context context, hdb_od d,
int flags, hdb_entry_ex *entry)
{
if (entry->entry.principal)
return 0;
return HDB_ERR_NOENTRY;
}
static krb5_error_code
hdb2od_principal_lkdc(krb5_context context, hdb_od d, ODAttributeType type,
unsigned flags, hdb_entry_ex *entry, ODRecordRef record)
{
size_t num = krb5_principal_get_num_comp(context, entry->entry.principal);
CFMutableArrayRef array = NULL;
krb5_principal principal = NULL;
krb5_error_code ret = 0;
CFStringRef element;
char *user;
if (d->LKDCRealm == NULL)
return 0;
if (num == 1)
return 0;
ret = krb5_copy_principal(context, entry->entry.principal, &principal);
if (ret)
return ret;
ret = map_lkdc_principal(context, principal, d->LKDCRealm, wellknown_lkdc);
if (ret)
goto out;
if (num == 2) {
krb5_principal eprinc;
type = CFSTR("dsAttrTypeNative:KerberosServerName");
ret = map_service(context, principal, &eprinc);
if (ret)
goto out;
krb5_free_principal(context, principal);
principal = eprinc;
}
array = CFArrayCreateMutable(kCFAllocatorDefault, 1,
&kCFTypeArrayCallBacks);
if (array == NULL) {
ret = ENOMEM;
goto out;
}
ret = krb5_unparse_name(context, principal, &user);
if (ret)
goto out;
element = CFStringCreateWithCString(kCFAllocatorDefault, user, kCFStringEncodingUTF8);
if (element == NULL) {
ret = ENOMEM;
goto out;
}
CFArrayAppendValue(array, element);
CFRelease(element);
bool r = ODRecordSetValue(record, type, array, NULL);
if (!r)
ret = HDB_ERR_UK_SERROR;
out:
if (principal)
krb5_free_principal(context, principal);
if (array)
CFRelease(array);
return ret;
}
static krb5_error_code
hdb2od_principal_server(krb5_context context, hdb_od d, ODAttributeType type,
unsigned flags, hdb_entry_ex *entry, ODRecordRef record)
{
krb5_error_code ret;
CFStringRef name;
char *user;
if (d->LKDCRealm)
return 0;
ret = krb5_unparse_name(context, entry->entry.principal, &user);
if (ret)
return ret;
name = CFStringCreateWithCString(NULL, user, kCFStringEncodingUTF8);
free(user);
if (name == NULL)
return ENOMEM;
bool r = ODRecordSetValue(record, type, name, NULL);
CFRelease(name);
if (!r)
return HDB_ERR_UK_SERROR;
return 0;
}
static krb5_error_code
od2hdb_alias(krb5_context context, hdb_od d,
CFArrayRef data, unsigned flags, hdb_entry_ex *entry)
{
hdb_entry_ex_ctx *ctx = entry->ctx;
krb5_error_code ret;
krb5_principal principal;
char *str;
if (ctx && ctx->principal)
return 0;
str = rk_cfstring2cstring((CFStringRef)CFArrayGetValueAtIndex(data, 0));
if (str == NULL)
return ENOMEM;
ret = krb5_parse_name(context, str, &principal);
free(str);
if (ret)
return ret;
krb5_free_principal(context, principal);
return 0;
}
static krb5_error_code
od2hdb_keys(krb5_context context, hdb_od d,
CFArrayRef akeys, unsigned flags, hdb_entry_ex *entry)
{
CFIndex i, count = CFArrayGetCount(akeys);
krb5_error_code ret;
hdb_keyset_aapl *keys;
krb5_kvno specific_match = -1, general_match = -1;
krb5_kvno specific_kvno = 0, general_kvno = 0;
CFIndex len;
if (count == 0)
return ENOMEM;
keys = calloc(count, sizeof(keys[0]));
if (keys == NULL)
return ENOMEM;
ret = 0;
for (i = 0; i < count; i++) {
CFTypeRef type = CFArrayGetValueAtIndex(akeys, i);
void *ptr;
if (CFGetTypeID(type) == CFDataGetTypeID()) {
CFDataRef data = (CFDataRef) type;
len = CFDataGetLength(data);
ptr = malloc(len);
if (ptr == NULL) {
ret = ENOMEM;
goto out;
}
memcpy(ptr, CFDataGetBytePtr(data), len);
} else if (CFGetTypeID(type) == CFStringGetTypeID()) {
CFStringRef cfstr = (CFStringRef)type;
char *str;
str = rk_cfstring2cstring(cfstr);
if (str == NULL) {
ret = ENOMEM;
goto out;
}
ptr = malloc(strlen(str));
if (ptr == NULL) {
free(str);
ret = ENOMEM;
goto out;
}
ret = base64_decode(str, ptr);
free(str);
if (ret < 0) {
free(ptr);
ret = EINVAL;
goto out;
}
len = ret;
} else {
ret = EINVAL;
goto out;
}
ret = decode_hdb_keyset_aapl(ptr, len, &keys[i], NULL);
free(ptr);
if (ret)
goto out;
if (keys[i].principal) {
if (krb5_principal_compare(context, keys[i].principal, entry->entry.principal)) {
if (keys[i].kvno > specific_kvno) {
hdb_entry_ex_ctx *ctx = get_ex_ctx(entry);
if (ctx == NULL) {
ret = ENOMEM;
goto out;
}
ctx->principal = entry->entry.principal;
specific_match = (krb5_kvno)i;
specific_kvno = keys[i].kvno;
}
}
} else {
if (keys[i].kvno > general_kvno) {
general_match = (uint32_t)i;
general_kvno = keys[i].kvno;
}
}
}
if (specific_match != -1) {
i = specific_match;
entry->entry.kvno = specific_kvno;
} else if (general_match != -1) {
i = general_match;
entry->entry.kvno = general_kvno;
} else {
ret = HDB_ERR_NOENTRY;
goto out;
}
free_Keys(&entry->entry.keys);
entry->entry.keys.len = keys[i].keys.len;
entry->entry.keys.val = keys[i].keys.val;
keys[i].keys.val = NULL;
keys[i].keys.len = 0;
out:
for (i = 0; i < count; i++)
free_hdb_keyset_aapl(&keys[i]);
free(keys);
return ret;
}
static krb5_error_code
hdb2od_keys(krb5_context context, hdb_od d, ODAttributeType type,
unsigned flags, hdb_entry_ex *entry, ODRecordRef record)
{
CFMutableArrayRef array;
heim_octet_string data;
krb5_error_code ret;
CFDataRef element;
hdb_keyset_aapl key;
size_t size = 0;
if (flags & HDB_F_CHANGE_PASSWORD)
return 0;
key.kvno = entry->entry.kvno;
key.keys.len = entry->entry.keys.len;
key.keys.val = entry->entry.keys.val;
key.principal = NULL;
array = CFArrayCreateMutable(kCFAllocatorDefault, 1,
&kCFTypeArrayCallBacks);
if (array == NULL) {
ret = ENOMEM;
goto out;
}
ASN1_MALLOC_ENCODE(hdb_keyset_aapl, data.data, data.length, &key, &size, ret);
if (ret)
goto out;
if (data.length != size)
krb5_abortx(context, "internal asn.1 encoder error");
element = CFDataCreate(kCFAllocatorDefault, data.data, data.length);
if (element == NULL) {
ret = ENOMEM;
goto out;
}
CFArrayAppendValue(array, element);
CFRelease(element);
bool r = ODRecordSetValue(record, type, array, NULL);
if (!r)
ret = HDB_ERR_UK_SERROR;
out:
if (array)
CFRelease(array);
return ret;
}
static krb5_error_code
od2hdb_srp(krb5_context context, hdb_od d,
CFArrayRef akeys, unsigned flags, hdb_entry_ex *entry)
{
CFIndex i, count = CFArrayGetCount(akeys);
krb5_error_code ret;
HDB_extension ext;
hdb_srp srp;
CFIndex len;
if (count == 0)
return ENOMEM;
memset(&ext, 0, sizeof(ext));
ext.mandatory = FALSE;
ext.data.element = choice_HDB_extension_data_srp;
ext.data.u.srp.len = 0;
ext.data.u.srp.val = NULL;
ret = 0;
for (i = 0; i < count; i++) {
CFTypeRef type = CFArrayGetValueAtIndex(akeys, i);
void *ptr;
if (CFGetTypeID(type) == CFDataGetTypeID()) {
CFDataRef data = (CFDataRef) type;
len = CFDataGetLength(data);
ptr = malloc(len);
if (ptr == NULL) {
ret = ENOMEM;
goto out;
}
memcpy(ptr, CFDataGetBytePtr(data), len);
} else if (CFGetTypeID(type) == CFStringGetTypeID()) {
CFStringRef cfstr = (CFStringRef)type;
char *str;
str = rk_cfstring2cstring(cfstr);
if (str == NULL) {
ret = ENOMEM;
goto out;
}
ptr = malloc(strlen(str));
if (ptr == NULL) {
free(str);
ret = ENOMEM;
goto out;
}
ret = base64_decode(str, ptr);
free(str);
if (ret < 0) {
free(ptr);
ret = EINVAL;
goto out;
}
len = ret;
} else {
ret = EINVAL;
goto out;
}
ret = decode_hdb_srp(ptr, len, &srp, NULL);
free(ptr);
if (ret)
goto out;
ret = add_hdb_srp_set(&ext.data.u.srp, &srp);
free_hdb_srp(&srp);
if (ret) {
goto out;
}
}
ret = hdb_replace_extension(context, &entry->entry, &ext);
out:
free_HDB_extension(&ext);
return ret;
}
static krb5_error_code
hdb2od_srp(krb5_context context, hdb_od d, ODAttributeType type,
unsigned flags, hdb_entry_ex *entry, ODRecordRef record)
{
CFMutableArrayRef array;
heim_octet_string data;
krb5_error_code ret;
CFDataRef element;
hdb_srp_set key;
size_t size = 0;
memset(&key, 0, sizeof(key));
if (flags & HDB_F_CHANGE_PASSWORD)
return 0;
array = CFArrayCreateMutable(kCFAllocatorDefault, 1,
&kCFTypeArrayCallBacks);
if (array == NULL) {
ret = ENOMEM;
goto out;
}
ASN1_MALLOC_ENCODE(hdb_srp_set, data.data, data.length, &key, &size, ret);
if (ret)
goto out;
if (data.length != size)
krb5_abortx(context, "internal asn.1 encoder error");
element = CFDataCreate(kCFAllocatorDefault, data.data, data.length);
if (element == NULL) {
ret = ENOMEM;
goto out;
}
CFArrayAppendValue(array, element);
CFRelease(element);
bool r = ODRecordSetValue(record, type, array, NULL);
if (!r)
ret = HDB_ERR_UK_SERROR;
out:
if (array)
CFRelease(array);
return ret;
}
static krb5_error_code
od2hdb_flags(krb5_context context, hdb_od d,
CFArrayRef data, unsigned qflags, hdb_entry_ex *entry)
{
int flags;
char *str;
str = rk_cfstring2cstring((CFStringRef)CFArrayGetValueAtIndex(data, 0));
if (str == NULL)
return ENOMEM;
flags = atoi(str);
free(str);
entry->entry.flags = int2HDBFlags(flags);
return 0;
}
static krb5_error_code
od2hdb_def_flags(krb5_context context, hdb_od d,
int flags, hdb_entry_ex *entry)
{
entry->entry.flags.client = 1;
entry->entry.flags.forwardable = 1;
entry->entry.flags.renewable = 1;
entry->entry.flags.proxiable = 1;
return 0;
}
static krb5_error_code
hdb2od_flags(krb5_context context, hdb_od d, ODAttributeType type,
unsigned flags, hdb_entry_ex *entry, ODRecordRef record)
{
int eflags = HDBFlags2int(entry->entry.flags);
CFStringRef value;
value = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
CFSTR("%d"), eflags);
if (value == NULL)
return ENOMEM;
bool r = ODRecordSetValue(record, type, value, NULL);
CFRelease(value);
if (!r)
return HDB_ERR_UK_SERROR;
return 0;
}
static krb5_error_code
od2hdb_altsecids(krb5_context context, hdb_od d,
CFArrayRef akeys, unsigned flags, hdb_entry_ex *entry)
{
CFIndex i, count = CFArrayGetCount(akeys);
krb5_error_code ret;
if (count == 0)
return ENOMEM;
for (i = 0; i < count; i++) {
CFStringRef name = CFArrayGetValueAtIndex(akeys, i);
char *str, *str0, *subj;
if (CFGetTypeID(name) != CFStringGetTypeID())
continue;
if (!CFStringHasPrefix(name, CFSTR("X509:<T>")))
continue;
str0 = str = rk_cfstring2cstring(name);
str += 8;
subj = strstr(str, "<S>");
if (subj == NULL) {
free(str0);
continue;
}
*subj = '\0';
subj += 3;
if (strstr(subj, "<T>") || strstr(subj, "<S>") ||
strstr(str, "<T>") || strstr(str, "<S>"))
{
free(str0);
continue;
}
ret = hdb_entry_set_pkinit_acl(&entry->entry, subj, NULL, str);
free(str0);
if (ret)
return ret;
}
return 0;
}
static krb5_error_code
od2hdb_acl_rights(krb5_context context, hdb_od d,
CFArrayRef data, unsigned flags, hdb_entry_ex *entry)
{
char *str;
str = rk_cfstring2cstring((CFStringRef)CFArrayGetValueAtIndex(data, 0));
if (str == NULL)
return ENOMEM;
entry->entry.acl_rights = malloc(sizeof(*entry->entry.acl_rights));
if (entry->entry.acl_rights == NULL) {
free(str);
return ENOMEM;
}
*entry->entry.acl_rights = atoi(str);
free(str);
return 0;
}
typedef krb5_error_code
(*od2hdb_func)(krb5_context, hdb_od, CFArrayRef, unsigned flags, hdb_entry_ex *);
typedef krb5_error_code
(*od2hdb_default)(krb5_context, hdb_od, int, hdb_entry_ex *);
typedef krb5_error_code
(*hdb2od_func)(krb5_context, hdb_od, ODAttributeType,
unsigned flags, hdb_entry_ex *, ODRecordRef);
static const struct key {
const char *desc;
CFStringRef name;
int flags;
#define MULTI_VALUE 1
#define OPTIONAL_VALUE 2
#define SERVER_VALUE 4
#define DELETE_KEY 8
od2hdb_func od2hdb;
od2hdb_default od2hdb_def;
hdb2od_func hdb2od;
} keys[] = {
{
"keyset",
CFSTR("dsAttrTypeNative:draft-krbKeySet"), OPTIONAL_VALUE | MULTI_VALUE | SERVER_VALUE | DELETE_KEY,
od2hdb_keys,
NULL,
hdb2od_keys
},
{
"srp-keyset",
CFSTR("dsAttrTypeNative:HeimdalSRPKey"), OPTIONAL_VALUE | MULTI_VALUE | DELETE_KEY,
od2hdb_srp,
NULL,
hdb2od_srp
},
{
"KerberosKeys",
CFSTR("dsAttrTypeNative:KerberosKeys"), OPTIONAL_VALUE | MULTI_VALUE | DELETE_KEY,
od2hdb_keys,
NULL,
hdb2od_keys
},
{
"username",
CFSTR("dsAttrTypeNative:KerberosUserName"), OPTIONAL_VALUE | DELETE_KEY,
od2hdb_principal,
od2hdb_def_principal,
hdb2od_principal_lkdc
},
{
"principalName",
CFSTR("dsAttrTypeNative:draft-krbPrincipalName"), OPTIONAL_VALUE | SERVER_VALUE | DELETE_KEY,
od2hdb_principal,
NULL,
hdb2od_principal_server
},
{
"principalAlias",
CFSTR("dsAttrTypeNative:draft-krbPrincipalAliases"), MULTI_VALUE | OPTIONAL_VALUE | DELETE_KEY,
od2hdb_alias,
NULL,
NULL
},
{
"KerberosFlags",
CFSTR("dsAttrTypeNative:KerberosFlags"), DELETE_KEY,
od2hdb_flags,
od2hdb_def_flags,
hdb2od_flags
},
{
"KerberosPolicy",
CFSTR("dsAttrTypeNative:draft-krbTicketPolicy"), OPTIONAL_VALUE | SERVER_VALUE | DELETE_KEY,
od2hdb_flags,
NULL,
hdb2od_flags
},
{
"MaxLife",
CFSTR("dsAttrTypeNative:KerberosMaxLife"), OPTIONAL_VALUE | DELETE_KEY,
od2hdb_null,
NULL,
NULL
},
{
"MaxRenewLife",
CFSTR("dsAttrTypeNative:KerberosMaxRenew"), OPTIONAL_VALUE | DELETE_KEY,
od2hdb_null,
NULL,
NULL
},
{
"UserCertificate",
CFSTR("dsAttrTypeStandard:UserCertificate"), MULTI_VALUE | OPTIONAL_VALUE,
od2hdb_usercert,
NULL,
NULL
},
{
"AltSecurityIdentities",
CFSTR("dsAttrTypeStandard:AltSecurityIdentities"), MULTI_VALUE | OPTIONAL_VALUE,
od2hdb_altsecids,
NULL,
NULL
},
{
"principalACL",
CFSTR("dsAttrTypeNative:draft-krbPrincipalACL"), OPTIONAL_VALUE,
od2hdb_acl_rights,
NULL,
NULL
}
};
static const int num_keys = sizeof(keys)/sizeof(keys[0]);
static krb5_error_code
updateTimePolicy(int64_t reltime, time_t **t)
{
if (reltime == kODExpirationTimeNeverExpires) {
if (*t) {
free(*t);
*t = NULL;
}
} else if (reltime == kODExpirationTimeExpired || reltime < 0) {
if (*t == NULL) {
*t = malloc(sizeof(**t));
if (*t == NULL)
return ENOMEM;
}
**t = time(NULL) - 60;
} else {
if (*t == NULL) {
*t = malloc(sizeof(**t));
if (*t == NULL)
return ENOMEM;
}
time_t truncated = (time_t)reltime;
if ((int64_t)truncated < reltime) {
truncated = 3600 * 24 * 365 * 2;
}
time_t now = time(NULL);
time_t expire = now + truncated;
if (expire < now) {
expire = now + (3600 * 24 * 365 * 2);
}
**t = expire;
}
return 0;
}
static krb5_error_code
hod_close(krb5_context context, HDB *db)
{
return 0;
}
static krb5_error_code
hod_destroy(krb5_context context, HDB *db)
{
hdb_od d = (hdb_od)db->hdb_db;
krb5_error_code ret;
if (d->rootNode)
CFRelease(d->rootNode);
if (d->inNodeAttributes)
CFRelease(d->inNodeAttributes);
if (d->inKDCAttributes)
CFRelease(d->inKDCAttributes);
if (d->LKDCRealm)
free(d->LKDCRealm);
ret = hdb_clear_master_key (context, db);
free(db->hdb_name);
free(db);
return ret;
}
static krb5_error_code
hod_lock(krb5_context context, HDB *db, int operation)
{
return 0;
}
static krb5_error_code
hod_unlock(krb5_context context, HDB *db)
{
return 0;
}
static krb5_error_code
od_record2entry(krb5_context context, HDB * db, ODRecordRef cfRecord,
unsigned flags, hdb_entry_ex * entry)
{
hdb_od d = (hdb_od)db->hdb_db;
krb5_error_code ret = HDB_ERR_NOENTRY;
int i;
for (i = 0; i < num_keys; i++) {
CFArrayRef data;
data = ODRecordCopyValues(cfRecord, keys[i].name, NULL);
if (data == NULL) {
if (keys[i].flags & OPTIONAL_VALUE)
continue;
if (keys[i].od2hdb_def) {
ret = (*keys[i].od2hdb_def)(context, d, flags, entry);
if (ret)
goto out;
continue;
}
krb5_warnx(context, "Failed to copy non-optional value %s", keys[i].desc);
ret = HDB_ERR_NOENTRY;
goto out;
}
if ((keys[i].flags & MULTI_VALUE) && CFArrayGetCount(data) < 1) {
krb5_warnx(context, "Expected multivalue got zero %s for", keys[i].desc);
CFRelease(data);
ret = HDB_ERR_NOENTRY;
goto out;
} else if ((keys[i].flags & MULTI_VALUE) == 0 && CFArrayGetCount(data) != 1) {
krb5_warnx(context, "Expected single-value got non zero %d for %s",
(int)CFArrayGetCount(data), keys[i].desc);
CFRelease(data);
ret = HDB_ERR_NOENTRY;
goto out;
}
ret = (*keys[i].od2hdb)(context, d, data, flags, entry);
CFRelease(data);
if (ret) {
krb5_warn(context, ret, "od2hdb failed for %s", keys[i].desc);
goto out;
}
}
if (entry->entry.principal == NULL) {
krb5_warnx(context, "principal missing");
ret = HDB_ERR_NOENTRY;
goto out;
}
ret = krb5_parse_name(context, "hdb/od@WELL-KNOWN:OD-BACKEND",
&entry->entry.created_by.principal);
if (ret)
goto out;
entry->entry.created_by.time = time(NULL);
if ((flags & HDB_F_GET_KRBTGT) == 0 && !krb5_principal_is_krbtgt(context, entry->entry.principal)) {
ODRecordRef userrecord;
userrecord = HODODNodeCopyLinkageRecordFromAuthenticationData(context, d->rootNode, cfRecord);
if (userrecord) {
int64_t reltime;
CFIndex errorcode = 0;
CFErrorRef error = NULL;
if (!ODRecordAuthenticationAllowed(userrecord, &error)) {
if (error) {
errorcode = CFErrorGetCode(error);
CFRelease(error);
}
}
if (errorcode == kODErrorCredentialsPasswordChangeRequired || errorcode == kODErrorCredentialsPasswordExpired) {
reltime = kODExpirationTimeExpired;
} else {
reltime = ODRecordSecondsUntilPasswordExpires(userrecord);
}
ret = updateTimePolicy(reltime, &entry->entry.pw_end);
if (ret) {
CFRelease(userrecord);
goto out;
}
if (errorcode == kODErrorCredentialsAccountDisabled) {
reltime = kODExpirationTimeExpired;
entry->entry.flags.invalid = 1;
} else if (errorcode == kODErrorCredentialsAccountExpired) {
reltime = kODExpirationTimeExpired;
} else {
reltime = ODRecordSecondsUntilAuthenticationsExpire(userrecord);
}
ret = updateTimePolicy(reltime, &entry->entry.valid_end);
CFRelease(userrecord);
if (ret)
goto out;
}
}
if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) {
ret = hdb_unseal_keys(context, db, &entry->entry);
if(ret) {
krb5_warn(context, ret, "unseal keys failed");
goto out;
}
}
out:
return ret;
}
static krb5_error_code
hod_nextkey(krb5_context context, HDB * db, unsigned flags,
hdb_entry_ex * entry)
{
ODRecordRef cfRecord = NULL;
hdb_od d = (hdb_od)db->hdb_db;
krb5_error_code ret;
memset(entry, 0, sizeof(*entry));
heim_assert(d->iter.odRecordArray != NULL, "hdb: hod_next w/o hod_first");
while (d->iter.idx < CFArrayGetCount(d->iter.odRecordArray)) {
cfRecord = (ODRecordRef) CFArrayGetValueAtIndex(d->iter.odRecordArray, d->iter.idx);
d->iter.idx++;
if (cfRecord == NULL) {
ret = HDB_ERR_NOENTRY;
goto out;
}
CFArrayRef kerberosKeysArray = ODRecordCopyValues(cfRecord, CFSTR("dsAttrTypeNative:KerberosKeys"), NULL);
if (kerberosKeysArray == NULL) {
cfRecord = NULL;
continue;
}
if (CFArrayGetCount(kerberosKeysArray) == 0) {
CFRelease(kerberosKeysArray);
cfRecord = NULL;
continue;
}
CFRelease(kerberosKeysArray);
break;
}
if (cfRecord) {
CFStringRef name = ODRecordGetRecordName(cfRecord);
char *str;
str = rk_cfstring2cstring(name);
if (str == NULL) {
ret = ENOENT;
goto out;
}
ret = krb5_make_principal(context, &entry->entry.principal,
d->LKDCRealm, str, NULL);
free(str);
if (ret)
goto out;
ret = od_record2entry(context, db, cfRecord, flags, entry);
} else
ret = HDB_ERR_NOENTRY;
out:
if (ret) {
free_hdb_entry(&entry->entry);
if (d->iter.odRecordArray) {
CFRelease(d->iter.odRecordArray);
d->iter.odRecordArray = NULL;
d->iter.idx = 0;
}
}
return ret;
}
static krb5_error_code
hod_firstkey(krb5_context context, HDB *db,
unsigned flags, hdb_entry_ex *entry)
{
int ret = 0;
ODQueryRef query = NULL;
hdb_od d = (hdb_od)db->hdb_db;
int tryAgain = MAX_TRIES;
if (d->LKDCRealm == NULL) {
krb5_set_error_message(context, EINVAL, "iteration over database only supported for DSLocal");
return EINVAL;
}
heim_assert(d->iter.odRecordArray == NULL, "hdb: more then one iteration at the same time");
do {
CFErrorRef error = NULL;
query = ODQueryCreateWithNode(NULL, d->rootNode,
kODRecordTypeUsers, NULL,
kODMatchAny,
NULL,
kODAttributeTypeAllAttributes,
0,
&error);
if (query == NULL) {
ret = HDB_ERR_NOENTRY;
goto out;
}
d->iter.odRecordArray = ODQueryCopyResults(query, FALSE, &error);
CFRelease(query);
if (d->iter.odRecordArray == NULL && error == NULL)
tryAgain = 0;
if (error)
CFRelease(error);
} while(d->iter.odRecordArray == NULL && tryAgainP(context, &tryAgain, NULL));
if (d->iter.odRecordArray == NULL) {
ret = HDB_ERR_NOENTRY;
goto out;
}
d->iter.idx = 0;
return hod_nextkey(context, db, flags, entry);
out:
return ret;
}
static krb5_error_code
hod_open(krb5_context context, HDB * db, int flags, mode_t mode)
{
hdb_od d = (hdb_od)db->hdb_db;
if (d->rootNode == NULL)
return nodeCreateWithName(context, d, &d->rootNode);
return 0;
}
static krb5_error_code
lkdc_locate_record(krb5_context context, hdb_od d, krb5_principal principal,
unsigned flags, int createp, int *createdp, ODRecordRef *result)
{
ODMatchType matchtype = kODMatchEqualTo;
CFStringRef queryString = NULL;
ODRecordRef cfRecord;
ODQueryRef query = NULL;
CFArrayRef res = NULL;
CFStringRef attr;
krb5_error_code ret = 0;
krb5_principal eprinc = NULL;
char *kuser = NULL;
const char *node = NULL;
CFTypeRef rtype = NULL;
int upflags = 0;
int tryAgain = MAX_TRIES;
*result = NULL;
if (principal->name.name_type == KRB5_NT_X509_GENERAL_NAME) {
#ifdef PKINIT
char *anode = NULL;
hx509_name name = NULL;
GeneralName gn;
ssize_t len;
void *buf;
Name n;
memset(&gn, 0, sizeof(gn));
memset(&n, 0, sizeof(n));
if (krb5_principal_get_num_comp(context, principal) != 1) {
krb5_warnx(context, "wrong length of x509 name");
return HDB_ERR_NOENTRY;
}
len = strlen(principal->name.name_string.val[0]);
buf = malloc(len);
if (buf == NULL)
return HDB_ERR_NOENTRY;
len = base64_decode(principal->name.name_string.val[0], buf);
if (len <= 0) {
free(buf);
return HDB_ERR_NOENTRY;
}
ret = decode_GeneralName(buf, len, &gn, NULL);
free(buf);
if (ret) {
krb5_warnx(context, "x500 GeneralName malformed: %d", ret);
return HDB_ERR_NOENTRY;
}
if (gn.element != choice_GeneralName_directoryName) {
krb5_warnx(context, "x500 name not directory Name");
free_GeneralName(&gn);
return HDB_ERR_NOENTRY;
}
n.element = choice_Name_rdnSequence;
n.u.rdnSequence.len = gn.u.directoryName.u.rdnSequence.len;
n.u.rdnSequence.val = gn.u.directoryName.u.rdnSequence.val;
ret = hx509_name_from_Name(&n, &name);
free_GeneralName(&gn);
if (ret)
return HDB_ERR_NOENTRY;
attr = kODAttributeTypeRecordName;
rtype = kODRecordTypeUsers;
ret = hx509_name_to_string(name, &anode);
hx509_name_free(&name);
if (ret)
return HDB_ERR_NOENTRY;
queryString = CFStringCreateWithCString(NULL, anode, kCFStringEncodingUTF8);
free(anode);
if (queryString == NULL) {
ret = HDB_ERR_NOENTRY;
goto out;
}
createp = 0;
#else
return HDB_ERR_NOENTRY;
#endif
} else if (principal->name.name_type == KRB5_NT_NTLM) {
attr = kODAttributeTypeRecordName;
rtype = kODRecordTypeUsers;
if (krb5_principal_get_num_comp(context, principal) != 1) {
krb5_warnx(context, "wrong length of ntlm name");
return HDB_ERR_NOENTRY;
}
if (d->ntlmDomain == NULL) {
krb5_warnx(context, "NTLM domain not configured");
return HDB_ERR_NOENTRY;
}
krb5_principal_set_realm(context, principal, d->ntlmDomain);
node = krb5_principal_get_comp_string(context, principal, 0);
} else if (krb5_principal_is_pku2u(context, principal)) {
attr = kODAttributeTypeRecordName;
if (flags & HDB_F_GET_CLIENT) {
int num = krb5_principal_get_num_comp(context, principal);
if (num != 1) {
ret = HDB_ERR_NOENTRY;
goto out;
}
rtype = kODRecordTypeUsers;
upflags = KRB5_PRINCIPAL_UNPARSE_NO_REALM;
upflags |= KRB5_PRINCIPAL_UNPARSE_DISPLAY;
} else {
node = "localhost";
rtype = kODRecordTypeComputers;
}
} else if (is_lkdc(d, principal->realm)) {
int num = krb5_principal_get_num_comp(context, principal);
const char *comp0;
size_t lencomp0;
if (num < 1) {
ret = HDB_ERR_NOENTRY;
goto out;
}
ret = map_lkdc_principal(context, principal, d->LKDCRealm, wellknown_lkdc);
if (ret)
goto out;
comp0 = krb5_principal_get_comp_string(context, principal, 0);
lencomp0 = strlen(comp0);
if (lencomp0 == 0) {
ret = HDB_ERR_NOENTRY;
goto out;
}
if (num == 2 || (num == 1 && comp0[lencomp0 - 1] == '$')) {
const char *host;
attr = kODAttributeTypeRecordName;
ret = map_service(context, principal, &eprinc);
if (ret) {
ret = HDB_ERR_NOENTRY;
goto out;
}
principal = eprinc;
host = krb5_principal_get_comp_string(context, principal, 1);
comp0 = krb5_principal_get_comp_string(context, principal, 0);
if (strcasecmp(KRB5_TGS_NAME, comp0) == 0) {
node = "_krbtgt";
rtype = kODRecordTypeUsers;
} else if (num == 2 && strcmp(KRB5_WELLKNOWN_NAME, comp0) == 0 && strcmp(KRB5_FAST_COOKIE, host) == 0) {
node = "_krbfast";
rtype = kODRecordTypeUsers;
} else if (num == 2 && is_lkdc(d, host)) {
node = "localhost";
rtype = kODRecordTypeComputers;
} else if (num == 2) {
node = host;
rtype = kODRecordTypeComputers;
} else {
node = comp0;
rtype = kODRecordTypeComputers;
}
} else if (num == 1) {
attr = kODAttributeTypeRecordName;
rtype = kODRecordTypeUsers;
upflags = KRB5_PRINCIPAL_UNPARSE_NO_REALM;
upflags |= KRB5_PRINCIPAL_UNPARSE_DISPLAY;
} else {
ret = HDB_ERR_NOENTRY;
goto out;
}
} else {
ret = krb5_unparse_name_flags(context, principal, KRB5_PRINCIPAL_UNPARSE_DISPLAY, &kuser);
if (ret)
goto out;
rtype = d->inComputerOrUsers;
matchtype = kODMatchCompoundExpression;
attr = NULL;
queryString = CFStringCreateWithFormat(NULL, NULL, CFSTR("(|(%@=%s)(%@=%s))"),
CFSTR("dsAttrTypeNative:KerberosUserName"),
kuser,
CFSTR("dsAttrTypeNative:KerberosServerName"),
kuser);
}
if (queryString == NULL) {
if (node) {
queryString = CFStringCreateWithCString(NULL, node, kCFStringEncodingUTF8);
} else {
ret = krb5_unparse_name_flags(context, principal, upflags, &kuser);
if (ret)
goto out;
queryString = CFStringCreateWithCString(NULL, kuser, kCFStringEncodingUTF8);
}
}
if (queryString == NULL) {
ret = ENOMEM;
goto out;
}
do {
CFErrorRef error = NULL;
query = ODQueryCreateWithNode(NULL, d->rootNode,
rtype, attr,
matchtype,
queryString,
d->inNodeAttributes,
1,
NULL);
if (query == NULL) {
CFRelease(queryString);
ret = ENOMEM;
goto out;
}
res = ODQueryCopyResults(query, FALSE, &error);
if (res == NULL && error == NULL)
tryAgain = 0;
if (error)
CFRelease(error);
} while(res == NULL && tryAgainP(context, &tryAgain, NULL));
CFRelease(queryString);
if (res == NULL) {
ret = HDB_ERR_NOENTRY;
goto out;
}
if (CFArrayGetCount(res) == 0 && node && createp) {
queryString = CFStringCreateWithCString(NULL, node, kCFStringEncodingUTF8);
cfRecord = ODNodeCopyRecord(d->rootNode, rtype, queryString, d->inNodeAttributes, NULL);
if (cfRecord == NULL) {
CFMutableDictionaryRef attributes;
attributes = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
cfRecord = ODNodeCreateRecord(d->rootNode,
rtype,
queryString,
attributes,
NULL);
CFRelease(attributes);
}
CFRelease(queryString);
if (cfRecord == NULL) {
ret = HDB_ERR_UK_RERROR;
goto out;
}
if (createdp)
*createdp = TRUE;
} else if (CFArrayGetCount(res) == 1) {
cfRecord = (ODRecordRef) CFArrayGetValueAtIndex(res, 0);
if (cfRecord == NULL) {
ret = HDB_ERR_NOENTRY;
goto out;
}
CFRetain(cfRecord);
} else {
ret = HDB_ERR_NOENTRY;
goto out;
}
*result = cfRecord;
out:
if (kuser)
free(kuser);
if (eprinc)
krb5_free_principal(context, eprinc);
if (res)
CFRelease(res);
if (query)
CFRelease(query);
return ret;
}
static krb5_error_code
server_locate_record(krb5_context context, hdb_od d, krb5_principal principal,
unsigned flags, int createp, int *createdp, ODRecordRef *result)
{
ODRecordRef cfRecord = NULL;
krb5_error_code ret;
ODMatchType matchtype;
CFStringRef queryString = NULL;
ODQueryRef query = NULL;
CFArrayRef res = NULL;
CFStringRef attr;
char *kuser = NULL;
CFStringRef client_key, server_key;
int tryAgain = MAX_TRIES;
*result = NULL;
ret = krb5_unparse_name_flags(context, principal, KRB5_PRINCIPAL_UNPARSE_DISPLAY, &kuser);
if (ret)
goto out;
client_key = CFSTR("dsAttrTypeNative:draft-krbPrincipalName");
server_key = CFSTR("dsAttrTypeNative:draft-krbPrincipalAliases");
if (flags & HDB_F_GET_KRBTGT) {
matchtype = kODMatchEqualTo;
attr = client_key;
queryString = CFStringCreateWithCString(NULL, kuser, kCFStringEncodingUTF8);
} else {
matchtype = kODMatchCompoundExpression;
attr = NULL;
queryString = CFStringCreateWithFormat(NULL, NULL, CFSTR("(|(%@=%s)(%@=%s))"),
server_key,
kuser,
client_key,
kuser);
}
if (queryString == NULL) {
ret = HDB_ERR_NOENTRY;
goto out;
}
do {
CFErrorRef error = NULL;
query = ODQueryCreateWithNode(NULL, d->rootNode,
kODRecordTypeUserAuthenticationData,
attr,
matchtype,
queryString,
d->inNodeAttributes,
2,
NULL);
if (query == NULL) {
CFRelease(queryString);
ret = ENOMEM;
goto out;
}
res = ODQueryCopyResults(query, FALSE, &error);
if (res == NULL && error == NULL)
tryAgain = 0;
if (error)
CFRelease(error);
} while(res == NULL && tryAgainP(context, &tryAgain, NULL));
CFRelease(queryString);
if (res == NULL) {
ret = HDB_ERR_NOENTRY;
goto out;
}
CFIndex count = CFArrayGetCount(res);
if (count == 0 && createp) {
CFMutableDictionaryRef attributes;
CFStringRef name;
CFUUIDRef uuid;
uuid = CFUUIDCreate(NULL);
if (uuid == NULL) {
ret = HDB_ERR_NOENTRY;
goto out;
}
name = CFUUIDCreateString(NULL, uuid);
CFRelease(uuid);
if (name == NULL) {
ret = HDB_ERR_NOENTRY;
goto out;
}
attributes = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (attributes == NULL) {
CFRelease(name);
ret = HDB_ERR_NOENTRY;
goto out;
}
cfRecord = ODNodeCreateRecord(d->rootNode,
kODRecordTypeUserAuthenticationData,
name,
attributes,
NULL);
CFRelease(name);
CFRelease(attributes);
if (cfRecord == NULL) {
ret = HDB_ERR_NOENTRY;
goto out;
}
if (createdp)
*createdp = 1;
} else if (count != 1) {
ret = HDB_ERR_NOENTRY;
goto out;
} else {
cfRecord = (ODRecordRef) CFArrayGetValueAtIndex(res, 0);
if (cfRecord == NULL) {
ret = HDB_ERR_NOENTRY;
goto out;
}
CFRetain(cfRecord);
}
*result = cfRecord;
out:
if (res)
CFRelease(res);
if (query)
CFRelease(query);
if (kuser)
free(kuser);
return ret;
}
static krb5_error_code
hod_lkdc_fetch(krb5_context context, HDB * db, krb5_const_principal principal,
unsigned flags, krb5_kvno kvno, hdb_entry_ex * entry)
{
krb5_principal eprincipal = NULL;
hdb_od d = (hdb_od)db->hdb_db;
ODRecordRef cfRecord = NULL;
krb5_error_code ret;
ret = krb5_copy_principal(context, principal, &eprincipal);
if (ret)
goto out;
ret = d->locate_record(context, d, eprincipal, flags, FALSE, NULL, &cfRecord);
if (ret)
goto out;
if (is_lkdc(d, eprincipal->realm))
map_lkdc_principal(context, eprincipal, wellknown_lkdc, d->LKDCRealm);
if (krb5_principal_is_pku2u(context, eprincipal) ||
(krb5_principal_is_lkdc(context, eprincipal) && krb5_principal_get_num_comp(context, eprincipal) == 1))
{
CFStringRef name = ODRecordGetRecordName(cfRecord);
char *str;
str = rk_cfstring2cstring(name);
if (str == NULL) {
ret = ENOENT;
goto out;
}
ret = krb5_make_principal(context, &entry->entry.principal,
eprincipal->realm, str, NULL);
free(str);
if (ret)
goto out;
} else {
ret = krb5_copy_principal(context, eprincipal, &entry->entry.principal);
if (ret)
goto out;
}
ret = od_record2entry(context, db, cfRecord, flags, entry);
if (ret)
goto out;
out:
if (eprincipal)
krb5_free_principal(context, eprincipal);
if (cfRecord)
CFRelease(cfRecord);
if (ret) {
free_hdb_entry(&entry->entry);
memset(&entry->entry, 0, sizeof(entry->entry));
}
return ret;
}
static krb5_error_code
hod_server_fetch(krb5_context context, HDB * db, krb5_const_principal principal,
unsigned flags, krb5_kvno kvno, hdb_entry_ex * entry)
{
krb5_principal eprincipal = NULL;
hdb_od d = (hdb_od)db->hdb_db;
ODRecordRef cfRecord = NULL;
krb5_error_code ret;
ret = krb5_copy_principal(context, principal, &eprincipal);
if (ret)
goto out;
ret = d->locate_record(context, d, eprincipal, flags, FALSE, NULL, &cfRecord);
if (ret)
goto out;
ret = krb5_copy_principal(context, eprincipal, &entry->entry.principal);
if (ret)
goto out;
ret = od_record2entry(context, db, cfRecord, flags, entry);
if (ret)
goto out;
out:
if (cfRecord)
CFRelease(cfRecord);
if (eprincipal)
krb5_free_principal(context, eprincipal);
if (ret) {
free_hdb_entry(&entry->entry);
memset(&entry->entry, 0, sizeof(entry->entry));
}
return ret;
}
static krb5_error_code
hod_store(krb5_context context, HDB * db, unsigned flags,
hdb_entry_ex * entry)
{
krb5_principal eprincipal = NULL;
hdb_od d = (hdb_od)db->hdb_db;
ODRecordRef record = NULL;
krb5_error_code ret;
int i, created = 0;
ret = krb5_copy_principal(context, entry->entry.principal, &eprincipal);
if (ret)
goto out;
ret = d->locate_record(context, d, eprincipal, flags, (flags & HDB_F_REPLACE) == 0, &created, &record);
if (ret)
goto out;
for (i = 0; i < num_keys; i++) {
if (keys[i].hdb2od == NULL)
continue;
if ((!!(keys[i].flags & SERVER_VALUE)) ^ (!d->LKDCRealm))
continue;
ret = (*keys[i].hdb2od)(context, d, keys[i].name, flags, entry, record);
if (ret)
goto out;
}
if ((flags & HDB_F_CHANGE_PASSWORD) == 0) {
ret = hdb_seal_keys(context, db, &entry->entry);
if(ret)
goto out;
}
ODRecordSynchronize(record, NULL);
out:
if (record) {
if (ret && created)
ODRecordDelete(record, NULL);
CFRelease(record);
}
if (eprincipal)
krb5_free_principal(context, eprincipal);
return ret;
}
static krb5_error_code
hod_remove(krb5_context context, HDB *db, krb5_const_principal principal)
{
krb5_principal eprincipal = NULL;
hdb_od d = (hdb_od)db->hdb_db;
ODRecordRef record = NULL;
CFMutableArrayRef array = NULL;
krb5_error_code ret;
int i;
ret = krb5_copy_principal(context, principal, &eprincipal);
if (ret)
goto out;
ret = d->locate_record(context, d, eprincipal, 0, FALSE, NULL, &record);
if (ret)
goto out;
array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
if (array == NULL) {
ret = ENOMEM;
goto out;
}
for (i = 0; i < num_keys; i++) {
if ((keys[i].flags & DELETE_KEY) == 0)
continue;
if (!ODRecordSetValue(record, keys[i].name, array, NULL)) {
ret = HDB_ERR_UK_SERROR;
break;
}
}
ODRecordSynchronize(record, NULL);
out:
if (eprincipal)
krb5_free_principal(context, eprincipal);
if (array)
CFRelease(array);
if (record)
CFRelease(record);
return ret;
}
static krb5_error_code
hod_get_realms(krb5_context context, HDB *db, krb5_realm **realms)
{
hdb_od d = (hdb_od)db->hdb_db;
*realms = NULL;
if (d->LKDCRealm) {
*realms = calloc(2, sizeof((*realms)[0]));
if (*realms == NULL)
return ENOMEM;
(*realms)[0] = strdup(d->LKDCRealm);
if ((*realms)[0] == NULL) {
free(*realms);
*realms = NULL;
return ENOMEM;
}
(*realms)[1] = NULL;
}
return 0;
}
static void
update_ntlm(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info)
{
hdb_od d = info;
CFDictionaryRef settings;
CFStringRef n;
if (store == NULL || info == NULL)
return;
settings = (CFDictionaryRef)SCDynamicStoreCopyValue(store, CFSTR("com.apple.smb"));
if (settings == NULL)
return;
n = CFDictionaryGetValue(settings, CFSTR("NetBIOSName"));
if (n == NULL || CFGetTypeID(n) != CFStringGetTypeID())
goto fin;
if (d->ntlmDomain)
free(d->ntlmDomain);
d->ntlmDomain = rk_cfstring2cstring(n);
strupr(d->ntlmDomain);
fin:
CFRelease(settings);
return;
}
static void
ntlm_notification(hdb_od d)
{
SCDynamicStoreRef store;
dispatch_queue_t queue;
SCDynamicStoreContext context;
memset(&context, 0, sizeof(context));
context.info = (void*)d;
store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("com.apple.Kerberos.hdb-od"), update_ntlm, (void *)&context);
if (store == NULL)
return;
CFTypeRef key[] = {CFSTR("com.apple.smb")};
CFArrayRef nkeys = CFArrayCreate(kCFAllocatorDefault, key, 1, NULL);
SCDynamicStoreSetNotificationKeys(store, nkeys, NULL);
CFRelease(nkeys);
queue = dispatch_queue_create("com.apple.hdb-od.ntlm-name", NULL);
if (queue == NULL) {
CFRelease(store);
errx(1, "dispatch_queue_create");
}
SCDynamicStoreSetDispatchQueue(store, queue);
CFRelease(store);
dispatch_sync(queue, ^{ update_ntlm(store, NULL, d); });
}
static krb5_error_code
hod_get_ntlm_domain(krb5_context context, struct HDB *db, char **name)
{
hdb_od d = (hdb_od)db->hdb_db;
if (d->ntlmDomain == NULL) {
krb5_set_error_message(context, EINVAL, "no ntlm domain");
return EINVAL;
}
*name = strdup(d->ntlmDomain);
return 0;
}
#ifdef __APPLE_PRIVATE__
static struct {
typeof(DSChangePasswordWithPolicy) *_HDBDSChangePasswordWithPolicy;
typeof(DSUpdateLoginStatus) *_HDBDSUpdateLoginStatus;
} ds_ptrs;
static void
DirectoryServer_framework(void)
{
static dispatch_once_t once;
dispatch_once(&once, ^{
void *ptr = dlopen("/System/Library/PrivateFrameworks/DirectoryServer.framework/DirectoryServer", RTLD_LAZY);
if (ptr) {
ds_ptrs._HDBDSChangePasswordWithPolicy = dlsym(ptr, "DSChangePasswordWithPolicy");
ds_ptrs._HDBDSUpdateLoginStatus = dlsym(ptr, "DSUpdateLoginStatus");
}
});
}
static krb5_error_code
hod_password(krb5_context context, struct HDB *db, hdb_entry_ex *entry, const char *password, int flags)
{
char *user;
int ret;
DirectoryServer_framework();
if (ds_ptrs._HDBDSChangePasswordWithPolicy == NULL)
return EINVAL;
if (krb5_principal_get_num_comp(context, entry->entry.principal) != 1)
return EINVAL;
user = (char *)krb5_principal_get_comp_string(context, entry->entry.principal, 0);
ret = ds_ptrs._HDBDSChangePasswordWithPolicy(user, password, (flags & HDB_PWD_CONDITIONAL) == 0);
if (ret != 0) {
krb5_set_error_message(context, EINVAL, "DSPasswordServer rejected password");
return EINVAL;
}
return 0;
}
static krb5_error_code
hod_auth_status(krb5_context context, struct HDB *db, hdb_entry_ex *entry, int status)
{
char *user;
DirectoryServer_framework();
if (ds_ptrs._HDBDSUpdateLoginStatus == NULL)
return EINVAL;
if (krb5_principal_get_num_comp(context, entry->entry.principal) != 1)
return EINVAL;
user = (char *)krb5_principal_get_comp_string(context, entry->entry.principal, 0);
ds_ptrs._HDBDSUpdateLoginStatus(user, status);
return 0;
}
#endif
krb5_error_code
hdb_od_create(krb5_context context, HDB ** db, const char *arg)
{
CFMutableArrayRef attrs;
ODNodeRef localRef = NULL;
ODRecordRef kdcConfRef = NULL;
CFArrayRef data = NULL;
krb5_error_code ret;
hdb_od d = NULL;
char *path = NULL;
int i;
*db = calloc(1, sizeof(**db));
if (*db == NULL) {
ret = ENOMEM;
krb5_set_error_message(context, ret, "malloc: out of memory");
goto out;
}
memset(*db, 0, sizeof(**db));
path = strdup(arg);
if (path == NULL) {
ret = ENOMEM;
krb5_set_error_message(context, ret, "malloc: out of memory");
goto out;
}
d = calloc(1, sizeof(*d));
if (d == NULL) {
ret = ENOMEM;
krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
goto out;
}
{
char *p = strchr(path, '&');
if (p) {
*p++ = '\0';
d->restoreRoot = CFStringCreateWithCString(NULL, p, kCFStringEncodingUTF8);
}
}
d->rootName = CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8);
if (d == NULL) {
ret = ENOMEM;
krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
goto out;
}
attrs = CFArrayCreateMutable(NULL, num_keys, &kCFTypeArrayCallBacks);
for (i = 0; i < num_keys; i++)
CFArrayAppendValue(attrs, keys[i].name);
d->inNodeAttributes = attrs;
attrs = CFArrayCreateMutable(NULL, 1, &kCFTypeArrayCallBacks);
CFArrayAppendValue(attrs, kRealName);
d->inKDCAttributes = attrs;
attrs = CFArrayCreateMutable(NULL, 2, &kCFTypeArrayCallBacks);
CFArrayAppendValue(attrs, kODRecordTypeComputers);
CFArrayAppendValue(attrs, kODRecordTypeUsers);
d->inComputerOrUsers = attrs;
(*db)->hdb_db = d;
(*db)->hdb_capability_flags = HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL;
(*db)->hdb_master_key_set = 0;
(*db)->hdb_openp = 0;
(*db)->hdb_open = hod_open;
(*db)->hdb_close = hod_close;
(*db)->hdb_store = hod_store;
(*db)->hdb_remove = hod_remove;
(*db)->hdb_firstkey = hod_firstkey;
(*db)->hdb_nextkey = hod_nextkey;
(*db)->hdb_lock = hod_lock;
(*db)->hdb_unlock = hod_unlock;
(*db)->hdb_rename = NULL;
(*db)->hdb__get = NULL;
(*db)->hdb__put = NULL;
(*db)->hdb__del = NULL;
(*db)->hdb_destroy = hod_destroy;
(*db)->hdb_get_realms = hod_get_realms;
(*db)->hdb_get_ntlm_domain = hod_get_ntlm_domain;
if (strncmp(path, "/Local/", 7) == 0) {
ret = nodeCreateWithName(context, d, &localRef);
if (ret)
goto out;
kdcConfRef = ODNodeCopyRecord(localRef, kODRecordTypeConfiguration,
CFSTR("KerberosKDC"),
d->inKDCAttributes, NULL);
if (kdcConfRef == NULL) {
ret = HDB_ERR_NOENTRY;
krb5_set_error_message(context, ret, "Failed to find KerberosKDC node");
goto out;
}
data = ODRecordCopyValues(kdcConfRef, kRealName, NULL);
if (data == NULL) {
ret = HDB_ERR_NOENTRY;
krb5_set_error_message(context, ret, "Failed to copy RealName from KerberosKDC node");
goto out;
}
if (CFArrayGetCount(data) != 1) {
ret = HDB_ERR_NOENTRY;
krb5_set_error_message(context, ret, "Found RealName %d from KerberosKDC",
(int)CFArrayGetCount(data));
goto out;
}
d->LKDCRealm = rk_cfstring2cstring((CFStringRef)CFArrayGetValueAtIndex(data, 0));
if (d->LKDCRealm == NULL) {
ret = HDB_ERR_NOENTRY;
krb5_set_error_message(context, ret, "failed to find realm");
goto out;
}
CFRelease(data); data = NULL;
CFRelease(kdcConfRef); kdcConfRef = NULL;
CFRelease(localRef); localRef = NULL;
ntlm_notification(d);
(*db)->hdb_fetch_kvno = hod_lkdc_fetch;
d->locate_record = lkdc_locate_record;
} else {
#ifdef HAVE_SIGACTION
struct sigaction sa;
sa.sa_flags = 0;
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sigaction(SIGPIPE, &sa, NULL);
#else
signal(SIGPIPE, SIG_IGN);
#endif
(*db)->hdb_capability_flags |= HDB_CAP_F_HANDLE_PASSWORDS;
#ifdef __APPLE_PRIVATE__
(*db)->hdb_password = hod_password;
#endif
(*db)->hdb_fetch_kvno = hod_server_fetch;
d->locate_record = server_locate_record;
(*db)->hdb_auth_status = hod_auth_status;
}
free(path);
return 0;
out:
if (path)
free(path);
if (data)
CFRelease(data);
if (kdcConfRef)
CFRelease(kdcConfRef);
if (localRef)
CFRelease(localRef);
if (*db) {
free(*db);
*db = NULL;
}
if (d) {
if (d->rootName)
CFRelease(d->rootName);
if (d->restoreRoot)
CFRelease(d->restoreRoot);
free(d);
}
return ret;
}
#endif