1/*
2 * Copyright (c) 2006 - 2007 Kungliga Tekniska H�gskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Portions Copyright (c) 2009 - 2011 Apple Inc. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * 3. Neither the name of the Institute nor the names of its contributors
20 *    may be used to endorse or promote products derived from this software
21 *    without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36#define HC_DEPRECATED_CRYPTO
37
38#include "headers.h"
39
40typedef struct pk_client_params pk_client_params;
41struct DigestREQ;
42struct Kx509Request;
43typedef struct kdc_request_desc *kdc_request_t;
44
45#include <kdc-private.h>
46
47#include <asn1-common.h>
48#include <digest_asn1.h>
49#include <heimntlm.h>
50#include <roken.h>
51
52#include <gssapi.h>
53#include <gssapi_spi.h>
54#include <gssapi_ntlm.h>
55
56#include "heimbase.h"
57
58#ifdef HAVE_OPENDIRECTORY
59
60#include <CoreFoundation/CoreFoundation.h>
61#include <OpenDirectory/OpenDirectory.h>
62#include <SystemConfiguration/SCPreferences.h>
63
64#ifdef __APPLE_PRIVATE__
65#include <OpenDirectory/OpenDirectoryPriv.h>
66#endif
67
68
69#endif
70
71#if defined(__APPLE_PRIVATE__) && !defined(__APPLE_TARGET_EMBEDDED__)
72#include <dlfcn.h>
73#define HAVE_APPLE_NETLOGON 1
74#endif
75
76
77#ifdef __APPLE__
78#include <sandbox.h>
79
80static int sandbox_flag = 1;
81#endif
82static int use_unix_flag = 0;
83static int help_flag = 0;
84static int version_flag = 0;
85
86
87
88static krb5_kdc_configuration *config = NULL;
89
90static void
91fillin_hostinfo(struct ntlm_targetinfo *ti);
92
93static const char *
94derive_version_ntq(const NTLMRequest2 *ntq)
95{
96    if ((ntq->ntlmFlags & NTLM_NEG_NTLM2_SESSION) &&
97	(ntq->lmChallengeResponse.length >= 8) &&
98	(ntq->ntChallengeResponse.length == 24))
99	return "ntlmv1-with-v2-session";
100    if (ntq->ntChallengeResponse.length == 24)
101	return "ntlmv1";
102    if (ntq->ntChallengeResponse.length > 24)
103	return "ntlmv2";
104    if (ntq->ntChallengeResponse.length == 0)
105	return "lm";
106    return "unknown";
107}
108
109static void
110log_complete(krb5_context context,
111	     const char *type,
112	     const NTLMReply *ntp,
113	     krb5_error_code ret,
114	     const char *ntlm_version)
115{
116
117    if (ntp->success) {
118	char str[256] = { 0 } ;
119	heim_ntlm_unparse_flags(ntp->ntlmFlags, str, sizeof(str));
120
121	kdc_log(context, config, 1, "digest-request %s: ok user=%s\\%s proto=%s flags: %s",
122		type,
123		ntp->domain, ntp->user, ntlm_version, str);
124    } else
125	kdc_log(context, config, 1, "digest-request: %s failed with %d proto=%s", type, ret, ntlm_version);
126}
127
128
129#ifdef ENABLE_NTLM
130
131static int
132validate_local(krb5_context context,
133	       const char *key,
134	       const char *local,
135	       const char *client)
136{
137    if (local == NULL)
138	return 0;
139    if (client == NULL || strcmp(local, client) != 0) {
140	kdc_log(context, config, 1, "validate failed for key %s: %s client: %s",
141		key, local, client ? client : "<not sent>");
142	return EAUTH;
143    }
144    return 0;
145}
146
147/*
148 * Validate that the client sent back the expected target info,
149 * the important keys are:
150 * - servername
151 * - timestamp
152 */
153
154static int
155validate_targetinfo(krb5_context context,
156		    struct ntlm_buf *tibuf,
157		    const char *domain,
158		    const char *acceptorUser,
159		    uint32_t *avflags)
160{
161    struct ntlm_targetinfo localti;
162    struct ntlm_targetinfo ti;
163    int ret;
164
165    if (tibuf->length == 0)
166	return EAUTH;
167
168    memset(&localti, 0, sizeof(localti));
169
170    fillin_hostinfo(&localti);
171
172    ret = heim_ntlm_decode_targetinfo(tibuf, 1, &ti);
173    if (ret)
174	goto out;
175
176    if (ti.timestamp) {
177	time_t now = time(NULL);
178	/*
179	 * "now" should be equal or forward in compared to
180	 * ti.timestamp, but lets be liberal here.
181	 */
182	if (krb5_time_abs(now, heim_ntlm_ts2unixtime(ti.timestamp)) > heim_ntlm_time_skew) {
183	    ret = EAUTH;
184	    goto out;
185	}
186    }
187
188    ret = validate_local(context, "servername", localti.servername, ti.servername);
189    if (ret)
190	goto out;
191    ret = validate_local(context, "domainname", domain, ti.domainname);
192    if (ret)
193	goto out;
194    if (domain == NULL) {
195	ret = validate_local(context, "domainname", localti.domainname, ti.domainname);
196	if (ret)
197	    goto out;
198    }
199
200    *avflags = ti.avflags;
201
202    /*
203     * Check that the service that the client send is matching what
204     * the server claims to be.
205     */
206    if (acceptorUser[0] != '\0' && ti.targetname) {
207	const char *p = strchr(ti.targetname, '/');
208	size_t len = strlen(acceptorUser);
209
210	if (p && len != (p - ti.targetname) &&
211	    strncasecmp(ti.targetname, acceptorUser, len) != 0)
212	{
213	    ret = EAUTH;
214	    goto out;
215	}
216    }
217
218    /* more validation here
219    char *dnstreename;
220    char *targetname;
221    uint32_t avflags;
222    struct ntlm_buf channel_bindings;
223    */
224
225 out:
226    heim_ntlm_free_targetinfo(&ti);
227    heim_ntlm_free_targetinfo(&localti);
228
229    return ret;
230}
231
232
233
234struct ntlm_ftable {
235    void		*ctx;
236    krb5_error_code	(*init)(krb5_context, void **);
237    void		(*fini)(void *);
238    int			(*ti)(void *, struct ntlm_targetinfo *ti);
239    uint32_t		(*filter_flags)(krb5_context, void *);
240    int			(*authenticate)(void *ctx,
241					krb5_context context,
242					const NTLMRequest2 *ntq,
243					void *response_ctx,
244					void (response)(void *, const NTLMReply *reply));
245};
246
247static uint32_t
248kdc_flags(krb5_context context, void *ctx)
249{
250    return 0;
251}
252
253
254static int
255kdc_authenticate(void *ctx,
256		 krb5_context context,
257		 const NTLMRequest2 *ntq,
258		 void *response_ctx,
259		 void (response)(void *, const NTLMReply *ntp))
260{
261    krb5_principal client = NULL;
262    unsigned char sessionkey[16];
263    krb5_data sessionkeydata;
264    hdb_entry_ex *user = NULL;
265    HDB *clientdb = NULL;
266    Key *key = NULL;
267    NTLMReply ntp;
268    const char *ntlm_version = "unknown";
269    int ret;
270
271    krb5_data_zero(&sessionkeydata);
272    memset(&ntp, 0, sizeof(ntp));
273
274    kdc_log(context, config, 1, "digest-request: user=%s\\%s",
275	    ntq->loginDomainName, ntq->loginUserName);
276
277    if (ntq->lmchallenge.length != 8){
278	ret = EINVAL;
279	goto failed;
280    }
281
282    if (ntq->ntChallengeResponse.length == 0) {
283	ret = EINVAL;
284	goto failed;
285    }
286
287    ret = krb5_make_principal(context, &client, ntq->loginDomainName,
288			      ntq->loginUserName, NULL);
289    if (ret)
290	goto failed;
291
292    krb5_principal_set_type(context, client, KRB5_NT_NTLM);
293
294    ret = _kdc_db_fetch(context, config, client,
295			HDB_F_GET_CLIENT, NULL, &clientdb, &user);
296    if (ret)
297	goto failed;
298
299    if (user->entry.principal->name.name_string.len < 1) {
300	kdc_log(context, config, 2,
301		"digest-request: user %s\\%s, have weired name",
302		ntq->loginDomainName, ntq->loginUserName);
303	goto failed;
304    }
305
306    ntp.domain = user->entry.principal->realm;
307    ntp.user = user->entry.principal->name.name_string.val[0];
308
309    ret = hdb_enctype2key(context, &user->entry,
310			  ETYPE_ARCFOUR_HMAC_MD5, &key);
311    if (ret) {
312	kdc_log(context, config, 2,
313		"digest-request: user %s\\%s, missing NTLM key",
314		ntp.domain, ntp.user);
315	goto failed;
316    }
317
318    kdc_log(context, config, 2,
319	    "digest-request: found user, processing ntlm request", ret);
320
321    if (ntq->ntChallengeResponse.length != 24) {
322	struct ntlm_buf targetinfo, answer;
323	unsigned char ntlmv2[16];
324
325	ntlm_version = "ntlmv2";
326
327	if (!gss_mo_get(GSS_NTLM_MECHANISM, GSS_C_NTLM_V2, NULL)) {
328	    kdc_log(context, config, 2, "NTLMv2 disabled");
329	    goto failed;
330	}
331
332#if 0 /* if there is an LM2 challange we should check it */
333	if (ntq->lmChallengeResponse.length < 8) {
334	    kdc_log(context, config, 2,
335		    "lmChallengeResponse field is < 8 (%d)",
336		    (int)ntq->lmChallengeResponse.length);
337	    goto failed;
338	}
339#endif
340
341	answer.length = ntq->ntChallengeResponse.length;
342	answer.data = ntq->ntChallengeResponse.data;
343
344	ret = heim_ntlm_verify_ntlm2(key->key.keyvalue.data,
345				     key->key.keyvalue.length,
346				     ntq->loginUserName,
347				     ntq->loginDomainName,
348				     0,
349				     ntq->lmchallenge.data,
350				     &answer,
351				     &targetinfo,
352				     ntlmv2);
353	if (ret) {
354	    if (clientdb->hdb_auth_status)
355		clientdb->hdb_auth_status(context, clientdb, user,
356					  HDB_AUTH_WRONG_PASSWORD);
357
358	    kdc_log(context, config, 2,
359		    "digest-request: verify ntlm2 hash failed", ret);
360	    goto failed;
361	}
362
363	if (clientdb->hdb_auth_status)
364	    clientdb->hdb_auth_status(context, clientdb, user,
365				      HDB_AUTH_SUCCESS);
366
367	{
368	    size_t len = MIN(ntq->ntChallengeResponse.length, 16);
369	    CCHmacContext c;
370
371	    kdc_log(context, config, 2,
372		    "digest-request: basic key");
373
374	    CCHmacInit(&c, kCCHmacAlgMD5, ntlmv2, sizeof(ntlmv2));
375	    CCHmacUpdate(&c, ntq->ntChallengeResponse.data, len);
376	    CCHmacFinal(&c, sessionkey);
377	    memset(&c, 0, sizeof(c));
378	}
379
380	memset(ntlmv2, 0, sizeof(ntlmv2));
381
382	ret = validate_targetinfo(context, &targetinfo, ntq->t2targetname, ntq->acceptorUser,
383				  &ntp.avflags);
384	if (ret) {
385	    kdc_log(context, config, 2, "NTLMv2 targetinfo validation failed");
386	    goto failed;
387	}
388
389	ntp.targetinfo.data = targetinfo.data;
390	ntp.targetinfo.length = targetinfo.length;
391
392    } else {
393	struct ntlm_buf answer;
394	uint8_t challenge[8];
395	uint8_t usk[16];
396	CCDigestRef digest;
397
398	ntlm_version = "ntlmv1";
399
400	if (!gss_mo_get(GSS_NTLM_MECHANISM, GSS_C_NTLM_V1, NULL)) {
401	    kdc_log(context, config, 2, "NTLMv1 disabled");
402	    goto failed;
403	}
404
405	if (ntq->lmchallenge.length != 8) {
406	    ret = EINVAL;
407	    goto failed;
408	}
409
410	if (ntq->ntlmFlags & NTLM_NEG_NTLM2_SESSION) {
411	    unsigned char sessionhash[CC_MD5_DIGEST_LENGTH];
412
413	    /* the first first 8 bytes is the challenge, what is the other 16 bytes ? */
414	    if (ntq->lmChallengeResponse.length != 24) {
415		ret = EINVAL;
416		goto failed;
417	    }
418
419	    digest = CCDigestCreate(kCCDigestMD5);
420	    CCDigestUpdate(digest, ntq->lmchallenge.data, 8);
421	    CCDigestUpdate(digest, ntq->lmChallengeResponse.data, 8);
422	    CCDigestFinal(digest, sessionhash);
423	    CCDigestDestroy(digest);
424	    memcpy(challenge, sessionhash, sizeof(challenge));
425	} else {
426	    memcpy(challenge, ntq->lmchallenge.data, ntq->lmchallenge.length);
427	}
428
429	ret = heim_ntlm_calculate_ntlm1(key->key.keyvalue.data,
430					key->key.keyvalue.length,
431					challenge, &answer);
432	if (ret)
433	    goto failed;
434
435	if (ntq->ntChallengeResponse.length != answer.length ||
436	    memcmp(ntq->ntChallengeResponse.data, answer.data, answer.length) != 0) {
437	    free(answer.data);
438	    kdc_log(context, config, 2,
439		    "digest-request: verify ntlm1 hash failed", ret);
440	    ret = EINVAL;
441	    goto failed;
442	}
443	free(answer.data);
444
445	digest = CCDigestCreate(kCCDigestMD4);
446	CCDigestUpdate(digest,
447		       key->key.keyvalue.data,
448		       key->key.keyvalue.length);
449	CCDigestFinal(digest, usk);
450	CCDigestDestroy(digest);
451
452
453	if ((ntq->ntlmFlags & NTLM_NEG_NTLM2_SESSION) != 0) {
454	    CCHmacContext hc;
455
456	    CCHmacInit(&hc, kCCHmacAlgMD5, usk, sizeof(usk));
457	    CCHmacUpdate(&hc, ntq->lmchallenge.data, 8);
458	    CCHmacUpdate(&hc, ntq->lmChallengeResponse.data, 8);
459	    CCHmacFinal(&hc, sessionkey);
460	    memset(&hc, 0, sizeof(hc));
461	} else {
462	    memcpy(sessionkey, usk, sizeof(sessionkey));
463	}
464    }
465
466    if (ntq->ntlmFlags & NTLM_NEG_KEYEX) {
467	struct ntlm_buf base, enc, sess;
468
469	base.data = sessionkey;
470	base.length = sizeof(sessionkey);
471	enc.data = ntq->encryptedSessionKey.data;
472	enc.length = ntq->encryptedSessionKey.length;
473
474	ret = heim_ntlm_keyex_unwrap(&base, &enc, &sess);
475	if (ret != 0)
476	    goto failed;
477	if (sess.length != sizeof(sessionkey)) {
478	    heim_ntlm_free_buf(&sess);
479	    ret = EINVAL;
480	    goto failed;
481	}
482	memcpy(sessionkey, sess.data, sizeof(sessionkey));
483	heim_ntlm_free_buf(&sess);
484    }
485
486    sessionkeydata.data = sessionkey;
487    sessionkeydata.length = sizeof(sessionkey);
488    ntp.sessionkey = &sessionkeydata;
489
490    ntp.success = 1;
491    ntp.ntlmFlags = ntq->ntlmFlags;
492
493    (*response)(response_ctx, &ntp);
494
495failed:
496    log_complete(context, "kdc", &ntp, ret, ntlm_version);
497
498    if (ntp.targetinfo.data)
499	free(ntp.targetinfo.data);
500
501    if (user)
502	_kdc_free_ent (context, user);
503
504    if (client)
505	krb5_free_principal(context, client);
506
507    return ret;
508}
509
510/*
511 *
512 */
513
514#ifdef HAVE_OPENDIRECTORY
515
516struct ntlmod {
517    ODNodeRef node;
518};
519
520/*
521 *
522 */
523
524static CFArrayRef meta_keys;
525static char *ntlmDomain;
526
527static void
528update_ntlm(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info)
529{
530    CFDictionaryRef settings;
531    CFStringRef n;
532
533    if (store == NULL)
534	return;
535
536    settings = (CFDictionaryRef)SCDynamicStoreCopyValue(store, CFSTR("com.apple.smb"));
537    if (settings == NULL)
538	return;
539
540    n = CFDictionaryGetValue(settings, CFSTR("NetBIOSName"));
541    if (n == NULL || CFGetTypeID(n) != CFStringGetTypeID())
542	goto fin;
543
544    if (ntlmDomain)
545	free(ntlmDomain);
546    ntlmDomain = rk_cfstring2cstring(n);
547    strupr(ntlmDomain);
548
549fin:
550    CFRelease(settings);
551    return;
552}
553
554static void
555ntlm_notification(void)
556{
557    SCDynamicStoreRef store;
558    dispatch_queue_t queue;
559
560    store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("com.apple.Kerberos.gss-od"), update_ntlm, NULL);
561    if (store == NULL)
562	return;
563
564    CFTypeRef key[] = {CFSTR("com.apple.smb")};
565    CFArrayRef keys = CFArrayCreate(kCFAllocatorDefault, key, 1, NULL);
566    SCDynamicStoreSetNotificationKeys(store, keys, NULL);
567    CFRelease(keys);
568
569    queue = dispatch_queue_create("com.apple.gss-od.ntlm-name", NULL);
570    if (queue == NULL) {
571	CFRelease(store);
572	errx(1, "dispatch_queue_create");
573    }
574
575    SCDynamicStoreSetDispatchQueue(store, queue);
576    CFRelease(store);
577
578    dispatch_sync(queue, ^{ update_ntlm(store, NULL, NULL); });
579}
580
581
582static void
583load_meta_keys(void)
584{
585    CFMutableArrayRef keys;
586
587    keys = CFArrayCreateMutable(NULL, 1, &kCFTypeArrayCallBacks);
588    CFArrayAppendValue(keys, kODAttributeTypeMetaNodeLocation);
589    meta_keys = keys;
590}
591
592
593static int
594od_init(krb5_context context, void **ctx)
595{
596    static dispatch_once_t once;
597    struct ntlmod *c;
598
599    dispatch_once(&once, ^{
600	ntlm_notification();
601	load_meta_keys();
602    });
603
604    if (ntlmDomain == NULL)
605	return ENOENT;
606
607    c = calloc(1, sizeof(*c));
608    if (c == NULL)
609	return ENOMEM;
610
611    c->node = ODNodeCreateWithNodeType(kCFAllocatorDefault,
612				       kODSessionDefault,
613				       kODNodeTypeAuthentication, NULL);
614    if (c->node == NULL) {
615	free(c);
616	return ENOENT;
617    }
618
619    *ctx = c;
620
621    return 0;
622}
623
624
625static uint32_t
626od_flags(krb5_context context, void *ctx)
627{
628    CFArrayRef subnodes;
629    CFIndex count, n;
630    ODNodeRef node;
631
632    if (gss_mo_get(GSS_NTLM_MECHANISM, GSS_C_NTLM_SESSION_KEY, NULL))
633	return 0;
634
635#ifdef __APPLE_PRIVATE__
636    subnodes = ODSessionCopySessionKeySupport(kODSessionDefault);
637    if (subnodes) {
638	int haveLDAPSessionKeySupport = 0;
639
640	count = CFArrayGetCount(subnodes);
641	for (n = 0; n < count; n++) {
642	    CFStringRef name;
643
644	    node = (ODNodeRef)CFArrayGetValueAtIndex(subnodes, n);
645
646	    name = ODNodeGetName(node);
647	    if (name && CFStringHasPrefix(name, CFSTR("/LDAPv3/"))) {
648		kdc_log(context, config, 2, "digest-request: have /LDAPv3/ nodes with signing");
649		haveLDAPSessionKeySupport = 1;
650		break;
651	    }
652	}
653	CFRelease(subnodes);
654
655	if (haveLDAPSessionKeySupport)
656	    return 0;
657
658	kdc_log(context, config, 2, "digest-request: have no LDAPv3 nodes with signing");
659
660    } else {
661	kdc_log(context, config, 2, "digest-request: have no nodes with signing");
662    }
663#endif
664
665    node = ODNodeCreateWithName(kCFAllocatorDefault, kODSessionDefault, CFSTR("/LDAPv3"), NULL);
666    if (node == NULL)
667	return 0;
668
669    subnodes = ODNodeCopySubnodeNames(node, NULL);
670    CFRelease(node);
671
672    if (subnodes == NULL)
673	return 0;
674
675    count = CFArrayGetCount(subnodes);
676    CFRelease(subnodes);
677    if (count == 0)
678	return 0;
679
680    /*
681     * Drat, we have OD configured. So no session key support, we must turn that off for everyone!
682     */
683
684    return NTLM_NEG_SIGN | NTLM_NEG_SEAL |
685	NTLM_NEG_ALWAYS_SIGN | NTLM_NEG_NTLM2_SESSION |
686	NTLM_NEG_KEYEX;
687}
688
689static int
690od_authenticate(void *ctx,
691		krb5_context context,
692		const NTLMRequest2 *ntq,
693		void *response_ctx,
694		void (response)(void *, const NTLMReply *ntp))
695{
696    struct ntlmod *c = ctx;
697    CFArrayRef outAuthItems = NULL;
698    CFMutableArrayRef authItems = NULL;
699    ODRecordRef record = NULL;
700    Boolean b;
701    CFStringRef username = NULL, domain = NULL;
702    CFErrorRef error = NULL;
703    CFMutableDataRef challenge = NULL, resp = NULL;
704    CFArrayRef values = NULL;
705    NTLMReply ntp;
706    int ret = EINVAL;
707
708    if (ntlmDomain == NULL || ctx == NULL) {
709	kdc_log(context, config, 2, "digest-request: no ntlmDomain");
710	return EINVAL;
711    }
712
713    memset(&ntp, 0, sizeof(ntp));
714
715    username = CFStringCreateWithCString(NULL, ntq->loginUserName,
716					 kCFStringEncodingUTF8);
717    if (username == NULL) {
718	ret = ENOMEM;
719	goto out;
720    }
721
722    challenge = CFDataCreateMutable(NULL, 0);
723    if (challenge == NULL) {
724	ret = ENOMEM;
725	goto out;
726    }
727    CFDataAppendBytes(challenge, ntq->lmchallenge.data, ntq->lmchallenge.length);
728
729    resp = CFDataCreateMutable(NULL, 0);
730    if (response == NULL) {
731	ret = ENOMEM;
732	goto out;
733    }
734    CFDataAppendBytes(resp, ntq->ntChallengeResponse.data, ntq->ntChallengeResponse.length);
735
736    domain = CFStringCreateWithCString(NULL, ntq->loginDomainName, kCFStringEncodingUTF8);
737    if (domain == NULL) {
738	ret = ENOMEM;
739	goto out;
740    }
741
742    authItems = CFArrayCreateMutable(NULL, 5, &kCFTypeArrayCallBacks);
743    if (authItems == NULL) {
744	ret = ENOMEM;
745	goto out;
746    }
747
748    CFArrayAppendValue(authItems, username);
749    CFArrayAppendValue(authItems, challenge);
750    CFArrayAppendValue(authItems, resp);
751    CFArrayAppendValue(authItems, username);
752    CFArrayAppendValue(authItems, domain);
753
754    record = ODNodeCopyRecord(c->node, kODRecordTypeUsers,
755			      username, meta_keys, &error);
756    if (record == NULL) {
757	kdc_log(context, config, 2, "digest-request: failed to find user %s in OD", ntq->loginUserName);
758	ret = ENOENT;
759	goto out;
760    }
761
762    values = ODRecordCopyValues(record, kODAttributeTypeMetaNodeLocation, NULL);
763    if (values && CFArrayGetCount(values) > 0) {
764	CFStringRef str = (CFStringRef)CFArrayGetValueAtIndex(values, 0);
765	if (CFStringGetTypeID() != CFGetTypeID(str)) {
766	    ret = EINVAL;
767	    goto out;
768	}
769	if (!CFStringHasPrefix(str, CFSTR("/LDAPv3/"))) {
770	    kdc_log(context, config, 2, "digest-request: user not in /LDAPv3");
771	    ret = ENOENT;
772	    goto out;
773	}
774    }
775
776    b = ODRecordVerifyPasswordExtended(record,
777				       kODAuthenticationTypeNTLMv2WithSessionKey,
778				       authItems, &outAuthItems, NULL,
779				       &error);
780
781    if (!b) {
782	kdc_log(context, config, 2, "digest-request: authentication failed");
783	ret = ENOENT;
784	goto out;
785    }
786
787    ntp.ntlmFlags = ntq->ntlmFlags;
788
789    /*
790     * If the NTLMv2 session key is supported, it is returned in the
791     * output buffer
792     */
793
794    if (outAuthItems && CFArrayGetCount(outAuthItems) > 0) {
795	CFDataRef data = (CFDataRef)CFArrayGetValueAtIndex(outAuthItems, 0);
796	if (CFGetTypeID(data) == CFDataGetTypeID() && CFDataGetLength(data) >= 16) {
797	    ntp.sessionkey = calloc(1, sizeof(*ntp.sessionkey));
798	    if (ntp.sessionkey == NULL) {
799		ret = ENOMEM;
800		goto out;
801	    }
802	    krb5_data_copy(ntp.sessionkey, CFDataGetBytePtr(data), 16);
803
804	    if (ntp.ntlmFlags & NTLM_NEG_KEYEX) {
805		struct ntlm_buf base, enc, sess;
806
807		base.data = ntp.sessionkey->data;
808		base.length = ntp.sessionkey->length;
809		enc.data = ntq->encryptedSessionKey.data;
810		enc.length = ntq->encryptedSessionKey.length;
811
812		ret = heim_ntlm_keyex_unwrap(&base, &enc, &sess);
813		if (ret != 0)
814		    goto out;
815		if (sess.length != ntp.sessionkey->length) {
816		    heim_ntlm_free_buf(&sess);
817		    ret = EINVAL;
818		    goto out;
819		}
820		memcpy(ntp.sessionkey->data, sess.data, ntp.sessionkey->length);
821		heim_ntlm_free_buf(&sess);
822	    }
823	}
824
825    } else {
826	/*
827	 * No ntlmv2 session key or keyex for OD (this it too late to strip of these flags)
828	 */
829	ntp.ntlmFlags &= ~(NTLM_NEG_KEYEX|NTLM_NEG_NTLM2_SESSION);
830    }
831
832
833    /*
834     * If no session key was passed back, strip of SIGN/SEAL
835     */
836
837    if (ntp.sessionkey == NULL || ntp.sessionkey->length == 0)
838	ntp.ntlmFlags &= ~(NTLM_NEG_SIGN|NTLM_NEG_SEAL|NTLM_NEG_ALWAYS_SIGN);
839
840
841    ntp.user = ntq->loginUserName;
842    ntp.domain = ntlmDomain;
843
844    ntp.success = 1;
845    response(response_ctx, &ntp);
846    ret = 0;
847
848out:
849    log_complete(context, "od", &ntp, ret, derive_version_ntq(ntq));
850
851    if (values)
852	CFRelease(values);
853    if (error)
854	CFRelease(error);
855    if (challenge)
856	CFRelease(challenge);
857    if (resp)
858	CFRelease(resp);
859    if (username)
860	CFRelease(username);
861    if (domain)
862	CFRelease(domain);
863    if (authItems)
864	CFRelease(authItems);
865    if (outAuthItems)
866	CFRelease(outAuthItems);
867    if (record)
868	CFRelease(record);
869
870    return ret;
871}
872
873#endif
874
875/*
876 *
877 */
878
879static uint32_t
880guest_flags(krb5_context context, void *ctx)
881{
882    return 0;
883}
884
885static int
886guest_authenticate(void *ctx,
887		   krb5_context context,
888		   const NTLMRequest2 *ntq,
889		   void *response_ctx,
890		   void (response)(void *, const NTLMReply *ntp))
891{
892    unsigned char sessionkey[16];
893    krb5_data sessionkeydata;
894    NTLMReply ntp;
895    int is_guest = (strcasecmp("GUEST", ntq->loginUserName) == 0);
896    int ret = EINVAL;
897
898    memset(&ntp, 0, sizeof(ntp));
899
900    if (is_guest || (ntq->ntlmFlags & NTLM_NEG_ANONYMOUS)) {
901	kdc_log(context, config, 2,
902		"digest-request: found guest, let in");
903	ntp.avflags |= NTLM_TI_AV_FLAG_GUEST;
904	ntp.user = "GUEST";
905
906#ifdef HAVE_OPENDIRECTORY
907	ntp.domain = ntlmDomain;
908#endif
909	if (ntp.domain == NULL)
910	    ntp.domain = ntq->loginDomainName;
911
912	if (ntp.user == NULL || ntp.domain == NULL)
913	    goto out;
914
915	if (is_guest) {
916	    /* no password */
917	    CCDigest(kCCDigestMD4, (void *)"", 0, sessionkey);
918	}
919    } else {
920	ret = EINVAL;
921	goto out;
922    }
923
924    if ((ntq->ntlmFlags & NTLM_NEG_ANONYMOUS)) {
925
926    } else if (ntq->ntChallengeResponse.length != 24) {
927	size_t len = MIN(ntq->ntChallengeResponse.length, 16);
928	struct ntlm_buf targetinfo, answer;
929	uint8_t ntlmv2[16];
930	CCHmacContext c;
931
932	answer.length = ntq->ntChallengeResponse.length;
933	answer.data = ntq->ntChallengeResponse.data;
934
935	ret = heim_ntlm_verify_ntlm2(sessionkey,
936				     sizeof(sessionkey),
937				     ntq->loginUserName,
938				     ntq->loginDomainName,
939				     0,
940				     ntq->lmchallenge.data,
941				     &answer,
942				     &targetinfo,
943				     ntlmv2);
944	if (ret) {
945	    kdc_log(context, config, 2,
946		    "digest-request: guest authentication failed: %d", ret);
947	    goto out;
948	}
949
950	free(targetinfo.data);
951
952	kdc_log(context, config, 2,
953		"digest-request: basic key");
954
955	CCHmacInit(&c, kCCHmacAlgMD5, ntlmv2, sizeof(ntlmv2));
956	CCHmacUpdate(&c, ntq->ntChallengeResponse.data, len);
957	CCHmacFinal(&c, sessionkey);
958	memset(&c, 0, sizeof(c));
959    } else {
960	CCDigestRef digest;
961	uint8_t usk[16];
962
963	digest = CCDigestCreate(kCCDigestMD4);
964	CCDigestUpdate(digest, sessionkey, sizeof(sessionkey));
965	CCDigestFinal(digest, usk);
966	CCDigestDestroy(digest);
967
968	if ((ntq->ntlmFlags & NTLM_NEG_NTLM2_SESSION) != 0) {
969	    CCHmacContext hc;
970
971	    CCHmacInit(&hc, kCCHmacAlgMD5, usk, sizeof(usk));
972	    CCHmacUpdate(&hc, ntq->lmchallenge.data, 8);
973	    CCHmacUpdate(&hc, ntq->lmChallengeResponse.data, 8);
974	    CCHmacFinal(&hc, sessionkey);
975	    memset(&hc, 0, sizeof(hc));
976	} else {
977	    memcpy(sessionkey, usk, sizeof(sessionkey));
978	}
979
980    }
981
982    if (ntq->ntlmFlags & NTLM_NEG_KEYEX) {
983	struct ntlm_buf base, enc, sess;
984
985	base.data = sessionkey;
986	base.length = sizeof(sessionkey);
987	enc.data = ntq->encryptedSessionKey.data;
988	enc.length = ntq->encryptedSessionKey.length;
989
990	ret = heim_ntlm_keyex_unwrap(&base, &enc, &sess);
991	if (ret != 0)
992	    goto out;
993	if (sess.length != sizeof(sessionkey)) {
994	    heim_ntlm_free_buf(&sess);
995	    ret = EINVAL;
996	    goto out;
997	}
998	memcpy(sessionkey, sess.data, sizeof(sessionkey));
999	heim_ntlm_free_buf(&sess);
1000    }
1001
1002    sessionkeydata.data = sessionkey;
1003    sessionkeydata.length = sizeof(sessionkey);
1004    ntp.sessionkey = &sessionkeydata;
1005
1006    ntp.ntlmFlags = ntq->ntlmFlags;
1007    ntp.success = 1;
1008
1009    response(response_ctx, &ntp);
1010    ret = 0;
1011out:
1012    log_complete(context, "guest", &ntp, ret, derive_version_ntq(ntq));
1013
1014    return ret;
1015}
1016
1017#ifdef HAVE_APPLE_NETLOGON
1018
1019#define LOGON_USER				0x00000001L
1020#define LOGON_NOENCRYPTION			0x00000002L
1021#define LOGON_CACHED_ACCOUNT			0x00000004L
1022#define LOGON_USED_LM_PASSWORD			0x00000008L
1023#define LOGON_EXTRA_SIDS			0x00000020L
1024#define LOGON_SUBAUTH_SESSION_KEY		0x00000040L
1025#define LOGON_SERVER_TRUST_ACCOUNT		0x00000080L
1026#define LOGON_NTLMV2_ENABLED			0x00000100L
1027#define LOGON_RESOURCE_GROUPS			0x00000200L
1028#define LOGON_PROFILE_PATH_RETURNED		0x00000400L
1029#define LOGON_NT_V2				0x00000800L
1030#define LOGON_LM_V2				0x00001000L
1031#define LOGON_NTLM_V2				0x00002000L
1032
1033#define NTLM_LOGON_REQ_VERSION    1
1034
1035typedef struct _NTLM_LOGON_REQ {
1036    uint32_t Version;
1037    const char *LogonDomainName;
1038    const char *UserName;
1039    const char *Workstation;
1040    uint8_t LmChallenge[8];
1041    const uint8_t *NtChallengeResponse;
1042    uint16_t NtChallengeResponseLength;
1043    const uint8_t *LmChallengeResponse;
1044    uint16_t LmChallengeResponseLength;
1045} NTLM_LOGON_REQ, *PNTLM_LOGON_REQ;
1046
1047typedef uint32_t
1048(*NTLM_LOGON_FN)(const NTLM_LOGON_REQ *LogonReq,
1049		 uint8_t **pAuthData,
1050		 size_t *pAuthDataSize,
1051		 char **pAccountName,
1052		 char **pAccountDomain,
1053		 uint8_t SessionKey[16],
1054		 uint32_t *Flags);
1055
1056typedef enum _NETLOGON_PROBE_STATUS {
1057    NETLOGON_PROBE_UNAVAILABLE = -1,    /* Could not reach RPC server */
1058    NETLOGON_PROBE_NOT_CONFIGURED = 0,  /* RPC server available but not joined to domain */
1059    NETLOGON_PROBE_CONFIGURED = 1,      /* RPC server available and joined to domain */
1060    NETLOGON_PROBE_CONNECTED = 2        /* RPC server available and secure channel up */
1061} NETLOGON_PROBE_STATUS;
1062
1063typedef NETLOGON_PROBE_STATUS
1064(*NET_LOGON_PROBE_FN)(const char *, char **);
1065
1066/*
1067 *
1068 */
1069
1070struct ntlmnetr {
1071    OM_uint32 flags;
1072    char *domain;
1073};
1074
1075/*
1076 *
1077 */
1078
1079static char *netlogonDomain;
1080static char *netlogonServer;
1081
1082
1083static void
1084update_domain(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info)
1085{
1086    CFDictionaryRef settings;
1087    CFStringRef n;
1088    size_t len;
1089
1090    if (store == NULL)
1091	return;
1092
1093    settings = (CFDictionaryRef)SCDynamicStoreCopyValue(store, CFSTR("com.apple.opendirectoryd.ActiveDirectory"));
1094    if (settings == NULL)
1095	return;
1096
1097    n = CFDictionaryGetValue(settings, CFSTR("DomainNameFlat"));
1098    if (n == NULL || CFGetTypeID(n) != CFStringGetTypeID())
1099	goto fin;
1100
1101    if (netlogonDomain)
1102	free(netlogonDomain);
1103    netlogonDomain = rk_cfstring2cstring(n);
1104
1105    n = CFDictionaryGetValue(settings, CFSTR("TrustAccount"));
1106    if (n == NULL || CFGetTypeID(n) != CFStringGetTypeID())
1107	goto fin;
1108
1109    if (netlogonServer)
1110	free(netlogonServer);
1111    netlogonServer = rk_cfstring2cstring(n);
1112
1113    len = strlen(netlogonServer);
1114    if (len > 1 && netlogonServer[len - 1] == '$')
1115	netlogonServer[len - 1] = '\0';
1116
1117    strupr(netlogonServer);
1118
1119fin:
1120    CFRelease(settings);
1121    return;
1122}
1123
1124
1125static void
1126domain_notification(void)
1127{
1128    SCDynamicStoreRef store;
1129    dispatch_queue_t queue;
1130
1131    store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("com.apple.Kerberos.gss-netlogon"), update_domain, NULL);
1132    if (store == NULL)
1133	return;
1134
1135    CFTypeRef key[] = {CFSTR("com.apple.opendirectoryd.ActiveDirectory")};
1136    CFArrayRef keys = CFArrayCreate(kCFAllocatorDefault, key, 1, NULL);
1137    SCDynamicStoreSetNotificationKeys(store, keys, NULL);
1138    CFRelease(keys);
1139
1140    queue = dispatch_queue_create("com.apple.gss-netlogon.ntlm-name", NULL);
1141    if (queue == NULL) {
1142	CFRelease(store);
1143	errx(1, "dispatch_queue_create");
1144    }
1145
1146    SCDynamicStoreSetDispatchQueue(store, queue);
1147
1148    dispatch_sync(queue, ^{ update_domain(store, NULL, NULL); });
1149}
1150
1151
1152/*
1153 *
1154 */
1155
1156static void *nt_framework_handle = NULL;
1157static NET_LOGON_PROBE_FN netLogonProbe = NULL;
1158static NTLM_LOGON_FN nTLMLogon = NULL;
1159
1160#define NT_FRAMEWORK_PATH "/System/Library/PrivateFrameworks/nt.framework/nt"
1161
1162static void
1163init_nt_framework(void *ctx)
1164{
1165    nt_framework_handle = dlopen(NT_FRAMEWORK_PATH, RTLD_LOCAL);
1166    if (nt_framework_handle == NULL)
1167	return;
1168
1169    netLogonProbe = dlsym(nt_framework_handle, "NetLogonProbe");
1170    nTLMLogon = dlsym(nt_framework_handle, "NTLMLogon");
1171
1172
1173    if (netLogonProbe && nTLMLogon) {
1174	domain_notification();
1175    }
1176}
1177
1178
1179
1180static int
1181netr_init(krb5_context context, void **ctx)
1182{
1183    static heim_base_once_t init_once = HEIM_BASE_ONCE_INIT;
1184    NETLOGON_PROBE_STATUS probeStatus;
1185    struct ntlmnetr *c;
1186    char *domain;
1187
1188    heim_base_once_f(&init_once, NULL, init_nt_framework);
1189
1190    if (netLogonProbe == NULL || nTLMLogon == NULL)
1191	return ENOENT;
1192
1193    probeStatus = netLogonProbe(NULL, &domain);
1194    kdc_log(context, config, 1, "digest-request: netr probe %d", (int)probeStatus);
1195    switch (probeStatus) {
1196    case NETLOGON_PROBE_CONFIGURED:
1197    case NETLOGON_PROBE_CONNECTED:
1198	break;
1199    default:
1200	return ENOENT;
1201    }
1202
1203    c = calloc(1, sizeof(*c));
1204    if (c == NULL) {
1205	free(domain);
1206	return ENOMEM;
1207    }
1208    c->domain = domain;
1209
1210    *ctx = c;
1211
1212    return GSS_S_COMPLETE;
1213}
1214
1215static uint32_t
1216netr_flags(krb5_context context, void *ctx)
1217{
1218    return 0;
1219}
1220
1221static int
1222netr_ti(void *ctx, struct ntlm_targetinfo *ti)
1223{
1224    if (netlogonServer == NULL || netlogonDomain == NULL)
1225	return EINVAL;
1226
1227    if ((ti->servername = strdup(netlogonServer)) == NULL)
1228	return ENOMEM;
1229
1230    if ((ti->domainname = strdup(netlogonDomain)) == NULL) {
1231	free(ti->servername);
1232	return ENOMEM;
1233    }
1234
1235    return 0;
1236}
1237
1238/*
1239 *
1240 */
1241
1242static int
1243netr_authenticate(void *ctx,
1244		  krb5_context context,
1245		  const NTLMRequest2 *ntq,
1246		  void *response_ctx,
1247		  void (response)(void *, const NTLMReply *ntp))
1248{
1249    struct ntlmnetr *c = ctx;
1250    OM_uint32 ret;
1251    NTLM_LOGON_REQ logonReq;
1252    krb5_data sessionkeydata;
1253    uint8_t sessionkey[16];
1254    uint32_t userFlags;
1255    int ntlm2;
1256    NTLMReply ntp;
1257    krb5_data pac;
1258
1259    if (netlogonDomain == NULL || netlogonServer == NULL || ctx == NULL)
1260	return EINVAL;
1261
1262    memset(&ntp, 0, sizeof(ntp));
1263
1264    krb5_data_zero(&sessionkeydata);
1265
1266    logonReq.Version = NTLM_LOGON_REQ_VERSION;
1267    logonReq.LogonDomainName = ntq->loginDomainName;
1268    logonReq.UserName = ntq->loginUserName;
1269    logonReq.Workstation = ntq->workstation;
1270
1271    /*
1272     * From http://davenport.sourceforge.net/ntlm.html:
1273     *
1274     *  "Instead of sending the server challenge directly
1275     *  over the NetLogon pipe to the domain controller,
1276     *  the server sends the MD5 hash of the server challenge
1277     *  concatenated with the client nonce (lifted from the LM
1278     *  response field)."
1279     *
1280     * Unfortunately, the client doesn't give us any indication
1281     * whether it's using NTLMv1 with NTLM2 security or rather
1282     * NTLMv2. It appears we can only determine this from the
1283     * response length, which will be >24 for NTLMv2.
1284     */
1285    ntlm2 = (ntq->ntlmFlags & NTLM_NEG_NTLM2_SESSION) &&
1286            (ntq->lmChallengeResponse.length >= 8) &&
1287            (ntq->ntChallengeResponse.length == 24);
1288
1289    if (ntlm2) {
1290        ret = heim_ntlm_calculate_ntlm2_sess_hash(ntq->lmChallengeResponse.data,
1291                                                  ntq->lmchallenge.data,
1292                                                  logonReq.LmChallenge);
1293        if (ret)
1294	    goto out;
1295    } else {
1296        memcpy(logonReq.LmChallenge, ntq->lmchallenge.data, 8);
1297        c->flags &= ~(LOGON_NTLMV2_ENABLED);
1298    }
1299
1300    logonReq.NtChallengeResponseLength = ntq->ntChallengeResponse.length;
1301    logonReq.NtChallengeResponse = ntq->ntChallengeResponse.data;
1302    logonReq.LmChallengeResponseLength = ntq->lmChallengeResponse.length;
1303    logonReq.LmChallengeResponse = ntq->lmChallengeResponse.data;
1304
1305    ret = nTLMLogon(&logonReq,
1306		    (uint8_t **)&pac.data,
1307		    &pac.length,
1308		    &ntp.user,
1309		    &ntp.domain,
1310		    sessionkey,
1311		    &userFlags);
1312    if (ret) {
1313	kdc_log(context, config, 1, "digest-request netr: failed user=%s\\%s DC status code %08x",
1314		ntq->loginDomainName,
1315		ntq->loginUserName,
1316		ret);
1317	goto out;
1318    }
1319
1320    /* XXX copy into ntp */
1321    free(pac.data);
1322
1323    if (ntlm2) {
1324        /* http://davenport.sourceforge.net/ntlm.html#theNtlm2SessionResponseUserSessionKey */
1325        heim_ntlm_derive_ntlm2_sess(sessionkey,
1326                                    ntq->lmChallengeResponse.data, 8,
1327                                    ntq->lmchallenge.data,
1328                                    sessionkey);
1329    }
1330
1331    ntp.ntlmFlags = ntq->ntlmFlags;
1332
1333    /*
1334     * If the client sent a NTLMv2 response (as opposed to a
1335     * NTLMv1 reponse with NTLM2 security), then we also need
1336     * to negotiate a NTLM2 session key.
1337     */
1338    if (userFlags & LOGON_NTLMV2_ENABLED)
1339	ntp.ntlmFlags |= NTLM_NEG_NTLM2_SESSION;
1340
1341    if (ntq->ntlmFlags & NTLM_NEG_KEYEX) {
1342	struct ntlm_buf base, enc, sess;
1343
1344	base.data = sessionkey;
1345	base.length = sizeof(sessionkey);
1346	enc.data = ntq->encryptedSessionKey.data;
1347	enc.length = ntq->encryptedSessionKey.length;
1348
1349	ret = heim_ntlm_keyex_unwrap(&base, &enc, &sess);
1350	if (ret != 0)
1351	    goto out;
1352	if (sess.length != sizeof(sessionkey)) {
1353	    heim_ntlm_free_buf(&sess);
1354	    ret = EINVAL;
1355	    goto out;
1356	}
1357	memcpy(sessionkey, sess.data, sizeof(sessionkey));
1358	heim_ntlm_free_buf(&sess);
1359    }
1360
1361    sessionkeydata.data = sessionkey;
1362    sessionkeydata.length = sizeof(sessionkey);
1363    ntp.sessionkey = &sessionkeydata;
1364
1365    ntp.success = 1;
1366
1367    response(response_ctx, &ntp);
1368    ret = 0;
1369
1370out:
1371    log_complete(context, "netr", &ntp, ret, derive_version_ntq(ntq));
1372
1373    if (ntp.user)
1374	free(ntp.user);
1375    if (ntp.domain)
1376	free(ntp.domain);
1377    return ret;
1378}
1379
1380#endif
1381
1382/*
1383 *
1384 */
1385
1386struct ntlm_ftable backends[] = {
1387#ifdef HAVE_APPLE_NETLOGON
1388    {
1389	NULL,
1390	netr_init,
1391	NULL,
1392	netr_ti,
1393	netr_flags,
1394	netr_authenticate
1395    },
1396#endif
1397#ifdef HAVE_OPENDIRECTORY
1398    {
1399	NULL,
1400	od_init,
1401	NULL,
1402	NULL,
1403	od_flags,
1404	od_authenticate
1405    },
1406#endif
1407    {
1408	NULL,
1409	NULL,
1410	NULL,
1411	NULL,
1412	kdc_flags,
1413	kdc_authenticate
1414    },
1415    {
1416	NULL,
1417	NULL,
1418	NULL,
1419	NULL,
1420	guest_flags,
1421	guest_authenticate
1422    }
1423};
1424
1425struct callback {
1426    heim_sipc_call cctx;
1427    heim_ipc_complete complete;
1428};
1429
1430static void
1431complete_callback(void *ctx, const NTLMReply *ntp)
1432{
1433    struct callback *c = ctx;
1434    heim_idata rep;
1435    size_t size;
1436    int ret;
1437
1438    ASN1_MALLOC_ENCODE(NTLMReply, rep.data, rep.length, ntp, &size, ret);
1439    if (ret)
1440	return;
1441    if (rep.length != size)
1442	abort();
1443
1444    c->complete(c->cctx, ret, &rep);
1445
1446    free(rep.data);
1447}
1448
1449/*
1450 *
1451 */
1452
1453static void
1454fillin_hostinfo(struct ntlm_targetinfo *ti)
1455{
1456    char local_hostname[MAXHOSTNAMELEN], *p;
1457
1458    if (netlogonServer && ti->servername == NULL)
1459	ti->servername = strdup(netlogonServer);
1460
1461    if (gethostname(local_hostname, sizeof(local_hostname)) < 0)
1462	return;
1463    local_hostname[sizeof(local_hostname) - 1] = '\0';
1464
1465    /* make up a server name */
1466    ti->dnsservername = strdup(local_hostname);
1467
1468    p = strchr(local_hostname, '.');
1469    if (p) {
1470	*p = '\0';
1471	p++;
1472    }
1473    if (ti->servername == NULL) {
1474	strupr(local_hostname);
1475	ti->servername = strdup(local_hostname);
1476    }
1477
1478    if (p && p[0])
1479	ti->dnsdomainname = strdup(p);
1480}
1481
1482
1483/*
1484 * Given the configuration, try to return the NTLM domain that is our
1485 * default DOMAIN.
1486 *
1487 * If we are not bound, pick the first in realm configuration that
1488 * matches the users preference.
1489 *
1490 * If we find no configuration, just return BUILTIN.
1491 */
1492
1493static krb5_error_code
1494get_ntlm_domain(krb5_context context,
1495		krb5_kdc_configuration *lconfig,
1496		const char *indomain,
1497		struct ntlm_targetinfo *ti)
1498{
1499    char *domain = NULL;
1500    krb5_error_code ret;
1501    unsigned int i;
1502
1503    for (i = 0; i < sizeof(backends) / sizeof(backends[0]); i++) {
1504	if (backends[i].ti == NULL)
1505	    continue;
1506
1507	memset(ti, 0, sizeof(*ti));
1508	ret = backends[i].ti(backends[i].ctx, ti);
1509	if (ret)
1510	    continue;
1511
1512	if (indomain == NULL || strcmp(indomain, ti->domainname) == 0)
1513	    goto dnsinfo;
1514
1515	heim_ntlm_free_targetinfo(ti);
1516    }
1517
1518    for (i = 0; i < lconfig->num_db; i++) {
1519	if (lconfig->db[i]->hdb_get_ntlm_domain == NULL)
1520	    continue;
1521	ret = lconfig->db[i]->hdb_get_ntlm_domain(context,
1522						 lconfig->db[i],
1523						 &domain);
1524	if (ret == 0 && domain)
1525	    break;
1526    }
1527    if (domain == NULL)
1528	domain = strdup("BUILTIN");
1529
1530    memset(ti, 0, sizeof(*ti));
1531
1532    ti->domainname = domain;
1533
1534 dnsinfo:
1535
1536    fillin_hostinfo(ti);
1537
1538    return 0;
1539}
1540
1541
1542/*
1543 * process type1/init request
1544 */
1545
1546static void
1547process_NTLMInit(krb5_context context,
1548		 NTLMInit *ni,
1549		 heim_ipc_complete complete,
1550		 heim_sipc_call cctx)
1551{
1552    heim_idata rep = { 0, NULL };
1553    struct ntlm_targetinfo ti;
1554    NTLMInitReply ir;
1555    char *indomain = NULL;
1556    size_t size, n;
1557    int ret = 0;
1558
1559    memset(&ir, 0, sizeof(ir));
1560    memset(&ti, 0, sizeof(ti));
1561
1562    kdc_log(context, config, 1, "digest-request: init request");
1563
1564    ir.ntlmNegFlags = 0;
1565    for (n = 0; n < sizeof(backends) / sizeof(backends[0]); n++)
1566	ir.ntlmNegFlags |= backends[n].filter_flags(context, backends[n].ctx);
1567
1568    if (ni->domain && (*ni->domain)[0] != '\0')
1569	indomain = *ni->domain;
1570
1571    ret = get_ntlm_domain(context, config, indomain, &ti);
1572    if (ret)
1573	goto failed;
1574
1575    ti.timestamp = heim_ntlm_unix2ts_time(time(NULL));
1576
1577    kdc_log(context, config, 1, "digest-request: init return domain: %s server: %s indomain was: %s",
1578	    ti.domainname, ti.servername, indomain ? indomain : "<NULL>");
1579
1580    {
1581	struct ntlm_buf d;
1582
1583	ret = heim_ntlm_encode_targetinfo(&ti, 1, &d);
1584	if (ret)
1585	    goto failed;
1586	ir.targetinfo.data = d.data;
1587	ir.targetinfo.length = d.length;
1588    }
1589
1590    ASN1_MALLOC_ENCODE(NTLMInitReply, rep.data, rep.length, &ir, &size, ret);
1591    free_NTLMInitReply(&ir);
1592    if (ret)
1593	goto failed;
1594    if (rep.length != size)
1595	abort();
1596
1597failed:
1598    if (ret)
1599	kdc_log(context, config, 1, "digest-request: %d", ret);
1600
1601    heim_ntlm_free_targetinfo(&ti);
1602
1603    (*complete)(cctx, ret, &rep);
1604    free(rep.data);
1605
1606    return;
1607}
1608
1609/*
1610 *
1611 */
1612
1613static void
1614ntlm_service(void *ctx, const heim_idata *req,
1615	     const heim_icred cred,
1616	     heim_ipc_complete complete,
1617	     heim_sipc_call cctx)
1618{
1619    krb5_context context = ctx;
1620    static dispatch_once_t once;
1621    NTLMRequest2 ntq;
1622    NTLMInit ni;
1623
1624    kdc_log(context, config, 1, "digest-request: uid=%d",
1625	    (int)heim_ipc_cred_get_uid(cred));
1626
1627    if (heim_ipc_cred_get_uid(cred) != 0) {
1628	(*complete)(cctx, EPERM, NULL);
1629	return;
1630    }
1631
1632    dispatch_once(&once, ^{
1633	size_t n;
1634	for (n = 0; n < sizeof(backends) / sizeof(backends[0]); n++)
1635	    if (backends[n].init)
1636		backends[n].init(context, &backends[n].ctx);
1637    });
1638
1639    /* check if its a type1/init request */
1640    if (decode_NTLMInit(req->data, req->length, &ni, NULL) == 0) {
1641        process_NTLMInit(context, &ni, complete, cctx);
1642	free_NTLMInit(&ni);
1643    } else if (decode_NTLMRequest2(req->data, req->length, &ntq, NULL) == 0) {
1644	struct callback c = {
1645	    .complete = complete,
1646	    .cctx = cctx
1647	};
1648	size_t n;
1649	int ret = EPERM;
1650
1651	for (n = 0; n < sizeof(backends) / sizeof(backends[0]); n++) {
1652	    ret = backends[n].authenticate(backends[n].ctx, context, &ntq, &c, complete_callback);
1653	    if (ret == 0)
1654		break;
1655	}
1656	free_NTLMRequest2(&ntq);
1657	if (ret)
1658	    (*complete)(cctx, ret, NULL);
1659    }
1660}
1661
1662
1663#endif
1664
1665static struct getargs args[] = {
1666#ifdef __APPLE__
1667    {	"sandbox",	0, 	arg_negative_flag, &sandbox_flag,
1668	"use sandbox or not"
1669    },
1670#endif /* __APPLE__ */
1671    {	"unix",		0, 	arg_flag, &use_unix_flag,
1672	"support unix sockets"
1673    },
1674    {	"help",		'h',	arg_flag,   &help_flag },
1675    {	"version",	'v',	arg_flag,   &version_flag }
1676};
1677
1678static int num_args = sizeof(args) / sizeof(args[0]);
1679
1680static void
1681usage(int ret)
1682{
1683    arg_printusage (args, num_args, NULL, "");
1684    exit (ret);
1685}
1686
1687
1688int
1689main(int argc, char **argv)
1690{
1691    krb5_context context;
1692    int ret, optidx = 0;
1693
1694    setprogname(argv[0]);
1695
1696    __gss_ntlm_is_digest_service = 1;
1697
1698    if (getarg(args, num_args, argc, argv, &optidx))
1699	usage(1);
1700
1701    if (help_flag)
1702	usage(0);
1703
1704    if (version_flag) {
1705	print_version(NULL);
1706	exit(0);
1707    }
1708
1709    ret = krb5_init_context(&context);
1710    if (ret)
1711	krb5_errx(context, 1, "krb5_init_context");
1712
1713    ret = krb5_kdc_get_config(context, &config);
1714    if (ret)
1715	krb5_err(context, 1, ret, "krb5_kdc_default_config");
1716
1717    kdc_openlog(context, "digest-service", config);
1718
1719    ret = krb5_kdc_set_dbinfo(context, config);
1720    if (ret)
1721	krb5_err(context, 1, ret, "krb5_kdc_set_dbinfo");
1722
1723#ifdef __APPLE__
1724    if (sandbox_flag) {
1725	char *errorstring;
1726	ret = sandbox_init("kdc", SANDBOX_NAMED, &errorstring);
1727	if (ret)
1728	    errx(1, "sandbox_init failed: %d: %s", ret, errorstring);
1729    }
1730#endif
1731
1732#ifdef ENABLE_NTLM
1733#if __APPLE__
1734    {
1735	heim_sipc mach;
1736	heim_sipc_launchd_mach_init("org.h5l.ntlm-service",
1737				    ntlm_service, context, &mach);
1738    }
1739#endif
1740    if (use_unix_flag) {
1741	heim_sipc un;
1742	heim_sipc_service_unix("org.h5l.ntlm-service", ntlm_service, NULL, &un);
1743    }
1744#endif
1745    heim_sipc_timeout(60);
1746
1747    heim_ipc_main();
1748    return 0;
1749}
1750