1/*
2 * Copyright (c) 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 "NetworkAuthenticationHelperGSS.h"
26#include "KerberosHelper.h"
27#include "KerberosHelperContext.h"
28
29#include <Heimdal/krb5.h>
30#include <Heimdal/hx509.h>
31
32#include <GSS/gssapi.h>
33#include <GSS/gssapi_ntlm.h>
34#include <GSS/gssapi_krb5.h>
35#include <GSS/gssapi_spi.h>
36
37#include <Kernel/gssd/gssd_mach_types.h>
38
39#include <CoreFoundation/CFRuntime.h>
40#include <Security/Security.h>
41#include <Security/SecCertificatePriv.h>
42#include <CommonCrypto/CommonDigest.h>
43
44#include <CoreServices/CoreServices.h>
45#include <CoreServices/CoreServicesPriv.h>
46
47#include <asl.h>
48
49#include "DeconstructServiceName.h"
50#include "utils.h"
51
52static void add_user_selections(NAHRef na);
53
54
55const CFStringRef kNAHServiceAFPServer = CFSTR("afpserver");
56const CFStringRef kNAHServiceCIFSServer = CFSTR("cifs");
57const CFStringRef kNAHServiceHostServer = CFSTR("host");
58const CFStringRef kNAHServiceVNCServer = CFSTR("vnc");
59
60static const char *nah_created = "nah-created";
61
62static bool nah_use_gss_uam = true;
63static bool nah_vnc_support_iakerb = true;
64static dispatch_once_t init_globals;
65
66enum NAHMechType {
67    NO_MECH = 0,
68    GSS_KERBEROS,
69    GSS_KERBEROS_U2U,
70    GSS_KERBEROS_IAKERB,
71    GSS_KERBEROS_PKU2U,
72    GSS_NTLM,
73    GSS_SPNEGO
74};
75
76struct NAHSelectionData {
77    CFRuntimeBase base;
78    NAHRef na;
79    int have_cred;
80
81    enum NAHMechType mech;
82    CFStringRef client;
83    CFStringRef clienttype;
84    CFStringRef server;
85    CFStringRef servertype;
86
87    SecIdentityRef certificate;
88    bool spnego;
89
90    CFStringRef inferredLabel;
91
92    krb5_ccache ccache;
93};
94
95struct NAHData {
96    CFRuntimeBase base;
97    CFAllocatorRef alloc;
98
99    /* Input */
100
101    CFMutableStringRef hostname;
102    CFMutableStringRef lchostname;
103    CFStringRef service;
104    CFStringRef username;
105    CFStringRef specificname;
106    CFDictionaryRef servermechs;
107
108    CFStringRef spnegoServerName;
109
110    /* credentials */
111    CFArrayRef x509identities;
112    CFStringRef password;
113
114    CFArrayRef mechs;
115
116    /* intermediate results */
117    dispatch_queue_t q;
118    dispatch_queue_t bgq;
119    krb5_context context;
120    hx509_context hxctx;
121
122    /* final result */
123
124    CFMutableArrayRef selections;
125};
126
127static void nalog(int level, CFStringRef fmt, ...)
128    __attribute__((format(CFString, 2, 3)));
129
130#define CFRELEASE(x) do { if ((x)) { CFRelease((x)); (x) = NULL; } } while(0)
131
132/*
133 *
134 */
135
136CFStringRef
137NAHCopyMMeUserNameFromCertificate(SecCertificateRef cert)
138{
139    unsigned char digest[CC_SHA1_DIGEST_LENGTH];
140    char str[CC_SHA1_DIGEST_LENGTH * 2 + 1];
141    CFDataRef certData;
142    CC_SHA1_CTX ctx;
143    char *cpOut;
144    unsigned dex;
145
146    certData = SecCertificateCopyData(cert);
147    CFRelease(cert);
148    if (NULL == certData)
149        return NULL;
150
151    CC_SHA1_Init(&ctx);
152    CC_SHA1_Update(&ctx, CFDataGetBytePtr(certData), CFDataGetLength(certData));
153    CC_SHA1_Final(digest, &ctx);
154
155    CFRelease(certData);
156
157    cpOut = str;
158
159    for(dex = 0; dex < CC_SHA1_DIGEST_LENGTH; dex++) {
160	snprintf(cpOut, 3, "%02X", (unsigned)(digest[dex]));
161	cpOut += 2;
162    }
163
164    return CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII);
165}
166
167/*
168 *
169 */
170
171static char *
172cf2cstring(CFStringRef inString)
173{
174    char *string = NULL;
175    CFIndex length;
176
177    string = (char *) CFStringGetCStringPtr(inString, kCFStringEncodingUTF8);
178    if (string)
179	return strdup(string);
180
181    length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(inString),
182					       kCFStringEncodingUTF8) + 1;
183    string = malloc (length);
184    if (string == NULL)
185	return NULL;
186    if (!CFStringGetCString(inString, string, length, kCFStringEncodingUTF8)) {
187	free (string);
188	return NULL;
189    }
190    return string;
191}
192
193/*
194 *
195 */
196
197static void
198nalog(int level, CFStringRef fmt, ...)
199{
200    CFStringRef str;
201    va_list ap;
202    char *s;
203
204    va_start(ap, fmt);
205    str = CFStringCreateWithFormatAndArguments(NULL, 0, fmt, ap);
206    va_end(ap);
207
208    if (str == NULL)
209	return;
210
211    if (__KRBCreateUTF8StringFromCFString (str, &s) == 0) {
212	asl_log(NULL, NULL, level, "%s", s);
213	__KRBReleaseUTF8String(s);
214    }
215    CFRelease(str);
216}
217
218static void
219naselrelease(NAHSelectionRef nasel)
220{
221    CFRELEASE(nasel->client);
222    CFRELEASE(nasel->clienttype);
223    CFRELEASE(nasel->server);
224    CFRELEASE(nasel->servertype);
225    if (nasel->ccache)
226	krb5_cc_close(nasel->na->context, nasel->ccache);
227    CFRELEASE(nasel->certificate);
228    CFRELEASE(nasel->inferredLabel);
229}
230
231static CFStringRef naseldebug(NAHSelectionRef nasel) CF_RETURNS_RETAINED;
232static CFStringRef naselformatting(CFTypeRef cf, CFDictionaryRef formatOptions) CF_RETURNS_RETAINED;
233
234
235
236
237static CFStringRef
238naseldebug(NAHSelectionRef nasel)
239{
240    CFStringRef mech = NAHSelectionGetInfoForKey(nasel, kNAHMechanism);
241    CFStringRef innermech = NAHSelectionGetInfoForKey(nasel, kNAHInnerMechanism);
242
243    return CFStringCreateWithFormat(NULL, NULL,
244				    CFSTR("<NetworkAuthenticationSelection: "
245					  "%@<%@>, %@ %@ spnego: %s>"),
246				    mech, innermech,
247				    nasel->client,
248				    nasel->server,
249				    nasel->spnego ? "yes" : "no");
250}
251
252static CFStringRef
253naselformatting(CFTypeRef cf, CFDictionaryRef formatOptions)
254{
255    return naseldebug((NAHSelectionRef)cf);
256}
257
258const CFStringRef kNAHErrorDomain = CFSTR("com.apple.NetworkAuthenticationHelper");
259
260static bool
261updateError(CFAllocatorRef alloc, CFErrorRef *error, CFIndex errorcode, CFStringRef fmt, ...)
262{
263    const void *keys[1] = { kCFErrorDescriptionKey };
264    void *values[1];
265    va_list va;
266
267    if (error == NULL)
268	return false;
269
270    va_start(va, fmt);
271    values[0] = (void *)CFStringCreateWithFormatAndArguments(alloc, NULL, fmt, va);
272    va_end(va);
273    if (values[0] == NULL) {
274	*error = NULL;
275	return false;
276    }
277
278    nalog(ASL_LEVEL_DEBUG, CFSTR("NAH: error: %@"), values[0]);
279
280    *error = CFErrorCreateWithUserInfoKeysAndValues(alloc, kNAHErrorDomain, errorcode,
281						    (const void * const *)keys,
282						    (const void * const *)values, 1);
283    CFRelease(values[0]);
284
285    return true;
286}
287
288static struct {
289    CFStringRef name;
290    enum NAHMechType mech;
291    gss_OID oid;
292} mechs[] = {
293    { CFSTR("Kerberos"), GSS_KERBEROS, GSS_KRB5_MECHANISM },
294    { CFSTR("KerberosUser2User"), GSS_KERBEROS_U2U, NULL },
295    { CFSTR("PKU2U"), GSS_KERBEROS_PKU2U, GSS_PKU2U_MECHANISM },
296    { CFSTR("IAKerb"), GSS_KERBEROS_IAKERB, GSS_IAKERB_MECHANISM },
297    { CFSTR("NTLM"), GSS_NTLM, GSS_NTLM_MECHANISM },
298    { CFSTR("SPNEGO"), GSS_SPNEGO, GSS_SPNEGO_MECHANISM },
299    { CFSTR("SPENGO"), GSS_SPNEGO, GSS_SPNEGO_MECHANISM }
300};
301
302static enum NAHMechType
303name2mech(CFStringRef name)
304{
305    size_t n;
306
307    if (name == NULL)
308	return NO_MECH;
309
310    for (n = 0; n < sizeof(mechs) / sizeof(mechs[0]); n++) {
311	if (CFStringCompare(mechs[n].name, name, kCFCompareCaseInsensitive) == kCFCompareEqualTo)
312	    return mechs[n].mech;
313    }
314    return NO_MECH;
315}
316
317static gss_OID
318name2oid(CFStringRef name)
319{
320    size_t n;
321
322    if (name == NULL)
323	return NO_MECH;
324
325    for (n = 0; n < sizeof(mechs) / sizeof(mechs[0]); n++) {
326	if (CFStringCompare(mechs[n].name, name, kCFCompareCaseInsensitive) == kCFCompareEqualTo)
327	    return mechs[n].oid;
328    }
329    return NULL;
330}
331
332static CFStringRef
333mech2name(enum NAHMechType mech)
334{
335    size_t n;
336    for (n = 0; n < sizeof(mechs) / sizeof(mechs[0]); n++) {
337	if (mechs[n].mech == mech)
338	    return mechs[n].name;
339    }
340    return NULL;
341}
342
343
344static gss_OID
345mech2oid(enum NAHMechType mech)
346{
347    size_t n;
348    for (n = 0; n < sizeof(mechs) / sizeof(mechs[0]); n++) {
349	if (mechs[n].mech == mech)
350	    return mechs[n].oid;
351    }
352    return GSS_C_NO_OID;
353}
354
355
356static CFTypeID
357NAHSelectionGetTypeID(void)
358{
359    static CFTypeID naselid = _kCFRuntimeNotATypeID;
360    static dispatch_once_t inited;
361
362    dispatch_once(&inited, ^{
363	    static const CFRuntimeClass naselclass = {
364		0,
365		"NetworkAuthenticationSelection",
366		NULL,
367		NULL,
368		(void(*)(CFTypeRef))naselrelease,
369		NULL,
370		NULL,
371		naselformatting,
372		(CFStringRef (*)(CFTypeRef))naseldebug
373	    };
374	    naselid = _CFRuntimeRegisterClass(&naselclass);
375	});
376
377    return naselid;
378}
379
380static NAHSelectionRef
381NAHSelectionAlloc(NAHRef na)
382{
383    CFTypeID id = NAHSelectionGetTypeID();
384    NAHSelectionRef nasel;
385
386    if (id == _kCFRuntimeNotATypeID)
387	return NULL;
388
389    nasel = (NAHSelectionRef)_CFRuntimeCreateInstance(na->alloc, id, sizeof(struct NAHSelectionData) - sizeof(CFRuntimeBase), NULL);
390    if (nasel == NULL)
391	return NULL;
392
393    nasel->na = na;
394    nasel->spnego = true;
395
396    return nasel;
397
398}
399
400static void
401nahrelease(NAHRef na)
402{
403    CFRELEASE(na->hostname);
404    CFRELEASE(na->lchostname);
405    CFRELEASE(na->service);
406    CFRELEASE(na->username);
407    CFRELEASE(na->specificname);
408    CFRELEASE(na->servermechs);
409    CFRELEASE(na->spnegoServerName);
410
411    CFRELEASE(na->x509identities);
412    CFRELEASE(na->password);
413
414    CFRELEASE(na->mechs);
415
416    CFRELEASE(na->selections);
417
418    if (na->q)
419	dispatch_release(na->q);
420    if (na->context)
421	krb5_free_context(na->context);
422    if (na->hxctx)
423	hx509_context_free(&na->hxctx);
424}
425
426static CFTypeID
427NAGetTypeID(void)
428{
429    static CFTypeID naid = _kCFRuntimeNotATypeID;
430    static dispatch_once_t inited;
431
432    dispatch_once(&inited, ^{
433	    static const CFRuntimeClass naclass = {
434		0,
435		"NetworkAuthentication",
436		NULL,
437		NULL,
438		(void(*)(CFTypeRef))nahrelease,
439		NULL,
440		NULL,
441		NULL,
442		NULL
443	    };
444	    naid = _CFRuntimeRegisterClass(&naclass);
445	});
446
447    return naid;
448}
449
450/*
451 *
452 */
453
454static NAHRef
455NAAlloc(CFAllocatorRef alloc)
456{
457    CFTypeID id = NAGetTypeID();
458    NAHRef na;
459
460    if (id == _kCFRuntimeNotATypeID)
461	return NULL;
462
463    na = (NAHRef)_CFRuntimeCreateInstance(alloc, id, sizeof(struct NAHData) - sizeof(CFRuntimeBase), NULL);
464    if (na == NULL)
465	return NULL;
466
467    na->q = dispatch_queue_create("network-authentication", NULL);
468    na->bgq = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
469
470    na->alloc = alloc;
471
472    return na;
473}
474
475static bool
476haveMech(NAHRef na, CFStringRef mech)
477{
478    if (na->servermechs == NULL)
479	return false;
480    if (CFDictionaryGetValue(na->servermechs, mech) != NULL)
481	return true;
482    return false;
483}
484
485/*
486 * Status of selection
487 */
488
489const CFStringRef kNAHSelectionHaveCredential = CFSTR("kNAHSelectionHaveCredential");
490const CFStringRef kNAHSelectionUserPrintable = CFSTR("kNAHSelectionUserPrintable");
491const CFStringRef kNAHClientPrincipal = CFSTR("kNAHClientPrincipal");
492const CFStringRef kNAHServerPrincipal = CFSTR("kNAHServerPrincipal");
493const CFStringRef kNAHMechanism  = CFSTR("kNAHMechanism");
494const CFStringRef kNAHInnerMechanism = CFSTR("kNAHInnerMechanism");
495const CFStringRef kNAHCredentialType  = CFSTR("kNAHCredentialType");
496const CFStringRef kNAHUseSPNEGO  = CFSTR("kNAHUseSPNEGO");
497
498const CFStringRef kNAHClientNameType = CFSTR("kNAHClientNameType");
499const CFStringRef kNAHClientNameTypeGSSD = CFSTR("kNAHClientNameTypeGSSD");
500
501const CFStringRef kNAHServerNameType = CFSTR("kNAHServerNameType");
502const CFStringRef kNAHServerNameTypeGSSD = CFSTR("kNAHServerNameTypeGSSD");
503
504const CFStringRef kNAHNTUsername = CFSTR("kNAHNTUsername");
505const CFStringRef kNAHNTServiceBasedName = CFSTR("kNAHNTServiceBasedName");
506const CFStringRef kNAHNTKRB5PrincipalReferral = CFSTR("kNAHNTKRB5PrincipalReferral");
507const CFStringRef kNAHNTKRB5Principal = CFSTR("kNAHNTKRB5Principal");
508const CFStringRef kNAHNTUUID = CFSTR("kNAHNTUUID");
509
510
511const CFStringRef kNAHInferredLabel = CFSTR("kNAHInferredLabel");
512
513
514/*
515 * Add selection
516 */
517
518enum {
519    USE_SPNEGO = 1,
520    FORCE_ADD = 2
521};
522
523static NAHSelectionRef
524addSelection(NAHRef na,
525	     CFStringRef client,
526	     CFStringRef clienttype,
527	     CFStringRef server,
528	     CFStringRef servertype,
529	     enum NAHMechType mech,
530	     int *duplicate,
531	     unsigned long flags)
532{
533    NAHSelectionRef nasel;
534    int matching;
535    CFIndex n;
536
537    if (clienttype == NULL)
538	clienttype = kNAHNTUsername;
539
540    if (servertype == NULL)
541	servertype = kNAHNTServiceBasedName;
542
543    matching = (flags & FORCE_ADD) || (na->specificname == NULL) || CFStringHasPrefix(client, na->specificname);
544
545    nalog(ASL_LEVEL_DEBUG, CFSTR("addSelection: %@ (%d) %@ %@ %s %s"),
546	  mech2name(mech), (int)mech, client, server, (flags & USE_SPNEGO) ? "SPNEGO" : "raw",
547	  matching ? "matching" : "no-matching");
548
549    /* If no matching, skip this credential */
550    if (matching == 0)
551	return NULL;
552
553    /* check for dups */
554    for (n = 0; n < CFArrayGetCount(na->selections); n++) {
555	nasel = (NAHSelectionRef)CFArrayGetValueAtIndex(na->selections, n);
556	if (nasel->mech != mech)
557	    continue;
558	if (CFStringCompare(nasel->client, client, 0) != kCFCompareEqualTo)
559	    continue;
560	if (nasel->server && server && CFStringCompare(nasel->server, server, 0) != kCFCompareEqualTo)
561	    continue;
562	if (CFStringCompare(nasel->servertype, servertype, 0) != kCFCompareEqualTo)
563	    continue;
564	if (duplicate)
565	    *duplicate = 1;
566	return nasel;
567    }
568    if (duplicate)
569	*duplicate = 0;
570
571    nasel = NAHSelectionAlloc(na);
572    if (nasel == NULL)
573	return NULL;
574
575    nasel->client = CFRetain(client);
576    nasel->server = CFRetain(server);
577    nasel->clienttype = clienttype;
578    CFRetain(clienttype);
579    nasel->servertype = servertype;
580    CFRetain(servertype);
581
582    nasel->mech = mech;
583    nasel->spnego = (flags & USE_SPNEGO) ? true : false;
584
585    CFArrayAppendValue(na->selections, nasel);
586
587    CFRelease(nasel); /* referenced by array */
588
589    return nasel;
590}
591
592static bool
593findUsername(CFAllocatorRef alloc, NAHRef na, CFDictionaryRef info)
594{
595    char *name;
596
597    if (info) {
598	na->username = CFDictionaryGetValue(info, kNAHUserName);
599	if (na->username) {
600	    CFRange range, ur;
601
602	    CFRetain(na->username);
603
604	    if (CFStringFindWithOptions(na->username, CFSTR("@"), CFRangeMake(0, CFStringGetLength(na->username)), 0, &range)) {
605		ur.location = 0;
606		ur.length = range.location;
607		na->specificname = CFStringCreateWithSubstring(na->alloc, na->username, ur);
608	    } else if (CFStringFindWithOptions(na->username, CFSTR("\\"), CFRangeMake(0, CFStringGetLength(na->username)), 0, &range)) {
609		ur.location = range.location + 1;
610		ur.length = CFStringGetLength(na->username) - ur.location;
611		na->specificname = CFStringCreateWithSubstring(na->alloc, na->username, ur);
612	    } else {
613		na->specificname = na->username;
614		CFRetain(na->specificname);
615	    }
616
617	    nalog(ASL_LEVEL_DEBUG, CFSTR("NAH: specific name is: %@ foo"), na->specificname);
618
619	    return true;
620	}
621    }
622
623    name = getlogin();
624    if (name == NULL)
625	return false;
626
627    na->username = CFStringCreateWithCString(alloc, name, kCFStringEncodingUTF8);
628    if (na->username == NULL)
629	return false;
630
631    return true;
632}
633
634/*
635 * Returns true for those hostname that looks like those hostname
636 * where we should use LKDC hostnames instead. This check is only used
637 * for protocol where we don't know wether we should use LKDC or
638 * classic Kerberos.
639 */
640
641static bool
642have_lkdcish_hostname(NAHRef na, bool localIsLKDC)
643{
644    CFMutableStringRef btmmDomain = NULL;
645    CFStringRef btmmDomainData;
646    bool ret = false;
647
648    btmmDomainData = _CSBackToMyMacCopyDomain();
649    if (btmmDomainData) {
650	btmmDomain = CFStringCreateMutableCopy(na->alloc, 0, btmmDomainData);
651	CFRELEASE(btmmDomainData);
652	if (btmmDomain) {
653	    CFStringTrim(btmmDomain, CFSTR("."));
654	    nalog(ASL_LEVEL_DEBUG, CFSTR("using BTMM domain %@"), btmmDomain);
655	}
656    }
657
658
659    if (na->lchostname == NULL) {
660	na->lchostname = CFStringCreateMutableCopy(NULL, 0, na->hostname);
661	if (na->lchostname == NULL) {
662	    CFRELEASE(btmmDomain);
663	    return false;
664	}
665    }
666
667    CFStringLowercase(na->lchostname, CFLocaleGetSystem());
668
669    if (localIsLKDC && CFStringHasSuffix(na->lchostname, CFSTR(".local")))
670	ret = true;
671    else if (btmmDomain && CFStringHasSuffix(na->lchostname, btmmDomain))
672	ret = true;
673
674    CFRELEASE(btmmDomain);
675
676    return ret;
677}
678
679static void
680add_realms(NAHRef na, char **realms, unsigned long flags)
681{
682    CFStringRef u, s;
683    size_t n;
684
685    for (n = 0; realms[n] != NULL; n++) {
686	u = CFStringCreateWithFormat(na->alloc, 0, CFSTR("%@@%s"), na->username, realms[n]);
687	s = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%@@%s"), na->service, na->hostname, realms[n]);
688
689	if (u && s)
690	    addSelection(na, u, kNAHNTKRB5Principal,
691			 s, kNAHNTKRB5PrincipalReferral, GSS_KERBEROS, NULL, flags);
692	CFRELEASE(u);
693	CFRELEASE(s);
694    }
695}
696
697
698static void
699use_classic_kerberos(NAHRef na, unsigned long flags)
700{
701    CFRange range, dr, ur;
702    char **realms, *str;
703    int ret;
704
705    if (have_lkdcish_hostname(na, false))
706	return;
707
708    ret = __KRBCreateUTF8StringFromCFString(na->hostname, &str);
709    if (ret)
710	return;
711
712    /*
713     * If user have @REALM, lets try that out
714     */
715
716    if (CFStringFindWithOptions(na->username, CFSTR("@"), CFRangeMake(0, CFStringGetLength(na->username)), 0, &range)) {
717	CFStringRef domain = NULL, s = NULL;
718	CFMutableStringRef domainm = NULL;
719
720	dr.location = range.location + 1;
721	dr.length = CFStringGetLength(na->username) - dr.location;
722
723	domain = CFStringCreateWithSubstring(na->alloc, na->username, dr);
724	if (domain) {
725	    domainm = CFStringCreateMutableCopy(na->alloc, 0, domain);
726
727	    if (domainm) {
728		CFStringUppercase(domainm, NULL);
729
730		s = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%@@%@"),
731					     na->service, na->hostname, domainm);
732
733		if (s)
734		    addSelection(na, na->username, kNAHNTKRB5Principal,
735				 s, kNAHNTKRB5PrincipalReferral,  GSS_KERBEROS, NULL, flags);
736	    }
737	}
738	CFRELEASE(domainm);
739	CFRELEASE(domain);
740	CFRELEASE(s);
741    }
742
743    if (CFStringFindWithOptions(na->username, CFSTR("\\"), CFRangeMake(0, CFStringGetLength(na->username)), 0, &range)) {
744	CFStringRef domain = NULL, user = NULL, user2 = NULL, s = NULL;
745	CFMutableStringRef domainm = NULL;
746
747	dr.location = 0;
748	dr.length = range.location;
749
750	ur.location = range.location + 1;
751	ur.length = CFStringGetLength(na->username) - ur.location;
752
753	user = CFStringCreateWithSubstring(na->alloc, na->username, ur);
754	domain = CFStringCreateWithSubstring(na->alloc, na->username, dr);
755	if (domain && user) {
756	    domainm = CFStringCreateMutableCopy(na->alloc, 0, domain);
757	    user2 = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@@%@"), user, domain);
758
759	    if (domainm && user2) {
760		CFStringUppercase(domainm, NULL);
761
762		s = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%@@%@"),
763					     na->service, na->hostname, domainm);
764
765		if (s)
766		    addSelection(na, user2, kNAHNTKRB5Principal,
767				 s, kNAHNTKRB5PrincipalReferral,  GSS_KERBEROS, NULL, flags|FORCE_ADD);
768	    }
769	}
770	CFRELEASE(domainm);
771	CFRELEASE(domain);
772	CFRELEASE(user2);
773	CFRELEASE(user);
774	CFRELEASE(s);
775    }
776
777    /*
778     * Try the host realm
779     */
780
781    ret = krb5_get_host_realm(na->context, str, &realms);
782    __KRBReleaseUTF8String(str);
783    if (ret == 0) {
784	add_realms(na, realms, flags);
785	krb5_free_host_realm(na->context, realms);
786    }
787
788    /*
789     * Also, just for the heck of it, check default realms
790     */
791
792    ret = krb5_get_default_realms(na->context, &realms);
793    if (ret == 0) {
794	add_realms(na, realms, flags);
795	krb5_free_host_realm(na->context, realms);
796    }
797}
798
799/*
800 *
801 */
802
803static void
804use_existing_principals(NAHRef na, int only_lkdc, unsigned long flags)
805{
806    krb5_cccol_cursor cursor;
807    krb5_principal client;
808    krb5_error_code ret;
809    CFStringRef server;
810    krb5_ccache id;
811    CFStringRef u;
812    char *c;
813
814    ret = krb5_cccol_cursor_new(na->context, &cursor);
815    if (ret)
816	return;
817
818    while ((ret = krb5_cccol_cursor_next(na->context, cursor, &id)) == 0 && id != NULL) {
819	NAHSelectionRef nasel;
820	int is_lkdc;
821	time_t t;
822
823	ret = krb5_cc_get_principal(na->context, id, &client);
824	if (ret) {
825	    krb5_cc_close(na->context, id);
826	    continue;
827	}
828
829	ret = krb5_cc_get_lifetime(na->context, id, &t);
830	if (ret || t <= 0) {
831	    krb5_cc_close(na->context, id);
832	    continue;
833	}
834
835	is_lkdc = krb5_principal_is_lkdc(na->context, client);
836
837	if ((only_lkdc && !is_lkdc) || (!only_lkdc && is_lkdc)) {
838	    krb5_free_principal(na->context, client);
839	    krb5_cc_close(na->context, id);
840	    continue;
841	}
842
843	ret = krb5_unparse_name(na->context, client, &c);
844	if (ret) {
845	    krb5_free_principal(na->context, client);
846	    krb5_cc_close(na->context, id);
847	    continue;
848	}
849
850	u = CFStringCreateWithCString(na->alloc, c, kCFStringEncodingUTF8);
851	free(c);
852	if (u == NULL) {
853	    krb5_free_principal(na->context, client);
854	    krb5_cc_close(na->context, id);
855	    continue;
856	}
857
858	if (is_lkdc) {
859	    CFStringRef cr = NULL;
860	    krb5_data data;
861
862	    ret = krb5_cc_get_config(na->context, id, NULL, "lkdc-hostname", &data);
863	    if (ret == 0) {
864		cr = CFStringCreateWithBytes(na->alloc, data.data, data.length, kCFStringEncodingUTF8, false);
865		krb5_data_free(&data);
866	    }
867
868	    if (cr == NULL || CFStringCompare(na->hostname, cr, 0) != kCFCompareEqualTo) {
869		krb5_free_principal(na->context, client);
870		krb5_cc_close(na->context, id);
871		continue;
872	    }
873
874	    /* Create server principal */
875	    server = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%s@%s"),
876					      na->service, client->realm, client->realm);
877
878	    nalog(ASL_LEVEL_DEBUG, CFSTR("Adding existing LKDC cache: %@ -> %@"), u, server);
879
880	} else {
881	    server = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%@@%s"), na->service, na->hostname,
882					      krb5_principal_get_realm(na->context, client));
883	    nalog(ASL_LEVEL_DEBUG, CFSTR("Adding existing cache: %@ -> %@"), u, server);
884	}
885	krb5_free_principal(na->context, client);
886
887	nasel = addSelection(na, u, kNAHNTKRB5Principal,
888			     server, kNAHNTKRB5PrincipalReferral, GSS_KERBEROS, NULL, flags);
889	CFRELEASE(u);
890	CFRELEASE(server);
891	if (nasel != NULL && nasel->ccache == NULL) {
892	    krb5_data data;
893	    nasel->ccache = id;
894	    nasel->have_cred = 1;
895
896	    if (nasel->inferredLabel == NULL) {
897		ret = krb5_cc_get_config(na->context, id, NULL, "FriendlyName", &data);
898		if (ret == 0) {
899		    nasel->inferredLabel = CFStringCreateWithBytes(na->alloc, data.data, data.length, kCFStringEncodingUTF8, false);
900		    krb5_data_free(&data);
901		}
902	    }
903
904	} else
905	    krb5_cc_close(na->context, id);
906
907    }
908
909    krb5_cccol_cursor_free(na->context, &cursor);
910}
911
912static CFStringRef kWELLKNOWN_LKDC = CFSTR("WELLKNOWN:COM.APPLE.LKDC");
913
914static void
915wellknown_lkdc(NAHRef na, enum NAHMechType mechtype, unsigned long flags)
916{
917    CFStringRef s, u;
918    CFIndex n;
919
920    u = CFStringCreateWithFormat(na->alloc, NULL,
921				 CFSTR("%@@%@"),
922				 na->username, kWELLKNOWN_LKDC);
923    if (u == NULL)
924	return;
925
926    s = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/localhost@%@"),
927				 na->service, kWELLKNOWN_LKDC);
928    if (s == NULL) {
929	CFRELEASE(u);
930	return;
931    }
932
933    if (na->password)
934	addSelection(na, u, kNAHNTKRB5Principal,
935		     s, kNAHNTKRB5Principal, mechtype, NULL, flags);
936    CFRELEASE(u);
937
938    /* if we have certs, lets push those names too */
939    for (n = 0; na->x509identities && n < CFArrayGetCount(na->x509identities); n++) {
940	SecIdentityRef identity = (void *)CFArrayGetValueAtIndex(na->x509identities, n);
941	CFStringRef csstr;
942	SecCertificateRef cert = NULL;
943
944	if (SecIdentityCopyCertificate(identity, &cert))
945	    continue;
946
947	csstr = _CSCopyKerberosPrincipalForCertificate(cert);
948	CFRelease(cert);
949	if (csstr == NULL) {
950	    hx509_cert hxcert;
951	    char *str;
952	    int ret;
953
954	    ret = hx509_cert_init_SecFramework(na->hxctx, identity, &hxcert);
955	    if (ret)
956		continue;
957
958	    ret = hx509_cert_get_appleid(na->hxctx, hxcert, &str);
959	    hx509_cert_free(hxcert);
960	    if (ret)
961		continue;
962
963	    u = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%s@%@"),
964					 str, kWELLKNOWN_LKDC);
965	    krb5_xfree(str);
966	    if (u == NULL)
967		continue;
968	} else {
969	    u = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@@%@"),
970					 csstr, kWELLKNOWN_LKDC);
971	    CFRelease(csstr);
972	    if (u == NULL)
973		continue;
974	}
975
976	NAHSelectionRef nasel = addSelection(na, u, kNAHNTKRB5Principal, s, kNAHNTKRB5PrincipalReferral, mechtype, NULL, flags);
977	CFRELEASE(u);
978	if (nasel) {
979	    CFRetain(identity);
980	    nasel->certificate = identity;
981	}
982    }
983
984    CFRELEASE(s);
985}
986
987static bool
988is_smb(NAHRef na)
989{
990    if (CFStringCompare(na->service, kNAHServiceHostServer, 0) == kCFCompareEqualTo ||
991	CFStringCompare(na->service, kNAHServiceCIFSServer, 0) == kCFCompareEqualTo)
992	return true;
993    return false;
994}
995
996static void
997guess_kerberos(NAHRef na)
998{
999    bool try_wlkdc = false;
1000    bool try_iakerb_with_lkdc = false;
1001    bool have_kerberos = false;
1002    krb5_error_code ret;
1003    unsigned long flags = USE_SPNEGO;
1004
1005    if (nah_use_gss_uam
1006	&& (na->password || na->x509identities)
1007	&& haveMech(na, kGSSAPIMechIAKERB)
1008	&& haveMech(na, kGSSAPIMechSupportsAppleLKDC))
1009    {
1010	/* if we support IAKERB and AppleLDKC and is not SMB (client can't handle it, rdar://problem/8437184), let go for iakerb with */
1011	try_iakerb_with_lkdc = true;
1012    } else if (haveMech(na, kGSSAPIMechPKU2UOID) || haveMech(na, kGSSAPIMechSupportsAppleLKDC)) {
1013	try_wlkdc = true;
1014    } else if (CFStringCompare(na->service, kNAHServiceVNCServer, 0) == kCFCompareEqualTo) {
1015	try_wlkdc = true;
1016	if (nah_vnc_support_iakerb && (na->password || na->x509identities))
1017	    try_iakerb_with_lkdc = true;
1018    }
1019
1020    /*
1021     * If we are using an old AFP server, disable SPNEGO
1022     */
1023    if (CFStringCompare(na->service, kNAHServiceAFPServer, 0) == kCFCompareEqualTo &&
1024	!haveMech(na, kGSSAPIMechSupportsAppleLKDC))
1025    {
1026	flags &= (~USE_SPNEGO);
1027    }
1028
1029
1030    have_kerberos = (na->servermechs == NULL) ||
1031	haveMech(na, kGSSAPIMechIAKERB) ||
1032	haveMech(na, kGSSAPIMechKerberosOID) ||
1033	haveMech(na, kGSSAPIMechKerberosMicrosoftOID) ||
1034	haveMech(na, kGSSAPIMechPKU2UOID);
1035
1036    nalog(ASL_LEVEL_DEBUG, CFSTR("NAHCreate-krb: have_kerberos=%s try_iakerb_with_lkdc=%s try-wkdc=%s use-spnego=%s"),
1037	  have_kerberos ? "yes" : "no",
1038	  try_iakerb_with_lkdc ? "yes" : "no",
1039	  try_wlkdc ? "yes" : "no",
1040	  (flags & USE_SPNEGO) ? "yes" : "no");
1041
1042    if (!have_kerberos)
1043	return;
1044
1045    /*
1046     *
1047     */
1048
1049    ret = krb5_init_context(&na->context);
1050    if (ret)
1051	return;
1052
1053    ret = hx509_context_init(&na->hxctx);
1054    if (ret)
1055	return;
1056
1057    /*
1058     * We'll use matching LKDC credentials to this host since they are
1059     * faster then public key operations.
1060     */
1061
1062    use_existing_principals(na, 1, flags);
1063
1064    /*
1065     * IAKERB with LKDC
1066     */
1067
1068    if (try_iakerb_with_lkdc) {
1069	wellknown_lkdc(na, GSS_KERBEROS_IAKERB, flags);
1070    }
1071
1072    /*
1073     * Wellknown:LKDC
1074     */
1075
1076    if (try_wlkdc)
1077	wellknown_lkdc(na, GSS_KERBEROS, flags);
1078
1079    /*
1080     * Do classic Kerberos too
1081     */
1082
1083    if (na->password)
1084	use_classic_kerberos(na, flags);
1085
1086
1087    /*
1088     * We'll use existing credentials if we have them
1089     */
1090
1091    use_existing_principals(na, 0, flags);
1092}
1093
1094static void
1095guess_ntlm(NAHRef na)
1096{
1097    CFStringRef s;
1098    unsigned long flags = USE_SPNEGO;
1099
1100    if (!haveMech(na, kGSSAPIMechNTLMOID))
1101	return;
1102
1103    if (na->servermechs) {
1104	CFDataRef data = CFDictionaryGetValue(na->servermechs, kGSSAPIMechNTLMOID);
1105	if (data && CFDataGetLength(data) == 3 && memcmp(CFDataGetBytePtr(data), "raw", 3) == 0)
1106	    flags &= (~USE_SPNEGO);
1107    }
1108
1109    s = CFStringCreateWithFormat(na->alloc, 0, CFSTR("%@@%@"), na->service, na->hostname);
1110    if (s == NULL)
1111	return;
1112
1113    if (na->password) {
1114	CFRange range, ur, dr;
1115	CFStringRef u = NULL;
1116	unsigned long flags2 = 0;
1117
1118	if (CFStringFindWithOptions(na->username, CFSTR("@"), CFRangeMake(0, CFStringGetLength(na->username)), 0, &range)) {
1119	    u = na->username;
1120	    CFRetain(u);
1121
1122	    flags2 = FORCE_ADD;
1123	} else if (CFStringFindWithOptions(na->username, CFSTR("\\"), CFRangeMake(0, CFStringGetLength(na->username)), 0, &range)) {
1124	    CFStringRef ustr, dstr;
1125
1126	    dr.location = 0;
1127	    dr.length = range.location;
1128
1129	    ur.location = range.location + 1;
1130	    ur.length = CFStringGetLength(na->username) - ur.location;
1131
1132	    dstr = CFStringCreateWithSubstring(NULL, na->username, dr);
1133	    ustr = CFStringCreateWithSubstring(NULL, na->username, ur);
1134
1135	    if (dstr && ustr)
1136		u = CFStringCreateWithFormat(na->alloc, 0, CFSTR("%@@%@"), ustr, dstr);
1137	    CFRELEASE(ustr);
1138	    CFRELEASE(dstr);
1139
1140	    flags2 = FORCE_ADD;
1141	} else {
1142	    u = CFStringCreateWithFormat(na->alloc, 0, CFSTR("%@@\\%@"), na->username, na->hostname);
1143	}
1144
1145	if (u) {
1146	    addSelection(na, u, kNAHNTUsername, s, NULL, GSS_NTLM, NULL, flags | flags2);
1147	    CFRELEASE(u);
1148	}
1149
1150	if (na->specificname) {
1151	    u = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@@\\%@"), na->specificname, na->hostname);
1152	    addSelection(na, u, kNAHNTUsername, s, NULL, GSS_NTLM, NULL, flags);
1153	    CFRELEASE(u);
1154	}
1155    }
1156
1157    /* pick up ntlm credentials in caches */
1158
1159    dispatch_semaphore_t sema = dispatch_semaphore_create(0);
1160    if (sema == NULL)
1161	goto out;
1162
1163    (void)gss_iter_creds(NULL, 0, GSS_NTLM_MECHANISM, ^(gss_OID oid, gss_cred_id_t cred) {
1164	    OM_uint32 min_stat;
1165	    gss_name_t name = GSS_C_NO_NAME;
1166	    gss_buffer_desc buffer = { 0, NULL };
1167
1168	    if (cred == NULL) {
1169		dispatch_semaphore_signal(sema);
1170		return;
1171	    }
1172
1173	    gss_inquire_cred(&min_stat, cred, &name, NULL, NULL, NULL);
1174	    gss_display_name(&min_stat, name, &buffer, NULL);
1175	    gss_release_name(&min_stat, &name);
1176
1177	    CFStringRef u = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%.*s"),
1178						     (int)buffer.length, buffer.value);
1179
1180	    gss_release_buffer(&min_stat, &buffer);
1181
1182	    if (u == NULL)
1183		return;
1184
1185	    /* if we where given a name, and it matches, add it */
1186
1187	    NAHSelectionRef nasel = addSelection(na, u, kNAHNTUsername, s, NULL, GSS_NTLM, NULL, flags);
1188	    CFRELEASE(u);
1189	    if (nasel) {
1190		nasel->have_cred = 1;
1191	    }
1192	});
1193
1194    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
1195    dispatch_release(sema);
1196
1197 out:
1198    CFRELEASE(s);
1199}
1200
1201/*
1202 * Flattern arrays and try
1203 */
1204
1205static void
1206addSecItem(CFMutableArrayRef certs, CFTypeRef item)
1207{
1208    CFTypeID type = CFGetTypeID(item);
1209
1210    if (CFArrayGetTypeID() == type) {
1211	CFIndex n, count = CFArrayGetCount(item);
1212	for (n = 0; n < count; n++) {
1213	    CFTypeRef arrayItem = CFArrayGetValueAtIndex(item, n);
1214	    addSecItem(certs, arrayItem);
1215	}
1216    } else if (SecCertificateGetTypeID() == type) {
1217	SecIdentityRef identity = NULL;
1218	SecIdentityCreateWithCertificate(NULL, (SecCertificateRef)item, &identity);
1219	if (identity) {
1220	    CFArrayAppendValue(certs, identity);
1221	    CFRelease(identity);
1222	}
1223    } else if (SecIdentityGetTypeID() == type) {
1224	CFArrayAppendValue(certs, item);
1225    } else {
1226	CFStringRef desc = CFCopyDescription(item);
1227	nalog(ASL_LEVEL_DEBUG, CFSTR("unknown type of certificates: %@"), desc);
1228	CFRELEASE(desc);
1229    }
1230}
1231
1232/*
1233 *
1234 */
1235
1236const CFStringRef kNAHNegTokenInit = CFSTR("kNAHNegTokenInit");
1237const CFStringRef kNAHUserName = CFSTR("kNAHUserName");
1238const CFStringRef kNAHCertificates = CFSTR("kNAHCertificates");
1239const CFStringRef kNAHPassword = CFSTR("kNAHPassword");
1240
1241NAHRef
1242NAHCreate(CFAllocatorRef alloc,
1243	  CFStringRef hostname,
1244	  CFStringRef service,
1245	  CFDictionaryRef info)
1246{
1247    NAHRef na = NAAlloc(alloc);
1248    CFStringRef canonname;
1249    char *hostnamestr = NULL;
1250
1251    dispatch_once(&init_globals, ^{
1252	Boolean have_key = false;
1253	nah_use_gss_uam = CFPreferencesGetAppBooleanValue(CFSTR("GSSEnable"), CFSTR("com.apple.NetworkAuthenticationHelper"), &have_key);
1254	if (!have_key)
1255	    nah_use_gss_uam = true;
1256	nah_vnc_support_iakerb = CFPreferencesGetAppBooleanValue(CFSTR("VNCSupportIAKerb"), CFSTR("com.apple.NetworkAuthenticationHelper"), &have_key);
1257    });
1258
1259    nalog(ASL_LEVEL_DEBUG, CFSTR("NAHCreate: hostname=%@ service=%@"), hostname, service);
1260
1261    /* first undo the damage BrowserServices have done to the hostname */
1262
1263    if (_CFNetServiceDeconstructServiceName(hostname, &hostnamestr)) {
1264	canonname = CFStringCreateWithCString(na->alloc, hostnamestr, kCFStringEncodingUTF8);
1265	free(hostnamestr);
1266	if (canonname == NULL)
1267	    return NULL;
1268    } else {
1269	canonname = hostname;
1270	CFRetain(canonname);
1271    }
1272
1273    na->hostname = CFStringCreateMutableCopy(alloc, 0, canonname);
1274    CFRelease(canonname);
1275    if (na->hostname == NULL) {
1276	CFRELEASE(na);
1277	return NULL;
1278    }
1279    CFStringTrim(na->hostname, CFSTR("."));
1280
1281    nalog(ASL_LEVEL_DEBUG, CFSTR("NAHCreate: will use hostname=%@"), na->hostname);
1282
1283    na->service = CFRetain(service);
1284
1285    nalog(ASL_LEVEL_DEBUG, CFSTR("NAHCreate: will use service=%@"), na->service);
1286
1287
1288    if (!findUsername(alloc, na, info)) {
1289	CFRELEASE(na);
1290	return NULL;
1291    }
1292
1293    nalog(ASL_LEVEL_DEBUG, CFSTR("NAHCreate: username=%@ username %s"), na->username, na->specificname ?
1294	  "given" : "generated");
1295
1296
1297    if (info) {
1298	na->password = CFDictionaryGetValue(info, kNAHPassword);
1299	if (na->password) {
1300	    nalog(ASL_LEVEL_DEBUG, CFSTR("NAHCreate: password"));
1301	    CFRetain(na->password);
1302	}
1303    }
1304
1305    na->selections = CFArrayCreateMutable(na->alloc, 0, &kCFTypeArrayCallBacks);
1306    if (na->selections == NULL) {
1307	CFRELEASE(na);
1308	return NULL;
1309    }
1310
1311    if (info) {
1312	CFDictionaryRef nti;
1313	CFArrayRef certs;
1314
1315	nti = CFDictionaryGetValue(info, kNAHNegTokenInit);
1316	if (nti) {
1317	    na->servermechs = CFDictionaryGetValue(nti, kSPNEGONegTokenInitMechs);
1318	    if (na->servermechs)
1319		CFRetain(na->servermechs);
1320	    na->spnegoServerName = CFDictionaryGetValue(nti, kSPNEGONegTokenInitHintsHostname);
1321	    if (na->spnegoServerName) {
1322		nalog(ASL_LEVEL_DEBUG, CFSTR("NAHCreate: SPNEGO hints name %@"), na->spnegoServerName);
1323		CFRetain(na->spnegoServerName);
1324	    }
1325	}
1326
1327	certs = CFDictionaryGetValue(info, kNAHCertificates);
1328	if (certs) {
1329	    CFMutableArrayRef a = CFArrayCreateMutable(na->alloc, 0, &kCFTypeArrayCallBacks);
1330
1331	    nalog(ASL_LEVEL_DEBUG, CFSTR("NAHCreate: certs %@"), certs);
1332
1333	    addSecItem(a, certs);
1334
1335	    if (CFArrayGetCount(a)) {
1336		na->x509identities = a;
1337	    } else {
1338		CFRelease(a);
1339		nalog(ASL_LEVEL_DEBUG, CFSTR("NAHCreate: we got no certs"));
1340	    }
1341	}
1342    }
1343
1344    /* here starts the guessing game */
1345
1346    add_user_selections(na);
1347
1348    guess_kerberos(na);
1349
1350    /* only do NTLM for SMB */
1351    if (na->x509identities == NULL && is_smb(na))
1352	guess_ntlm(na);
1353
1354    return na;
1355}
1356
1357CFArrayRef
1358NAHGetSelections(NAHRef na)
1359{
1360    return na->selections;
1361}
1362
1363static CFStringRef
1364copyInferedNameFromIdentity(SecIdentityRef identity)
1365{
1366    CFStringRef inferredLabel = NULL;
1367    SecCertificateRef cert = NULL;
1368
1369    SecIdentityCopyCertificate(identity, &cert);
1370    if (cert == NULL)
1371	return NULL;
1372
1373    inferredLabel = _CSCopyAppleIDAccountForAppleIDCertificate(cert, NULL);
1374    if (inferredLabel == NULL)
1375	SecCertificateInferLabel(cert, &inferredLabel);
1376
1377    CFRELEASE(cert);
1378
1379    return inferredLabel;
1380}
1381
1382
1383static void
1384setFriendlyName(NAHRef na,
1385		NAHSelectionRef selection,
1386		SecIdentityRef cert,
1387		krb5_ccache id,
1388		int is_lkdc)
1389{
1390    CFStringRef inferredLabel = NULL;
1391
1392    if (cert) {
1393	inferredLabel = copyInferedNameFromIdentity(cert);
1394
1395    } else if (na->specificname || is_lkdc) {
1396	inferredLabel = na->username;
1397	CFRetain(inferredLabel);
1398    } else {
1399	inferredLabel = selection->client;
1400	CFRetain(inferredLabel);
1401    }
1402
1403    if (inferredLabel) {
1404	char *label;
1405
1406	if (__KRBCreateUTF8StringFromCFString(inferredLabel, &label) == noErr) {
1407	    krb5_data data;
1408
1409	    data.data = label;
1410	    data.length = strlen(label) + 1;
1411
1412	    krb5_cc_set_config(na->context, id, NULL, "FriendlyName", &data);
1413	    free(label);
1414	}
1415	selection->inferredLabel = inferredLabel;
1416    }
1417}
1418
1419static int
1420acquire_kerberos(NAHRef na,
1421		 NAHSelectionRef selection,
1422		 CFStringRef password,
1423		 SecIdentityRef cert,
1424		 CFErrorRef *error)
1425{
1426    krb5_init_creds_context icc = NULL;
1427    krb5_get_init_creds_opt *opt = NULL;
1428    krb5_principal client = NULL;
1429    int destroy_cache = 0;
1430    krb5_ccache id = NULL;
1431    krb5_error_code ret;
1432    krb5_creds cred;
1433    char *str = NULL;
1434    int parseflags = 0;
1435    int is_lkdc = 0;
1436
1437    memset(&cred, 0, sizeof(cred));
1438
1439    nalog(ASL_LEVEL_DEBUG, CFSTR("acquire_kerberos: %@ with pw:%s cert:%s"),
1440	  selection->client,
1441	  password ? "yes" : "no",
1442	  cert ? "yes" : "no");
1443
1444    ret = __KRBCreateUTF8StringFromCFString(selection->client, &str);
1445    if (ret)
1446	goto out;
1447
1448    /*
1449     * Check if this is an enterprise name
1450     * XXX horrible, caller should tell us
1451     */
1452    {
1453	char *p = strchr(str, '@');
1454	if (p && (p = strchr(p + 1, '@')) != NULL)
1455	    parseflags |= KRB5_PRINCIPAL_PARSE_ENTERPRISE;
1456    }
1457
1458    ret = krb5_parse_name_flags(na->context, str, parseflags, &client);
1459    __KRBReleaseUTF8String(str);
1460    if (ret)
1461	goto out;
1462
1463    ret = krb5_unparse_name(na->context, client, &str);
1464    if (ret == 0) {
1465	nalog(ASL_LEVEL_DEBUG, CFSTR("acquire_kerberos: trying with %s as client principal"), str);
1466	free(str);
1467    }
1468
1469    ret = krb5_get_init_creds_opt_alloc(na->context, &opt);
1470    if (ret)
1471	goto out;
1472
1473    if (cert) {
1474	ret = krb5_get_init_creds_opt_set_pkinit(na->context, opt, client,
1475						 NULL, "KEYCHAIN:",
1476						 NULL, NULL, 0,
1477						 NULL, NULL, NULL);
1478	if (ret)
1479	    goto out;
1480    }
1481
1482    krb5_get_init_creds_opt_set_canonicalize(na->context, opt, TRUE);
1483    krb5_get_init_creds_opt_set_win2k(na->context, opt, TRUE);
1484
1485    ret = krb5_init_creds_init(na->context, client, NULL, NULL,
1486			       0, opt, &icc);
1487    if (ret)
1488	goto out;
1489
1490    if (krb5_principal_is_lkdc(na->context, client)) {
1491        char *tcphostname = NULL;
1492
1493	ret = __KRBCreateUTF8StringFromCFString(na->hostname, &str);
1494	if (ret)
1495	    goto out;
1496	asprintf(&tcphostname, "tcp/%s", str);
1497	__KRBReleaseUTF8String(str);
1498	if (tcphostname == NULL) {
1499	    ret = ENOMEM;
1500	    goto out;
1501	}
1502	krb5_init_creds_set_kdc_hostname(na->context, icc, tcphostname);
1503	free(tcphostname);
1504    }
1505
1506    if (cert) {
1507	hx509_cert hxcert;
1508
1509	ret = hx509_cert_init_SecFramework(na->hxctx, cert, &hxcert);
1510	if (ret)
1511	    goto out;
1512
1513	ret = krb5_init_creds_set_pkinit_client_cert(na->context, icc, hxcert);
1514	hx509_cert_free(hxcert);
1515	if (ret)
1516	    goto out;
1517
1518    } else if (password) {
1519	ret = __KRBCreateUTF8StringFromCFString(password, &str);
1520	if (ret)
1521	    goto out;
1522	ret = krb5_init_creds_set_password(na->context, icc, str);
1523	__KRBReleaseUTF8String(str);
1524	if (ret)
1525	    goto out;
1526    } else {
1527	abort();
1528    }
1529
1530    ret = krb5_init_creds_get(na->context, icc);
1531    if (ret)
1532	goto out;
1533
1534    ret = krb5_init_creds_get_creds(na->context, icc, &cred);
1535    if (ret)
1536	goto out;
1537
1538    ret = krb5_cc_cache_match(na->context, cred.client, &id);
1539    if (ret) {
1540	ret = krb5_cc_new_unique(na->context, NULL, NULL, &id);
1541	if (ret)
1542	    goto out;
1543	destroy_cache = 1;
1544    }
1545
1546    ret = krb5_cc_initialize(na->context, id, cred.client);
1547    if (ret)
1548	goto out;
1549
1550    ret = krb5_cc_store_cred(na->context, id, &cred);
1551    if (ret)
1552	goto out;
1553
1554    ret = krb5_init_creds_store_config(na->context, icc, id);
1555    if (ret)
1556	goto out;
1557
1558    /* the KDC might have done referrals games, let update the principals */
1559
1560    {
1561	CFStringRef newclient, newserver;
1562	const char *realm = krb5_principal_get_realm(na->context, cred.client);
1563
1564	is_lkdc = krb5_realm_is_lkdc(realm);
1565
1566	ret = krb5_unparse_name(na->context, cred.client, &str);
1567	if (ret)
1568	    goto out;
1569
1570	newclient = CFStringCreateWithCString(na->alloc, str, kCFStringEncodingUTF8);
1571	free(str);
1572	if (newclient == NULL) {
1573	    ret = ENOMEM;
1574	    goto out;
1575	}
1576
1577	nalog(ASL_LEVEL_DEBUG, CFSTR("acquire_kerberos: got %@ as client principal"), newclient);
1578
1579	if (CFStringCompare(newclient, selection->client, 0) != kCFCompareEqualTo) {
1580
1581	    CFRELEASE(selection->client);
1582	    selection->client = newclient;
1583
1584	    if (is_lkdc) {
1585		newserver = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%s@%s"),
1586						     na->service, realm, realm);
1587	    } else {
1588		newserver = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%@@%s"),
1589						     na->service, na->hostname, realm);
1590	    }
1591	    if (newserver) {
1592		CFRELEASE(selection->server);
1593		selection->server = newserver;
1594	    }
1595	} else {
1596	    CFRELEASE(newclient);
1597	}
1598    }
1599
1600    setFriendlyName(na, selection, cert, id, is_lkdc);
1601    {
1602	krb5_data data;
1603	data.data = "1";
1604	data.length = 1;
1605	krb5_cc_set_config(na->context, id, NULL, nah_created, &data);
1606    }
1607
1608
1609 out:
1610    if (ret) {
1611	const char *e = krb5_get_error_message(na->context, ret);
1612	updateError(NULL, error, ret, CFSTR("acquire_kerberos failed %@: %d - %s"),
1613	      selection->client, ret, e);
1614	krb5_free_error_message(na->context, e);
1615    } else {
1616	nalog(ASL_LEVEL_DEBUG, CFSTR("acquire_kerberos successful"));
1617    }
1618
1619    if (opt)
1620	krb5_get_init_creds_opt_free(na->context, opt);
1621
1622    if (icc)
1623	krb5_init_creds_free(na->context, icc);
1624
1625    if (id) {
1626	if (ret != 0 && destroy_cache)
1627	    krb5_cc_destroy(na->context, id);
1628	else
1629	    krb5_cc_close(na->context, id);
1630    }
1631    krb5_free_cred_contents(na->context, &cred);
1632
1633    if (client)
1634	krb5_free_principal(na->context, client);
1635
1636    return ret;
1637}
1638
1639/*
1640 *
1641 */
1642
1643Boolean
1644NAHSelectionAcquireCredentialAsync(NAHSelectionRef selection,
1645				  CFDictionaryRef info,
1646				  dispatch_queue_t q,
1647				  void (^result)(CFErrorRef error))
1648{
1649    void (^r)(CFErrorRef error) = (void (^)(CFErrorRef))Block_copy(result);
1650
1651    CFRetain(selection->na);
1652
1653    dispatch_async(selection->na->bgq, ^{
1654	    CFErrorRef e = NULL;
1655	    Boolean res;
1656
1657	    res = NAHSelectionAcquireCredential(selection, info, &e);
1658	    if (!res) {
1659		r(NULL);
1660		CFRelease(selection->na);
1661		Block_release(r);
1662		return;
1663	    }
1664
1665	    dispatch_async(q, ^{
1666		    r(e);
1667		    if (e)
1668			CFRelease(e);
1669		    CFRelease(selection->na);
1670		    Block_release(r);
1671		});
1672	});
1673
1674    return true;
1675}
1676
1677static void
1678setGSSLabel(gss_cred_id_t cred, const char *label, CFStringRef value)
1679{
1680    gss_buffer_desc buf;
1681    OM_uint32 junk;
1682
1683    buf.value = cf2cstring(value);
1684    if (buf.value == NULL)
1685	return;
1686    buf.length = strlen((char *)buf.value);
1687
1688    gss_cred_label_set(&junk, cred, label, &buf);
1689    free(buf.value);
1690}
1691
1692
1693const CFStringRef kNAHForceRefreshCredential = CFSTR("kNAHForceRefreshCredential");
1694
1695Boolean
1696NAHSelectionAcquireCredential(NAHSelectionRef selection,
1697			     CFDictionaryRef info,
1698			     CFErrorRef *error)
1699{
1700    if (error)
1701	*error = NULL;
1702
1703    CFRetain(selection->na);
1704
1705    if (selection->mech == GSS_KERBEROS) {
1706
1707	nalog(ASL_LEVEL_DEBUG, CFSTR("NAHSelectionAcquireCredential: kerberos client: %@ (server %@)"),
1708	      selection->client, selection->server);
1709
1710	/* if we already have a cache, skip acquire unless force */
1711	if (selection->ccache) {
1712	    nalog(ASL_LEVEL_DEBUG, CFSTR("have ccache"));
1713	    KRBCredChangeReferenceCount(selection->client, 1, 1);
1714	    return true;
1715	}
1716
1717	if (selection->na->password == NULL && selection->certificate == NULL) {
1718	    nalog(ASL_LEVEL_DEBUG, CFSTR("krb5: no password or cert, punting"));
1719	    CFRelease(selection->na);
1720	    return false;
1721	}
1722
1723	int ret;
1724
1725	ret = acquire_kerberos(selection->na,
1726			       selection,
1727			       selection->na->password,
1728			       selection->certificate,
1729			       error);
1730
1731	CFRelease(selection->na);
1732	if (ret && error && *error)
1733	    nalog(ASL_LEVEL_NOTICE, CFSTR("NAHSelectionAcquireCredential %@"), *error);
1734
1735	return (ret == 0) ? true : false;
1736
1737    } else if (selection->mech == GSS_NTLM) {
1738	gss_auth_identity_desc identity;
1739	gss_name_t name = GSS_C_NO_NAME;
1740	gss_buffer_desc gbuf;
1741	char *password, *user;
1742	OM_uint32 major, minor, junk;
1743	dispatch_semaphore_t s;
1744	char *str;
1745
1746	nalog(ASL_LEVEL_DEBUG, CFSTR("NAHSelectionAcquireCredential: ntlm"));
1747
1748	if (selection->have_cred) {
1749	    CFRelease(selection->na);
1750	    return true;
1751	}
1752
1753	if (selection->na->password == NULL) {
1754	    CFRelease(selection->na);
1755	    return false;
1756	}
1757
1758	__KRBCreateUTF8StringFromCFString(selection->client, &user);
1759
1760	gbuf.value = user;
1761	gbuf.length = strlen(user);
1762
1763	major = gss_import_name(&minor, &gbuf, GSS_C_NT_USER_NAME, &name);
1764	__KRBReleaseUTF8String(user);
1765	if (major) {
1766	    CFRelease(selection->na);
1767	    return false;
1768	}
1769
1770	s = dispatch_semaphore_create(0);
1771	__KRBCreateUTF8StringFromCFString(selection->client, &user);
1772	__KRBCreateUTF8StringFromCFString(selection->na->password, &password);
1773
1774	selection->inferredLabel = selection->client;
1775	CFRetain(selection->inferredLabel);
1776
1777	/* drop when name is completly supported */
1778	str = strchr(user, '@');
1779	if (str)
1780	    *str++ = '\0';
1781
1782	identity.username = user;
1783	if (str)
1784	    identity.realm = str;
1785	else
1786	    identity.realm = "";
1787	identity.password = password;
1788
1789	major = gss_acquire_cred_ex(name,
1790				    0,
1791				    GSS_C_INDEFINITE,
1792				    GSS_NTLM_MECHANISM,
1793				    GSS_C_INITIATE,
1794				    &identity,
1795				    ^(gss_status_id_t status, gss_cred_id_t cred,
1796				      gss_OID_set set, OM_uint32 flags) {
1797
1798					if (cred)  {
1799					    gss_buffer_desc buffer;
1800					    OM_uint32 min_stat;
1801
1802					    buffer.value = user;
1803					    buffer.length = strlen(user);
1804
1805					    gss_cred_label_set(&min_stat, cred, "FriendlyName", &buffer);
1806
1807					    buffer.value = "1";
1808					    buffer.length = 1;
1809					    gss_cred_label_set(&min_stat, cred, nah_created, &buffer);
1810					} else {
1811					    updateError(NULL, error, 1, CFSTR("failed to create ntlm cred"));
1812					}
1813
1814					dispatch_semaphore_signal(s);
1815					CFRelease(selection->na);
1816				    });
1817	gss_release_name(&junk, &name);
1818	if (major == GSS_S_COMPLETE) {
1819	    dispatch_semaphore_wait(s, DISPATCH_TIME_FOREVER);
1820
1821	    if (error && *error)
1822		nalog(ASL_LEVEL_NOTICE, CFSTR("NAHSelectionAcquireCredential ntlm %@"), *error);
1823
1824	} else {
1825	    updateError(NULL, error, major, CFSTR("Failed to acquire NTLM credentials"));
1826	    CFRelease(selection->na);
1827	}
1828
1829	__KRBReleaseUTF8String(user);
1830	__KRBReleaseUTF8String(password);
1831
1832	dispatch_release(s);
1833	return true;
1834    } else if (selection->mech == GSS_KERBEROS_IAKERB) {
1835	gss_name_t name = GSS_C_NO_NAME;
1836	gss_buffer_desc gbuf;
1837	char *user;
1838	OM_uint32 major, minor, junk;
1839	gss_cred_id_t cred;
1840
1841	nalog(ASL_LEVEL_DEBUG, CFSTR("NAHSelectionAcquireCredential: iakerb %@"), selection->client);
1842
1843	if (selection->have_cred) {
1844	    nalog(ASL_LEVEL_DEBUG, CFSTR("NAHSelectionAcquireCredential: already have cred, why iakerb then ?"));
1845	    CFRelease(selection->na);
1846	    return false;
1847	}
1848
1849
1850	if (selection->na->password == NULL && selection->certificate == NULL) {
1851	    nalog(ASL_LEVEL_DEBUG, CFSTR("NAHSelectionAcquireCredential: no password nor cert"));
1852	    CFRelease(selection->na);
1853	    return false;
1854	}
1855
1856	__KRBCreateUTF8StringFromCFString(selection->client, &user);
1857
1858	gbuf.value = user;
1859	gbuf.length = strlen(user);
1860
1861	major = gss_import_name(&minor, &gbuf, GSS_C_NT_USER_NAME, &name);
1862	__KRBReleaseUTF8String(user);
1863	if (major) {
1864	    CFRelease(selection->na);
1865	    return false;
1866	}
1867
1868	if (selection->certificate)
1869	    selection->inferredLabel = copyInferedNameFromIdentity(selection->certificate);
1870
1871	if (selection->inferredLabel == NULL) {
1872	    CFMutableStringRef str = CFStringCreateMutableCopy(NULL, 0, selection->client);
1873	    CFRange range = CFStringFind(str, CFSTR("@"), 0);
1874	    if (range.location != kCFNotFound)
1875		CFStringPad(str, NULL, range.location, 0);
1876	    selection->inferredLabel = str;
1877	}
1878
1879	CFMutableDictionaryRef dict = NULL;
1880
1881	dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1882
1883	if (selection->na->password)
1884	    CFDictionaryAddValue(dict, kGSSICPassword, selection->na->password);
1885	if (selection->certificate)
1886	    CFDictionaryAddValue(dict, kGSSICCertificate, selection->certificate);
1887
1888	major = gss_aapl_initial_cred(name, GSS_IAKERB_MECHANISM, dict, &cred, error);
1889	CFRelease(dict);
1890	gss_release_name(&junk, &name);
1891	if (major) {
1892	    if (error && *error)
1893		nalog(ASL_LEVEL_NOTICE, CFSTR("NAHSelectionAcquireCredential iakerb %@"), *error);
1894	    CFRelease(selection->na);
1895	    return false;
1896	}
1897
1898	setGSSLabel(cred, "FriendlyName", selection->inferredLabel);
1899	setGSSLabel(cred, "lkdc-hostname", selection->na->hostname);
1900
1901	{
1902	    gss_buffer_set_t dataset = GSS_C_NO_BUFFER_SET;
1903
1904	    major = gss_inquire_cred_by_oid(&minor, cred, GSS_C_NT_UUID, &dataset);
1905	    if (major || dataset->count != 1) {
1906		nalog(ASL_LEVEL_DEBUG, CFSTR("NAHSelectionAcquireCredential: failed with no uuid"));
1907		gss_release_buffer_set(&junk, &dataset);
1908		CFRelease(selection->na);
1909		return false;
1910	    }
1911
1912	    CFStringRef newclient = CFStringCreateWithBytes(NULL, dataset->elements[0].value, dataset->elements[0].length, kCFStringEncodingUTF8, false);
1913	    if (newclient) {
1914		CFRELEASE(selection->client);
1915		selection->client = newclient;
1916		selection->clienttype = kNAHNTUUID;
1917	    }
1918	    gss_release_buffer_set(&junk, &dataset);
1919	}
1920	nalog(ASL_LEVEL_NOTICE, CFSTR("NAHSelectionAcquireCredential complete: iakerb %@ - %@: %@"), selection->client, selection->inferredLabel, cred);
1921
1922	gss_release_cred(&junk, &cred);
1923
1924	CFRelease(selection->na);
1925
1926	return true;
1927    } else {
1928	nalog(ASL_LEVEL_DEBUG, CFSTR("NAHSelectionAcquireCredential: unknown"));
1929    }
1930
1931    return false;
1932}
1933
1934/*
1935 *
1936 */
1937
1938CFTypeRef
1939NAHSelectionGetInfoForKey(NAHSelectionRef selection, CFStringRef key)
1940{
1941    if (CFStringCompare(kNAHSelectionHaveCredential, key, 0) == kCFCompareEqualTo) {
1942	if (selection->ccache)
1943	    return kCFBooleanTrue;
1944	return kCFBooleanFalse;
1945    } else if (CFStringCompare(kNAHSelectionUserPrintable, key, 0) == kCFCompareEqualTo) {
1946	return selection->client; /* XXX make prettier ? */
1947    } else if (CFStringCompare(kNAHServerPrincipal, key, 0) == kCFCompareEqualTo) {
1948	return selection->server;
1949    } else if (CFStringCompare(kNAHClientPrincipal, key, 0) == kCFCompareEqualTo) {
1950	return selection->client;
1951    } else if (CFStringCompare(kNAHMechanism, key, 0) == kCFCompareEqualTo) {
1952	/* if not told otherwise, wrap everything in SPNEGO wrappings */
1953	if (!selection->spnego)
1954	    return mech2name(selection->mech);
1955	return kGSSAPIMechSPNEGO;
1956    } else if (CFStringCompare(kNAHInnerMechanism, key, 0) == kCFCompareEqualTo) {
1957	return mech2name(selection->mech);
1958    } else if (CFStringCompare(kNAHUseSPNEGO, key, 0) == kCFCompareEqualTo) {
1959	return selection->spnego ? kCFBooleanTrue : kCFBooleanFalse;
1960    } else if (CFStringCompare(kNAHCredentialType, key, 0) == kCFCompareEqualTo) {
1961	return mech2name(selection->mech);
1962    } else if (CFStringCompare(kNAHInferredLabel, key, 0) == kCFCompareEqualTo) {
1963	return selection->inferredLabel;
1964    }
1965
1966    return NULL;
1967}
1968
1969/*
1970 * Returns the data for kNetFSAuthenticationInfoKey
1971 */
1972
1973CFDictionaryRef
1974NAHSelectionCopyAuthInfo(NAHSelectionRef selection)
1975{
1976    CFMutableDictionaryRef dict;
1977    CFStringRef string;
1978    int gssdclient, gssdserver;
1979
1980    if (selection->server == NULL)
1981	return NULL;
1982
1983    dict = CFDictionaryCreateMutable (kCFAllocatorDefault, 5,
1984				      &kCFCopyStringDictionaryKeyCallBacks,
1985				      &kCFTypeDictionaryValueCallBacks);
1986    if (dict == NULL)
1987	return NULL;
1988
1989    CFDictionaryAddValue(dict, kNAHMechanism,
1990			 NAHSelectionGetInfoForKey(selection, kNAHMechanism));
1991
1992    CFDictionaryAddValue(dict, kNAHCredentialType,
1993			 NAHSelectionGetInfoForKey(selection, kNAHCredentialType));
1994
1995    CFDictionaryAddValue(dict, kNAHClientNameType, selection->clienttype);
1996
1997    if (CFStringCompare(selection->clienttype, kNAHNTUUID, 0) == 0) {
1998	gssdclient = GSSD_UUID;
1999    } else if (CFStringCompare(selection->clienttype, kNAHNTKRB5Principal, 0) == 0) {
2000	gssdclient = GSSD_KRB5_PRINCIPAL;
2001    } else if (CFStringCompare(selection->clienttype, kNAHNTUsername, 0) == 0) {
2002	gssdclient = GSSD_NTLM_PRINCIPAL;
2003    } else {
2004	gssdclient = GSSD_USER;
2005    }
2006
2007    CFDictionaryAddValue(dict, kNAHServerNameType, selection->servertype);
2008
2009    if (CFStringCompare(selection->servertype, kNAHNTServiceBasedName, 0) == 0)
2010	gssdserver = GSSD_HOSTBASED;
2011    else if (CFStringCompare(selection->servertype, kNAHNTKRB5PrincipalReferral, 0) == 0)
2012	gssdserver = GSSD_KRB5_REFERRAL;
2013    else if (CFStringCompare(selection->servertype, kNAHNTKRB5Principal, 0) == 0)
2014	gssdserver = GSSD_KRB5_PRINCIPAL;
2015    else
2016	gssdserver = GSSD_HOSTBASED;
2017
2018
2019    CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &gssdclient);
2020    if (num) {
2021	CFDictionaryAddValue(dict, kNAHClientNameTypeGSSD, num);
2022	CFRelease(num);
2023    }
2024
2025    num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &gssdserver);
2026    if (num) {
2027	CFDictionaryAddValue(dict, kNAHServerNameTypeGSSD, num);
2028	CFRelease(num);
2029    }
2030
2031    CFDictionaryAddValue(dict, kNAHClientPrincipal,
2032			 NAHSelectionGetInfoForKey(selection, kNAHClientPrincipal));
2033    CFDictionaryAddValue(dict, kNAHServerPrincipal,
2034			 NAHSelectionGetInfoForKey(selection, kNAHServerPrincipal));
2035
2036    /* add label if we have one */
2037    if ((string = NAHSelectionGetInfoForKey(selection, kNAHInferredLabel)) != NULL)
2038	CFDictionaryAddValue(dict, kNAHInferredLabel, string);
2039
2040    CFDictionaryAddValue(dict, kNAHUseSPNEGO, NAHSelectionGetInfoForKey(selection, kNAHUseSPNEGO));
2041
2042    return dict;
2043}
2044
2045/*
2046 *
2047 */
2048
2049void
2050NAHCancel(NAHRef na)
2051{
2052}
2053
2054/*
2055 * Reference counting
2056 */
2057
2058
2059static Boolean
2060CredChange(CFStringRef referenceKey, int count, const char *label)
2061{
2062    const char *mechname;
2063    gss_OID nametype;
2064    gss_OID oid;
2065
2066    if (referenceKey == NULL)
2067	return false;
2068
2069    nalog(ASL_LEVEL_DEBUG, CFSTR("NAHCredChange: %@ count: %d label: %s"),
2070	  referenceKey, count, label ? label : "<nolabel>");
2071
2072    if (CFStringHasPrefix(referenceKey, CFSTR("krb5:"))) {
2073	oid = GSS_KRB5_MECHANISM;
2074	nametype = GSS_C_NT_USER_NAME;
2075	mechname = "kerberos";
2076    } else if (CFStringHasPrefix(referenceKey, CFSTR("uuid:"))) {
2077	oid = NULL;
2078	nametype = GSS_C_NT_UUID;
2079	mechname = "uuid";
2080    } else if (CFStringHasPrefix(referenceKey, CFSTR("ntlm:"))) {
2081	oid = GSS_NTLM_MECHANISM;
2082	nametype = GSS_C_NT_USER_NAME;
2083	mechname = "ntlm";
2084    } else
2085	return false;
2086
2087    {
2088	gss_cred_id_t cred;
2089	gss_buffer_desc gbuf;
2090	OM_uint32 min_stat, maj_stat;
2091	CFStringRef name;
2092	gss_name_t gname;
2093	OSStatus ret;
2094	gss_OID_set_desc mechset;
2095	char *n;
2096
2097	if (oid) {
2098	    mechset.elements = oid;
2099	    mechset.count = 1;
2100	}
2101
2102	name = CFStringCreateWithSubstring(NULL, referenceKey, CFRangeMake(5, CFStringGetLength(referenceKey) - 5));
2103	if (name == NULL)
2104	    return false;
2105
2106	ret = __KRBCreateUTF8StringFromCFString(name, &n);
2107	CFRelease(name);
2108	if (ret)
2109	    return false;
2110
2111	gbuf.value = n;
2112	gbuf.length = strlen(n);
2113
2114	maj_stat = gss_import_name(&min_stat, &gbuf, nametype, &gname);
2115	if (maj_stat != GSS_S_COMPLETE) {
2116	    nalog(ASL_LEVEL_DEBUG, CFSTR("ChangeCred: name not importable %s/%s"), n, mechname);
2117	    free(n);
2118	    return false;
2119	}
2120
2121	maj_stat = gss_acquire_cred(&min_stat, gname, GSS_C_INDEFINITE, oid ? &mechset : NULL, GSS_C_INITIATE, &cred, NULL, NULL);
2122	gss_release_name(&min_stat, &gname);
2123
2124	if (maj_stat != GSS_S_COMPLETE) {
2125	    nalog(ASL_LEVEL_DEBUG, CFSTR("ChangeCred: cred name %s/%s not found"), n, mechname);
2126	    free(n);
2127	    return false;
2128	}
2129	free(n);
2130
2131	/* check that the credential is refcounted */
2132	{
2133	    gss_buffer_desc buffer;
2134	    maj_stat = gss_cred_label_get(&min_stat, cred, nah_created, &buffer);
2135	    if (maj_stat) {
2136		gss_release_cred(&min_stat, &cred);
2137		return false;
2138	    }
2139	    gss_release_buffer(&min_stat, &buffer);
2140	}
2141
2142	if (count == 0) {
2143	    /* do nothing */
2144	} else if (count > 0) {
2145	    gss_cred_hold(&min_stat, cred);
2146	} else {
2147	    gss_cred_unhold(&min_stat, cred);
2148	}
2149
2150	if (label) {
2151	    gss_buffer_desc buffer = {
2152		.value = "1",
2153		.length = 1
2154	    };
2155
2156	    gss_cred_label_set(&min_stat, cred, label, &buffer);
2157	}
2158
2159	gss_release_cred(&min_stat, &cred);
2160	return true;
2161    }
2162}
2163
2164char *
2165NAHCreateRefLabelFromIdentifier(CFStringRef identifier)
2166{
2167    CFStringRef str = CFStringCreateWithFormat(NULL, NULL, CFSTR("reference-label:%@"), identifier);
2168    OSStatus ret;
2169    char *label;
2170
2171    if (str == NULL)
2172	return NULL;
2173
2174    ret = __KRBCreateUTF8StringFromCFString(str, &label);
2175    CFRelease(str);
2176    if (ret != noErr)
2177	return NULL;
2178    return label;
2179}
2180
2181
2182Boolean
2183NAHAddReferenceAndLabel(NAHSelectionRef selection,
2184			CFStringRef identifier)
2185{
2186    CFStringRef ref;
2187    Boolean res;
2188    char *ident;
2189
2190    ref = NAHCopyReferenceKey(selection);
2191    if (ref == NULL)
2192	return false;
2193
2194    nalog(ASL_LEVEL_DEBUG, CFSTR("NAHAddReferenceAndLabel: %@ label: %@"), ref, identifier);
2195
2196    ident = NAHCreateRefLabelFromIdentifier(identifier);
2197    if (ident == NULL) {
2198	CFRelease(ref);
2199	return false;
2200    }
2201
2202    res = CredChange(ref, 1, ident);
2203    CFRelease(ref);
2204    __KRBReleaseUTF8String(ident);
2205
2206    return res;
2207}
2208
2209CFStringRef
2210NAHCopyReferenceKey(NAHSelectionRef selection)
2211{
2212    CFStringRef type;
2213    if (selection->client == NULL)
2214	return NULL;
2215
2216    switch (selection->mech) {
2217    case GSS_KERBEROS:
2218	type = CFSTR("krb5");
2219	break;
2220    case GSS_KERBEROS_PKU2U:
2221    case GSS_KERBEROS_IAKERB:
2222	type = CFSTR("uuid");
2223	break;
2224    case GSS_NTLM:
2225	type = CFSTR("ntlm");
2226	break;
2227    default:
2228	return NULL;
2229    }
2230
2231    /* if we are using UUID name types, prefer that over mech type */
2232    if (selection->clienttype && CFStringCompare(selection->clienttype, kNAHNTUUID, 0) == 0)
2233	type = CFSTR("uuid");
2234
2235    return CFStringCreateWithFormat(NULL, 0, CFSTR("%@:%@"),
2236				    type, selection->client);
2237}
2238
2239void
2240NAHFindByLabelAndRelease(CFStringRef identifier)
2241{
2242    char *str;
2243
2244    nalog(ASL_LEVEL_DEBUG, CFSTR("NAHFindByLabelAndRelease: looking for label %@"), identifier);
2245
2246    str = NAHCreateRefLabelFromIdentifier(identifier);
2247    if (str == NULL)
2248	return;
2249
2250    gss_iter_creds(NULL, 0, GSS_C_NO_OID, ^(gss_OID mech, gss_cred_id_t cred) {
2251	    OM_uint32 min_stat, maj_stat;
2252	    gss_buffer_desc buffer;
2253
2254	    if (cred == NULL)
2255		return;
2256
2257	    maj_stat = gss_cred_label_get(&min_stat, cred, nah_created, &buffer);
2258	    if (maj_stat) {
2259		gss_release_cred(&min_stat, &cred);
2260		return;
2261	    }
2262	    gss_release_buffer(&min_stat, &buffer);
2263
2264	    buffer.value = NULL;
2265	    buffer.length = 0;
2266
2267	    /* if there is a label, unhold */
2268	    maj_stat = gss_cred_label_get(&min_stat, cred, str, &buffer);
2269	    gss_release_buffer(&min_stat, &buffer);
2270	    if (maj_stat == GSS_S_COMPLETE) {
2271		nalog(ASL_LEVEL_DEBUG, CFSTR("NAHFindByLabelAndRelease: found credential unholding"));
2272		gss_cred_label_set(&min_stat, cred, str, NULL);
2273		gss_cred_unhold(&min_stat, cred);
2274	    }
2275	    gss_release_cred(&min_stat, &cred);
2276	});
2277
2278    __KRBReleaseUTF8String(str);
2279}
2280
2281Boolean
2282NAHCredAddReference(CFStringRef referenceKey)
2283{
2284    return CredChange(referenceKey, 1, NULL);
2285}
2286
2287Boolean
2288NAHCredRemoveReference(CFStringRef referenceKey)
2289{
2290    return CredChange(referenceKey, -1, NULL);
2291}
2292
2293/*
2294 *
2295 */
2296
2297static CFStringRef kDomainKey = CFSTR("domain");
2298static CFStringRef kUsername = CFSTR("user");
2299
2300static CFStringRef kMech = CFSTR("mech");
2301static CFStringRef kClient = CFSTR("client");
2302
2303
2304static void
2305add_user_selections(NAHRef na)
2306{
2307    CFArrayRef array;
2308    enum NAHMechType mech = GSS_KERBEROS;
2309    CFIndex n;
2310
2311    array = CFPreferencesCopyAppValue(CFSTR("UserSelections"),
2312				      CFSTR("com.apple.NetworkAuthenticationHelper"));
2313    if (array == NULL || CFGetTypeID(array) != CFArrayGetTypeID()) {
2314	CFRELEASE(array);
2315	return;
2316    }
2317
2318
2319    for (n = 0; n < CFArrayGetCount(array); n++) {
2320	CFDictionaryRef dict = CFArrayGetValueAtIndex(array, n);
2321	CFStringRef server = NULL;
2322	CFStringRef d, u, m, c;
2323
2324	if (CFGetTypeID(dict) != CFDictionaryGetTypeID())
2325	    continue;
2326
2327	m = CFDictionaryGetValue(dict, kMech);
2328	d = CFDictionaryGetValue(dict, kDomainKey);
2329	u = CFDictionaryGetValue(dict, kUsername);
2330	c = CFDictionaryGetValue(dict, kClient);
2331
2332	if (c == NULL || CFGetTypeID(c) != CFStringGetTypeID())
2333	    continue;
2334	if (m == NULL || CFGetTypeID(m) != CFStringGetTypeID())
2335	    continue;
2336	if (d == NULL || CFGetTypeID(d) != CFStringGetTypeID())
2337	    continue;
2338	if (u == NULL && CFGetTypeID(u) != CFStringGetTypeID())
2339	    continue;
2340
2341	/* find if matching */
2342	/* exact matching for now, should really be domain matching */
2343	if (CFStringCompare(d, na->hostname, kCFCompareCaseInsensitive) != kCFCompareEqualTo)
2344	    continue;
2345
2346	if (u == NULL) {
2347	    if (CFStringCompare(d, na->username, 0) != kCFCompareEqualTo)
2348		continue;
2349	}
2350
2351	mech = name2mech(m);
2352	if (mech == NO_MECH)
2353	    continue;
2354
2355	server = CFStringCreateWithFormat(na->alloc, 0, CFSTR("%@@%@"),
2356					  na->service, na->hostname);
2357
2358	/* add selection */
2359	if (server && c)
2360	    addSelection(na, c, NULL, server, NULL, mech, NULL, true);
2361	CFRELEASE(server);
2362    }
2363    CFRelease(array);
2364}
2365
2366
2367
2368
2369/*
2370 * GSS-API Support
2371 */
2372
2373static gss_OID
2374ntstring2oid(CFStringRef name)
2375{
2376    if (CFStringCompare(name, kNAHNTServiceBasedName, 0) == 0)
2377	return GSS_C_NT_HOSTBASED_SERVICE;
2378    else if (CFStringCompare(name, kNAHNTKRB5PrincipalReferral, 0) == 0)
2379	return GSS_KRB5_NT_PRINCIPAL_NAME_REFERRAL;
2380    else if (CFStringCompare(name, kNAHNTKRB5Principal, 0) == 0)
2381	return GSS_KRB5_NT_PRINCIPAL_NAME;
2382    else if (CFStringCompare(name, kNAHNTUUID, 0) == 0)
2383	return GSS_C_NT_UUID;
2384
2385    return NULL;
2386}
2387
2388
2389
2390gss_cred_id_t
2391NAHSelectionGetGSSCredential(NAHSelectionRef selection, CFErrorRef *error)
2392{
2393    gss_buffer_desc buffer;
2394    OM_uint32 minor_status, major_status, junk;
2395    gss_name_t name;
2396    gss_cred_id_t cred = NULL;
2397    gss_OID nt;
2398
2399    if (error)
2400	*error = NULL;
2401
2402    if (selection->client == NULL)
2403	return NULL;
2404
2405    nt = ntstring2oid(selection->clienttype);
2406    if (nt == NULL)
2407	nt = GSS_C_NT_USER_NAME;
2408
2409    buffer.value = cf2cstring(selection->client);
2410    if (buffer.value == NULL)
2411	return NULL;
2412    buffer.length = strlen((char *)buffer.value);
2413
2414    major_status = gss_import_name(&minor_status, &buffer, nt, &name);
2415    free(buffer.value);
2416    if (major_status) {
2417	updateError(NULL, error, major_status, CFSTR("Failed create name for %@"), selection->server);
2418	return NULL;
2419    }
2420
2421    major_status = gss_acquire_cred(&minor_status, name, GSS_C_INDEFINITE, NULL, GSS_C_INITIATE, &cred, NULL, NULL);
2422    gss_release_name(&junk, &name);
2423    if (major_status) {
2424	updateError(NULL, error, major_status, CFSTR("Failed create credential for %@"), selection->server);
2425	return NULL;
2426    }
2427
2428    return cred;
2429}
2430
2431gss_name_t
2432NAHSelectionGetGSSAcceptorName(NAHSelectionRef selection, CFErrorRef *error)
2433{
2434    gss_buffer_desc buffer;
2435    OM_uint32 minor_status, major_status;
2436    gss_name_t name;
2437    gss_OID nt;
2438
2439    if (error)
2440	*error = NULL;
2441
2442    if (selection->server == NULL)
2443	return GSS_C_NO_NAME;
2444
2445    buffer.value = cf2cstring(selection->server);
2446    if (buffer.value == NULL)
2447	return NULL;
2448    buffer.length = strlen((char *)buffer.value);
2449
2450    nt = ntstring2oid(selection->servertype);
2451    if (nt == NULL)
2452	nt = GSS_C_NT_HOSTBASED_SERVICE;
2453
2454    major_status = gss_import_name(&minor_status, &buffer, nt, &name);
2455    free(buffer.value);
2456    if (major_status)
2457	updateError(NULL, error, major_status, CFSTR("Failed create name for %@"), selection->server);
2458
2459    return name;
2460}
2461
2462gss_OID
2463NAHSelectionGetGSSMech(NAHSelectionRef selection)
2464{
2465    return mech2oid(selection->mech);
2466}
2467
2468/*
2469 * Same again, but for AuthenticationInfo dictionary
2470 */
2471
2472gss_cred_id_t
2473NAHAuthenticationInfoCopyClientCredential(CFDictionaryRef authInfo, CFErrorRef *error)
2474{
2475    gss_buffer_desc buffer;
2476    OM_uint32 minor_status, major_status, junk;
2477    gss_name_t name;
2478    gss_cred_id_t cred = NULL;
2479    gss_OID nt, mech;
2480
2481    if (error)
2482	*error = NULL;
2483
2484    CFStringRef mechanism = CFDictionaryGetValue(authInfo, kNAHCredentialType);
2485    CFStringRef clientName = CFDictionaryGetValue(authInfo, kNAHClientPrincipal);
2486    CFStringRef clientNameType = CFDictionaryGetValue(authInfo, kNAHClientNameType);
2487
2488    if (mechanism == NULL || clientName == NULL || clientNameType == NULL) {
2489	updateError(NULL, error, EINVAL, CFSTR("key missing from AuthenticationInfo"));
2490	return NULL;
2491    }
2492
2493    mech = name2oid(mechanism);
2494    if (mech == NULL) {
2495	updateError(NULL, error, EINVAL, CFSTR("unknown mech"));
2496	return NULL;
2497    }
2498
2499    nt = ntstring2oid(clientNameType);
2500    if (nt == NULL)
2501	nt = GSS_C_NT_USER_NAME;
2502
2503    buffer.value = cf2cstring(clientName);
2504    if (buffer.value == NULL)
2505	return NULL;
2506    buffer.length = strlen((char *)buffer.value);
2507
2508    major_status = gss_import_name(&minor_status, &buffer, nt, &name);
2509    free(buffer.value);
2510    if (major_status) {
2511	updateError(NULL, error, major_status, CFSTR("Failed create name for %@"), clientName);
2512	return NULL;
2513    }
2514
2515    major_status = gss_acquire_cred(&minor_status, name, GSS_C_INDEFINITE, NULL, GSS_C_INITIATE, &cred, NULL, NULL);
2516    gss_release_name(&junk, &name);
2517    if (major_status) {
2518	updateError(NULL, error, major_status, CFSTR("Failed create credential for %@"), clientName);
2519	return NULL;
2520    }
2521
2522    return cred;
2523}
2524
2525gss_name_t
2526NAHAuthenticationInfoCopyServerName(CFDictionaryRef authInfo, CFErrorRef *error)
2527{
2528    gss_buffer_desc buffer;
2529    OM_uint32 minor_status, major_status;
2530    gss_name_t name;
2531    gss_OID nt;
2532
2533    if (error)
2534	*error = NULL;
2535
2536    CFStringRef serverName = CFDictionaryGetValue(authInfo, kNAHServerPrincipal);
2537    CFStringRef serverNameType = CFDictionaryGetValue(authInfo, kNAHServerNameType);
2538
2539    if (serverName == NULL || serverNameType == NULL) {
2540	updateError(NULL, error, EINVAL, CFSTR("key missing from AuthenticationInfo"));
2541	return NULL;
2542    }
2543
2544    nt = ntstring2oid(serverNameType);
2545    if (nt == NULL)
2546	nt = GSS_C_NT_HOSTBASED_SERVICE;
2547
2548    buffer.value = cf2cstring(serverName);
2549    if (buffer.value == NULL)
2550	return NULL;
2551    buffer.length = strlen((char *)buffer.value);
2552
2553    major_status = gss_import_name(&minor_status, &buffer, nt, &name);
2554    free(buffer.value);
2555    if (major_status) {
2556	updateError(NULL, error, major_status, CFSTR("Failed create name for %@"), serverName);
2557	return NULL;
2558    }
2559
2560    return name;
2561}
2562
2563gss_OID
2564NAHAuthenticationInfoGetGSSMechanism(CFDictionaryRef authInfo, CFErrorRef *error)
2565{
2566    CFStringRef mechanism = CFDictionaryGetValue(authInfo, kNAHMechanism);
2567
2568    if (mechanism == NULL) {
2569	updateError(NULL, error, EINVAL, CFSTR("key missing from AuthenticationInfo"));
2570	return NULL;
2571    }
2572
2573    return name2oid(mechanism);
2574}
2575
2576/*
2577 * These are the maching the OID version kGSSAPIMech<name>OID
2578 */
2579
2580CFStringRef kGSSAPIMechNTLM = CFSTR("NTLM");
2581CFStringRef kGSSAPIMechKerberos = CFSTR("Kerberos");
2582CFStringRef kGSSAPIMechKerberosU2U = CFSTR("KerberosUser2User");
2583CFStringRef kGSSAPIMechKerberosMicrosoft = CFSTR("KerberosMicrosoft");
2584CFStringRef kGSSAPIMechIAKerb = CFSTR("IAKerb");
2585CFStringRef kGSSAPIMechPKU2U = CFSTR("PKU2U");
2586CFStringRef kGSSAPIMechSPNEGO = CFSTR("SPNEGO");
2587