1/*
2 * Copyright (c) 2006-2010 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include "NetworkAuthenticationHelper.h"
25#include "KerberosHelper.h"
26#include "KerberosHelperContext.h"
27#include "lookupDSLocalKDC.h"
28#include "util.h"
29
30#include <Heimdal/locate_plugin.h>
31
32#include <GSS/gssapi.h>
33#include <GSS/gssapi_spi.h>
34#include <GSS/gssapi_spnego.h>
35#include <GSS/spnego_asn1.h>
36
37#include "LKDCHelper.h"
38
39#include <Carbon/Carbon.h>
40#include <CoreServices/CoreServices.h>
41#include <CoreServices/CoreServicesPriv.h>
42#include <Security/Security.h>
43#include <Security/SecCertificateOIDs.h>
44#include <Security/SecCertificatePriv.h>
45#include <CommonCrypto/CommonDigest.h>
46#include <unistd.h>
47#include <sys/socket.h>
48#include <arpa/inet.h>
49#include <netdb.h>
50
51#include <asl.h>
52
53#include "DeconstructServiceName.h"
54#include "utils.h"
55#include "lookupDSLocalKDC.h"
56
57int
58der_print_heim_oid (
59	const heim_oid */*oid*/,
60	char /*delim*/,
61	char **/*str*/);
62
63
64#define DEBUG 0  /* Set to non-zero for more debug spew than you want. */
65
66static krb5_error_code
67_k5_check_err(krb5_error_code error, const char *function, const char *file, int line)
68{
69    //    if (error)
70    //        asl_log(NULL, NULL, ASL_LEVEL_DEBUG, "    %s: krb5 call got %d (%s) on %s:%d", function, error, error_message(error), file, line);
71    return error;
72}
73
74// static krb5_error_code _k5_check_err(krb5_error_code error, const char *function, const char *file, int line);
75#define k5_ok(x) _k5_check_err (x, __func__, __FILE__, __LINE__)
76
77#define KHLog(FMT, ...)     asl_log(NULL, NULL, ASL_LEVEL_DEBUG, FMT, __VA_ARGS__)
78
79static const char lkdc_prefix[] = "LKDC:";
80
81/*
82 *
83 */
84
85static const char wellknown_lkdc[] = "WELLKNOWN:COM.APPLE.LKDC";
86
87static int
88is_lkdc_realm(const char *realm)
89{
90    if (realm == NULL)
91	return 0;
92    return strncmp(realm, lkdc_prefix, sizeof(lkdc_prefix)-1) == 0 ||
93	strncmp(realm, wellknown_lkdc, sizeof(wellknown_lkdc) - 1) == 0;
94}
95
96/* If realms a and b have a common subrealm, returns the number of
97 * common components.  Otherwise, returns zero.
98 */
99static int
100has_common_subrealm(const char *a, const char *b)
101{
102    const char *ap = &a[strlen(a)];
103    const char *bp = &b[strlen(b)];
104    unsigned int n = 0;
105
106    if (ap == a || bp == b)
107        return 0;
108    for (--ap, --bp; ap >= a && bp >= b && *ap == *bp; --ap, --bp)
109        if ((ap == a && bp == b) ||
110            (ap == a && '.' == bp[-1]) ||
111            (bp == b && '.' == ap[-1]) ||
112            ('.' == ap[-1] && '.' == bp[-1]))
113            ++n;
114    return n;
115}
116
117static const char *
118principal_realm(const char *princ)
119{
120    const char *p = &princ[strlen(princ)];
121
122    while (p > princ) {
123        if ('@' == p[0] && '\\' != p[-1])
124            break;
125        --p;
126    }
127    if (p == princ)
128        return NULL;
129    else
130        return &p[1];
131}
132
133static void
134add_mapping(KRBHelperContextRef hCtx, const char *hostname, const char *realm, int islkdc)
135{
136    struct realm_mappings *p;
137
138    p = realloc(hCtx->realms.data, sizeof(hCtx->realms.data[0]) * (hCtx->realms.len + 1));
139    if (p == NULL)
140	return;
141    hCtx->realms.data = p;
142
143    hCtx->realms.data[hCtx->realms.len].lkdc = islkdc;
144    hCtx->realms.data[hCtx->realms.len].hostname = strdup(hostname);
145    if (hCtx->realms.data[hCtx->realms.len].hostname == NULL)
146	return;
147
148    hCtx->realms.data[hCtx->realms.len].realm = strdup(realm);
149    if (hCtx->realms.data[hCtx->realms.len].realm == NULL) {
150	free(hCtx->realms.data[hCtx->realms.len].hostname);
151	return;
152    }
153    hCtx->realms.len++;
154}
155
156static void
157find_mapping(KRBHelperContextRef hCtx, const char *hostname, int lkdcp)
158{
159    krb5_error_code ret;
160    char **realmlist = NULL;
161    char *realm;
162    size_t i;
163
164    for (i = 0; i < hCtx->realms.len; i++)
165	if (strcasecmp(hCtx->realms.data[i].hostname, hostname) == 0)
166	    return;
167
168    ret = krb5_get_host_realm(hCtx->krb5_ctx, hostname, &realmlist);
169    if (ret == 0) {
170	for (i = 0; realmlist && realmlist[i] && *(realmlist[i]); i++)
171	    add_mapping(hCtx, hostname, realmlist[i], is_lkdc_realm(realmlist[i]));
172	if (i == 0)
173            KHLog ("    %s: krb5_get_host_realm returned unusable realm!", __func__);
174    }
175    if (lkdcp && LKDCDiscoverRealm(hostname, &realm) == 0) {
176	add_mapping(hCtx, hostname, realm, is_lkdc_realm(realm));
177	free(realm);
178    }
179
180    if (realmlist)
181        krb5_free_host_realm (hCtx->krb5_ctx, realmlist);
182}
183
184static int
185parse_principal_name(krb5_context ctx, const char *princname, char **namep, char **instancep, char **realmp)
186{
187    krb5_principal principal = NULL;
188    int err = 0;
189    int len;
190
191    KHLog ("[[[ %s () decomposing %s", __func__, princname);
192    *namep = *instancep = *realmp = NULL;
193
194    if (krb5_parse_name(ctx, princname, &principal)) {
195        err = -1;
196        goto fin;
197    }
198
199    len = krb5_principal_get_num_comp(ctx, principal);
200    if (len > 0)
201	*namep = strdup(krb5_principal_get_comp_string(ctx, principal, 0));
202    if (len > 1)
203	*instancep = strdup(krb5_principal_get_comp_string(ctx, principal, 1));
204    *realmp = strdup(krb5_principal_get_realm(ctx, principal));
205
206 fin:
207    if (NULL != principal)
208        krb5_free_principal(ctx, principal);
209    KHLog ("]]] %s () - %d", __func__, err);
210    return err;
211}
212
213static int
214lookup_by_kdc(KRBhelperContext *hCtx, const char *name, char **realm)
215{
216    krb5_error_code ret;
217    krb5_cccol_cursor cursor;
218    krb5_ccache id;
219    krb5_creds mcred, *creds;
220
221    memset(&mcred, 0, sizeof(mcred));
222    *realm = NULL;
223
224    ret = krb5_build_principal(hCtx->krb5_ctx, &mcred.server,
225			       0, "",
226			       "host", name, NULL); /* XXX propper service name */
227    if (ret)
228	return ret;
229
230    ret = krb5_cccol_cursor_new(hCtx->krb5_ctx, &cursor);
231    if (ret)
232	return ret;
233
234    while ((ret = krb5_cccol_cursor_next(hCtx->krb5_ctx, cursor, &id)) == 0) {
235	const char *errmsg = NULL;
236	char *clientname;
237
238	if (id == NULL) {
239	    ret = memFullErr; /* XXX */
240	    break;
241	}
242
243	ret = krb5_cc_get_principal(hCtx->krb5_ctx, id, &mcred.client);
244	if (ret)
245	    goto next;
246
247	ret = krb5_unparse_name(hCtx->krb5_ctx, mcred.client, &clientname);
248	if (ret) {
249	    krb5_free_principal(hCtx->krb5_ctx, mcred.client);
250	    KHLog ("Failed to unparse name %s () - %d", __func__, ret);
251	    goto next;
252	}
253
254	ret = krb5_principal_set_realm(hCtx->krb5_ctx, mcred.server, mcred.client->realm);
255	if (ret) {
256	    free(clientname);
257	    krb5_free_principal(hCtx->krb5_ctx, mcred.client);
258	    goto next;
259	}
260
261	ret = krb5_get_credentials(hCtx->krb5_ctx, 0, id, &mcred, &creds);
262	krb5_free_principal(hCtx->krb5_ctx, mcred.client);
263	if (ret)
264	    errmsg = krb5_get_error_message(hCtx->krb5_ctx, ret);
265	KHLog ("krb5_get_credentials(%s): referrals %s () - %s (%d)",
266	       clientname, __func__, errmsg ? errmsg : "success", ret);
267	if (errmsg)
268	    krb5_free_error_message(hCtx->krb5_ctx, errmsg);
269	free(clientname);
270	if (ret == 0) {
271	    *realm = strdup(creds->server->realm);
272	    krb5_free_creds(hCtx->krb5_ctx, creds);
273	    krb5_cc_close(hCtx->krb5_ctx, id);
274	    break;
275	}
276    next:
277	krb5_cc_close(hCtx->krb5_ctx, id);
278    }
279
280    krb5_free_principal(hCtx->krb5_ctx, mcred.server);
281    krb5_cccol_cursor_free(hCtx->krb5_ctx, &cursor);
282
283    return ret;
284}
285
286static int
287strcmp_trailer(const char *f, const char *p)
288{
289    size_t flen = strlen(f), plen = strlen(p);
290
291    if (f[flen - 1] == '.')
292	flen--;
293
294    if (flen > plen)
295	return flen - plen;
296    return strcasecmp(&f[flen - plen], p);
297}
298
299static int
300is_local_hostname(const char *host)
301{
302    static dispatch_once_t once;
303    static char *btmmDomain;
304
305    dispatch_once(&once, ^{
306	    CFStringRef d = _CSBackToMyMacCopyDomain();
307	    if (d) {
308		__KRBCreateUTF8StringFromCFString(d, &btmmDomain);
309		CFRelease(d);
310		if (btmmDomain) {
311		    size_t len = strlen(btmmDomain);
312		    if (len > 0 && btmmDomain[len - 1] == '.')
313			btmmDomain[len - 1] = '\0';
314		}
315	    }
316	});
317
318
319    if (strcmp_trailer(host, ".local") == 0)
320	return 1;
321    if (btmmDomain && strcmp_trailer(host, btmmDomain) == 0)
322	return 1;
323    if (strchr(host, '.'))
324	return 1;
325    return 0;
326}
327
328/*
329 *
330 */
331
332OSStatus
333KRBCreateSession(CFStringRef inHostName, CFStringRef inAdvertisedPrincipal,
334                 void **outKerberosSession)
335{
336    KRBHelperContextRef session = NULL;
337    CFMutableDictionaryRef inInfo;
338    OSStatus error;
339
340    *outKerberosSession = NULL;
341
342    inInfo = CFDictionaryCreateMutable (kCFAllocatorDefault, 2, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
343    if (inInfo == NULL)
344	return memFullErr;
345
346    if (inHostName)
347	CFDictionarySetValue (inInfo, kKRBHostnameKey, inHostName);
348    if (inAdvertisedPrincipal)
349	CFDictionarySetValue (inInfo, kKRBAdvertisedPrincipalKey, inAdvertisedPrincipal);
350
351    error = KRBCreateSessionInfo(inInfo, &session);
352    CFRelease(inInfo);
353    if (error == noErr)
354	*outKerberosSession = session;
355
356    return error;
357}
358
359
360OSStatus
361KRBCreateSessionInfo (CFDictionaryRef inDict, KRBHelperContextRef *outKerberosSession)
362{
363    CFStringRef inHostName, inAdvertisedPrincipal, noLocalKDC;
364    char hbuf[NI_MAXHOST];
365    struct addrinfo hints, *aip = NULL;
366    KRBhelperContext *hCtx = NULL;
367    char *tmp = NULL;
368    char *hintname = NULL, *hinthost = NULL, *hintrealm = NULL;
369    char *localname = NULL, *hostname = NULL;
370    struct realm_mappings *selected_mapping = NULL;
371    OSStatus err = noErr;
372    int avoidDNSCanonicalizationBug = 0;
373    int lkdcp = 0;
374    size_t i;
375
376    *outKerberosSession = NULL;
377
378    if (NULL == outKerberosSession)
379	return paramErr;
380
381    inHostName = CFDictionaryGetValue(inDict, kKRBHostnameKey);
382    inAdvertisedPrincipal = CFDictionaryGetValue(inDict, kKRBAdvertisedPrincipalKey);
383    noLocalKDC = CFDictionaryGetValue(inDict, kKRBNoLKDCKey);
384
385    KHLog ("[[[ %s () - required parameters okay: %s %s %s", __func__,
386	   inHostName ? "iHN" : "-",
387	   inAdvertisedPrincipal ? "iAP" : "-",
388	   noLocalKDC ? "nLK" : "-");
389
390    /*
391     * Create the context that we will return on success.
392     * We almost always require a Kerberos context and the default
393     * realm, so populate those up front.
394     */
395    if (NULL == (hCtx = calloc(1, sizeof(*hCtx))))
396	return memFullErr;
397
398    if (0 != k5_ok( krb5_init_context (&hCtx->krb5_ctx) )) {
399	err = memFullErr;
400	goto out;
401    }
402
403    if (0 != hx509_context_init(&hCtx->hx_ctx)) {
404	err = memFullErr;
405	goto out;
406    }
407
408    if (0 == k5_ok( krb5_get_default_realm (hCtx->krb5_ctx, &tmp) ) && NULL != tmp) {
409	if (NULL == (hCtx->defaultRealm = strdup (tmp))) {
410	    err = memFullErr;
411	    goto out;
412	}
413	krb5_xfree(tmp);
414    }
415
416    /* If no host name was given, then the caller is inquiring
417     * about the Local KDC realm.  Our work here is done.
418     */
419    if (NULL == inHostName) {
420        err = DSCopyLocalKDC (&hCtx->realm);
421        KHLog ("    %s: LocalKDC realm lookup only", __func__);
422        goto out;
423    }
424
425    /* Retain any given advertised principal name in the context
426     * and break it down into service, instance, and realm.  These
427     * will be used as hints when better information is not
428     * available.
429     */
430    if (NULL != inAdvertisedPrincipal) {
431        char *s = NULL;
432        hCtx->inAdvertisedPrincipal = CFRetain (inAdvertisedPrincipal);
433        if (0 != __KRBCreateUTF8StringFromCFString (inAdvertisedPrincipal, &s))
434            KHLog ("    %s: __KRBCreateUTF8StringFromCFString failed", __func__);
435        else {
436            (void)parse_principal_name (hCtx->krb5_ctx, s, &hintname, &hinthost, &hintrealm);
437            __KRBReleaseUTF8String (s);
438        }
439    }
440
441    /* Decode the given host name with _CFNetServiceDeconstructServiceName before proceeding. */
442
443    if (! _CFNetServiceDeconstructServiceName (inHostName, &hostname))
444        __KRBCreateUTF8StringFromCFString (inHostName, &hostname);
445    else
446        avoidDNSCanonicalizationBug = 1;
447
448    /* remove trailing dot */
449    i = strlen(hostname);
450    if (hostname[i - 1] == '.') {
451	hostname[i - 1] = '\0';
452
453	/* Stuff it back into context */
454	hCtx->inHostName = CFStringCreateWithCString (NULL, hostname, kCFStringEncodingUTF8);
455	if (hCtx->inHostName == NULL) {
456	    err = memFullErr;
457	    goto out;
458	}
459    } else {
460	hCtx->inHostName = CFRetain (inHostName);
461    }
462
463    KHLog ("    %s: processed host name = %s", __func__, hostname);
464
465    /*
466     * Try find name by asking the KDC first
467     */
468
469    if (lookup_by_kdc(hCtx, hostname, &tmp) == 0) {
470	add_mapping(hCtx, hostname, tmp, 0);
471	free(tmp);
472	err = noErr;
473	hCtx->noGuessing = 1;
474	goto done;
475    }
476
477
478    /*
479     * If the given name is a bare name (i.e. no dots), we may need
480     * to attempt to look it up as `<name>.local' later.
481     */
482    if (NULL == strchr(hostname, '.') &&
483	(0 > asprintf(&localname, "%s.local", hostname) || NULL == localname)) {
484	err = memFullErr;
485	goto out;
486    }
487
488    /*
489     * If the service didn't announce a realm, or it a annouced a LKDC
490     * realm, lets consider it when looking up hosts/realm mappings.
491     */
492
493    if ((hintrealm == NULL || is_lkdc_realm(hintrealm)) && noLocalKDC == 0)
494	lkdcp = 1;
495
496    /*
497     * Before we canonlize the hostname, lets find the realm.
498     */
499
500    find_mapping(hCtx, hostname, lkdcp);
501
502    /* Normalize the given host name using getaddrinfo AI_CANONNAME if
503     * possible. Track the resulting host name (normalized or not) as
504     * `hostname'.
505     *
506     * Avoid canonicalization if possible because of
507     * <rdar://problem/5517187> getaddrinfo with hints.ai_flags =
508     * AI_CANONNAME mangles quoted DNS Names.
509     */
510    {
511        memset (&hints, 0, sizeof(hints));
512        hints.ai_flags = AI_CANONNAME;
513        err = getaddrinfo (hostname, NULL, &hints, &hCtx->addr);
514        KHLog ("    %s: getaddrinfo = %s (%d)", __func__, 0 == err ? "success" : gai_strerror (err), (int)err);
515        if (0 == err && avoidDNSCanonicalizationBug == 0 && hCtx->addr->ai_canonname) {
516	    if ((tmp = strdup(hCtx->addr->ai_canonname)) != NULL) {
517		free(hostname);
518		hostname = tmp;
519	    }
520            KHLog ("    %s: canonical host name = %s", __func__, hostname);
521        }
522    }
523
524    /*
525     * Try adding the mapping for the canonlical name if we got one
526     */
527
528    find_mapping(hCtx, hostname, lkdcp);
529
530    /*
531     * If we have a hintrealm and there our initial guessing is right,
532     * lets skip the reverse lookup since that is potentially very
533     * expensive.
534     */
535
536    if (hintrealm) {
537	for (i = 0; i < hCtx->realms.len; i++)
538	    if (strcmp(hintrealm, hCtx->realms.data[i].realm) == 0)
539		goto done;
540    }
541
542    /*
543     * Try to find all name for this address, and the check if we can
544     * find a mapping.
545     */
546
547    for (aip = hCtx->addr; NULL != aip; aip = aip->ai_next) {
548	char ipbuf[NI_MAXHOST];
549
550	/* pretty print name first for logging */
551	err = getnameinfo(aip->ai_addr, aip->ai_addrlen,
552			  ipbuf, sizeof(ipbuf),
553			  NULL, 0, NI_NUMERICHOST);
554	if (err)
555	    snprintf(ipbuf, sizeof(ipbuf), "getnameinfo-%d", (int)err);
556
557        err = getnameinfo (aip->ai_addr, aip->ai_addr->sa_len, hbuf,
558			   sizeof(hbuf), NULL, 0, NI_NAMEREQD);
559        KHLog("    %s: getnameinfo(%s) -> %s result %d %s",
560	      __func__, ipbuf, hbuf, (int)err, 0 == err ? "success" : gai_strerror (err));
561        if (err) {
562	    /* This is not a fatal error.  We'll keep looking for candidate host names. */
563            continue;
564        }
565	find_mapping(hCtx, hbuf, lkdcp);
566    }
567
568    /* Reset err */
569    err = noErr;
570
571    /*
572     * Also, add localname (bare name) that we turned into a .local
573     * name if we had one.
574     */
575
576    if (localname)
577	find_mapping(hCtx, localname, lkdcp);
578
579 done:
580    /*
581     * Done fetching all data, will no try to find a mapping
582     */
583
584    for (i = 0; i < hCtx->realms.len; i++) {
585	KHLog ("    %s: available mappings: %s -> %s (%s)", __func__,
586	       hCtx->realms.data[i].hostname,
587	       hCtx->realms.data[i].realm,
588	       hCtx->realms.data[i].lkdc ? "LKDC" : "managed");
589    }
590
591    /*
592     * If we have noGuessing mapping, lets pick the first guess then.
593     */
594
595    if (!selected_mapping && hCtx->noGuessing && hCtx->realms.len)
596	selected_mapping = &hCtx->realms.data[0];
597
598    /*
599     * If we have "local" hostname, lets consider LKDC more aggressively.
600     */
601
602    if (noLocalKDC == 0 && is_local_hostname(hostname)) {
603
604	if (hintrealm) {
605	    /*
606	     * Search for localKDC mapping when we have a hint realm.
607	     */
608	    for (i = 0; i < hCtx->realms.len && !selected_mapping; i++)
609		if (strcasecmp(hintrealm, hCtx->realms.data[i].realm) == 0)
610		    selected_mapping = &hCtx->realms.data[i];
611	} else {
612	    /*
613	     * If we are using have no hintrealm, just pick any LKDC realm.
614	     */
615	    for (i = 0; i < hCtx->realms.len && !selected_mapping; i++)
616		if (hCtx->realms.data[i].lkdc)
617		    selected_mapping = &hCtx->realms.data[i];
618	}
619    }
620    /*
621     * Search for managed realm, the make us prefer manged realms for
622     * no local hostnames.
623     */
624    for (i = 0; i < hCtx->realms.len && !selected_mapping; i++) {
625	if (hCtx->realms.data[i].lkdc)
626	    continue;
627	if (hintrealm == NULL || strcasecmp(hintrealm, hCtx->realms.data[i].realm) == 0)
628	    selected_mapping = &hCtx->realms.data[i];
629    }
630
631    /*
632     * Search for LKDC again if no managed realm was found
633     */
634    for (i = 0; i < hCtx->realms.len && !selected_mapping; i++)
635	if (hintrealm == NULL || strcasecmp(hintrealm, hCtx->realms.data[i].realm) == 0)
636	    selected_mapping = &hCtx->realms.data[i];
637
638    /*
639     * If we still failed to find a mapping, just pick the first.
640     */
641    for (i = 0; i < hCtx->realms.len && !selected_mapping; i++)
642	selected_mapping = &hCtx->realms.data[i];
643
644    if (selected_mapping == NULL) {
645	KHLog ("    %s: No mapping for host name = %s found", __func__, hostname);
646	err = memFullErr;
647	goto out;
648    }
649    KHLog ("    %s: Using host name = %s, realm = %s (%s)", __func__,
650	   selected_mapping->hostname,
651	   selected_mapping->realm, selected_mapping->lkdc ? "LKDC" : "managed");
652
653    hCtx->realm = CFStringCreateWithCString (kCFAllocatorDefault, selected_mapping->realm, kCFStringEncodingASCII);
654    if (hCtx->realm == NULL) {
655	err = memFullErr;
656	goto out;
657    }
658
659    /* If its a LKDC realm, the hostname is the LKDC realm */
660    if (selected_mapping->lkdc)
661	hCtx->hostname = CFRetain(hCtx->realm);
662    else
663	hCtx->hostname = CFStringCreateWithCString(kCFAllocatorDefault, selected_mapping->hostname, kCFStringEncodingASCII);
664    if (hCtx->hostname == NULL) {
665	err = memFullErr;
666	goto out;
667    }
668
669 out:
670    free (hintname);
671    free (hinthost);
672    free (hintrealm);
673    free (hostname);
674    free (localname);
675
676    /*
677     * On error, free all members of the context and the context itself.
678     */
679    if (noErr != err) {
680	for (i = 0; i < hCtx->realms.len; i++) {
681	    free(hCtx->realms.data[i].hostname);
682	    free(hCtx->realms.data[i].realm);
683	}
684	free(hCtx->realms.data);
685	free (hCtx->defaultRealm);
686	if (NULL != hCtx->realm)
687	    CFRelease (hCtx->realm);
688	if (NULL != hCtx->inAdvertisedPrincipal)
689	    CFRelease (hCtx->inAdvertisedPrincipal);
690	if (NULL != hCtx->hostname)
691	    CFRelease (hCtx->hostname);
692	if (NULL != hCtx->inHostName)
693	    CFRelease (hCtx->inHostName);
694	if (NULL != hCtx->krb5_ctx)
695	    krb5_free_context (hCtx->krb5_ctx);
696	if (NULL != hCtx->hx_ctx)
697	    hx509_context_free(&hCtx->hx_ctx);
698	if (NULL != hCtx->addr)
699	    freeaddrinfo(hCtx->addr);
700
701	free (hCtx);
702    } else
703        *outKerberosSession = hCtx;
704
705    KHLog ("]]] %s () = %d", __func__, (int)err);
706    return err;
707}
708
709/*
710
711  KRBCopyREALM will return the REALM for the host that was passed to KRBCreateSession
712  inKerberosSession is the pointer returned by KRBCreateSession
713  outREALM is the REALM of the host
714*/
715OSStatus KRBCopyRealm(KRBHelperContextRef inKerberosSession, CFStringRef *outRealm)
716{
717    OSStatus err = noErr;
718
719    KRBhelperContext *hCtx = (KRBhelperContext *)inKerberosSession;
720
721    if (NULL == hCtx) {
722        err = paramErr; /* Invalid session context */
723        goto Done;
724    }
725
726    KHLog ("%s", "[[[ KRBCopyRealm () - required parameters okay");
727
728    if (NULL == hCtx->realm) {
729        err = paramErr;
730        goto Done;
731    }
732
733    *outRealm = CFRetain (hCtx->realm);
734 Done:
735    KHLog ("]]] KRBCopyRealm () = %d", (int)err);
736
737    return err;
738}
739
740/*
741  KRBCopyKeychainLookupInfo will return a dictionary containing information related to Kerberos and keychain items.
742  inKerberosSession is the pointer returned by KRBCreateSession
743  inUsername is an available and usable Username or NULL
744  outKeychainLookupInfo is a dictionary containing keychain lookup info and if it is acceptable to store a
745  password in the keychain.
746
747  outKeychainLookupInfo
748  kKRBUsernameKey                   : CFStringRef
749  kKRBKeychainAccountNameKey           : CFStringRef
750  kKRBDisableSaveToKeychainKey  : CFBooleanRef
751
752*/
753OSStatus KRBCopyKeychainLookupInfo (KRBHelperContextRef inKerberosSession, CFStringRef inUsername, CFDictionaryRef *outKeychainLookupInfo)
754{
755    OSStatus                err = noErr;
756    KRBhelperContext        *hCtx = (KRBhelperContext *)inKerberosSession;
757    CFMutableDictionaryRef  outInfo = NULL;
758    CFStringRef             accountName = NULL;
759
760    if (NULL == inKerberosSession || NULL == outKeychainLookupInfo) { err = paramErr; goto Error; }
761
762    *outKeychainLookupInfo = NULL;
763
764    outInfo = CFDictionaryCreateMutable (kCFAllocatorDefault, /* hint */ 3, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
765
766    if (NULL == outInfo) { err = memFullErr; goto Error; }
767
768    KHLog ("%s", "[[[ KRBCopyKeychainLookupInfo () - required parameters okay");
769
770    if (NULL != inUsername) {
771        CFDictionarySetValue (outInfo, kKRBUsernameKey, inUsername);
772        accountName = hCtx->realm;
773    } else {
774        accountName = hCtx->realm;
775    }
776
777    if (NULL != accountName) {
778        CFDictionarySetValue (outInfo, kKRBKeychainAccountNameKey, accountName);
779    }
780
781    /* Can a Kerberos password be saved in the keychain? */
782    CFDictionarySetValue (outInfo, kKRBDisableSaveToKeychainKey, kCFBooleanFalse);
783
784    CFPropertyListRef savePasswordDisabled = CFPreferencesCopyAppValue(CFSTR("SavePasswordDisabled"), kKRBAgentBundleIdentifier);
785
786    if (savePasswordDisabled != NULL) {
787        if (CFGetTypeID(savePasswordDisabled) == CFBooleanGetTypeID() && CFBooleanGetValue(savePasswordDisabled)) {
788            CFDictionarySetValue (outInfo, kKRBDisableSaveToKeychainKey, kCFBooleanTrue);
789            KHLog ("%s", "    KRBCopyKeychainLookupInfo: DisableSaveToKeychainKey = TRUE");
790        }
791        CFRelease(savePasswordDisabled);
792    } else {
793        KHLog ("%s", "    KRBCopyKeychainLookupInfo: CFPreferencesCopyAppValue == NULL");
794    }
795
796    *outKeychainLookupInfo = outInfo;
797 Error:
798    KHLog ("]]] KRBCopyKeychainLookupInfo () = %d", (int)err);
799
800    return err;
801}
802
803/*
804  KRBCopyServicePrincipal will return the service principal for a service on the host given inInstance.
805  inKerberosSession is the pointer returned by KRBCreateSession
806  inServiceName is the name of the service on the host, it can be NULL if inAdvertisedPrincipal was non-NULL.
807  However it is highly recommended that this be set as it is insecure to rely on remotely provided information
808  outServicePrincipal the service principal
809*/
810OSStatus KRBCopyServicePrincipal (KRBHelperContextRef inKerberosSession, CFStringRef inServiceName, CFStringRef *outServicePrincipal)
811{
812    CFDictionaryRef outDict = NULL;
813    CFStringRef outSPN;
814    OSStatus            err;
815
816    KHLog ("%s", "KRBCopyServicePrincipal () - enter");
817
818    if (NULL == inKerberosSession || NULL == outServicePrincipal)
819	return paramErr;
820
821    *outServicePrincipal = NULL;
822
823    err = KRBCopyServicePrincipalInfo(inKerberosSession,
824				      inServiceName,
825				      &outDict);
826    if (err)
827	return err;
828
829    outSPN = CFDictionaryGetValue(outDict, kKRBServicePrincipalKey);
830
831    *outServicePrincipal = CFRetain(outSPN);
832    CFRelease(outDict);
833
834    KHLog ("%s", "KRBCopyServicePrincipal () - return");
835
836    return noErr;
837}
838
839
840OSStatus
841KRBCopyServicePrincipalInfo (KRBHelperContextRef inKerberosSession, CFStringRef inServiceName, CFDictionaryRef *outServiceInfo)
842{
843    KRBhelperContext    *hCtx = (KRBhelperContext *)inKerberosSession;
844    CFMutableDictionaryRef outInfo = NULL;
845    CFStringRef outString = NULL;
846    OSStatus            err = noErr;
847
848    if (NULL == hCtx || NULL == outServiceInfo || NULL == inServiceName)
849	return paramErr;
850
851    *outServiceInfo = NULL;
852
853    KHLog ("%s", "[[[ KRBCopyServicePrincipalInfo () - required parameters okay");
854
855    outString = CFStringCreateWithFormat (NULL, NULL, CFSTR("%@/%@@%@"), inServiceName, hCtx->hostname, hCtx->realm);
856    if (outString == NULL) {
857	err = memFullErr;
858	goto out;
859    }
860
861    outInfo = CFDictionaryCreateMutable (kCFAllocatorDefault, 2, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
862    if (outInfo == NULL) {
863	err = memFullErr;
864	goto out;
865    }
866
867    CFDictionarySetValue (outInfo, kKRBServicePrincipalKey, outString);
868    if (hCtx->noGuessing)
869	CFDictionarySetValue (outInfo, kKRBNoCanonKey, CFSTR("nodns"));
870
871
872    {
873	char *spn;
874	__KRBCreateUTF8StringFromCFString (outString, &spn);
875	KHLog ("    KRBCopyServicePrincipalInfo: principal = \"%s\"", spn);
876	__KRBReleaseUTF8String (spn);
877    }
878
879    *outServiceInfo = outInfo;
880
881 out:
882    if (outString) CFRelease(outString);
883
884    KHLog ("]]] KRBCopyServicePrincipalInfo () = %d", (int)err);
885
886    return err;
887}
888
889static CFTypeRef
890search_array(CFArrayRef array, CFTypeRef key)
891{
892    CFDictionaryRef dict;
893    CFIndex n;
894
895    for (n = 0 ; n < CFArrayGetCount(array); n++) {
896	dict = CFArrayGetValueAtIndex(array, n);
897	if (CFGetTypeID(dict) != CFDictionaryGetTypeID())
898	    continue;
899	CFTypeRef dictkey = CFDictionaryGetValue(dict, kSecPropertyKeyLabel);
900	if (CFEqual(dictkey, key))
901	    return CFDictionaryGetValue(dict, kSecPropertyKeyValue);
902    }
903    return NULL;
904}
905
906/*
907  KRBCopyClientPrincipalInfo will return a dictionary with the user principal and other information.
908  inKerberosSession is the pointer returned by KRBCreateSession.
909  inOptions a dictionary with options regarding the acquisition of the user principal.
910  inIdentityRef is a reference to list of usable identities
911  outClientPrincipalInfo a dictionary containing the user principal and other information necessary to get a ticket.
912
913  inOptions Dictionary Keys
914  kKRBAllowKerberosUIKey            : CFStringRef [See AllowKeberosUI values]
915  kKRBServerDisplayNameKey      : CFStringRef
916  kKRBUsernameKey                   : CFStringRef
917  kKRBClientPasswordKey         : CFStringRef
918  kKRBCertificateKey                : SecCertificateRef
919
920  outClientPrincipalInfo
921  kKRBClientPrincipalKey            : CFStringRef
922  kKRBUsernameKey                   : CFStringRef
923  and private information
924*/
925
926OSStatus KRBCopyClientPrincipalInfo (KRBHelperContextRef inKerberosSession,  CFDictionaryRef inOptions, CFDictionaryRef *outClientPrincipalInfo)
927{
928    OSStatus            err = noErr;
929    KRBhelperContext    *hCtx = (KRBhelperContext *)inKerberosSession;
930
931    CFIndex                 newCount;
932    CFMutableDictionaryRef  outInfo = NULL;
933    CFStringRef             clientPrincipal = NULL;
934    char                    *clientPrincipalString = NULL;
935    char                    *useRealm = NULL;
936    CFStringRef             useClientName = NULL;
937    CFStringRef             inferredLabel = NULL;
938    SecCertificateRef certRef = NULL;
939    CFStringRef             certificateHash = NULL;
940    char *cert_hash = NULL;
941    int usingCertificate = 0;
942    int clientNameProvided = 0;
943
944    if (NULL == hCtx || NULL == outClientPrincipalInfo) { err = paramErr; goto Error; }
945    *outClientPrincipalInfo = NULL;
946
947    KHLog ("%s", "[[[ KRBCopyClientPrincipalInfo () - required parameters okay");
948
949    if (NULL != inOptions) {
950        /* Figure out the maximum expansion we'll make */
951        newCount = CFDictionaryGetCount (inOptions) + 3;
952
953        /* Create a mutable copy of the Dictionary. */
954        outInfo = CFDictionaryCreateMutableCopy (kCFAllocatorDefault, newCount, inOptions);
955
956        /* Extract the certRef if there was an input dictionary */
957        CFDictionaryGetValueIfPresent (inOptions, kKRBCertificateKey, (const void **)&certRef);
958
959        if (NULL != certRef) {
960            KHLog ("%s", "    KRBCopyClientPrincipalInfo: Certificate information in dictionary");
961        } else {
962            KHLog ("%s", "    KRBCopyClientPrincipalInfo: Certificate not present in dictionary");
963        }
964    } else {
965        outInfo = CFDictionaryCreateMutable (kCFAllocatorDefault, 3, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
966    }
967
968    if (NULL != certRef && SecCertificateGetTypeID() == CFGetTypeID (certRef)) {
969	const CFStringRef dotmac = CFSTR(".Mac Sharing Certificate");
970	const CFStringRef mobileMe = CFSTR("MobileMe Sharing Certificate");
971
972	useClientName = NAHCopyMMeUserNameFromCertificate(certRef);
973	if (useClientName == NULL) { goto Error; }
974
975        usingCertificate = 1;
976
977        if (NULL != useClientName) {
978            certificateHash = CFRetain (useClientName);
979        }
980
981	void *values[4] = { (void *)kSecOIDDescription, (void *)kSecOIDCommonName, (void *)kSecOIDOrganizationalUnitName, (void *)kSecOIDX509V1SubjectName };
982	CFArrayRef attrs = CFArrayCreate(NULL, (const void **)values, sizeof(values) / sizeof(values[0]), &kCFTypeArrayCallBacks);
983	if (NULL == attrs) { goto Error; }
984
985	CFDictionaryRef certval = SecCertificateCopyValues(certRef, attrs, NULL);
986	CFRelease(attrs);
987	if (NULL == certval) { goto Error; }
988
989	err = 0;
990
991	CFDictionaryRef subject = CFDictionaryGetValue(certval, kSecOIDX509V1SubjectName);
992	if (NULL != subject) {
993
994	    CFArrayRef val = CFDictionaryGetValue(subject, kSecPropertyKeyValue);
995
996	    if (NULL != val) {
997
998		CFStringRef description = search_array(val, kSecOIDDescription);
999
1000		if (NULL != description &&
1001		    (kCFCompareEqualTo == CFStringCompare(description, dotmac, 0) || kCFCompareEqualTo == CFStringCompare(description, mobileMe, 0)))
1002		{
1003		    CFStringRef commonName = search_array(val, kSecOIDCommonName);
1004		    CFStringRef organizationalUnit = search_array(val, kSecOIDOrganizationalUnitName);
1005
1006		    if (NULL != commonName && NULL != organizationalUnit) {
1007			inferredLabel = CFStringCreateWithFormat (NULL, NULL, CFSTR("%@@%@"), commonName, organizationalUnit);
1008		    }
1009		}
1010	    }
1011	}
1012
1013	CFRelease(certval);
1014
1015	if (NULL == inferredLabel)
1016	    err = SecCertificateInferLabel (certRef, &inferredLabel);
1017
1018        if (0 != err) { goto Error; }
1019
1020    } else if (NULL != inOptions) {
1021        CFDictionaryGetValueIfPresent (inOptions, kKRBUsernameKey, (const void **)&useClientName);
1022        if (NULL != useClientName) {
1023            CFRetain(useClientName);
1024            clientNameProvided = 1;
1025        }
1026    }
1027
1028    /* The ultimate fallback is to use the same username as we have locally */
1029    if (NULL == useClientName) {
1030        char *clientName = getlogin ();
1031
1032        if (NULL != clientName) {
1033            useClientName = CFStringCreateWithCString (NULL, clientName, kCFStringEncodingUTF8);
1034            /* free (clientName); */
1035            KHLog ("    KRBCopyClientPrincipalInfo: Using login name = \"%s\"", clientName);
1036        }
1037    }
1038
1039    /*
1040     * Check to see if the defaultRealm is heirarchically related to the realm used for
1041     * the service principal.
1042     */
1043    if (NULL != hCtx->useRealm && NULL != hCtx->defaultRealm) {
1044        size_t  useRealmLength = strlen (hCtx->useRealm);
1045        size_t  defaultRealmLength = strlen (hCtx->defaultRealm);
1046
1047        if (defaultRealmLength < useRealmLength) {
1048            char *subRealm = hCtx->useRealm + (useRealmLength - defaultRealmLength);
1049            // printf ("defaultRealm = %s, subRealm = %s\n", hCtx->defaultRealm, subRealm);
1050            if (0 == strcmp (subRealm, hCtx->defaultRealm)) {
1051                useRealm = hCtx->defaultRealm;
1052            }
1053        }
1054    }
1055
1056    if (NULL != useRealm) {
1057        clientPrincipal = CFStringCreateWithFormat (NULL, NULL, CFSTR("%@@%s"), useClientName, useRealm);
1058    } else {
1059        clientPrincipal = CFStringCreateWithFormat (NULL, NULL, CFSTR("%@@%@"), useClientName, hCtx->realm);
1060    }
1061
1062    if (NULL != clientPrincipal) {
1063        __KRBCreateUTF8StringFromCFString (clientPrincipal, &clientPrincipalString);
1064        KHLog ("    KRBCopyClientPrincipalInfo: principal guess = \"%s\"", clientPrincipalString);
1065    }
1066
1067    /*
1068     * Look in the ccache for a TGT from a REALM that matches "useRealm".  If we find one, then
1069     * use that principal.  Also extract the "Username" portion just in
1070     * case a password is needed (the ticket may have expired).
1071     */
1072    if (NULL != clientPrincipalString) {
1073        krb5_error_code krb_err = 0;
1074	krb5_cccol_cursor cursor;
1075        const char *alternateRealm = NULL;
1076        const char *clientRealm = principal_realm(clientPrincipalString);
1077        char        *alternateClientPrincipal = NULL;
1078        char        *bestClientPrincipal = NULL;
1079        int commonSubrealms = 1, tmp, found;
1080
1081	found = 0;
1082
1083	krb_err = krb5_cccol_cursor_new (hCtx->krb5_ctx, &cursor);
1084	if (0 != krb_err) {
1085	    krb_err = memFullErr;
1086	}
1087
1088	/* We exit if we find more than one match */
1089        while (!krb_err && !found) {
1090            krb5_ccache ccache = NULL;
1091            krb5_principal ccachePrinc = NULL;
1092
1093	    if (krb5_cccol_cursor_next (hCtx->krb5_ctx, cursor, &ccache) != 0 || ccache == NULL)
1094		krb_err = ENOENT;
1095
1096            if (!krb_err) { krb_err = k5_ok (krb5_cc_get_principal (hCtx->krb5_ctx, ccache, &ccachePrinc)); }
1097            if (!krb_err) { krb_err = k5_ok (krb5_unparse_name (hCtx->krb5_ctx, ccachePrinc, &alternateClientPrincipal)); }
1098
1099            if (!krb_err) { alternateRealm = principal_realm(alternateClientPrincipal); }
1100
1101            /* If the client principal realm and the service principal
1102             * realm are an exact match or have a common subrealm with
1103             * multiple components, then in most cases it will be the
1104             * one to use.  If there are multiple matches, choose the
1105             * one with the largest number of common components.  The
1106             * commonSubrealms variable keeps track of the number of
1107             * components matched in the best match found so far.
1108             * Because it was initialized to 1 earlier, a realm must
1109             * have at least two common components to be considered.
1110             */
1111            if (!krb_err && (0 == (tmp = strcmp(clientRealm, alternateRealm)) ||
1112                             commonSubrealms < (tmp = has_common_subrealm(clientRealm, alternateRealm)))) {
1113                if (NULL != bestClientPrincipal) { free(bestClientPrincipal); }
1114                bestClientPrincipal = strdup(alternateClientPrincipal);
1115                if (0 == tmp) {
1116                    /* Exact match (strcmp set tmp to 0.)
1117                     * Exit the loop.
1118                     */
1119                    found = 1;
1120                } else {
1121                    /* Inexact match (has_common_subrealm set tmp nonzero.)
1122                     * Keep track of the number of components matched.
1123                     */
1124                    commonSubrealms = tmp;
1125                }
1126            }
1127
1128            if (NULL != alternateClientPrincipal) {
1129                krb5_xfree(alternateClientPrincipal);
1130                alternateClientPrincipal = NULL;
1131            }
1132            if (NULL != ccache)      { krb5_cc_close (hCtx->krb5_ctx, ccache); }
1133            if (NULL != ccachePrinc) { krb5_free_principal (hCtx->krb5_ctx, ccachePrinc); }
1134        }
1135
1136        if (NULL != cursor)   { krb5_cccol_cursor_free(hCtx->krb5_ctx, &cursor);
1137	}
1138
1139        if (NULL != bestClientPrincipal) {
1140            KHLog ("    KRBCopyClientPrincipalInfo: ccache principal match = \"%s\"", bestClientPrincipal);
1141        }
1142
1143        /* We only accept the principal when it doesn't match the one we computed.
1144         */
1145        if (NULL != bestClientPrincipal && !clientNameProvided &&
1146            0 != strcmp(clientPrincipalString, bestClientPrincipal)) {
1147            char *useClientNameString = NULL, *startOfRealm;
1148
1149            KHLog ("%s", "    KRBCopyClientPrincipalInfo: found a single ticket for realm, replacing principal & username");
1150            usingCertificate = 0;
1151
1152            if (NULL != clientPrincipal) { CFRelease (clientPrincipal); }
1153            if (NULL != clientPrincipalString) { __KRBReleaseUTF8String (clientPrincipalString); }
1154
1155            clientPrincipal = CFStringCreateWithCString (NULL, bestClientPrincipal, kCFStringEncodingASCII);
1156            clientPrincipalString = strdup (bestClientPrincipal);
1157
1158            /* Extract the "Username" from the principal */
1159            useClientNameString = strdup (bestClientPrincipal);
1160
1161            /* This is an ugly loop.  It does the reverse of krb5_unparse_name () in that it searches from the
1162             * end of the principal looking for an unquoted "@".  This is a guaranteed way to strip the realm from
1163             * a principal with the smallest number of lines and the minimum amount of "special" quoting logic
1164             * and knowledge.  The alternative would have been to copy the first part of krb5_unparse_name () and
1165             * quoting all of the "special" characters and skipping the last part which appends the realm - this
1166             * takes 95 lines of code in the MIT Kerberos library.
1167             */
1168
1169            do {
1170                /* This will be NULL, the last '@' char or the 2nd character of useClientNameString */
1171                startOfRealm = strrchr (&useClientNameString[1], '@');
1172
1173                if (NULL != startOfRealm) {
1174                    /* Is it an escaped realm character? */
1175                    if ('\\' == *(startOfRealm - 1)) {
1176                        *(startOfRealm - 1) = '\0';
1177                    } else {
1178                        *startOfRealm = '\0';
1179                        break;
1180                    }
1181                }
1182            } while (NULL != startOfRealm);
1183
1184            if (NULL != useClientName) { CFRelease (useClientName); }
1185
1186            useClientName = CFStringCreateWithCString (NULL, useClientNameString, kCFStringEncodingUTF8);
1187
1188            KHLog ("    KRBCopyClientPrincipalInfo: Setting found Username to = \"%s\"", useClientNameString);
1189
1190            if (NULL != useClientNameString) { free (useClientNameString); }
1191        }
1192
1193        if (NULL != bestClientPrincipal)      { free (bestClientPrincipal); }
1194        if (NULL != alternateClientPrincipal) { krb5_xfree(alternateClientPrincipal); }
1195    }
1196
1197    KHLog ("    KRBCopyClientPrincipalInfo: using principal = \"%s\"", clientPrincipalString);
1198
1199    CFDictionarySetValue (outInfo, kKRBClientPrincipalKey, clientPrincipal);
1200    CFDictionarySetValue (outInfo, kKRBUsernameKey, useClientName);
1201
1202    KHLog ("    KRBCopyClientPrincipalInfo: usingCertificate == %d", usingCertificate);
1203    if (usingCertificate && NULL != certRef) {
1204        CFDictionarySetValue (outInfo, kKRBUsingCertificateKey, certRef);
1205
1206        if (NULL != certificateHash)
1207            CFDictionarySetValue (outInfo, kKRBCertificateHashKey, certificateHash);
1208
1209        if (NULL != inferredLabel) {
1210            CFDictionarySetValue (outInfo, kKRBCertificateInferredLabelKey, inferredLabel);
1211
1212            char *inferredLabelString = NULL;
1213            __KRBCreateUTF8StringFromCFString (inferredLabel, &inferredLabelString);
1214            KHLog ("    KRBCopyClientPrincipalInfo: InferredLabel = \"%s\"", inferredLabelString);
1215            if (NULL != inferredLabelString) { __KRBReleaseUTF8String (inferredLabelString); }
1216        }
1217    }
1218
1219    *outClientPrincipalInfo = outInfo;
1220
1221 Error:
1222    if (NULL != useClientName)   { CFRelease (useClientName); }
1223    if (NULL != certificateHash) { CFRelease (certificateHash); }
1224    if (NULL != inferredLabel)   { CFRelease (inferredLabel); }
1225    if (NULL != cert_hash)       { free (cert_hash); }
1226    if (NULL != clientPrincipal) { CFRelease (clientPrincipal); }
1227    if (NULL != clientPrincipalString) { __KRBReleaseUTF8String (clientPrincipalString); }
1228
1229    KHLog ("]]] KRBCopyClientPrincipalInfo () = %d", (int)err);
1230
1231    return err;
1232}
1233
1234/*
1235  KRBTestForExistingTicket will look for an existing ticket in the
1236  ccache.  This call looks for a principal that matches the principal
1237  stored in the outClientPrincipalInfo dictionary fom the
1238  KRBCopyClientPrincipalInfo call.
1239  This call should be performed before prompting the user to enter credential
1240  information.
1241  inKerberosSession is the pointer returned by KRBCreateSession
1242  inClientPrincipalInfo the dictionary containing the
1243  kKRBClientPrincipalKey.
1244*/
1245OSStatus KRBTestForExistingTicket (KRBHelperContextRef inKerberosSession, CFDictionaryRef inClientPrincipalInfo)
1246{
1247    OSStatus            err = noErr;
1248    KRBhelperContext    *hCtx = (KRBhelperContext *)inKerberosSession;
1249    CFStringRef     clientPrincipal = NULL;
1250    char            *principalString = NULL;
1251
1252    if (NULL == hCtx || NULL == inClientPrincipalInfo) { err = paramErr; goto Done; }
1253
1254    KHLog ("%s", "[[[ KRBTestForExistingTicket () - required parameters okay");
1255
1256    CFDictionaryGetValueIfPresent (inClientPrincipalInfo, kKRBClientPrincipalKey, (const void **)&clientPrincipal);
1257
1258    if (NULL != clientPrincipal) {
1259        krb5_error_code    krb_err = 0;
1260        krb5_principal principal = NULL;
1261	krb5_ccache ccache = NULL;
1262	time_t lifetime;
1263
1264	err = ENOENT;
1265
1266        __KRBCreateUTF8StringFromCFString (clientPrincipal, &principalString);
1267        KHLog ("    KRBTestForExistingTicket: principal = \"%s\"", principalString);
1268
1269	krb_err = krb5_parse_name(hCtx->krb5_ctx, principalString, &principal);
1270	if (0 == krb_err) {
1271	    krb_err = krb5_cc_cache_match(hCtx->krb5_ctx, principal, &ccache);
1272	    if (krb_err == 0) {
1273		krb_err = krb5_cc_get_lifetime(hCtx->krb5_ctx, ccache, &lifetime);
1274		if (krb_err == 0 && lifetime > 60) {
1275		    KHLog ("    KRBTestForExistingTicket: Valid Ticket, ccacheName = \"%s\"", krb5_cc_get_name(hCtx->krb5_ctx, ccache));
1276		    err = 0;
1277		}
1278	    }
1279	}
1280
1281	if (NULL != ccache) { krb5_cc_close(hCtx->krb5_ctx, ccache); }
1282	if (NULL != principalString) { __KRBReleaseUTF8String (principalString); }
1283        if (NULL != principal)  { krb5_free_principal(hCtx->krb5_ctx, principal); }
1284    }
1285
1286 Done:
1287    KHLog ("]]] KRBTestForExistingTicket () = %d", (int)err);
1288
1289    return err;
1290}
1291
1292/*
1293  KRBAcquireTicket will acquire a ticket for the user.
1294  inKerberosSession is the pointer returned by KRBCreateSession.
1295  inClientPrincipalInfo is the outClientPrincipalInfo dictionary from KRBCopyClientPrincipalInfo.
1296*/
1297OSStatus KRBAcquireTicket(KRBHelperContextRef inKerberosSession, CFDictionaryRef inClientPrincipalInfo)
1298{
1299    OSStatus            err = noErr;
1300    KRBhelperContext    *hCtx = (KRBhelperContext *)inKerberosSession;
1301    krb5_error_code            krb_err = 0;
1302    krb5_principal      clientPrincipal = NULL;
1303    CFStringRef         principal = NULL, password = NULL;
1304    char                *principalString = NULL, *passwordString = NULL;
1305    SecIdentityRef      usingCertificate = NULL;
1306    CFStringRef		    inferredLabel = NULL;
1307    krb5_get_init_creds_opt *opt;
1308    krb5_ccache id = NULL;
1309    krb5_creds cred;
1310    int destroy_cache = 0;
1311    krb5_init_creds_context icc = NULL;
1312
1313    memset(&cred, 0, sizeof(cred));
1314
1315    if (NULL == hCtx) {
1316	KHLog ("%s", "[[[ KRBAcquireTicket () - no context will raise() in the future");
1317	return paramErr;
1318    }
1319
1320    KHLog ("%s", "[[[ KRBAcquireTicket () - required parameters okay");
1321
1322    principal = CFDictionaryGetValue (inClientPrincipalInfo, kKRBClientPrincipalKey);
1323    if (NULL == principal) { err = paramErr; goto Error; }
1324    __KRBCreateUTF8StringFromCFString (principal, &principalString);
1325
1326    krb_err = krb5_parse_name(hCtx->krb5_ctx, principalString, &clientPrincipal);
1327    if (krb_err) {
1328	err = paramErr; goto Error;
1329    }
1330
1331    CFDictionaryGetValueIfPresent (inClientPrincipalInfo, kKRBUsingCertificateKey, (const void **)&usingCertificate);
1332
1333    krb_err = k5_ok(krb5_get_init_creds_opt_alloc (hCtx->krb5_ctx, &opt));
1334    if (krb_err) {
1335	err = paramErr; goto Error;
1336    }
1337
1338    if (usingCertificate) {
1339	krb_err = k5_ok(krb5_get_init_creds_opt_set_pkinit(hCtx->krb5_ctx, opt, clientPrincipal,
1340							   NULL, "KEYCHAIN:",
1341							   NULL, NULL, 0,
1342							   NULL, NULL, NULL));
1343	KHLog ("    KRBAcquireTicket: using a cert, GIC set pkinit returned: %d", krb_err);
1344	if (krb_err) {
1345	    err = paramErr; goto Error;
1346	}
1347    }
1348
1349    krb_err = krb5_init_creds_init(hCtx->krb5_ctx, clientPrincipal, NULL, NULL,
1350				   0, opt, &icc);
1351    if (krb_err) {
1352	err = paramErr; goto Error;
1353    }
1354
1355    if (is_lkdc_realm(krb5_principal_get_realm(hCtx->krb5_ctx, clientPrincipal))) {
1356	char *hostname;
1357
1358	__KRBCreateUTF8StringFromCFString (hCtx->inHostName, &hostname);
1359	krb5_init_creds_set_kdc_hostname(hCtx->krb5_ctx, icc, hostname);
1360	free(hostname);
1361    }
1362
1363
1364    if (NULL != usingCertificate) {
1365	CFStringRef certInferredLabel;
1366	hx509_cert cert;
1367
1368	krb_err = hx509_cert_init_SecFramework(hCtx->hx_ctx, usingCertificate, &cert);
1369	if (krb_err) {
1370	    err = paramErr; goto Error;
1371	}
1372
1373	krb_err = krb5_init_creds_set_pkinit_client_cert(hCtx->krb5_ctx, icc, cert);
1374	if (krb_err) {
1375	    err = paramErr; goto Error;
1376	}
1377
1378	passwordString = NULL;
1379
1380	certInferredLabel = CFDictionaryGetValue (inClientPrincipalInfo, kKRBCertificateInferredLabelKey);
1381	if (certInferredLabel)
1382	    inferredLabel = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%@)"),
1383						     certInferredLabel, hCtx->inHostName);
1384    } else {
1385
1386	CFStringRef clientName;
1387
1388	KHLog ("    %s: Not using certificate", __func__);
1389
1390        password  = CFDictionaryGetValue (inClientPrincipalInfo, kKRBClientPasswordKey);
1391        if (NULL == password) {
1392	    KHLog ("    %s: No password, cant get tickets", __func__);
1393	    err = paramErr; goto Error;
1394	}
1395
1396	clientName = CFDictionaryGetValue(inClientPrincipalInfo, kKRBUsernameKey);
1397	if (clientName)
1398	    inferredLabel = CFStringCreateWithFormat(NULL, NULL, CFSTR("LKDC %@@%@"), clientName, hCtx->inHostName);
1399
1400	__KRBCreateUTF8StringFromCFString (password, &passwordString);
1401
1402	krb_err = krb5_init_creds_set_password(hCtx->krb5_ctx, icc, passwordString);
1403	if (krb_err) {
1404	    err = paramErr; goto Error;
1405	}
1406    }
1407
1408    krb_err = krb5_init_creds_get(hCtx->krb5_ctx, icc);
1409    krb5_get_init_creds_opt_free(hCtx->krb5_ctx, opt);
1410    KHLog ("   %s: krb5_get_init_creds_password: %d", __func__, krb_err);
1411    if (krb_err != 0) {
1412	err = paramErr; goto Error;
1413    }
1414
1415    krb_err = krb5_init_creds_get_creds(hCtx->krb5_ctx, icc, &cred);
1416    if (krb_err != 0) {
1417	err = paramErr; goto Error;
1418    }
1419
1420    krb_err = krb5_cc_cache_match(hCtx->krb5_ctx, clientPrincipal, &id);
1421    if (krb_err) {
1422	krb_err = krb5_cc_new_unique(hCtx->krb5_ctx, NULL, NULL, &id);
1423	if (krb_err) {
1424	    err = paramErr; goto Error;
1425	}
1426	destroy_cache = 1;
1427    }
1428
1429    krb_err = krb5_cc_initialize(hCtx->krb5_ctx, id, clientPrincipal);
1430    if (krb_err) {
1431	err = paramErr; goto Error;
1432    }
1433
1434    krb_err = krb5_cc_store_cred(hCtx->krb5_ctx, id, &cred);
1435    if (krb_err) {
1436	err = paramErr; goto Error;
1437    }
1438
1439    krb_err = krb5_init_creds_store_config(hCtx->krb5_ctx, icc, id);
1440    if (krb_err) {
1441	err = paramErr; goto Error;
1442    }
1443
1444    KRBCredAddReference(principal);
1445
1446    if (inferredLabel) {
1447	char *label = NULL;
1448
1449	KHLog ("%s", "    KRBAcquireTicket setting friendly name");
1450
1451	if (__KRBCreateUTF8StringFromCFString (inferredLabel, &label) == noErr) {
1452	    krb5_data data;
1453	    data.data = label;
1454	    data.length = strlen(label) + 1;
1455
1456	    krb5_cc_set_config(hCtx->krb5_ctx, id, NULL, "FriendlyName", &data);
1457	    free(label);
1458	}
1459    }
1460    {
1461	krb5_data data;
1462	data.data = "1";
1463	data.length = 1;
1464	krb5_cc_set_config(hCtx->krb5_ctx, id, NULL, "nah-created", &data);
1465    }
1466
1467    err = noErr;
1468
1469 Error:
1470    if (icc)
1471	krb5_init_creds_free(hCtx->krb5_ctx, icc);
1472    if (id) {
1473	if (err != noErr && destroy_cache)
1474	    krb5_cc_close(hCtx->krb5_ctx, id);
1475	else
1476	    krb5_cc_close(hCtx->krb5_ctx, id);
1477    }
1478    krb5_free_cred_contents(hCtx->krb5_ctx, &cred);
1479    if (NULL != inferredLabel) { CFRelease(inferredLabel); }
1480    if (NULL != principalString) { __KRBReleaseUTF8String (principalString); }
1481    if (NULL != passwordString) {  __KRBReleaseUTF8String (passwordString); }
1482
1483    if (NULL != clientPrincipal) { krb5_free_principal(hCtx->krb5_ctx, clientPrincipal); }
1484
1485    KHLog ("]]] KRBAcquireTicket () = %d", (int)err);
1486
1487    return err;
1488}
1489
1490
1491/*
1492  KRBCloseSession will release the kerberos session
1493  inKerberosSession is the pointer returned by KRBCreateSession.
1494*/
1495OSStatus KRBCloseSession(KRBHelperContextRef inKerberosSession)
1496{
1497    OSStatus            err = noErr;
1498    KRBhelperContext    *hCtx = (KRBhelperContext *)inKerberosSession;
1499    size_t i;
1500
1501    if (NULL == hCtx) { err = paramErr; goto Error; }
1502
1503    KHLog ("%s", "[[[ KRBCloseSession () - required parameters okay");
1504
1505    for (i = 0; i < hCtx->realms.len; i++) {
1506	free(hCtx->realms.data[i].hostname);
1507	free(hCtx->realms.data[i].realm);
1508    }
1509    free(hCtx->realms.data);
1510
1511    if (NULL != hCtx->inAdvertisedPrincipal) { CFRelease (hCtx->inAdvertisedPrincipal); }
1512    if (NULL != hCtx->hostname)              { CFRelease(hCtx->hostname); }
1513    if (NULL != hCtx->inHostName)            { CFRelease(hCtx->inHostName); }
1514    if (NULL != hCtx->realm)                 { CFRelease (hCtx->realm); }
1515
1516    if (NULL != hCtx->defaultRealm) { free(hCtx->defaultRealm); }
1517    if (NULL != hCtx->krb5_ctx)     { krb5_free_context (hCtx->krb5_ctx); }
1518    if (NULL != hCtx->hx_ctx)	    { hx509_context_free(&hCtx->hx_ctx); }
1519    if (NULL != hCtx->addr)         { freeaddrinfo(hCtx->addr); }
1520
1521    free(hCtx);
1522 Error:
1523    KHLog ("]]] KRBCloseSession () = %d", (int)err);
1524
1525    return err;
1526}
1527
1528static OSStatus
1529findCred(CFStringRef clientPrincipal, krb5_context context, krb5_ccache *id)
1530{
1531    krb5_principal client;
1532    krb5_error_code kret;
1533    char *str;
1534
1535    if (__KRBCreateUTF8StringFromCFString (clientPrincipal, &str) != noErr)
1536	return memFullErr;
1537
1538    kret = k5_ok(krb5_parse_name(context, str, &client));
1539    free(str);
1540    if (0 != kret)
1541	return memFullErr;
1542
1543    kret = k5_ok(krb5_cc_cache_match(context, client, id));
1544    krb5_free_principal(context, client);
1545    if (0 != kret)
1546	return memFullErr;
1547
1548    return noErr;
1549}
1550
1551OSStatus
1552KRBCredChangeReferenceCount(CFStringRef clientPrincipal, int change, int excl)
1553{
1554    OSStatus ret = 0;
1555    krb5_context kcontext = NULL;
1556    krb5_ccache id = NULL;
1557    krb5_error_code kret;
1558    krb5_data data;
1559
1560    KHLog ("[[[ KRBCredChangeReferenceCount: %d", change);
1561
1562    kret = k5_ok(krb5_init_context(&kcontext));
1563    if (0 != kret) {
1564	ret = memFullErr;
1565	goto out;
1566    }
1567
1568    ret = findCred(clientPrincipal, kcontext, &id);
1569    if (ret != noErr)
1570	goto out;
1571
1572    /* Skip SSO cred-caches */
1573    ret = k5_ok(krb5_cc_get_config(kcontext, id, NULL, "nah-created", &data));
1574    if (ret) {
1575	ret = 0;
1576	goto out;
1577    }
1578    krb5_data_free(&data);
1579
1580    if (change > 0)
1581	ret = krb5_cc_hold(kcontext, id);
1582    else
1583	ret = krb5_cc_unhold(kcontext, id);
1584
1585 out:
1586    if (id)
1587	krb5_cc_close(kcontext, id);
1588
1589    KHLog ("]]] KRBCredChangeReferenceCount: %d", (int)ret);
1590
1591    if (kcontext)
1592	krb5_free_context(kcontext);
1593
1594    return ret;
1595}
1596
1597OSStatus KRBCredAddReference(CFStringRef clientPrincipal)
1598{
1599    return KRBCredChangeReferenceCount(clientPrincipal, 1, 0);
1600}
1601
1602OSStatus KRBCredRemoveReference(CFStringRef clientPrincipal)
1603{
1604    return KRBCredChangeReferenceCount(clientPrincipal, -1, 0);
1605}
1606
1607
1608OSStatus KRBCredAddReferenceAndLabel(CFStringRef clientPrincipal,
1609				     CFStringRef identifier)
1610{
1611    OSStatus ret = 0;
1612    krb5_error_code kret;
1613    krb5_context kcontext = NULL;
1614    krb5_ccache id = NULL;
1615    krb5_data data;
1616    char *label = NULL;
1617
1618    label = NAHCreateRefLabelFromIdentifier(identifier);
1619    if (label == NULL) {
1620	ret = memFullErr;
1621	goto out;
1622    }
1623
1624    KHLog ("%s", "[[[ KRBCredAddReferenceAndLabel");
1625
1626    kret = k5_ok(krb5_init_context(&kcontext));
1627    if (0 != kret) {
1628	ret = memFullErr;
1629	goto out;
1630    }
1631
1632    ret = findCred(clientPrincipal, kcontext, &id);
1633    if (ret != noErr)
1634	goto out;
1635
1636    /* Skip SSO cred-caches */
1637    kret = k5_ok(krb5_cc_get_config(kcontext, id, NULL, "nah-created", &data));
1638    if (kret) {
1639	ret = 0;
1640	goto out;
1641    }
1642    krb5_data_free(&data);
1643
1644    data.data = (void *)"1";
1645    data.length = 1;
1646
1647    kret = krb5_cc_set_config(kcontext, id, NULL, label, &data);
1648    if (kret) {
1649	ret = memFullErr;
1650	goto out;
1651    }
1652
1653    ret = krb5_cc_hold(kcontext, id);
1654    if (ret)
1655	goto out;
1656
1657 out:
1658    KHLog ("]]] KRBCredAddReferenceAndLabel () = %d (label %s)", (int)ret, label);
1659    if (id)
1660	krb5_cc_close(kcontext, id);
1661    if (kcontext)
1662	krb5_free_context(kcontext);
1663    if (label)
1664	free(label);
1665
1666    return ret;
1667}
1668
1669OSStatus
1670KRBCredFindByLabelAndRelease(CFStringRef identifier)
1671{
1672    NAHFindByLabelAndRelease(identifier);
1673    return noErr;
1674}
1675
1676/*
1677 * Parses the initial request from a SMB server and constructs a
1678 * resulting CFDictionaryRef.
1679 *
1680 *
1681 */
1682
1683CFDictionaryRef
1684KRBDecodeNegTokenInit(CFAllocatorRef alloc, CFDataRef data)
1685{
1686    CFMutableDictionaryRef dict = NULL, mechs = NULL;
1687    union {
1688	NegotiationToken rfc2478;
1689	NegotiationTokenWin win;
1690    } u;
1691    int win = 0;
1692    MechTypeList *mechtypes;
1693    char *hintsname = NULL;
1694    gss_buffer_desc input_buffer, output_buffer = { 0, NULL };
1695    OM_uint32 junk;
1696    int ret;
1697
1698    input_buffer.value = (void *)CFDataGetBytePtr(data);
1699    input_buffer.length = CFDataGetLength(data);
1700
1701    junk = gss_decapsulate_token(&input_buffer, GSS_SPNEGO_MECHANISM, &output_buffer);
1702    if (junk)
1703	goto out;
1704
1705    memset(&u, 0, sizeof(u));
1706    ret = decode_NegotiationToken(output_buffer.value, output_buffer.length, &u.rfc2478, NULL);
1707    if (ret == 0) {
1708	if (u.rfc2478.element != choice_NegotiationToken_negTokenInit)
1709	    goto out;
1710
1711	mechtypes = &u.rfc2478.u.negTokenInit.mechTypes;
1712    } else {
1713	win = 1;
1714
1715	memset(&u, 0, sizeof(u));
1716
1717	ret = decode_NegotiationTokenWin(output_buffer.value, output_buffer.length, &u.win, NULL);
1718	if (ret)
1719	    goto out;
1720
1721	if (u.win.element != choice_NegotiationTokenWin_negTokenInit)
1722	    goto out;
1723
1724	mechtypes = &u.win.u.negTokenInit.mechTypes;
1725
1726	if (u.win.u.negTokenInit.negHints && u.win.u.negTokenInit.negHints->hintName)
1727	    hintsname = *(u.win.u.negTokenInit.negHints->hintName);
1728    }
1729
1730    dict = CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1731    if (dict == NULL)
1732	goto out;
1733
1734    if (mechtypes) {
1735	CFDataRef empty;
1736	unsigned n;
1737
1738	mechs = CFDictionaryCreateMutable(alloc, mechtypes->len,
1739					  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1740	if (mechs == NULL)
1741	    goto out;
1742
1743	CFDictionaryAddValue(dict, kSPNEGONegTokenInitMechs, mechs);
1744	CFRelease(mechs); /* dict have a ref */
1745
1746	empty = CFDataCreateWithBytesNoCopy(alloc, NULL, 0, kCFAllocatorNull);
1747	if (empty == NULL)
1748	    goto out;
1749
1750	for (n = 0; n < mechtypes->len; n++) {
1751	    char *str;
1752	    ret = der_print_heim_oid(&mechtypes->val[n], '.', &str);
1753	    if (ret)
1754		continue;
1755
1756	    CFStringRef s = CFStringCreateWithCString(alloc, str, kCFStringEncodingUTF8);
1757	    free(str);
1758	    if (s) {
1759		CFDictionaryAddValue(mechs, s, empty);
1760		CFRelease(s);
1761	    } else {
1762		CFRelease(empty);
1763		CFRelease(dict);
1764		dict = NULL;
1765		goto out;
1766	    }
1767	}
1768	CFRelease(empty);
1769    }
1770    if (hintsname) {
1771	CFStringRef s = CFStringCreateWithCString(alloc, hintsname, kCFStringEncodingUTF8);
1772	if (s) {
1773	    CFDictionaryAddValue(dict, kSPNEGONegTokenInitHintsHostname, s);
1774	    CFRelease(s);
1775	} else {
1776	    CFRelease(dict);
1777	    dict = NULL;
1778	    goto out;
1779	}
1780    }
1781
1782    if (mechs) {
1783	if (CFDictionaryGetValue(mechs, kGSSAPIMechSupportsAppleLKDC))
1784	    CFDictionaryAddValue(dict, KSPNEGOSupportsLKDC, CFSTR("yes"));
1785    }
1786
1787 out:
1788    gss_release_buffer(&junk, &output_buffer);
1789    if (win)
1790	free_NegotiationTokenWin(&u.win);
1791    else
1792	free_NegotiationToken(&u.rfc2478);
1793
1794    return dict;
1795}
1796
1797static CFDictionaryRef
1798CreateNegTokenLegacyMech(CFAllocatorRef alloc,
1799			 CFStringRef mech,
1800			 CFDataRef value,
1801			 bool support_wlkdc)
1802{
1803    CFMutableDictionaryRef dict = NULL;
1804    CFMutableDictionaryRef mechs;
1805
1806    dict = CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1807    if (dict == NULL)
1808	return NULL;
1809
1810    mechs = CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1811    if (mechs == NULL) {
1812	CFRelease(dict);
1813	return NULL;
1814    }
1815
1816    CFDictionaryAddValue(dict, kSPNEGONegTokenInitMechs, mechs);
1817    CFRelease(mechs);
1818
1819    CFDictionaryAddValue(mechs, mech, value);
1820    if (support_wlkdc)
1821	CFDictionaryAddValue(mechs, kGSSAPIMechSupportsAppleLKDC, CFSTR("yes"));
1822
1823    return dict;
1824}
1825
1826
1827/*
1828 * Create a NegToken reference that only contains a Kerberos mech
1829 */
1830
1831CFDictionaryRef
1832KRBCreateNegTokenLegacyKerberos(CFAllocatorRef alloc)
1833{
1834    CFDictionaryRef dict;
1835    CFDataRef empty;
1836
1837    empty = CFDataCreateWithBytesNoCopy(alloc, (void *)"", 0, kCFAllocatorNull);
1838    if (empty == NULL)
1839	return NULL;
1840
1841    dict = CreateNegTokenLegacyMech(alloc, kGSSAPIMechKerberosOID, empty, false);
1842    CFRelease(empty);
1843    return dict;
1844}
1845
1846/*
1847 * Create a NegToken reference that only contains a NTLM mech, also
1848 * hint that this is a raw NTLM (w/o SPNEGO wrappings).
1849 */
1850
1851CFDictionaryRef
1852KRBCreateNegTokenLegacyNTLM(CFAllocatorRef alloc)
1853{
1854    CFDictionaryRef dict;
1855    CFDataRef empty;
1856
1857    empty = CFDataCreateWithBytesNoCopy(alloc, (void *)"raw", 3, kCFAllocatorNull);
1858    if (empty == NULL)
1859	return NULL;
1860
1861    dict = CreateNegTokenLegacyMech(alloc, kGSSAPIMechNTLMOID, empty, false);
1862    if (empty)
1863	CFRelease(empty);
1864    return dict;
1865}
1866
1867/*
1868  ;; Local Variables: **
1869  ;; tab-width: 8 **
1870  ;; c-basic-offset: 4 **
1871  ;; End: **
1872*/
1873