1/*
2 * Copyright (c) 2006 - 2008 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
36static int
37from_file(const char *fn, const char *target_domain,
38	  char **username, struct ntlm_buf *key)
39{
40    char *str, buf[1024];
41    FILE *f;
42
43    f = fopen(fn, "r");
44    if (f == NULL)
45	return ENOENT;
46    rk_cloexec_file(f);
47
48    while (fgets(buf, sizeof(buf), f) != NULL) {
49	char *d, *u, *p;
50	buf[strcspn(buf, "\r\n")] = '\0';
51	if (buf[0] == '#')
52	    continue;
53	str = NULL;
54	d = strtok_r(buf, ":", &str);
55	if (d && strcasecmp(target_domain, d) != 0)
56	    continue;
57	u = strtok_r(NULL, ":", &str);
58	p = strtok_r(NULL, ":", &str);
59	if (u == NULL || p == NULL)
60	    continue;
61
62	*username = strdup(u);
63
64	heim_ntlm_nt_key(p, key);
65
66	memset(buf, 0, sizeof(buf));
67	fclose(f);
68	return 0;
69    }
70    memset(buf, 0, sizeof(buf));
71    fclose(f);
72    return ENOENT;
73}
74
75static int
76get_user_file(const ntlm_name target_name,
77	      char **username, struct ntlm_buf *key)
78{
79    const char *fn;
80
81    if (issuid())
82	return ENOENT;
83
84    fn = getenv("NTLM_USER_FILE");
85    if (fn == NULL)
86	return ENOENT;
87    if (from_file(fn, target_name->domain, username, key) == 0)
88	return 0;
89
90    return ENOENT;
91}
92
93/*
94 * Pick up the ntlm cred from the default krb5 credential cache.
95 */
96
97static int
98get_user_ccache(const ntlm_name name, char **username, struct ntlm_buf *key)
99{
100    krb5_context context = NULL;
101    krb5_principal client;
102    krb5_ccache id = NULL;
103    krb5_error_code ret;
104    char *confname;
105    krb5_data data;
106
107    *username = NULL;
108    krb5_data_zero(&data);
109    key->length = 0;
110    key->data = NULL;
111
112    ret = krb5_init_context(&context);
113    if (ret)
114	return ret;
115
116    ret = krb5_cc_default(context, &id);
117    if (ret)
118	goto out;
119
120    ret = krb5_cc_get_principal(context, id, &client);
121    if (ret)
122	goto out;
123
124    ret = krb5_unparse_name_flags(context, client,
125				  KRB5_PRINCIPAL_UNPARSE_NO_REALM,
126				  username);
127    krb5_free_principal(context, client);
128    if (ret)
129	goto out;
130
131    asprintf(&confname, "ntlm-key-%s", name->domain);
132    if (confname == NULL) {
133	krb5_clear_error_message(context);
134	ret = ENOMEM;
135	goto out;
136    }
137
138    ret = krb5_cc_get_config(context, id, NULL,
139			     confname, &data);
140    if (ret)
141	goto out;
142
143    key->data = malloc(data.length);
144    if (key->data == NULL) {
145	ret = ENOMEM;
146	goto out;
147    }
148    key->length = data.length;
149    memcpy(key->data, data.data, data.length);
150
151 out:
152    krb5_data_free(&data);
153    if (id)
154	krb5_cc_close(context, id);
155
156    krb5_free_context(context);
157
158    return ret;
159}
160
161int
162_gss_ntlm_get_user_cred(const ntlm_name target_name,
163			ntlm_cred *rcred)
164{
165    ntlm_cred cred;
166    int ret;
167
168    cred = calloc(1, sizeof(*cred));
169    if (cred == NULL)
170	return ENOMEM;
171
172    ret = get_user_file(target_name, &cred->username, &cred->key);
173    if (ret)
174	ret = get_user_ccache(target_name, &cred->username, &cred->key);
175    if (ret) {
176	free(cred);
177	return ret;
178    }
179
180    cred->domain = strdup(target_name->domain);
181    *rcred = cred;
182
183    return ret;
184}
185
186static int
187_gss_copy_cred(ntlm_cred from, ntlm_cred *to)
188{
189    *to = calloc(1, sizeof(**to));
190    if (*to == NULL)
191	return ENOMEM;
192    (*to)->username = strdup(from->username);
193    if ((*to)->username == NULL) {
194	free(*to);
195	return ENOMEM;
196    }
197    (*to)->domain = strdup(from->domain);
198    if ((*to)->domain == NULL) {
199	free((*to)->username);
200	free(*to);
201	return ENOMEM;
202    }
203    (*to)->key.data = malloc(from->key.length);
204    if ((*to)->key.data == NULL) {
205	free((*to)->domain);
206	free((*to)->username);
207	free(*to);
208	return ENOMEM;
209    }
210    memcpy((*to)->key.data, from->key.data, from->key.length);
211    (*to)->key.length = from->key.length;
212
213    return 0;
214}
215
216OM_uint32 GSSAPI_CALLCONV
217_gss_ntlm_init_sec_context
218           (OM_uint32 * minor_status,
219            const gss_cred_id_t initiator_cred_handle,
220            gss_ctx_id_t * context_handle,
221            const gss_name_t target_name,
222            const gss_OID mech_type,
223            OM_uint32 req_flags,
224            OM_uint32 time_req,
225            const gss_channel_bindings_t input_chan_bindings,
226            const gss_buffer_t input_token,
227            gss_OID * actual_mech_type,
228            gss_buffer_t output_token,
229            OM_uint32 * ret_flags,
230            OM_uint32 * time_rec
231	   )
232{
233    ntlm_ctx ctx;
234    ntlm_name name = (ntlm_name)target_name;
235
236    *minor_status = 0;
237
238    if (ret_flags)
239	*ret_flags = 0;
240    if (time_rec)
241	*time_rec = 0;
242    if (actual_mech_type)
243	*actual_mech_type = GSS_C_NO_OID;
244
245    if (*context_handle == GSS_C_NO_CONTEXT) {
246	struct ntlm_type1 type1;
247	struct ntlm_buf data;
248	uint32_t flags = 0;
249	int ret;
250
251	ctx = calloc(1, sizeof(*ctx));
252	if (ctx == NULL) {
253	    *minor_status = EINVAL;
254	    return GSS_S_FAILURE;
255	}
256	*context_handle = (gss_ctx_id_t)ctx;
257
258	if (initiator_cred_handle != GSS_C_NO_CREDENTIAL) {
259	    ntlm_cred cred = (ntlm_cred)initiator_cred_handle;
260	    ret = _gss_copy_cred(cred, &ctx->client);
261	} else
262	    ret = _gss_ntlm_get_user_cred(name, &ctx->client);
263
264	if (ret) {
265	    _gss_ntlm_delete_sec_context(minor_status, context_handle, NULL);
266	    *minor_status = ret;
267	    return GSS_S_FAILURE;
268	}
269
270	if (req_flags & GSS_C_CONF_FLAG)
271	    flags |= NTLM_NEG_SEAL;
272	if (req_flags & GSS_C_INTEG_FLAG)
273	    flags |= NTLM_NEG_SIGN;
274	else
275	    flags |= NTLM_NEG_ALWAYS_SIGN;
276
277	flags |= NTLM_NEG_UNICODE;
278	flags |= NTLM_NEG_NTLM;
279	flags |= NTLM_NEG_NTLM2_SESSION;
280	flags |= NTLM_NEG_KEYEX;
281
282	memset(&type1, 0, sizeof(type1));
283
284	type1.flags = flags;
285	type1.domain = name->domain;
286	type1.hostname = NULL;
287	type1.os[0] = 0;
288	type1.os[1] = 0;
289
290	ret = heim_ntlm_encode_type1(&type1, &data);
291	if (ret) {
292	    _gss_ntlm_delete_sec_context(minor_status, context_handle, NULL);
293	    *minor_status = ret;
294	    return GSS_S_FAILURE;
295	}
296
297	output_token->value = data.data;
298	output_token->length = data.length;
299
300	return GSS_S_CONTINUE_NEEDED;
301    } else {
302	krb5_error_code ret;
303	struct ntlm_type2 type2;
304	struct ntlm_type3 type3;
305	struct ntlm_buf data;
306
307	ctx = (ntlm_ctx)*context_handle;
308
309	data.data = input_token->value;
310	data.length = input_token->length;
311
312	ret = heim_ntlm_decode_type2(&data, &type2);
313	if (ret) {
314	    _gss_ntlm_delete_sec_context(minor_status, context_handle, NULL);
315	    *minor_status = ret;
316	    return GSS_S_FAILURE;
317	}
318
319	ctx->flags = type2.flags;
320
321	/* XXX check that type2.targetinfo matches `target_name´ */
322	/* XXX check verify targetinfo buffer */
323
324	memset(&type3, 0, sizeof(type3));
325
326	type3.username = ctx->client->username;
327	type3.flags = type2.flags;
328	type3.targetname = type2.targetname;
329	type3.ws = rk_UNCONST("workstation");
330
331	/*
332	 * NTLM Version 1 if no targetinfo buffer.
333	 */
334
335	if (1 || type2.targetinfo.length == 0) {
336	    struct ntlm_buf sessionkey;
337
338	    if (type2.flags & NTLM_NEG_NTLM2_SESSION) {
339		unsigned char nonce[8];
340
341		if (RAND_bytes(nonce, sizeof(nonce)) != 1) {
342		    _gss_ntlm_delete_sec_context(minor_status,
343						 context_handle, NULL);
344		    *minor_status = EINVAL;
345		    return GSS_S_FAILURE;
346		}
347
348		ret = heim_ntlm_calculate_ntlm2_sess(nonce,
349						     type2.challenge,
350						     ctx->client->key.data,
351						     &type3.lm,
352						     &type3.ntlm);
353	    } else {
354		ret = heim_ntlm_calculate_ntlm1(ctx->client->key.data,
355						ctx->client->key.length,
356						type2.challenge,
357						&type3.ntlm);
358
359	    }
360	    if (ret) {
361		_gss_ntlm_delete_sec_context(minor_status,context_handle,NULL);
362		*minor_status = ret;
363		return GSS_S_FAILURE;
364	    }
365
366	    ret = heim_ntlm_build_ntlm1_master(ctx->client->key.data,
367					       ctx->client->key.length,
368					       &sessionkey,
369					       &type3.sessionkey);
370	    if (ret) {
371		if (type3.lm.data)
372		    free(type3.lm.data);
373		if (type3.ntlm.data)
374		    free(type3.ntlm.data);
375		_gss_ntlm_delete_sec_context(minor_status,context_handle,NULL);
376		*minor_status = ret;
377		return GSS_S_FAILURE;
378	    }
379
380	    ret = krb5_data_copy(&ctx->sessionkey,
381				 sessionkey.data, sessionkey.length);
382	    free(sessionkey.data);
383	    if (ret) {
384		if (type3.lm.data)
385		    free(type3.lm.data);
386		if (type3.ntlm.data)
387		    free(type3.ntlm.data);
388		_gss_ntlm_delete_sec_context(minor_status,context_handle,NULL);
389		*minor_status = ret;
390		return GSS_S_FAILURE;
391	    }
392	    ctx->status |= STATUS_SESSIONKEY;
393
394	} else {
395	    struct ntlm_buf sessionkey;
396	    unsigned char ntlmv2[16];
397	    struct ntlm_targetinfo ti;
398
399	    /* verify infotarget */
400
401	    ret = heim_ntlm_decode_targetinfo(&type2.targetinfo, 1, &ti);
402	    if(ret) {
403		_gss_ntlm_delete_sec_context(minor_status,
404					     context_handle, NULL);
405		*minor_status = ret;
406		return GSS_S_FAILURE;
407	    }
408
409	    if (ti.domainname && strcmp(ti.domainname, name->domain) != 0) {
410		_gss_ntlm_delete_sec_context(minor_status,
411					     context_handle, NULL);
412		*minor_status = EINVAL;
413		return GSS_S_FAILURE;
414	    }
415
416	    ret = heim_ntlm_calculate_ntlm2(ctx->client->key.data,
417					    ctx->client->key.length,
418					    ctx->client->username,
419					    name->domain,
420					    type2.challenge,
421					    &type2.targetinfo,
422					    ntlmv2,
423					    &type3.ntlm);
424	    if (ret) {
425		_gss_ntlm_delete_sec_context(minor_status,
426					     context_handle, NULL);
427		*minor_status = ret;
428		return GSS_S_FAILURE;
429	    }
430
431	    ret = heim_ntlm_build_ntlm1_master(ntlmv2, sizeof(ntlmv2),
432					       &sessionkey,
433					       &type3.sessionkey);
434	    memset(ntlmv2, 0, sizeof(ntlmv2));
435	    if (ret) {
436		_gss_ntlm_delete_sec_context(minor_status,
437					     context_handle, NULL);
438		*minor_status = ret;
439		return GSS_S_FAILURE;
440	    }
441
442	    ctx->flags |= NTLM_NEG_NTLM2_SESSION;
443
444	    ret = krb5_data_copy(&ctx->sessionkey,
445				 sessionkey.data, sessionkey.length);
446	    free(sessionkey.data);
447	    if (ret) {
448		_gss_ntlm_delete_sec_context(minor_status,
449					     context_handle, NULL);
450		*minor_status = ret;
451		return GSS_S_FAILURE;
452	    }
453	}
454
455	if (ctx->flags & NTLM_NEG_NTLM2_SESSION) {
456	    ctx->status |= STATUS_SESSIONKEY;
457	    _gss_ntlm_set_key(&ctx->u.v2.send, 0, (ctx->flags & NTLM_NEG_KEYEX),
458			      ctx->sessionkey.data,
459			      ctx->sessionkey.length);
460	    _gss_ntlm_set_key(&ctx->u.v2.recv, 1, (ctx->flags & NTLM_NEG_KEYEX),
461			      ctx->sessionkey.data,
462			      ctx->sessionkey.length);
463	} else {
464	    ctx->status |= STATUS_SESSIONKEY;
465	    RC4_set_key(&ctx->u.v1.crypto_recv.key,
466			ctx->sessionkey.length,
467			ctx->sessionkey.data);
468	    RC4_set_key(&ctx->u.v1.crypto_send.key,
469			ctx->sessionkey.length,
470			ctx->sessionkey.data);
471	}
472
473
474
475	ret = heim_ntlm_encode_type3(&type3, &data);
476	free(type3.sessionkey.data);
477	if (type3.lm.data)
478	    free(type3.lm.data);
479	if (type3.ntlm.data)
480	    free(type3.ntlm.data);
481	if (ret) {
482	    _gss_ntlm_delete_sec_context(minor_status, context_handle, NULL);
483	    *minor_status = ret;
484	    return GSS_S_FAILURE;
485	}
486
487	output_token->length = data.length;
488	output_token->value = data.data;
489
490	if (actual_mech_type)
491	    *actual_mech_type = GSS_NTLM_MECHANISM;
492	if (ret_flags)
493	    *ret_flags = 0;
494	if (time_rec)
495	    *time_rec = GSS_C_INDEFINITE;
496
497	ctx->status |= STATUS_OPEN;
498
499	return GSS_S_COMPLETE;
500    }
501}
502