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