1/*
2 * Copyright (c) 2009 Kungliga Tekniska H�gskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Portions Copyright (c) 2009 - 2011 Apple Inc. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * 3. Neither the name of the Institute nor the names of its contributors
20 *    may be used to endorse or promote products derived from this software
21 *    without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36
37#include "hdb_locl.h"
38#include <heimbase.h>
39#include <hx509.h>
40#include <base64.h>
41#include <rfc2459_asn1.h>
42#include <ifaddrs.h>
43#include <heimbase.h>
44#include <roken.h>
45
46#ifdef HAVE_OPENDIRECTORY
47
48#include <CoreFoundation/CoreFoundation.h>
49#include <OpenDirectory/OpenDirectory.h>
50#include <opendirectory/odconstants.h>
51#ifdef __APPLE_PRIVATE__
52#include <OpenDirectory/OpenDirectoryPriv.h>
53#include <DirectoryServer/DirectoryServer.h>
54#include <dlfcn.h>
55#endif
56#include <SystemConfiguration/SCPreferences.h>
57
58struct iter_ctx {
59    CFIndex idx;
60    CFArrayRef odRecordArray;
61} iter;
62
63struct hdb_od;
64
65typedef krb5_error_code (*hod_locate_record)(krb5_context, struct hdb_od *, krb5_principal, unsigned, int, int *, ODRecordRef *);
66
67typedef struct hdb_od {
68    CFStringRef rootName;
69    ODNodeRef rootNode;
70    CFArrayRef inNodeAttributes;
71    CFArrayRef inKDCAttributes;
72    CFArrayRef inComputerOrUsers;
73    CFStringRef restoreRoot;
74    char *LKDCRealm;
75    SCDynamicStoreRef *store;
76    char *ntlmDomain;
77    struct iter_ctx iter;
78    hod_locate_record locate_record;
79} *hdb_od;
80
81static CFStringRef kRealName = CFSTR("dsAttrTypeStandard:RealName");
82
83static const char wellknown_lkdc[] = "WELLKNOWN:COM.APPLE.LKDC";
84
85/*
86 *
87 */
88
89typedef struct hdb_entry_ex_ctx {
90    krb5_principal principal;
91    ODRecordRef record;
92} hdb_entry_ex_ctx;
93
94static void
95free_hod_ctx(krb5_context context, hdb_entry_ex *entry)
96{
97    hdb_entry_ex_ctx *ctx = entry->ctx;
98    if (ctx) {
99	if (ctx->record)
100	    CFRelease(ctx->record);
101	free(ctx);
102    }
103    entry->ctx = NULL;
104}
105
106
107static hdb_entry_ex_ctx *
108get_ex_ctx(hdb_entry_ex *entry)
109{
110    if (entry->ctx == NULL) {
111	entry->ctx = calloc(1, sizeof(*entry->ctx));
112	if (entry->ctx == NULL)
113	    return NULL;
114	entry->free_entry = free_hod_ctx;
115    }
116    return entry->ctx;
117}
118
119/*
120 *
121 */
122
123static int
124is_lkdc(hdb_od d, const char *realm)
125{
126    return d->LKDCRealm != NULL && krb5_realm_is_lkdc(realm);
127}
128
129static krb5_error_code
130map_service(krb5_context context, krb5_const_principal principal, krb5_principal *eprinc)
131{
132    const char *service;
133    krb5_error_code ret;
134
135    ret = krb5_copy_principal(context, principal, eprinc);
136    if (ret)
137	return ret;
138
139    service = krb5_principal_get_comp_string(context, principal, 0);
140    if (strcasecmp(service, "afpserver") == 0 ||
141	strcasecmp(service, "cifs") == 0 ||
142	strcasecmp(service, "smb") == 0 ||
143	strcasecmp(service, "vnc") == 0) {
144
145	free((*eprinc)->name.name_string.val[0]);
146	(*eprinc)->name.name_string.val[0] = strdup("host");
147    }
148    return 0;
149}
150
151static bool
152tryAgainP(krb5_context context, int *tryAgain, CFErrorRef *error)
153{
154    if (*tryAgain <= 1)
155	return 0;
156
157    if (error && *error) {
158	CFRelease(*error);
159	*error = NULL;
160    }
161    (*tryAgain)--;
162
163    krb5_warnx(context, "try fetching result again");
164
165    return 1;
166}
167
168#define MAX_TRIES	3
169
170
171/*
172 * Returns the matching User or Computer record, if not, returns the node itself
173 */
174
175static ODRecordRef
176HODODNodeCopyLinkageRecordFromAuthenticationData(krb5_context context, ODNodeRef node, ODRecordRef record)
177{
178    CFMutableArrayRef types = NULL;
179    CFArrayRef linkageArray = NULL, resultArray = NULL;
180    CFTypeRef userLinkage;
181    ODQueryRef query = NULL;
182    CFErrorRef error = NULL;
183    ODRecordRef res;
184    int tryAgain = MAX_TRIES;
185
186    types = CFArrayCreateMutable(NULL, 2, &kCFTypeArrayCallBacks);
187    if (types == NULL)
188	goto out;
189
190    CFArrayAppendValue(types, kODRecordTypeUsers);
191    CFArrayAppendValue(types, kODRecordTypeComputers);
192
193    linkageArray = ODRecordCopyValues(record, CFSTR("dsAttrTypeNative:userLinkage"), &error);
194    if (linkageArray == NULL)
195	goto out;
196
197    if (CFArrayGetCount(linkageArray) == 0)
198	goto out;
199
200    userLinkage = CFArrayGetValueAtIndex(linkageArray, 0);
201    if (userLinkage == NULL)
202	goto out;
203
204    do {
205	query = ODQueryCreateWithNode(NULL, node, types,
206				      CFSTR("dsAttrTypeNative:entryUUID"),
207				      kODMatchEqualTo,
208				      userLinkage, NULL, 1, &error);
209	if (query == NULL)
210	    goto out;
211
212	resultArray = ODQueryCopyResults(query, FALSE, &error);
213        CFRelease(query);
214	if (resultArray == NULL && error == NULL)
215	    tryAgain = 0;
216    } while(resultArray == NULL && tryAgainP(context, &tryAgain, &error));
217    if (resultArray == NULL)
218	goto out;
219
220    if (CFArrayGetCount(resultArray) == 0)
221	goto out;
222
223    res = (ODRecordRef)CFArrayGetValueAtIndex(resultArray, 0);
224    if (res)
225	record = res;
226
227out:
228    CFRetain(record);
229    if (error)
230	CFRelease(error);
231    if (linkageArray)
232	CFRelease(linkageArray);
233    if (resultArray)
234	CFRelease(resultArray);
235    if (types)
236	CFRelease(types);
237    return record;
238
239}
240
241/*
242 *
243 */
244
245static krb5_error_code
246od2hdb_usercert(krb5_context context, hdb_od d,
247		CFArrayRef data, unsigned flags, hdb_entry_ex *entry)
248{
249    HDB_extension ext;
250    void *ptr;
251    krb5_error_code ret;
252    CFIndex i;
253
254    memset(&ext, 0, sizeof(ext));
255
256    ext.data.element = choice_HDB_extension_data_pkinit_cert;
257
258    for (i = 0; i < CFArrayGetCount(data); i++) {
259	CFDataRef c = (CFDataRef) CFArrayGetValueAtIndex(data, i);
260	krb5_data cd;
261
262	if (CFGetTypeID((CFTypeRef)c) != CFDataGetTypeID())
263	    continue;
264
265	ret = krb5_data_copy(&cd, CFDataGetBytePtr(c),
266			     CFDataGetLength(c));
267	if (ret) {
268	    free_HDB_extension(&ext);
269	    return ENOMEM;
270	}
271
272	ext.data.u.pkinit_cert.len += 1;
273
274	ptr = realloc(ext.data.u.pkinit_cert.val,
275		      sizeof(ext.data.u.pkinit_cert.val[0]) * ext.data.u.pkinit_cert.len);
276	if (ptr == NULL) {
277	    krb5_data_free(&cd);
278	    free_HDB_extension(&ext);
279	    return ENOMEM;
280	}
281	ext.data.u.pkinit_cert.val = ptr;
282
283	memset(&ext.data.u.pkinit_cert.val[ext.data.u.pkinit_cert.len - 1],
284	       0, sizeof(ext.data.u.pkinit_cert.val[0]));
285
286	ext.data.u.pkinit_cert.val[ext.data.u.pkinit_cert.len - 1].cert = cd;
287    }
288
289    ret = hdb_replace_extension(context, &entry->entry, &ext);
290    free_HDB_extension(&ext);
291
292    return ret;
293}
294
295/*
296 *
297 */
298
299static krb5_error_code
300nodeCreateWithName(krb5_context context, hdb_od d, ODNodeRef *node)
301{
302    ODSessionRef session = kODSessionDefault;
303
304    *node = NULL;
305
306#ifdef __APPLE_PRIVATE__
307    if (d->restoreRoot) {
308	CFMutableDictionaryRef options;
309
310	options = CFDictionaryCreateMutable(NULL, 0,
311					    &kCFTypeDictionaryKeyCallBacks,
312					    &kCFTypeDictionaryValueCallBacks);
313	if (options == NULL)
314	    return ENOMEM;
315
316	CFDictionaryAddValue(options, kODSessionLocalPath, d->restoreRoot);
317
318	session = ODSessionCreate(kCFAllocatorDefault, options, NULL);
319	CFRelease(options);
320	if (session == NULL) {
321	    krb5_set_error_message(context, HDB_ERR_DB_INUSE, "failed to create session");
322	    return HDB_ERR_DB_INUSE;
323	}
324    }
325#endif
326
327    *node = ODNodeCreateWithName(kCFAllocatorDefault,
328				 session,
329				 d->rootName,
330				 NULL);
331    if (session)
332	CFRelease(session);
333
334    if (*node == NULL) {
335	char *restoreRoot = NULL, *path = NULL;
336
337	if (d->restoreRoot)
338	    restoreRoot = rk_cfstring2cstring(d->restoreRoot);
339	path = rk_cfstring2cstring(d->rootName);
340	krb5_set_error_message(context, HDB_ERR_DB_INUSE, "failed to create root node: %s %s",
341			       path ? path : "<nopath>",
342			       restoreRoot ? restoreRoot : "");
343	free(restoreRoot);
344	free(path);
345	return HDB_ERR_DB_INUSE;
346    }
347
348    return 0;
349}
350
351
352
353/*
354 *
355 */
356
357static krb5_error_code
358od2hdb_null(krb5_context context, hdb_od d,
359	    CFArrayRef data, unsigned flags, hdb_entry_ex *entry)
360{
361    return 0;
362}
363
364/*
365 *
366 */
367
368static krb5_error_code
369map_lkdc_principal(krb5_context context, krb5_principal principal, const char *from, const char *to)
370{
371    size_t num = krb5_principal_get_num_comp(context, principal);
372    krb5_error_code ret;
373
374    if (strcasecmp(principal->realm, from) == 0) {
375	ret = krb5_principal_set_realm(context, principal, to);
376	if (ret)
377	    return ret;
378    }
379
380    if (num == 2 && strcasecmp(principal->name.name_string.val[1], from) == 0) {
381	free(principal->name.name_string.val[1]);
382	principal->name.name_string.val[1] = strdup(to);
383	if (principal->name.name_string.val[1] == NULL)
384	    return ENOMEM;
385    }
386    return 0;
387}
388
389/*
390 *
391 */
392
393static krb5_error_code
394od2hdb_principal(krb5_context context, hdb_od d,
395		 CFArrayRef data, unsigned flags, hdb_entry_ex *entry)
396{
397    hdb_entry_ex_ctx *ctx = entry->ctx;
398    krb5_error_code ret;
399    char *str;
400
401    /* the magic store principal in hdb entry was found, lets skip this step */
402    if (ctx && ctx->principal)
403	return 0;
404
405    if ((flags & HDB_F_CANON) == 0)
406	return 0;
407
408    if (entry->entry.principal) {
409	krb5_free_principal(context, entry->entry.principal);
410	entry->entry.principal = NULL;
411    }
412
413    str = rk_cfstring2cstring((CFStringRef)CFArrayGetValueAtIndex(data, 0));
414    if (str == NULL)
415	return ENOMEM;
416
417    ret = krb5_parse_name(context, str, &entry->entry.principal);
418    free(str);
419    if (ret)
420	return ret;
421
422    if (d->LKDCRealm) {
423	ret = map_lkdc_principal(context, entry->entry.principal, wellknown_lkdc, d->LKDCRealm);
424	if (ret)
425	    return ret;
426    }
427
428    return 0;
429}
430
431static krb5_error_code
432od2hdb_def_principal(krb5_context context, hdb_od d,
433		     int flags, hdb_entry_ex *entry)
434{
435    if (entry->entry.principal)
436	return 0;
437    return HDB_ERR_NOENTRY;
438}
439
440static krb5_error_code
441hdb2od_principal_lkdc(krb5_context context, hdb_od d, ODAttributeType type,
442		      unsigned flags, hdb_entry_ex *entry, ODRecordRef record)
443{
444    size_t num = krb5_principal_get_num_comp(context, entry->entry.principal);
445    CFMutableArrayRef array = NULL;
446    krb5_principal principal = NULL;
447    krb5_error_code ret = 0;
448    CFStringRef element;
449    char *user;
450
451    if (d->LKDCRealm == NULL)
452	return 0;
453
454    /*
455     * "User" LKDC names are implicitly stored
456     */
457    if (num == 1)
458	return 0;
459
460    ret = krb5_copy_principal(context, entry->entry.principal, &principal);
461    if (ret)
462	return ret;
463
464    ret = map_lkdc_principal(context, principal, d->LKDCRealm, wellknown_lkdc);
465    if (ret)
466	goto out;
467
468    /* 2 entry nodes are servers :/ */
469    if (num == 2) {
470	krb5_principal eprinc;
471	type = CFSTR("dsAttrTypeNative:KerberosServerName");
472
473	ret = map_service(context, principal, &eprinc);
474	if (ret)
475	    goto out;
476	krb5_free_principal(context, principal);
477	principal = eprinc;
478    }
479
480    array = CFArrayCreateMutable(kCFAllocatorDefault, 1,
481				 &kCFTypeArrayCallBacks);
482    if (array == NULL) {
483	ret = ENOMEM;
484	goto out;
485    }
486
487    ret = krb5_unparse_name(context, principal, &user);
488    if (ret)
489	goto out;
490
491    element = CFStringCreateWithCString(kCFAllocatorDefault, user, kCFStringEncodingUTF8);
492    if (element == NULL) {
493	ret = ENOMEM;
494	goto out;
495    }
496
497    CFArrayAppendValue(array, element);
498    CFRelease(element);
499
500    bool r = ODRecordSetValue(record, type, array, NULL);
501    if (!r)
502	ret = HDB_ERR_UK_SERROR;
503
504 out:
505    if (principal)
506	krb5_free_principal(context, principal);
507    if (array)
508	CFRelease(array);
509
510    return ret;
511}
512
513
514static krb5_error_code
515hdb2od_principal_server(krb5_context context, hdb_od d, ODAttributeType type,
516			unsigned flags, hdb_entry_ex *entry, ODRecordRef record)
517{
518    krb5_error_code ret;
519    CFStringRef name;
520    char *user;
521
522    if (d->LKDCRealm)
523	return 0;
524
525    ret = krb5_unparse_name(context, entry->entry.principal, &user);
526    if (ret)
527	return ret;
528
529    name = CFStringCreateWithCString(NULL, user, kCFStringEncodingUTF8);
530    free(user);
531    if (name == NULL)
532	return ENOMEM;
533
534    bool r = ODRecordSetValue(record, type, name, NULL);
535    CFRelease(name);
536    if (!r)
537	return HDB_ERR_UK_SERROR;
538
539    return 0;
540}
541
542/*
543 *
544 */
545
546static krb5_error_code
547od2hdb_alias(krb5_context context, hdb_od d,
548	     CFArrayRef data, unsigned flags, hdb_entry_ex *entry)
549{
550    hdb_entry_ex_ctx *ctx = entry->ctx;
551    krb5_error_code ret;
552    krb5_principal principal;
553    char *str;
554
555    /* skip aliases on magic principal since they don't have aliases */
556    if (ctx && ctx->principal)
557	return 0;
558
559    str = rk_cfstring2cstring((CFStringRef)CFArrayGetValueAtIndex(data, 0));
560    if (str == NULL)
561	return ENOMEM;
562
563    ret = krb5_parse_name(context, str, &principal);
564    free(str);
565    if (ret)
566	return ret;
567
568    krb5_free_principal(context, principal);
569
570    return 0;
571}
572/*
573 *
574 */
575
576static krb5_error_code
577od2hdb_keys(krb5_context context, hdb_od d,
578	    CFArrayRef akeys, unsigned flags, hdb_entry_ex *entry)
579{
580    CFIndex i, count = CFArrayGetCount(akeys);
581    krb5_error_code ret;
582    hdb_keyset_aapl *keys;
583    krb5_kvno specific_match = -1, general_match = -1;
584    krb5_kvno specific_kvno = 0, general_kvno = 0;
585    CFIndex len;
586
587    if (count == 0)
588	return ENOMEM;
589
590    keys = calloc(count, sizeof(keys[0]));
591    if (keys == NULL)
592	return ENOMEM;
593
594    ret = 0;
595    for (i = 0; i < count; i++) {
596	CFTypeRef type = CFArrayGetValueAtIndex(akeys, i);
597	void *ptr;
598
599	if (CFGetTypeID(type) == CFDataGetTypeID()) {
600	    CFDataRef data = (CFDataRef) type;
601
602	    len = CFDataGetLength(data);
603	    ptr = malloc(len);
604	    if (ptr == NULL) {
605		ret = ENOMEM;
606		goto out;
607	    }
608	    memcpy(ptr, CFDataGetBytePtr(data), len);
609	} else if (CFGetTypeID(type) == CFStringGetTypeID()) {
610	    CFStringRef cfstr = (CFStringRef)type;
611	    char *str;
612
613	    str = rk_cfstring2cstring(cfstr);
614	    if (str == NULL) {
615		ret = ENOMEM;
616		goto out;
617	    }
618	    ptr = malloc(strlen(str));
619	    if (ptr == NULL) {
620		free(str);
621		ret = ENOMEM;
622		goto out;
623	    }
624
625	    ret = base64_decode(str, ptr);
626	    free(str);
627	    if (ret < 0) {
628		free(ptr);
629		ret = EINVAL;
630		goto out;
631	    }
632
633	    len = ret;
634
635	} else {
636	    ret = EINVAL; /* XXX */
637	    goto out;
638	}
639
640	ret = decode_hdb_keyset_aapl(ptr, len, &keys[i], NULL);
641	free(ptr);
642	if (ret)
643	    goto out;
644
645	if (keys[i].principal) {
646	    if (krb5_principal_compare(context, keys[i].principal, entry->entry.principal)) {
647
648		if (keys[i].kvno > specific_kvno) {
649		    hdb_entry_ex_ctx *ctx = get_ex_ctx(entry);
650		    if (ctx == NULL) {
651			ret = ENOMEM;
652			goto out;
653		    }
654		    ctx->principal = entry->entry.principal;
655
656		    specific_match = (krb5_kvno)i;
657		    specific_kvno = keys[i].kvno;
658		}
659	    }
660	} else {
661	    if (keys[i].kvno > general_kvno) {
662		general_match = (uint32_t)i;
663		general_kvno = keys[i].kvno;
664	    }
665	}
666    }
667
668    if (specific_match != -1) {
669	i = specific_match;
670	entry->entry.kvno = specific_kvno;
671    } else if (general_match != -1) {
672	i = general_match;
673	entry->entry.kvno = general_kvno;
674    } else {
675	ret = HDB_ERR_NOENTRY;
676	goto out;
677    }
678
679    free_Keys(&entry->entry.keys);
680
681    entry->entry.keys.len = keys[i].keys.len;
682    entry->entry.keys.val = keys[i].keys.val;
683    keys[i].keys.val = NULL;
684    keys[i].keys.len = 0;
685
686 out:
687    for (i = 0; i < count; i++)
688	free_hdb_keyset_aapl(&keys[i]);
689    free(keys);
690
691    return ret;
692}
693
694static krb5_error_code
695hdb2od_keys(krb5_context context, hdb_od d, ODAttributeType type,
696	    unsigned flags, hdb_entry_ex *entry, ODRecordRef record)
697{
698    CFMutableArrayRef array;
699    heim_octet_string data;
700    krb5_error_code ret;
701    CFDataRef element;
702    hdb_keyset_aapl key;
703    size_t size = 0;
704
705    /* if this is a change password operation, the keys have already been updated with ->hdb_password */
706    if (flags & HDB_F_CHANGE_PASSWORD)
707	return 0;
708
709    /* this overwrites other keys that are stored in the special "alias" keyset for server */
710
711    key.kvno = entry->entry.kvno;
712    key.keys.len = entry->entry.keys.len;
713    key.keys.val = entry->entry.keys.val;
714    key.principal = NULL;
715
716    array = CFArrayCreateMutable(kCFAllocatorDefault, 1,
717				 &kCFTypeArrayCallBacks);
718    if (array == NULL) {
719	ret = ENOMEM;
720	goto out;
721    }
722
723    ASN1_MALLOC_ENCODE(hdb_keyset_aapl, data.data, data.length, &key, &size, ret);
724    if (ret)
725	goto out;
726    if (data.length != size)
727	krb5_abortx(context, "internal asn.1 encoder error");
728
729    element = CFDataCreate(kCFAllocatorDefault, data.data, data.length);
730    if (element == NULL) {
731	ret = ENOMEM;
732	goto out;
733    }
734
735    CFArrayAppendValue(array, element);
736    CFRelease(element);
737
738    bool r = ODRecordSetValue(record, type, array, NULL);
739    if (!r)
740	ret = HDB_ERR_UK_SERROR;
741
742 out:
743    if (array)
744	CFRelease(array);
745
746    return ret;
747}
748
749/*
750 *
751 */
752
753static krb5_error_code
754od2hdb_srp(krb5_context context, hdb_od d,
755	   CFArrayRef akeys, unsigned flags, hdb_entry_ex *entry)
756{
757    CFIndex i, count = CFArrayGetCount(akeys);
758    krb5_error_code ret;
759    HDB_extension ext;
760    hdb_srp srp;
761    CFIndex len;
762
763    if (count == 0)
764	return ENOMEM;
765
766    memset(&ext, 0, sizeof(ext));
767    ext.mandatory = FALSE;
768    ext.data.element = choice_HDB_extension_data_srp;
769
770    ext.data.u.srp.len = 0;
771    ext.data.u.srp.val = NULL;
772
773    ret = 0;
774    for (i = 0; i < count; i++) {
775	CFTypeRef type = CFArrayGetValueAtIndex(akeys, i);
776	void *ptr;
777
778	if (CFGetTypeID(type) == CFDataGetTypeID()) {
779	    CFDataRef data = (CFDataRef) type;
780
781	    len = CFDataGetLength(data);
782	    ptr = malloc(len);
783	    if (ptr == NULL) {
784		ret = ENOMEM;
785		goto out;
786	    }
787	    memcpy(ptr, CFDataGetBytePtr(data), len);
788	} else if (CFGetTypeID(type) == CFStringGetTypeID()) {
789	    CFStringRef cfstr = (CFStringRef)type;
790	    char *str;
791
792	    str = rk_cfstring2cstring(cfstr);
793	    if (str == NULL) {
794		ret = ENOMEM;
795		goto out;
796	    }
797	    ptr = malloc(strlen(str));
798	    if (ptr == NULL) {
799		free(str);
800		ret = ENOMEM;
801		goto out;
802	    }
803
804	    ret = base64_decode(str, ptr);
805	    free(str);
806	    if (ret < 0) {
807		free(ptr);
808		ret = EINVAL;
809		goto out;
810	    }
811
812	    len = ret;
813
814	} else {
815	    ret = EINVAL; /* XXX */
816	    goto out;
817	}
818
819	ret = decode_hdb_srp(ptr, len, &srp, NULL);
820	free(ptr);
821	if (ret)
822	    goto out;
823
824	ret = add_hdb_srp_set(&ext.data.u.srp, &srp);
825	free_hdb_srp(&srp);
826	if (ret) {
827	    goto out;
828	}
829    }
830
831    ret = hdb_replace_extension(context, &entry->entry, &ext);
832
833 out:
834    free_HDB_extension(&ext);
835
836    return ret;
837}
838
839static krb5_error_code
840hdb2od_srp(krb5_context context, hdb_od d, ODAttributeType type,
841	    unsigned flags, hdb_entry_ex *entry, ODRecordRef record)
842{
843    CFMutableArrayRef array;
844    heim_octet_string data;
845    krb5_error_code ret;
846    CFDataRef element;
847    hdb_srp_set key;
848    size_t size = 0;
849
850    /* XXX find key */
851    memset(&key, 0, sizeof(key));
852
853    /* if this is a change password operation, the keys have already been updated with ->hdb_password */
854    if (flags & HDB_F_CHANGE_PASSWORD)
855	return 0;
856
857    /* this overwrites other keys that are stored in the special "alias" keyset for server */
858
859    array = CFArrayCreateMutable(kCFAllocatorDefault, 1,
860				 &kCFTypeArrayCallBacks);
861    if (array == NULL) {
862	ret = ENOMEM;
863	goto out;
864    }
865
866    ASN1_MALLOC_ENCODE(hdb_srp_set, data.data, data.length, &key, &size, ret);
867    if (ret)
868	goto out;
869    if (data.length != size)
870	krb5_abortx(context, "internal asn.1 encoder error");
871
872    element = CFDataCreate(kCFAllocatorDefault, data.data, data.length);
873    if (element == NULL) {
874	ret = ENOMEM;
875	goto out;
876    }
877
878    CFArrayAppendValue(array, element);
879    CFRelease(element);
880
881    bool r = ODRecordSetValue(record, type, array, NULL);
882    if (!r)
883	ret = HDB_ERR_UK_SERROR;
884
885 out:
886    if (array)
887	CFRelease(array);
888
889    return ret;
890}
891
892/*
893 *
894 */
895
896static krb5_error_code
897od2hdb_flags(krb5_context context, hdb_od d,
898	     CFArrayRef data, unsigned qflags, hdb_entry_ex *entry)
899{
900    int flags;
901    char *str;
902
903    str = rk_cfstring2cstring((CFStringRef)CFArrayGetValueAtIndex(data, 0));
904    if (str == NULL)
905	return ENOMEM;
906
907    flags = atoi(str);
908    free(str);
909    entry->entry.flags = int2HDBFlags(flags);
910    return 0;
911}
912
913static krb5_error_code
914od2hdb_def_flags(krb5_context context, hdb_od d,
915		 int flags, hdb_entry_ex *entry)
916{
917    entry->entry.flags.client = 1;
918    entry->entry.flags.forwardable = 1;
919    entry->entry.flags.renewable = 1;
920    entry->entry.flags.proxiable = 1;
921
922    return 0;
923}
924
925static krb5_error_code
926hdb2od_flags(krb5_context context, hdb_od d, ODAttributeType type,
927	     unsigned flags, hdb_entry_ex *entry, ODRecordRef record)
928{
929    int eflags = HDBFlags2int(entry->entry.flags);
930    CFStringRef value;
931
932    value = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
933				       CFSTR("%d"), eflags);
934    if (value == NULL)
935	return ENOMEM;
936
937    bool r = ODRecordSetValue(record, type, value, NULL);
938    CFRelease(value);
939    if (!r)
940	return HDB_ERR_UK_SERROR;
941
942    return 0;
943}
944
945static krb5_error_code
946od2hdb_altsecids(krb5_context context, hdb_od d,
947		 CFArrayRef akeys, unsigned flags, hdb_entry_ex *entry)
948{
949    CFIndex i, count = CFArrayGetCount(akeys);
950    krb5_error_code ret;
951
952    if (count == 0)
953	return ENOMEM;
954
955    for (i = 0; i < count; i++) {
956	CFStringRef name = CFArrayGetValueAtIndex(akeys, i);
957	char *str, *str0, *subj;
958
959	if (CFGetTypeID(name) != CFStringGetTypeID())
960	    continue;
961
962	if (!CFStringHasPrefix(name, CFSTR("X509:<T>")))
963	    continue;
964
965	/* split out X509:<T>(.*)<S>(.*) from the string and store */
966
967	str0 = str = rk_cfstring2cstring(name);
968	str += 8; /* skip X509:<T> */
969
970	subj = strstr(str, "<S>");
971	if (subj == NULL) {
972	    free(str0);
973	    continue;
974	}
975	*subj = '\0';
976	subj += 3; /* skip <S> */
977
978	/* make sure that there is no <T> or <S> in the string */
979	if (strstr(subj, "<T>") || strstr(subj, "<S>") ||
980	    strstr(str, "<T>") || strstr(str, "<S>"))
981	{
982	    free(str0);
983	    continue;
984	}
985
986	ret = hdb_entry_set_pkinit_acl(&entry->entry, subj, NULL, str);
987	free(str0);
988	if (ret)
989	    return ret;
990    }
991    return 0;
992}
993
994/*
995 *
996 */
997
998static krb5_error_code
999od2hdb_acl_rights(krb5_context context, hdb_od d,
1000		  CFArrayRef data, unsigned flags, hdb_entry_ex *entry)
1001{
1002    char *str;
1003
1004    str = rk_cfstring2cstring((CFStringRef)CFArrayGetValueAtIndex(data, 0));
1005    if (str == NULL)
1006	return ENOMEM;
1007
1008    entry->entry.acl_rights = malloc(sizeof(*entry->entry.acl_rights));
1009    if (entry->entry.acl_rights == NULL) {
1010	free(str);
1011	return ENOMEM;
1012    }
1013
1014    *entry->entry.acl_rights = atoi(str);
1015    free(str);
1016
1017    return 0;
1018}
1019
1020/*
1021 *
1022 */
1023
1024typedef krb5_error_code
1025(*od2hdb_func)(krb5_context, hdb_od, CFArrayRef, unsigned flags, hdb_entry_ex *);
1026
1027typedef krb5_error_code
1028(*od2hdb_default)(krb5_context, hdb_od, int, hdb_entry_ex *);
1029
1030typedef krb5_error_code
1031(*hdb2od_func)(krb5_context, hdb_od, ODAttributeType,
1032	       unsigned flags, hdb_entry_ex *, ODRecordRef);
1033
1034
1035static const struct key {
1036    const char *desc;
1037    CFStringRef name;
1038    int flags;
1039#define MULTI_VALUE	1
1040#define OPTIONAL_VALUE	2
1041#define SERVER_VALUE	4
1042#define DELETE_KEY	8
1043    od2hdb_func od2hdb;
1044    od2hdb_default od2hdb_def;
1045    hdb2od_func hdb2od;
1046} keys[] = {
1047    {
1048	"keyset",
1049	CFSTR("dsAttrTypeNative:draft-krbKeySet"), OPTIONAL_VALUE | MULTI_VALUE | SERVER_VALUE | DELETE_KEY,
1050	od2hdb_keys,
1051	NULL,
1052	hdb2od_keys
1053    },
1054    {
1055	"srp-keyset",
1056	CFSTR("dsAttrTypeNative:HeimdalSRPKey"), OPTIONAL_VALUE | MULTI_VALUE | DELETE_KEY,
1057	od2hdb_srp,
1058	NULL,
1059	hdb2od_srp
1060    },
1061    {
1062	"KerberosKeys",
1063	CFSTR("dsAttrTypeNative:KerberosKeys"), OPTIONAL_VALUE | MULTI_VALUE | DELETE_KEY,
1064	od2hdb_keys,
1065	NULL,
1066	hdb2od_keys
1067    },
1068    {
1069	"username",
1070	CFSTR("dsAttrTypeNative:KerberosUserName"), OPTIONAL_VALUE | DELETE_KEY,
1071	od2hdb_principal,
1072	od2hdb_def_principal,
1073	hdb2od_principal_lkdc
1074    },
1075    {
1076	"principalName",
1077	CFSTR("dsAttrTypeNative:draft-krbPrincipalName"), OPTIONAL_VALUE | SERVER_VALUE | DELETE_KEY,
1078	od2hdb_principal,
1079	NULL,
1080	hdb2od_principal_server
1081    },
1082    {
1083	"principalAlias",
1084	CFSTR("dsAttrTypeNative:draft-krbPrincipalAliases"), MULTI_VALUE | OPTIONAL_VALUE | DELETE_KEY,
1085	od2hdb_alias,
1086	NULL,
1087	NULL
1088    },
1089    {
1090	"KerberosFlags",
1091	CFSTR("dsAttrTypeNative:KerberosFlags"), DELETE_KEY,
1092	od2hdb_flags,
1093	od2hdb_def_flags,
1094	hdb2od_flags
1095    },
1096    {
1097	"KerberosPolicy",
1098	CFSTR("dsAttrTypeNative:draft-krbTicketPolicy"), OPTIONAL_VALUE | SERVER_VALUE | DELETE_KEY,
1099	od2hdb_flags,
1100	NULL,
1101	hdb2od_flags
1102    },
1103    {
1104	"MaxLife",
1105	CFSTR("dsAttrTypeNative:KerberosMaxLife"), OPTIONAL_VALUE | DELETE_KEY,
1106	od2hdb_null,
1107	NULL,
1108	NULL
1109    },
1110    {
1111	"MaxRenewLife",
1112	CFSTR("dsAttrTypeNative:KerberosMaxRenew"), OPTIONAL_VALUE | DELETE_KEY,
1113	od2hdb_null,
1114	NULL,
1115	NULL
1116    },
1117    {
1118	"UserCertificate",
1119	CFSTR("dsAttrTypeStandard:UserCertificate"), MULTI_VALUE | OPTIONAL_VALUE,
1120	od2hdb_usercert,
1121	NULL,
1122	NULL
1123    },
1124    {
1125	"AltSecurityIdentities",
1126	CFSTR("dsAttrTypeStandard:AltSecurityIdentities"), MULTI_VALUE | OPTIONAL_VALUE,
1127	od2hdb_altsecids,
1128	NULL,
1129	NULL
1130    },
1131    {
1132	"principalACL",
1133	CFSTR("dsAttrTypeNative:draft-krbPrincipalACL"), OPTIONAL_VALUE,
1134	od2hdb_acl_rights,
1135	NULL,
1136	NULL
1137    }
1138
1139};
1140static const int num_keys = sizeof(keys)/sizeof(keys[0]);
1141
1142/*
1143 * Policy mappings
1144 */
1145
1146static krb5_error_code
1147updateTimePolicy(int64_t reltime, time_t **t)
1148{
1149    if (reltime == kODExpirationTimeNeverExpires) {
1150	if (*t) {
1151	    free(*t);
1152	    *t = NULL;
1153	}
1154    } else if (reltime == kODExpirationTimeExpired || reltime < 0) {
1155	if (*t == NULL) {
1156	    *t = malloc(sizeof(**t));
1157	    if (*t == NULL)
1158		return ENOMEM;
1159	}
1160	**t = time(NULL) - 60; /* expired on 60s ago */
1161    } else {
1162	if (*t == NULL) {
1163	    *t = malloc(sizeof(**t));
1164	    if (*t == NULL)
1165		return ENOMEM;
1166	}
1167	time_t truncated = (time_t)reltime;
1168	if ((int64_t)truncated < reltime) {
1169	    /* We can't really express this expiration time.  so just
1170	     * move it 2 years into the future, hopefully we wont get
1171	     * tickets that live that long.
1172	     */
1173	    truncated = 3600 * 24 * 365 * 2;
1174	}
1175	time_t now = time(NULL);
1176	time_t expire = now + truncated;
1177	if (expire < now) {
1178	    /* same goes for time overrun */
1179	    expire = now + (3600 * 24 * 365 * 2);
1180	}
1181	**t = expire;
1182    }
1183    return 0;
1184}
1185
1186/*
1187 * HDB entry points
1188 */
1189
1190static krb5_error_code
1191hod_close(krb5_context context, HDB *db)
1192{
1193    return 0;
1194}
1195
1196static krb5_error_code
1197hod_destroy(krb5_context context, HDB *db)
1198{
1199    hdb_od d = (hdb_od)db->hdb_db;
1200    krb5_error_code ret;
1201
1202    if (d->rootNode)
1203	CFRelease(d->rootNode);
1204    if (d->inNodeAttributes)
1205	CFRelease(d->inNodeAttributes);
1206    if (d->inKDCAttributes)
1207	CFRelease(d->inKDCAttributes);
1208    if (d->LKDCRealm)
1209	free(d->LKDCRealm);
1210
1211    ret = hdb_clear_master_key (context, db);
1212    free(db->hdb_name);
1213    free(db);
1214    return ret;
1215}
1216
1217static krb5_error_code
1218hod_lock(krb5_context context, HDB *db, int operation)
1219{
1220    return 0;
1221}
1222
1223static krb5_error_code
1224hod_unlock(krb5_context context, HDB *db)
1225{
1226    return 0;
1227}
1228
1229static krb5_error_code
1230od_record2entry(krb5_context context, HDB * db, ODRecordRef cfRecord,
1231		unsigned flags, hdb_entry_ex * entry)
1232{
1233    hdb_od d = (hdb_od)db->hdb_db;
1234    krb5_error_code ret = HDB_ERR_NOENTRY;
1235    int i;
1236
1237    for (i = 0; i < num_keys; i++) {
1238	CFArrayRef data;
1239
1240	data = ODRecordCopyValues(cfRecord, keys[i].name, NULL);
1241	if (data == NULL) {
1242	    if (keys[i].flags & OPTIONAL_VALUE)
1243		continue;
1244	    if (keys[i].od2hdb_def) {
1245		ret = (*keys[i].od2hdb_def)(context, d, flags, entry);
1246		if (ret)
1247		    goto out;
1248		continue;
1249	    }
1250	    krb5_warnx(context, "Failed to copy non-optional value %s", keys[i].desc);
1251	    ret = HDB_ERR_NOENTRY;
1252	    goto out;
1253	}
1254
1255	if ((keys[i].flags & MULTI_VALUE) && CFArrayGetCount(data) < 1) {
1256	    krb5_warnx(context, "Expected multivalue got zero %s for", keys[i].desc);
1257	    CFRelease(data);
1258	    ret = HDB_ERR_NOENTRY;
1259	    goto out;
1260	} else if ((keys[i].flags & MULTI_VALUE) == 0 && CFArrayGetCount(data) != 1) {
1261	    krb5_warnx(context, "Expected single-value got non zero %d for %s",
1262		       (int)CFArrayGetCount(data), keys[i].desc);
1263	    CFRelease(data);
1264	    ret = HDB_ERR_NOENTRY;
1265	    goto out;
1266	}
1267
1268	ret = (*keys[i].od2hdb)(context, d, data, flags, entry);
1269	CFRelease(data);
1270	if (ret) {
1271	    krb5_warn(context, ret, "od2hdb failed for %s", keys[i].desc);
1272	    goto out;
1273	}
1274    }
1275
1276    /*
1277     * If entry didn't contain a principal, something is wrong with
1278     * the entry, lets skip it.
1279     */
1280    if (entry->entry.principal == NULL) {
1281	krb5_warnx(context, "principal missing");
1282	ret = HDB_ERR_NOENTRY;
1283	goto out;
1284    }
1285
1286    /* Not recorded in the OD backend, make something up */
1287    ret = krb5_parse_name(context, "hdb/od@WELL-KNOWN:OD-BACKEND",
1288			  &entry->entry.created_by.principal);
1289    if (ret)
1290	goto out;
1291
1292    entry->entry.created_by.time = time(NULL);
1293
1294    if ((flags & HDB_F_GET_KRBTGT) == 0 && !krb5_principal_is_krbtgt(context, entry->entry.principal)) {
1295        ODRecordRef userrecord;
1296
1297	userrecord = HODODNodeCopyLinkageRecordFromAuthenticationData(context, d->rootNode, cfRecord);
1298	if (userrecord) {
1299	    int64_t reltime;
1300	    CFIndex errorcode = 0;
1301	    CFErrorRef error = NULL;
1302
1303	    if (!ODRecordAuthenticationAllowed(userrecord, &error)) {
1304		if (error) {
1305		    errorcode = CFErrorGetCode(error);
1306		    CFRelease(error);
1307		}
1308	    }
1309
1310	    if (errorcode == kODErrorCredentialsPasswordChangeRequired || errorcode == kODErrorCredentialsPasswordExpired) {
1311		reltime = kODExpirationTimeExpired;
1312	    } else {
1313		reltime = ODRecordSecondsUntilPasswordExpires(userrecord);
1314	    }
1315	    ret = updateTimePolicy(reltime, &entry->entry.pw_end);
1316	    if (ret) {
1317		CFRelease(userrecord);
1318		goto out;
1319	    }
1320
1321	    if (errorcode == kODErrorCredentialsAccountDisabled) {
1322		reltime = kODExpirationTimeExpired;
1323		entry->entry.flags.invalid = 1;
1324	    } else if (errorcode == kODErrorCredentialsAccountExpired) {
1325		reltime = kODExpirationTimeExpired;
1326	    } else {
1327		reltime = ODRecordSecondsUntilAuthenticationsExpire(userrecord);
1328	    }
1329	    ret = updateTimePolicy(reltime, &entry->entry.valid_end);
1330	    CFRelease(userrecord);
1331	    if (ret)
1332		goto out;
1333	}
1334    }
1335
1336    if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) {
1337        ret = hdb_unseal_keys(context, db, &entry->entry);
1338        if(ret) {
1339	    krb5_warn(context, ret, "unseal keys failed");
1340	    goto out;
1341	}
1342    }
1343
1344out:
1345    return ret;
1346}
1347
1348static krb5_error_code
1349hod_nextkey(krb5_context context, HDB * db, unsigned flags,
1350	     hdb_entry_ex * entry)
1351{
1352    ODRecordRef cfRecord = NULL;
1353    hdb_od d = (hdb_od)db->hdb_db;
1354    krb5_error_code ret;
1355
1356    memset(entry, 0, sizeof(*entry));
1357
1358    heim_assert(d->iter.odRecordArray != NULL, "hdb: hod_next w/o hod_first");
1359
1360    while (d->iter.idx < CFArrayGetCount(d->iter.odRecordArray)) {
1361	cfRecord = (ODRecordRef) CFArrayGetValueAtIndex(d->iter.odRecordArray, d->iter.idx);
1362	d->iter.idx++;
1363	if (cfRecord == NULL) {
1364	    ret = HDB_ERR_NOENTRY;
1365	    goto out;
1366	}
1367
1368	CFArrayRef kerberosKeysArray = ODRecordCopyValues(cfRecord, CFSTR("dsAttrTypeNative:KerberosKeys"), NULL);
1369	if (kerberosKeysArray == NULL) {
1370	    cfRecord = NULL;
1371	    continue;
1372	}
1373	if (CFArrayGetCount(kerberosKeysArray) == 0) {
1374	    CFRelease(kerberosKeysArray);
1375	    cfRecord = NULL;
1376	    continue;
1377	}
1378	CFRelease(kerberosKeysArray);
1379
1380	break;
1381    }
1382
1383    if (cfRecord) {
1384	CFStringRef name = ODRecordGetRecordName(cfRecord);
1385	char *str;
1386
1387	str = rk_cfstring2cstring(name);
1388	if (str == NULL) {
1389	    ret = ENOENT;
1390	    goto out;
1391	}
1392
1393	ret = krb5_make_principal(context, &entry->entry.principal,
1394				  d->LKDCRealm, str, NULL);
1395	free(str);
1396	if (ret)
1397	    goto out;
1398
1399	ret = od_record2entry(context, db, cfRecord, flags, entry);
1400    } else
1401	ret = HDB_ERR_NOENTRY;
1402
1403out:
1404    if (ret) {
1405	free_hdb_entry(&entry->entry);
1406
1407	if (d->iter.odRecordArray) {
1408	    CFRelease(d->iter.odRecordArray);
1409	    d->iter.odRecordArray = NULL;
1410	    d->iter.idx = 0;
1411	}
1412    }
1413
1414    return ret;
1415}
1416
1417static krb5_error_code
1418hod_firstkey(krb5_context context, HDB *db,
1419	     unsigned flags, hdb_entry_ex *entry)
1420{
1421    int ret = 0;
1422    ODQueryRef query = NULL;
1423    hdb_od d = (hdb_od)db->hdb_db;
1424    int tryAgain = MAX_TRIES;
1425
1426    if (d->LKDCRealm == NULL) {
1427	krb5_set_error_message(context, EINVAL, "iteration over database only supported for DSLocal");
1428	return EINVAL;
1429    }
1430
1431    heim_assert(d->iter.odRecordArray == NULL, "hdb: more then one iteration at the same time");
1432
1433    do {
1434	CFErrorRef error = NULL;
1435
1436	query = ODQueryCreateWithNode(NULL, d->rootNode,
1437				      kODRecordTypeUsers, NULL,
1438				      kODMatchAny,
1439				      NULL,
1440				      kODAttributeTypeAllAttributes,
1441				      0,
1442				      &error);
1443	if (query == NULL) {
1444	    ret = HDB_ERR_NOENTRY;
1445	    goto out;
1446	}
1447
1448	d->iter.odRecordArray = ODQueryCopyResults(query, FALSE, &error);
1449	CFRelease(query);
1450	if (d->iter.odRecordArray == NULL && error == NULL)
1451	    tryAgain = 0;
1452	if (error)
1453	    CFRelease(error);
1454    } while(d->iter.odRecordArray == NULL && tryAgainP(context, &tryAgain, NULL));
1455
1456    if (d->iter.odRecordArray == NULL) {
1457	ret = HDB_ERR_NOENTRY;
1458	goto out;
1459    }
1460    d->iter.idx = 0;
1461
1462    return hod_nextkey(context, db, flags, entry);
1463out:
1464    return ret;
1465}
1466
1467static krb5_error_code
1468hod_open(krb5_context context, HDB * db, int flags, mode_t mode)
1469{
1470    hdb_od d = (hdb_od)db->hdb_db;
1471
1472    if (d->rootNode == NULL)
1473	return nodeCreateWithName(context, d, &d->rootNode);
1474
1475    return 0;
1476}
1477
1478static krb5_error_code
1479lkdc_locate_record(krb5_context context, hdb_od d, krb5_principal principal,
1480		   unsigned flags, int createp, int *createdp, ODRecordRef *result)
1481{
1482    ODMatchType matchtype = kODMatchEqualTo;
1483    CFStringRef queryString = NULL;
1484    ODRecordRef cfRecord;
1485    ODQueryRef query = NULL;
1486    CFArrayRef res = NULL;
1487    CFStringRef attr;
1488    krb5_error_code ret = 0;
1489    krb5_principal eprinc = NULL;
1490    char *kuser = NULL;
1491    const char *node = NULL;
1492    CFTypeRef rtype = NULL;
1493    int upflags = 0;
1494    int tryAgain = MAX_TRIES;
1495
1496    *result = NULL;
1497
1498    /*
1499     * Really need something like CrackName for uniform unparsing
1500     */
1501
1502    if (principal->name.name_type == KRB5_NT_X509_GENERAL_NAME) {
1503#ifdef PKINIT
1504	char *anode = NULL;
1505	hx509_name name = NULL;
1506	GeneralName gn;
1507	ssize_t len;
1508	void *buf;
1509	Name n;
1510
1511	memset(&gn, 0, sizeof(gn));
1512	memset(&n, 0, sizeof(n));
1513
1514	if (krb5_principal_get_num_comp(context, principal) != 1) {
1515	    krb5_warnx(context, "wrong length of x509 name");
1516	    return HDB_ERR_NOENTRY;
1517	}
1518
1519	len = strlen(principal->name.name_string.val[0]);
1520	buf = malloc(len);
1521	if (buf == NULL)
1522	    return HDB_ERR_NOENTRY;
1523
1524	len = base64_decode(principal->name.name_string.val[0], buf);
1525	if (len <= 0) {
1526	    free(buf);
1527	    return HDB_ERR_NOENTRY;
1528	}
1529
1530	ret = decode_GeneralName(buf, len, &gn, NULL);
1531	free(buf);
1532	if (ret) {
1533	    krb5_warnx(context, "x500 GeneralName malformed: %d", ret);
1534	    return HDB_ERR_NOENTRY;
1535	}
1536
1537	if (gn.element != choice_GeneralName_directoryName) {
1538	    krb5_warnx(context, "x500 name not directory Name");
1539	    free_GeneralName(&gn);
1540	    return HDB_ERR_NOENTRY;
1541	}
1542
1543	n.element = choice_Name_rdnSequence;
1544	n.u.rdnSequence.len = gn.u.directoryName.u.rdnSequence.len;
1545	n.u.rdnSequence.val = gn.u.directoryName.u.rdnSequence.val;
1546
1547	ret = hx509_name_from_Name(&n, &name);
1548	free_GeneralName(&gn);
1549	if (ret)
1550	    return HDB_ERR_NOENTRY;
1551
1552	attr = kODAttributeTypeRecordName;
1553	rtype = kODRecordTypeUsers;
1554
1555	ret = hx509_name_to_string(name, &anode);
1556	hx509_name_free(&name);
1557	if (ret)
1558	    return HDB_ERR_NOENTRY;
1559
1560	queryString = CFStringCreateWithCString(NULL, anode, kCFStringEncodingUTF8);
1561	free(anode);
1562	if (queryString == NULL) {
1563	    ret = HDB_ERR_NOENTRY;
1564	    goto out;
1565	}
1566	createp = 0;
1567#else
1568	return HDB_ERR_NOENTRY;
1569#endif
1570    } else if (principal->name.name_type == KRB5_NT_NTLM) {
1571	attr = kODAttributeTypeRecordName;
1572	rtype = kODRecordTypeUsers;
1573
1574	if (krb5_principal_get_num_comp(context, principal) != 1) {
1575	    krb5_warnx(context, "wrong length of ntlm name");
1576	    return HDB_ERR_NOENTRY;
1577	}
1578
1579	if (d->ntlmDomain == NULL) {
1580	    krb5_warnx(context, "NTLM domain not configured");
1581	    return HDB_ERR_NOENTRY;
1582	}
1583
1584	krb5_principal_set_realm(context, principal, d->ntlmDomain);
1585	node = krb5_principal_get_comp_string(context, principal, 0);
1586
1587    } else if (krb5_principal_is_pku2u(context, principal)) {
1588	attr = kODAttributeTypeRecordName;
1589	if (flags & HDB_F_GET_CLIENT) {
1590	    int num = krb5_principal_get_num_comp(context, principal);
1591	    if (num != 1) {
1592		ret = HDB_ERR_NOENTRY;
1593		goto out;
1594	    }
1595	    rtype = kODRecordTypeUsers;
1596	    upflags = KRB5_PRINCIPAL_UNPARSE_NO_REALM;
1597	    upflags |= KRB5_PRINCIPAL_UNPARSE_DISPLAY;
1598	} else {
1599	    node = "localhost";
1600	    rtype = kODRecordTypeComputers;
1601	}
1602    } else if (is_lkdc(d, principal->realm)) {
1603	/* check if user is a LKDC user, just look for RecordName then */
1604	int num = krb5_principal_get_num_comp(context, principal);
1605	const char *comp0;
1606	size_t lencomp0;
1607
1608	if (num < 1) {
1609	    ret = HDB_ERR_NOENTRY;
1610	    goto out;
1611	}
1612
1613	ret = map_lkdc_principal(context, principal, d->LKDCRealm, wellknown_lkdc);
1614	if (ret)
1615	    goto out;
1616
1617	comp0 = krb5_principal_get_comp_string(context, principal, 0);
1618	lencomp0 = strlen(comp0);
1619	if (lencomp0 == 0) {
1620	    ret = HDB_ERR_NOENTRY;
1621	    goto out;
1622	}
1623
1624	if (num == 2 || (num == 1 && comp0[lencomp0 - 1] == '$')) {
1625	    const char *host;
1626
1627	    attr = kODAttributeTypeRecordName;
1628
1629	    ret = map_service(context, principal, &eprinc);
1630	    if (ret) {
1631		ret = HDB_ERR_NOENTRY;
1632		goto out;
1633	    }
1634	    principal = eprinc;
1635
1636	    host = krb5_principal_get_comp_string(context, principal, 1);
1637	    comp0 = krb5_principal_get_comp_string(context, principal, 0);
1638
1639	    if (strcasecmp(KRB5_TGS_NAME, comp0) == 0) {
1640		node = "_krbtgt";
1641		rtype = kODRecordTypeUsers;
1642	    } else if (num == 2 && strcmp(KRB5_WELLKNOWN_NAME, comp0) == 0 && strcmp(KRB5_FAST_COOKIE, host) == 0) {
1643		node = "_krbfast";
1644		rtype = kODRecordTypeUsers;
1645	    } else if (num == 2 && is_lkdc(d, host)) {
1646		node = "localhost";
1647		rtype = kODRecordTypeComputers;
1648	    } else if (num == 2) {
1649		node = host;
1650		rtype = kODRecordTypeComputers;
1651	    } else {
1652		node = comp0;
1653		rtype = kODRecordTypeComputers;
1654	    }
1655
1656	} else if (num == 1) {
1657	    attr = kODAttributeTypeRecordName;
1658	    rtype = kODRecordTypeUsers;
1659	    upflags = KRB5_PRINCIPAL_UNPARSE_NO_REALM;
1660	    upflags |= KRB5_PRINCIPAL_UNPARSE_DISPLAY;
1661	} else {
1662	    ret = HDB_ERR_NOENTRY;
1663	    goto out;
1664	}
1665    } else {
1666	/* managed realm, lets check for real entries */
1667
1668	ret = krb5_unparse_name_flags(context, principal, KRB5_PRINCIPAL_UNPARSE_DISPLAY, &kuser);
1669	if (ret)
1670	    goto out;
1671
1672	rtype = d->inComputerOrUsers;
1673	matchtype = kODMatchCompoundExpression;
1674	attr = NULL;
1675
1676	queryString = CFStringCreateWithFormat(NULL, NULL, CFSTR("(|(%@=%s)(%@=%s))"),
1677					       CFSTR("dsAttrTypeNative:KerberosUserName"),
1678					       kuser,
1679					       CFSTR("dsAttrTypeNative:KerberosServerName"),
1680					       kuser);
1681    }
1682
1683    if (queryString == NULL) {
1684	if (node) {
1685	    queryString = CFStringCreateWithCString(NULL, node, kCFStringEncodingUTF8);
1686	} else {
1687	    ret = krb5_unparse_name_flags(context, principal, upflags, &kuser);
1688	    if (ret)
1689		goto out;
1690
1691	    queryString = CFStringCreateWithCString(NULL, kuser, kCFStringEncodingUTF8);
1692	}
1693    }
1694    if (queryString == NULL) {
1695	ret = ENOMEM;
1696	goto out;
1697    }
1698
1699    do {
1700	CFErrorRef error = NULL;
1701
1702	query = ODQueryCreateWithNode(NULL, d->rootNode,
1703				      rtype, attr,
1704				      matchtype,
1705				      queryString,
1706				      d->inNodeAttributes,
1707				      1,
1708				      NULL);
1709	if (query == NULL) {
1710	    CFRelease(queryString);
1711	    ret = ENOMEM;
1712	    goto out;
1713	}
1714
1715	res = ODQueryCopyResults(query, FALSE, &error);
1716	if (res == NULL && error == NULL)
1717	    tryAgain = 0;
1718	if (error)
1719	    CFRelease(error);
1720    } while(res == NULL && tryAgainP(context, &tryAgain, NULL));
1721
1722    CFRelease(queryString);
1723    if (res == NULL) {
1724	ret = HDB_ERR_NOENTRY;
1725	goto out;
1726    }
1727
1728    if (CFArrayGetCount(res) == 0 && node && createp) {
1729
1730	queryString = CFStringCreateWithCString(NULL, node, kCFStringEncodingUTF8);
1731
1732	cfRecord = ODNodeCopyRecord(d->rootNode, rtype, queryString, d->inNodeAttributes, NULL);
1733	if (cfRecord == NULL) {
1734	    CFMutableDictionaryRef attributes;
1735
1736	    attributes = CFDictionaryCreateMutable(NULL, 0,
1737						   &kCFTypeDictionaryKeyCallBacks,
1738						   &kCFTypeDictionaryValueCallBacks);
1739
1740	    cfRecord = ODNodeCreateRecord(d->rootNode,
1741					  rtype,
1742					  queryString,
1743					  attributes,
1744					  NULL);
1745	    CFRelease(attributes);
1746	}
1747	CFRelease(queryString);
1748
1749	if (cfRecord == NULL) {
1750	    ret = HDB_ERR_UK_RERROR;
1751	    goto out;
1752	}
1753
1754	if (createdp)
1755	    *createdp = TRUE;
1756
1757    } else if (CFArrayGetCount(res) == 1) {
1758	cfRecord = (ODRecordRef) CFArrayGetValueAtIndex(res, 0);
1759	if (cfRecord == NULL) {
1760	    ret = HDB_ERR_NOENTRY;
1761	    goto out;
1762	}
1763	CFRetain(cfRecord);
1764
1765    } else {
1766	ret = HDB_ERR_NOENTRY;
1767	goto out;
1768    }
1769
1770    *result = cfRecord;
1771
1772 out:
1773    if (kuser)
1774	free(kuser);
1775    if (eprinc)
1776	krb5_free_principal(context, eprinc);
1777    if (res)
1778	CFRelease(res);
1779    if (query)
1780	CFRelease(query);
1781    return ret;
1782}
1783
1784static krb5_error_code
1785server_locate_record(krb5_context context, hdb_od d, krb5_principal principal,
1786		     unsigned flags, int createp, int *createdp, ODRecordRef *result)
1787{
1788    ODRecordRef cfRecord = NULL;
1789    krb5_error_code ret;
1790    ODMatchType matchtype;
1791    CFStringRef queryString = NULL;
1792    ODQueryRef query = NULL;
1793    CFArrayRef res = NULL;
1794    CFStringRef attr;
1795    char *kuser = NULL;
1796    CFStringRef client_key, server_key;
1797    int tryAgain = MAX_TRIES;
1798
1799    *result = NULL;
1800
1801    ret = krb5_unparse_name_flags(context, principal, KRB5_PRINCIPAL_UNPARSE_DISPLAY, &kuser);
1802    if (ret)
1803	goto out;
1804
1805    client_key = CFSTR("dsAttrTypeNative:draft-krbPrincipalName");
1806    server_key = CFSTR("dsAttrTypeNative:draft-krbPrincipalAliases");
1807
1808    /* XXX support referrals here */
1809
1810    if (flags & HDB_F_GET_KRBTGT) {
1811	matchtype = kODMatchEqualTo;
1812	attr = client_key;
1813	queryString = CFStringCreateWithCString(NULL, kuser, kCFStringEncodingUTF8);
1814    } else {
1815	matchtype = kODMatchCompoundExpression;
1816	attr = NULL;
1817	queryString = CFStringCreateWithFormat(NULL, NULL, CFSTR("(|(%@=%s)(%@=%s))"),
1818					       server_key,
1819					       kuser,
1820					       client_key,
1821					       kuser);
1822    }
1823    if (queryString == NULL) {
1824	ret = HDB_ERR_NOENTRY;
1825	goto out;
1826    }
1827
1828    do {
1829	CFErrorRef error = NULL;
1830
1831	query = ODQueryCreateWithNode(NULL, d->rootNode,
1832				      kODRecordTypeUserAuthenticationData,
1833				      attr,
1834				      matchtype,
1835				      queryString,
1836				      d->inNodeAttributes,
1837				      2,
1838				      NULL);
1839	if (query == NULL) {
1840	    CFRelease(queryString);
1841	    ret = ENOMEM;
1842	    goto out;
1843	}
1844
1845	res = ODQueryCopyResults(query, FALSE, &error);
1846	if (res == NULL && error == NULL)
1847	    tryAgain = 0;
1848	if (error)
1849	    CFRelease(error);
1850    } while(res == NULL && tryAgainP(context, &tryAgain, NULL));
1851    CFRelease(queryString);
1852
1853    if (res == NULL) {
1854	ret = HDB_ERR_NOENTRY;
1855	goto out;
1856    }
1857
1858
1859    CFIndex count = CFArrayGetCount(res);
1860    if (count == 0 && createp) {
1861	CFMutableDictionaryRef attributes;
1862	CFStringRef name;
1863	CFUUIDRef uuid;
1864
1865	uuid = CFUUIDCreate(NULL);
1866	if (uuid == NULL) {
1867	    ret = HDB_ERR_NOENTRY;
1868	    goto out;
1869	}
1870
1871	name = CFUUIDCreateString(NULL, uuid);
1872	CFRelease(uuid);
1873	if (name == NULL) {
1874	    ret = HDB_ERR_NOENTRY;
1875	    goto out;
1876	}
1877
1878	attributes = CFDictionaryCreateMutable(NULL, 0,
1879					       &kCFTypeDictionaryKeyCallBacks,
1880					       &kCFTypeDictionaryValueCallBacks);
1881	if (attributes == NULL) {
1882	    CFRelease(name);
1883	    ret = HDB_ERR_NOENTRY;
1884	    goto out;
1885	}
1886
1887	cfRecord = ODNodeCreateRecord(d->rootNode,
1888				      kODRecordTypeUserAuthenticationData,
1889				      name,
1890				      attributes,
1891				      NULL);
1892	CFRelease(name);
1893	CFRelease(attributes);
1894	if (cfRecord == NULL) {
1895	    ret = HDB_ERR_NOENTRY;
1896	    goto out;
1897	}
1898
1899	if (createdp)
1900	    *createdp = 1;
1901
1902    } else if (count != 1) {
1903	ret = HDB_ERR_NOENTRY;
1904	goto out;
1905    } else {
1906	cfRecord = (ODRecordRef) CFArrayGetValueAtIndex(res, 0);
1907	if (cfRecord == NULL) {
1908	    ret = HDB_ERR_NOENTRY;
1909	    goto out;
1910	}
1911	CFRetain(cfRecord);
1912    }
1913    *result = cfRecord;
1914
1915out:
1916    if (res)
1917	CFRelease(res);
1918    if (query)
1919	CFRelease(query);
1920    if (kuser)
1921	free(kuser);
1922
1923    return ret;
1924}
1925
1926
1927static krb5_error_code
1928hod_lkdc_fetch(krb5_context context, HDB * db, krb5_const_principal principal,
1929	       unsigned flags, krb5_kvno kvno, hdb_entry_ex * entry)
1930{
1931    krb5_principal eprincipal = NULL;
1932    hdb_od d = (hdb_od)db->hdb_db;
1933    ODRecordRef cfRecord = NULL;
1934    krb5_error_code ret;
1935
1936    ret = krb5_copy_principal(context, principal, &eprincipal);
1937    if (ret)
1938	goto out;
1939
1940    ret = d->locate_record(context, d, eprincipal, flags, FALSE, NULL, &cfRecord);
1941    if (ret)
1942	goto out;
1943
1944    if (is_lkdc(d, eprincipal->realm))
1945	map_lkdc_principal(context, eprincipal, wellknown_lkdc, d->LKDCRealm);
1946
1947    /* set the principal to the username for users in pku2u and lkdc */
1948    if (krb5_principal_is_pku2u(context, eprincipal) ||
1949	(krb5_principal_is_lkdc(context, eprincipal) && krb5_principal_get_num_comp(context, eprincipal) == 1))
1950    {
1951	CFStringRef name = ODRecordGetRecordName(cfRecord);
1952	char *str;
1953
1954	str = rk_cfstring2cstring(name);
1955	if (str == NULL) {
1956	    ret = ENOENT;
1957	    goto out;
1958	}
1959
1960	ret = krb5_make_principal(context, &entry->entry.principal,
1961				  eprincipal->realm, str, NULL);
1962	free(str);
1963	if (ret)
1964	    goto out;
1965    } else {
1966	ret = krb5_copy_principal(context, eprincipal, &entry->entry.principal);
1967	if (ret)
1968	    goto out;
1969    }
1970
1971    ret = od_record2entry(context, db, cfRecord, flags, entry);
1972    if (ret)
1973	goto out;
1974
1975 out:
1976    if (eprincipal)
1977	krb5_free_principal(context, eprincipal);
1978    if (cfRecord)
1979	CFRelease(cfRecord);
1980    if (ret) {
1981	free_hdb_entry(&entry->entry);
1982	memset(&entry->entry, 0, sizeof(entry->entry));
1983    }
1984
1985    return ret;
1986}
1987
1988static krb5_error_code
1989hod_server_fetch(krb5_context context, HDB * db, krb5_const_principal principal,
1990		 unsigned flags, krb5_kvno kvno, hdb_entry_ex * entry)
1991{
1992    krb5_principal eprincipal = NULL;
1993    hdb_od d = (hdb_od)db->hdb_db;
1994    ODRecordRef cfRecord = NULL;
1995    krb5_error_code ret;
1996
1997
1998    ret = krb5_copy_principal(context, principal, &eprincipal);
1999    if (ret)
2000	goto out;
2001
2002    ret = d->locate_record(context, d, eprincipal, flags, FALSE, NULL, &cfRecord);
2003    if (ret)
2004        goto out;
2005
2006    ret = krb5_copy_principal(context, eprincipal, &entry->entry.principal);
2007    if (ret)
2008	goto out;
2009
2010    ret = od_record2entry(context, db, cfRecord, flags, entry);
2011    if (ret)
2012	goto out;
2013
2014out:
2015    if (cfRecord)
2016        CFRelease(cfRecord);
2017    if (eprincipal)
2018	krb5_free_principal(context, eprincipal);
2019
2020    if (ret) {
2021	free_hdb_entry(&entry->entry);
2022	memset(&entry->entry, 0, sizeof(entry->entry));
2023    }
2024
2025    return ret;
2026}
2027
2028
2029static krb5_error_code
2030hod_store(krb5_context context, HDB * db, unsigned flags,
2031	  hdb_entry_ex * entry)
2032{
2033    krb5_principal eprincipal = NULL;
2034    hdb_od d = (hdb_od)db->hdb_db;
2035    ODRecordRef record = NULL;
2036    krb5_error_code ret;
2037    int i, created = 0;
2038
2039    ret = krb5_copy_principal(context, entry->entry.principal, &eprincipal);
2040    if (ret)
2041	goto out;
2042
2043    ret = d->locate_record(context, d, eprincipal, flags, (flags & HDB_F_REPLACE) == 0, &created, &record);
2044    if (ret)
2045	goto out;
2046
2047    for (i = 0; i < num_keys; i++) {
2048	if (keys[i].hdb2od == NULL)
2049	    continue;
2050
2051	/* logical XOR between server values and LKDC connection */
2052	if ((!!(keys[i].flags & SERVER_VALUE)) ^ (!d->LKDCRealm))
2053	    continue;
2054
2055	ret = (*keys[i].hdb2od)(context, d, keys[i].name, flags, entry, record);
2056	if (ret)
2057	    goto out;
2058    }
2059
2060    if ((flags & HDB_F_CHANGE_PASSWORD) == 0) {
2061	ret = hdb_seal_keys(context, db, &entry->entry);
2062	if(ret)
2063	    goto out;
2064    }
2065
2066    ODRecordSynchronize(record, NULL);
2067
2068 out:
2069    if (record) {
2070	if (ret && created)
2071	    ODRecordDelete(record, NULL);
2072	CFRelease(record);
2073    }
2074    if (eprincipal)
2075	krb5_free_principal(context, eprincipal);
2076
2077    return ret;
2078}
2079
2080static krb5_error_code
2081hod_remove(krb5_context context, HDB *db, krb5_const_principal principal)
2082{
2083    krb5_principal eprincipal = NULL;
2084    hdb_od d = (hdb_od)db->hdb_db;
2085    ODRecordRef record = NULL;
2086    CFMutableArrayRef array = NULL;
2087    krb5_error_code ret;
2088    int i;
2089
2090    ret = krb5_copy_principal(context, principal, &eprincipal);
2091    if (ret)
2092	goto out;
2093
2094    ret = d->locate_record(context, d, eprincipal, 0, FALSE, NULL, &record);
2095    if (ret)
2096	goto out;
2097
2098    array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
2099    if (array == NULL) {
2100	ret = ENOMEM;
2101	goto out;
2102    }
2103
2104    for (i = 0; i < num_keys; i++) {
2105
2106	if ((keys[i].flags & DELETE_KEY) == 0)
2107	    continue;
2108
2109	if (!ODRecordSetValue(record, keys[i].name, array, NULL)) {
2110	    ret = HDB_ERR_UK_SERROR;
2111	    break;
2112	}
2113    }
2114
2115    ODRecordSynchronize(record, NULL);
2116
2117 out:
2118    if (eprincipal)
2119	krb5_free_principal(context, eprincipal);
2120    if (array)
2121	CFRelease(array);
2122    if (record)
2123	CFRelease(record);
2124    return ret;
2125}
2126
2127
2128static krb5_error_code
2129hod_get_realms(krb5_context context, HDB *db, krb5_realm **realms)
2130{
2131    hdb_od d = (hdb_od)db->hdb_db;
2132
2133    *realms = NULL;
2134
2135    if (d->LKDCRealm) {
2136	*realms = calloc(2, sizeof(realms[0]));
2137	if (*realms == NULL)
2138	    return ENOMEM;
2139	(*realms)[0] = strdup(d->LKDCRealm);
2140	if ((*realms)[0] == NULL) {
2141	    free(*realms);
2142	    *realms = NULL;
2143	    return ENOMEM;
2144	}
2145	(*realms)[1] = NULL;
2146    }
2147
2148    return 0;
2149}
2150
2151static void
2152update_ntlm(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info)
2153{
2154    hdb_od d = info;
2155    CFDictionaryRef settings;
2156    CFStringRef n;
2157
2158    if (store == NULL || info == NULL)
2159	return;
2160
2161    settings = (CFDictionaryRef)SCDynamicStoreCopyValue(store, CFSTR("com.apple.smb"));
2162    if (settings == NULL)
2163	return;
2164
2165    n = CFDictionaryGetValue(settings, CFSTR("NetBIOSName"));
2166    if (n == NULL || CFGetTypeID(n) != CFStringGetTypeID())
2167	goto fin;
2168
2169    if (d->ntlmDomain)
2170	free(d->ntlmDomain);
2171    d->ntlmDomain = rk_cfstring2cstring(n);
2172    strupr(d->ntlmDomain);
2173
2174fin:
2175    CFRelease(settings);
2176    return;
2177}
2178
2179static void
2180ntlm_notification(hdb_od d)
2181{
2182    SCDynamicStoreRef store;
2183    dispatch_queue_t queue;
2184    SCDynamicStoreContext context;
2185
2186    memset(&context, 0, sizeof(context));
2187    context.info = (void*)d;
2188
2189    store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("com.apple.Kerberos.hdb-od"), update_ntlm, (void *)&context);
2190    if (store == NULL)
2191	return;
2192
2193    CFTypeRef key[] = {CFSTR("com.apple.smb")};
2194    CFArrayRef nkeys = CFArrayCreate(kCFAllocatorDefault, key, 1, NULL);
2195    SCDynamicStoreSetNotificationKeys(store, nkeys, NULL);
2196    CFRelease(nkeys);
2197
2198    queue = dispatch_queue_create("com.apple.hdb-od.ntlm-name", NULL);
2199    if (queue == NULL) {
2200	CFRelease(store);
2201	errx(1, "dispatch_queue_create");
2202    }
2203
2204    SCDynamicStoreSetDispatchQueue(store, queue);
2205    CFRelease(store);
2206
2207    dispatch_sync(queue, ^{ update_ntlm(store, NULL, d); });
2208}
2209
2210static krb5_error_code
2211hod_get_ntlm_domain(krb5_context context, struct HDB *db, char **name)
2212{
2213    hdb_od d = (hdb_od)db->hdb_db;
2214    if (d->ntlmDomain == NULL) {
2215	krb5_set_error_message(context, EINVAL, "no ntlm domain");
2216	return EINVAL;
2217    }
2218    *name = strdup(d->ntlmDomain);
2219    return 0;
2220}
2221
2222
2223#ifdef __APPLE_PRIVATE__
2224
2225static struct  {
2226    typeof(DSChangePasswordWithPolicy) *_HDBDSChangePasswordWithPolicy;
2227    typeof(DSUpdateLoginStatus) *_HDBDSUpdateLoginStatus;
2228} ds_ptrs;
2229
2230static void
2231DirectoryServer_framework(void)
2232{
2233    static dispatch_once_t once;
2234
2235    /*
2236     * Do softlinking to avoid having a dependency on DirectoryServer framework
2237     */
2238
2239    dispatch_once(&once, ^{
2240	    void *ptr = dlopen("/System/Library/PrivateFrameworks/DirectoryServer.framework/DirectoryServer", RTLD_LAZY);
2241	    if (ptr) {
2242		ds_ptrs._HDBDSChangePasswordWithPolicy = dlsym(ptr, "DSChangePasswordWithPolicy");
2243		ds_ptrs._HDBDSUpdateLoginStatus = dlsym(ptr, "DSUpdateLoginStatus");
2244	    }
2245	});
2246}
2247
2248static krb5_error_code
2249hod_password(krb5_context context, struct HDB *db, hdb_entry_ex *entry, const char *password, int flags)
2250{
2251    char *user;
2252    int ret;
2253
2254    DirectoryServer_framework();
2255
2256    if (ds_ptrs._HDBDSChangePasswordWithPolicy == NULL)
2257	return EINVAL;
2258
2259    if (krb5_principal_get_num_comp(context, entry->entry.principal) != 1)
2260	return EINVAL;
2261
2262    user = (char *)krb5_principal_get_comp_string(context, entry->entry.principal, 0);
2263
2264    ret = ds_ptrs._HDBDSChangePasswordWithPolicy(user, password, (flags & HDB_PWD_CONDITIONAL) == 0);
2265    if (ret != 0) {
2266	krb5_set_error_message(context, EINVAL, "DSPasswordServer rejected password");
2267	return EINVAL;
2268    }
2269
2270    return 0;
2271}
2272
2273static krb5_error_code
2274hod_auth_status(krb5_context context, struct HDB *db, hdb_entry_ex *entry, int status)
2275{
2276    char *user;
2277
2278    DirectoryServer_framework();
2279
2280    if (ds_ptrs._HDBDSUpdateLoginStatus == NULL)
2281	return EINVAL;
2282
2283    if (krb5_principal_get_num_comp(context, entry->entry.principal) != 1)
2284	return EINVAL;
2285
2286    user = (char *)krb5_principal_get_comp_string(context, entry->entry.principal, 0);
2287
2288    /* DS and HDB status code matches, so just pass them though */
2289    ds_ptrs._HDBDSUpdateLoginStatus(user, status);
2290
2291    return 0;
2292}
2293
2294#endif
2295
2296
2297krb5_error_code
2298hdb_od_create(krb5_context context, HDB ** db, const char *arg)
2299{
2300    CFMutableArrayRef attrs;
2301    ODNodeRef localRef = NULL;
2302    ODRecordRef kdcConfRef = NULL;
2303    CFArrayRef data = NULL;
2304    krb5_error_code ret;
2305    hdb_od d = NULL;
2306    char *path = NULL;
2307    int i;
2308
2309    *db = calloc(1, sizeof(**db));
2310    if (*db == NULL) {
2311	ret = ENOMEM;
2312	krb5_set_error_message(context, ret, "malloc: out of memory");
2313	goto out;
2314    }
2315    memset(*db, 0, sizeof(**db));
2316
2317    path = strdup(arg);
2318    if (path == NULL) {
2319	ret = ENOMEM;
2320	krb5_set_error_message(context, ret, "malloc: out of memory");
2321	goto out;
2322    }
2323
2324    d = calloc(1, sizeof(*d));
2325    if (d == NULL) {
2326	ret = ENOMEM;
2327	krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
2328	goto out;
2329    }
2330
2331    {
2332	char *p = strchr(path, '&');
2333	if (p) {
2334	    *p++ = '\0';
2335	    d->restoreRoot = CFStringCreateWithCString(NULL, p, kCFStringEncodingUTF8);
2336	}
2337    }
2338
2339    d->rootName = CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8);
2340    if (d == NULL) {
2341	ret = ENOMEM;
2342	krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
2343	goto out;
2344    }
2345
2346    attrs = CFArrayCreateMutable(NULL, num_keys, &kCFTypeArrayCallBacks);
2347    for (i = 0; i < num_keys; i++)
2348	CFArrayAppendValue(attrs, keys[i].name);
2349    d->inNodeAttributes = attrs;
2350
2351    attrs = CFArrayCreateMutable(NULL, 1, &kCFTypeArrayCallBacks);
2352    CFArrayAppendValue(attrs, kRealName);
2353    d->inKDCAttributes = attrs;
2354
2355    attrs = CFArrayCreateMutable(NULL, 2, &kCFTypeArrayCallBacks);
2356    CFArrayAppendValue(attrs, kODRecordTypeComputers);
2357    CFArrayAppendValue(attrs, kODRecordTypeUsers);
2358
2359    d->inComputerOrUsers = attrs;
2360
2361    (*db)->hdb_db = d;
2362
2363    (*db)->hdb_capability_flags = HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL;
2364    (*db)->hdb_master_key_set = 0;
2365    (*db)->hdb_openp = 0;
2366    (*db)->hdb_open = hod_open;
2367    (*db)->hdb_close = hod_close;
2368    (*db)->hdb_store = hod_store;
2369    (*db)->hdb_remove = hod_remove;
2370    (*db)->hdb_firstkey = hod_firstkey;
2371    (*db)->hdb_nextkey = hod_nextkey;
2372    (*db)->hdb_lock = hod_lock;
2373    (*db)->hdb_unlock = hod_unlock;
2374    (*db)->hdb_rename = NULL;
2375    (*db)->hdb__get = NULL;
2376    (*db)->hdb__put = NULL;
2377    (*db)->hdb__del = NULL;
2378    (*db)->hdb_destroy = hod_destroy;
2379    (*db)->hdb_get_realms = hod_get_realms;
2380    (*db)->hdb_get_ntlm_domain = hod_get_ntlm_domain;
2381
2382
2383    /*
2384     * The /Local/Default realm is the LKDC realm, so lets pick up the
2385     * LKDC configuration that we will use later.
2386     */
2387
2388    if (strncmp(path, "/Local/", 7) == 0) {
2389
2390	/*
2391	 * Pull out KerberosLocalKDC configuration
2392	 */
2393
2394	ret = nodeCreateWithName(context, d, &localRef);
2395	if (ret)
2396	    goto out;
2397
2398	kdcConfRef = ODNodeCopyRecord(localRef, kODRecordTypeConfiguration,
2399				      CFSTR("KerberosKDC"),
2400				      d->inKDCAttributes, NULL);
2401	if (kdcConfRef == NULL) {
2402	    ret = HDB_ERR_NOENTRY;
2403	    krb5_set_error_message(context, ret, "Failed to find KerberosKDC node");
2404	    goto out;
2405	}
2406
2407	data = ODRecordCopyValues(kdcConfRef, kRealName, NULL);
2408	if (data == NULL) {
2409	    ret = HDB_ERR_NOENTRY;
2410	    krb5_set_error_message(context, ret, "Failed to copy RealName from KerberosKDC node");
2411	    goto out;
2412	}
2413
2414	if (CFArrayGetCount(data) != 1) {
2415	    ret = HDB_ERR_NOENTRY;
2416	    krb5_set_error_message(context, ret, "Found RealName %d from KerberosKDC",
2417				   (int)CFArrayGetCount(data));
2418	    goto out;
2419	}
2420	d->LKDCRealm = rk_cfstring2cstring((CFStringRef)CFArrayGetValueAtIndex(data, 0));
2421	if (d->LKDCRealm == NULL) {
2422	    ret = HDB_ERR_NOENTRY;
2423	    krb5_set_error_message(context, ret, "failed to find realm");
2424	    goto out;
2425	}
2426
2427	CFRelease(data); data = NULL;
2428	CFRelease(kdcConfRef); kdcConfRef = NULL;
2429	CFRelease(localRef); localRef = NULL;
2430
2431	ntlm_notification(d);
2432
2433	(*db)->hdb_fetch_kvno = hod_lkdc_fetch;
2434	d->locate_record = lkdc_locate_record;
2435
2436    } else {
2437	/* hod_password interface is stupid, pipe can fail and we might not expect it to */
2438	/* Not the right place for this. */
2439#ifdef HAVE_SIGACTION
2440	struct sigaction sa;
2441
2442	sa.sa_flags = 0;
2443	sa.sa_handler = SIG_IGN;
2444	sigemptyset(&sa.sa_mask);
2445
2446	sigaction(SIGPIPE, &sa, NULL);
2447#else
2448	signal(SIGPIPE, SIG_IGN);
2449#endif /* HAVE_SIGACTION */
2450
2451	(*db)->hdb_capability_flags |= HDB_CAP_F_HANDLE_PASSWORDS;
2452#ifdef __APPLE_PRIVATE__
2453	(*db)->hdb_password = hod_password;
2454#endif
2455	(*db)->hdb_fetch_kvno = hod_server_fetch;
2456	d->locate_record = server_locate_record;
2457
2458	(*db)->hdb_auth_status = hod_auth_status;
2459    }
2460
2461
2462    free(path);
2463
2464    return 0;
2465
2466  out:
2467    if (path)
2468	free(path);
2469    if (data)
2470	CFRelease(data);
2471    if (kdcConfRef)
2472	CFRelease(kdcConfRef);
2473    if (localRef)
2474	CFRelease(localRef);
2475
2476    if (*db) {
2477	free(*db);
2478	*db = NULL;
2479    }
2480    if (d) {
2481	if (d->rootName)
2482	    CFRelease(d->rootName);
2483	if (d->restoreRoot)
2484	    CFRelease(d->restoreRoot);
2485	free(d);
2486    }
2487
2488    return ret;
2489}
2490
2491#endif
2492