1/*
2 * Copyright (c) 2006 - 2007 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of the Institute nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#include "ntlm.h"
35
36#ifdef DIGEST
37
38/*
39 *
40 */
41
42struct ntlmkrb5 {
43    krb5_context context;
44    krb5_ntlm ntlm;
45    krb5_realm kerberos_realm;
46    krb5_ccache id;
47    krb5_data opaque;
48    int destroy;
49    OM_uint32 flags;
50    struct ntlm_buf key;
51    krb5_data sessionkey;
52};
53
54static OM_uint32 kdc_destroy(OM_uint32 *, void *);
55
56/*
57 * Get credential cache that the ntlm code can use to talk to the KDC
58 * using the digest API.
59 */
60
61static krb5_error_code
62get_ccache(krb5_context context, int *destroy, krb5_ccache *id)
63{
64    krb5_principal principal = NULL;
65    krb5_error_code ret;
66    krb5_keytab kt = NULL;
67
68    *id = NULL;
69
70    if (!issuid()) {
71	const char *cache;
72
73	cache = getenv("NTLM_ACCEPTOR_CCACHE");
74	if (cache) {
75	    ret = krb5_cc_resolve(context, cache, id);
76	    if (ret)
77		goto out;
78	    return 0;
79	}
80    }
81
82    ret = krb5_sname_to_principal(context, NULL, "host",
83				  KRB5_NT_SRV_HST, &principal);
84    if (ret)
85	goto out;
86
87    ret = krb5_cc_cache_match(context, principal, id);
88    if (ret == 0)
89	return 0;
90
91    /* did not find in default credcache, lets try default keytab */
92    ret = krb5_kt_default(context, &kt);
93    if (ret)
94	goto out;
95
96    /* XXX check in keytab */
97    {
98	krb5_get_init_creds_opt *opt;
99	krb5_creds cred;
100
101	memset(&cred, 0, sizeof(cred));
102
103	ret = krb5_cc_new_unique(context, "MEMORY", NULL, id);
104	if (ret)
105	    goto out;
106	*destroy = 1;
107	ret = krb5_get_init_creds_opt_alloc(context, &opt);
108	if (ret)
109	    goto out;
110	ret = krb5_get_init_creds_keytab (context,
111					  &cred,
112					  principal,
113					  kt,
114					  0,
115					  NULL,
116					  opt);
117	krb5_get_init_creds_opt_free(context, opt);
118	if (ret)
119	    goto out;
120	ret = krb5_cc_initialize (context, *id, cred.client);
121	if (ret) {
122	    krb5_free_cred_contents (context, &cred);
123	    goto out;
124	}
125	ret = krb5_cc_store_cred (context, *id, &cred);
126	krb5_free_cred_contents (context, &cred);
127	if (ret)
128	    goto out;
129    }
130
131    krb5_kt_close(context, kt);
132
133    return 0;
134
135out:
136    if (*id) {
137	if (*destroy)
138	    krb5_cc_destroy(context, *id);
139	else
140	    krb5_cc_close(context, *id);
141	*id = NULL;
142    }
143
144    if (kt)
145	krb5_kt_close(context, kt);
146
147    if (principal)
148	krb5_free_principal(context, principal);
149    return ret;
150}
151
152/*
153 *
154 */
155
156static OM_uint32
157kdc_alloc(OM_uint32 *minor, void **ctx)
158{
159    krb5_error_code ret;
160    struct ntlmkrb5 *c;
161    OM_uint32 junk;
162
163    c = calloc(1, sizeof(*c));
164    if (c == NULL) {
165	*minor = ENOMEM;
166	return GSS_S_FAILURE;
167    }
168
169    ret = krb5_init_context(&c->context);
170    if (ret) {
171	kdc_destroy(&junk, c);
172	*minor = ret;
173	return GSS_S_FAILURE;
174    }
175
176    ret = get_ccache(c->context, &c->destroy, &c->id);
177    if (ret) {
178	kdc_destroy(&junk, c);
179	*minor = ret;
180	return GSS_S_FAILURE;
181    }
182
183    ret = krb5_ntlm_alloc(c->context, &c->ntlm);
184    if (ret) {
185	kdc_destroy(&junk, c);
186	*minor = ret;
187	return GSS_S_FAILURE;
188    }
189
190    *ctx = c;
191
192    return GSS_S_COMPLETE;
193}
194
195static int
196kdc_probe(OM_uint32 *minor, void *ctx, const char *realm, unsigned int *flags)
197{
198    struct ntlmkrb5 *c = ctx;
199    krb5_error_code ret;
200    unsigned flags;
201
202    ret = krb5_digest_probe(c->context, rk_UNCONST(realm), c->id, &flags);
203    if (ret)
204	return ret;
205
206    if ((flags & (1|2|4)) == 0)
207	return HNTLM_ERR_AUTH;
208
209    return 0;
210}
211
212/*
213 *
214 */
215
216static OM_uint32
217kdc_destroy(OM_uint32 *minor, void *ctx)
218{
219    struct ntlmkrb5 *c = ctx;
220    krb5_data_free(&c->opaque);
221    krb5_data_free(&c->sessionkey);
222    if (c->ntlm)
223	krb5_ntlm_free(c->context, c->ntlm);
224    if (c->id) {
225	if (c->destroy)
226	    krb5_cc_destroy(c->context, c->id);
227	else
228	    krb5_cc_close(c->context, c->id);
229    }
230    if (c->context)
231	krb5_free_context(c->context);
232    memset(c, 0, sizeof(*c));
233    free(c);
234
235    return GSS_S_COMPLETE;
236}
237
238/*
239 *
240 */
241
242static OM_uint32
243kdc_type2(OM_uint32 *minor_status,
244	  ntlm_ctx ntlmctx,
245	  void *ctx,
246	  uint32_t flags,
247	  const char *hostname,
248	  const char *domain,
249	  uint32_t *ret_flags,
250	  struct ntlm_buf *out)
251{
252    struct ntlmkrb5 *c = ctx;
253    krb5_error_code ret;
254    struct ntlm_type2 type2;
255    krb5_data challenge;
256    struct ntlm_buf data;
257    krb5_data ti;
258
259    memset(&type2, 0, sizeof(type2));
260
261    /*
262     * Request data for type 2 packet from the KDC.
263     */
264    ret = krb5_ntlm_init_request(c->context,
265				 c->ntlm,
266				 NULL,
267				 c->id,
268				 flags,
269				 hostname,
270				 domain);
271    if (ret) {
272	*minor_status = ret;
273	return GSS_S_FAILURE;
274    }
275
276    /*
277     *
278     */
279
280    ret = krb5_ntlm_init_get_opaque(c->context, c->ntlm, &c->opaque);
281    if (ret) {
282	*minor_status = ret;
283	return GSS_S_FAILURE;
284    }
285
286    /*
287     *
288     */
289
290    ret = krb5_ntlm_init_get_flags(c->context, c->ntlm, &type2.flags);
291    if (ret) {
292	*minor_status = ret;
293	return GSS_S_FAILURE;
294    }
295    *ret_flags = type2.flags;
296
297    ret = krb5_ntlm_init_get_challenge(c->context, c->ntlm, &challenge);
298    if (ret) {
299	*minor_status = ret;
300	return GSS_S_FAILURE;
301    }
302
303    if (challenge.length != sizeof(type2.challenge)) {
304	*minor_status = HNTLM_ERR_INVALID_LENGTH;
305	return GSS_S_FAILURE;
306    }
307    memcpy(type2.challenge, challenge.data, sizeof(type2.challenge));
308    krb5_data_free(&challenge);
309
310    ret = krb5_ntlm_init_get_targetname(c->context, c->ntlm,
311					&type2.targetname);
312    if (ret) {
313	*minor_status = ret;
314	return GSS_S_FAILURE;
315    }
316
317    ret = krb5_ntlm_init_get_targetinfo(c->context, c->ntlm, &ti);
318    if (ret) {
319	free(type2.targetname);
320	*minor_status = ret;
321	return GSS_S_FAILURE;
322    }
323
324    type2.targetinfo.data = ti.data;
325    type2.targetinfo.length = ti.length;
326
327    ret = heim_ntlm_encode_type2(&type2, &data);
328    free(type2.targetname);
329    krb5_data_free(&ti);
330    if (ret) {
331	*minor_status = ret;
332	return GSS_S_FAILURE;
333    }
334
335    out->data = data.data;
336    out->length = data.length;
337
338    return GSS_S_COMPLETE;
339}
340
341static OM_uint32
342kdc_ti(OM_uint32 *minor_status,
343       ntlm_ctx ntlmctx,
344       void *ctx,
345       const char *hostname,
346       const char *domain)
347{
348    return GSS_S_FAILURE;
349}
350
351/*
352 *
353 */
354
355static OM_uint32
356kdc_type3(OM_uint32 *minor_status,
357	  void *ctx,
358	  const struct ntlm_type3 *type3,
359	  ntlm_cred accept_cred,
360	  uint32_t *flags,
361	  uint32_t *avflags,
362	  struct ntlm_buf *sessionkey,
363	  ntlm_name *name, struct ntlm_buf *uuid,
364	  struct ntlm_buf *pac)
365{
366    struct ntlmkrb5 *c = ctx;
367    krb5_error_code ret;
368
369    *avflags = *flags = 0;
370
371    sessionkey->data = NULL;
372    sessionkey->length = 0;
373    *name = NULL;
374    uuid->data = NULL;
375    uuid->length = 0;
376    pac->data = NULL;
377    pac->length = 0;
378
379    ret = krb5_ntlm_req_set_flags(c->context, c->ntlm, type3->flags);
380    if (ret) goto out;
381    ret = krb5_ntlm_req_set_username(c->context, c->ntlm, type3->username);
382    if (ret) goto out;
383    ret = krb5_ntlm_req_set_targetname(c->context, c->ntlm,
384				       type3->targetname);
385    if (ret) goto out;
386    ret = krb5_ntlm_req_set_lm(c->context, c->ntlm,
387			       type3->lm.data, type3->lm.length);
388    if (ret) goto out;
389    ret = krb5_ntlm_req_set_ntlm(c->context, c->ntlm,
390				 type3->ntlm.data, type3->ntlm.length);
391    if (ret) goto out;
392    ret = krb5_ntlm_req_set_opaque(c->context, c->ntlm, &c->opaque);
393    if (ret) goto out;
394
395    if (type3->sessionkey.length) {
396	ret = krb5_ntlm_req_set_session(c->context, c->ntlm,
397					type3->sessionkey.data,
398					type3->sessionkey.length);
399	if (ret) goto out;
400    }
401
402    /*
403     * Verify with the KDC the type3 packet is ok
404     */
405    ret = krb5_ntlm_request(c->context,
406			    c->ntlm,
407			    NULL,
408			    c->id);
409    if (ret)
410	goto out;
411
412    if (krb5_ntlm_rep_get_status(c->context, c->ntlm) != TRUE) {
413	ret = HNTLM_ERR_AUTH;
414	goto out;
415    }
416
417    if (type3->sessionkey.length) {
418	ret = krb5_ntlm_rep_get_sessionkey(c->context,
419					   c->ntlm,
420					   &c->sessionkey);
421	if (ret)
422	    goto out;
423
424	sessionkey->data = c->sessionkey.data;
425	sessionkey->length = c->sessionkey.length;
426    }
427
428    return 0;
429
430 out:
431    *minor_status = ret;
432    return GSS_S_FAILURE;
433}
434
435/*
436 *
437 */
438
439static void
440kdc_free_buffer(struct ntlm_buf *sessionkey)
441{
442    if (sessionkey->data)
443	free(sessionkey->data);
444    sessionkey->data = NULL;
445    sessionkey->length = 0;
446}
447
448/*
449 *
450 */
451
452struct ntlm_server_interface ntlmsspi_kdc_digest = {
453    "kdc"
454    kdc_alloc,
455    kdc_destroy,
456    kdc_probe,
457    kdc_type3,
458    kdc_free_buffer,
459    kdc_ti
460};
461
462#endif /* DIGEST */
463