1234370Sjasone/*	$NetBSD: init_sec_context.c,v 1.4 2023/06/19 21:41:43 christos Exp $	*/
2234370Sjasone
3234370Sjasone/*
4234370Sjasone * Copyright (c) 2006 - 2008 Kungliga Tekniska H��gskolan
5234370Sjasone * (Royal Institute of Technology, Stockholm, Sweden).
6234370Sjasone * All rights reserved.
7234370Sjasone *
8234370Sjasone * Redistribution and use in source and binary forms, with or without
9234370Sjasone * modification, are permitted provided that the following conditions
10234370Sjasone * are met:
11234370Sjasone *
12234370Sjasone * 1. Redistributions of source code must retain the above copyright
13234370Sjasone *    notice, this list of conditions and the following disclaimer.
14234370Sjasone *
15234370Sjasone * 2. Redistributions in binary form must reproduce the above copyright
16234370Sjasone *    notice, this list of conditions and the following disclaimer in the
17235238Sjasone *    documentation and/or other materials provided with the distribution.
18234370Sjasone *
19234370Sjasone * 3. Neither the name of the Institute nor the names of its contributors
20234370Sjasone *    may be used to endorse or promote products derived from this software
21234370Sjasone *    without specific prior written permission.
22234370Sjasone *
23234370Sjasone * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24234370Sjasone * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25234370Sjasone * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26234370Sjasone * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27234370Sjasone * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28234370Sjasone * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29234370Sjasone * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30234370Sjasone * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31234370Sjasone * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32234370Sjasone * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33234370Sjasone * SUCH DAMAGE.
34235238Sjasone */
35235238Sjasone
36235238Sjasone#include "ntlm.h"
37235238Sjasone
38234370Sjasonestatic int
39234370Sjasonefrom_file(const char *fn, const char *target_domain,
40234370Sjasone          char **domainp, char **usernamep, struct ntlm_buf *key)
41235238Sjasone{
42234370Sjasone    char *str, buf[1024];
43234370Sjasone    FILE *f;
44234370Sjasone
45234370Sjasone    *domainp = NULL;
46234370Sjasone
47234370Sjasone    f = fopen(fn, "r");
48234370Sjasone    if (f == NULL)
49234370Sjasone	return ENOENT;
50234370Sjasone    rk_cloexec_file(f);
51234543Sjasone
52234370Sjasone    while (fgets(buf, sizeof(buf), f) != NULL) {
53234370Sjasone	char *d, *u, *p;
54234370Sjasone	buf[strcspn(buf, "\r\n")] = '\0';
55234370Sjasone	if (buf[0] == '#')
56234370Sjasone	    continue;
57234370Sjasone	str = NULL;
58234370Sjasone	d = strtok_r(buf, ":", &str);
59234370Sjasone        free(*domainp);
60234370Sjasone	*domainp = NULL;
61234543Sjasone        if (!d)
62234370Sjasone            continue;
63234370Sjasone	if (d && target_domain != NULL && strcasecmp(target_domain, d) != 0)
64234370Sjasone	    continue;
65234543Sjasone        *domainp = strdup(d);
66234370Sjasone        if (*domainp == NULL)
67234370Sjasone            return ENOMEM;
68234370Sjasone	u = strtok_r(NULL, ":", &str);
69234370Sjasone	p = strtok_r(NULL, ":", &str);
70234370Sjasone	if (u == NULL || p == NULL)
71234370Sjasone	    continue;
72234370Sjasone
73234370Sjasone	*usernamep = strdup(u);
74234370Sjasone        if (*usernamep == NULL)
75235238Sjasone            return ENOMEM;
76235238Sjasone
77235238Sjasone	heim_ntlm_nt_key(p, key);
78235238Sjasone
79235238Sjasone	memset_s(buf, sizeof(buf), 0, sizeof(buf));
80235238Sjasone	fclose(f);
81235238Sjasone	return 0;
82235238Sjasone    }
83235238Sjasone    memset_s(buf, sizeof(buf), 0, sizeof(buf));
84235238Sjasone    fclose(f);
85235238Sjasone    return ENOENT;
86235238Sjasone}
87235238Sjasone
88235238Sjasonestatic int
89235238Sjasoneget_user_file(const ntlm_name target_name,
90235238Sjasone	      char **domainp, char **usernamep, struct ntlm_buf *key)
91235238Sjasone{
92235238Sjasone    const char *domain;
93235238Sjasone    const char *fn;
94235238Sjasone
95235238Sjasone    *domainp = NULL;
96235238Sjasone
97235238Sjasone    if (issuid())
98235238Sjasone	return ENOENT;
99235238Sjasone
100235238Sjasone    domain = target_name != NULL ? target_name->domain : NULL;
101235238Sjasone
102235238Sjasone    fn = getenv("NTLM_USER_FILE");
103235238Sjasone    if (fn == NULL)
104235238Sjasone	return ENOENT;
105235238Sjasone    if (from_file(fn, domain, domainp, usernamep, key) == 0)
106235238Sjasone	return 0;
107235238Sjasone
108    return ENOENT;
109}
110
111/*
112 * Pick up the ntlm cred from the default krb5 credential cache.
113 */
114
115static int
116get_user_ccache(const ntlm_name name, char **domainp, char **usernamep, struct ntlm_buf *key)
117{
118    krb5_context context = NULL;
119    krb5_principal client;
120    krb5_ccache id = NULL;
121    krb5_error_code ret;
122    char *confname;
123    krb5_data data;
124    int aret;
125
126    *domainp = NULL;
127    *usernamep = NULL;
128    krb5_data_zero(&data);
129    key->length = 0;
130    key->data = NULL;
131
132    ret = krb5_init_context(&context);
133    if (ret)
134	return ret;
135
136    ret = krb5_cc_default(context, &id);
137    if (ret)
138	goto out;
139
140    ret = krb5_cc_get_principal(context, id, &client);
141    if (ret)
142	goto out;
143
144    ret = krb5_unparse_name_flags(context, client,
145				  KRB5_PRINCIPAL_UNPARSE_NO_REALM,
146				  usernamep);
147    krb5_free_principal(context, client);
148    if (ret)
149	goto out;
150
151    if (name != NULL) {
152        *domainp = strdup(name->domain);
153    } else {
154        krb5_data data_domain;
155
156        krb5_data_zero(&data_domain);
157        ret = krb5_cc_get_config(context, id, NULL, "default-ntlm-domain",
158                                 &data_domain);
159        if (ret)
160            goto out;
161
162        *domainp = strndup(data_domain.data, data_domain.length);
163        krb5_data_free(&data_domain);
164    }
165
166    if (*domainp == NULL) {
167        ret = krb5_enomem(context);
168	goto out;
169    }
170
171    aret = asprintf(&confname, "ntlm-key-%s", *domainp);
172    if (aret == -1) {
173        ret = krb5_enomem(context);
174	goto out;
175    }
176
177    ret = krb5_cc_get_config(context, id, NULL, confname, &data);
178    if (ret)
179        goto out;
180
181    key->data = malloc(data.length);
182    if (key->data == NULL) {
183	ret = ENOMEM;
184	goto out;
185    }
186    key->length = data.length;
187    memcpy(key->data, data.data, data.length);
188
189 out:
190    krb5_data_free(&data);
191    if (id)
192	krb5_cc_close(context, id);
193
194    krb5_free_context(context);
195
196    return ret;
197}
198
199int
200_gss_ntlm_get_user_cred(const ntlm_name target_name,
201			ntlm_cred *rcred)
202{
203    ntlm_cred cred;
204    int ret;
205
206    cred = calloc(1, sizeof(*cred));
207    if (cred == NULL)
208	return ENOMEM;
209
210    ret = get_user_file(target_name,
211                        &cred->domain, &cred->username, &cred->key);
212    if (ret)
213	ret = get_user_ccache(target_name,
214                              &cred->domain, &cred->username, &cred->key);
215    if (ret) {
216	free(cred);
217	return ret;
218    }
219
220    *rcred = cred;
221
222    return ret;
223}
224
225static int
226_gss_copy_cred(ntlm_cred from, ntlm_cred *to)
227{
228    *to = calloc(1, sizeof(**to));
229    if (*to == NULL)
230	return ENOMEM;
231    (*to)->username = strdup(from->username);
232    if ((*to)->username == NULL) {
233	free(*to);
234	return ENOMEM;
235    }
236    (*to)->domain = strdup(from->domain);
237    if ((*to)->domain == NULL) {
238	free((*to)->username);
239	free(*to);
240	return ENOMEM;
241    }
242    (*to)->key.data = malloc(from->key.length);
243    if ((*to)->key.data == NULL) {
244	free((*to)->domain);
245	free((*to)->username);
246	free(*to);
247	return ENOMEM;
248    }
249    memcpy((*to)->key.data, from->key.data, from->key.length);
250    (*to)->key.length = from->key.length;
251
252    return 0;
253}
254
255OM_uint32 GSSAPI_CALLCONV
256_gss_ntlm_init_sec_context
257           (OM_uint32 * minor_status,
258            gss_const_cred_id_t initiator_cred_handle,
259            gss_ctx_id_t * context_handle,
260            gss_const_name_t target_name,
261            const gss_OID mech_type,
262            OM_uint32 req_flags,
263            OM_uint32 time_req,
264            const gss_channel_bindings_t input_chan_bindings,
265            const gss_buffer_t input_token,
266            gss_OID * actual_mech_type,
267            gss_buffer_t output_token,
268            OM_uint32 * ret_flags,
269            OM_uint32 * time_rec
270	   )
271{
272    ntlm_ctx ctx;
273    ntlm_name name = (ntlm_name)target_name;
274
275    *minor_status = 0;
276
277    if (ret_flags)
278	*ret_flags = 0;
279    if (time_rec)
280	*time_rec = 0;
281    if (actual_mech_type)
282	*actual_mech_type = GSS_C_NO_OID;
283
284    if (*context_handle == GSS_C_NO_CONTEXT) {
285	struct ntlm_type1 type1;
286	struct ntlm_buf data;
287	uint32_t flags = 0;
288	int ret;
289
290	ctx = calloc(1, sizeof(*ctx));
291	if (ctx == NULL) {
292	    *minor_status = EINVAL;
293	    return GSS_S_FAILURE;
294	}
295	*context_handle = (gss_ctx_id_t)ctx;
296
297	if (initiator_cred_handle != GSS_C_NO_CREDENTIAL) {
298	    ntlm_cred cred = (ntlm_cred)initiator_cred_handle;
299	    ret = _gss_copy_cred(cred, &ctx->client);
300	} else
301	    ret = _gss_ntlm_get_user_cred(name, &ctx->client);
302
303	if (ret) {
304	    _gss_ntlm_delete_sec_context(minor_status, context_handle, NULL);
305	    *minor_status = ret;
306	    return GSS_S_FAILURE;
307	}
308
309	if (req_flags & GSS_C_CONF_FLAG)
310	    flags |= NTLM_NEG_SEAL;
311	if (req_flags & GSS_C_INTEG_FLAG)
312	    flags |= NTLM_NEG_SIGN;
313	else
314	    flags |= NTLM_NEG_ALWAYS_SIGN;
315
316	flags |= NTLM_NEG_UNICODE;
317	flags |= NTLM_NEG_NTLM;
318	flags |= NTLM_NEG_NTLM2_SESSION;
319	flags |= NTLM_NEG_KEYEX;
320
321	memset(&type1, 0, sizeof(type1));
322
323	type1.flags = flags;
324	type1.domain = name->domain;
325	type1.hostname = NULL;
326	type1.os[0] = 0;
327	type1.os[1] = 0;
328
329	ret = heim_ntlm_encode_type1(&type1, &data);
330	if (ret) {
331	    _gss_ntlm_delete_sec_context(minor_status, context_handle, NULL);
332	    *minor_status = ret;
333	    return GSS_S_FAILURE;
334	}
335
336	output_token->value = data.data;
337	output_token->length = data.length;
338
339	return GSS_S_CONTINUE_NEEDED;
340    } else {
341	krb5_error_code ret;
342	struct ntlm_type2 type2;
343	struct ntlm_type3 type3;
344	struct ntlm_buf data;
345
346	ctx = (ntlm_ctx)*context_handle;
347
348	data.data = input_token->value;
349	data.length = input_token->length;
350
351	ret = heim_ntlm_decode_type2(&data, &type2);
352	if (ret) {
353	    _gss_ntlm_delete_sec_context(minor_status, context_handle, NULL);
354	    *minor_status = ret;
355	    return GSS_S_FAILURE;
356	}
357
358	ctx->flags = type2.flags;
359
360	/* XXX check that type2.targetinfo matches `target_name�� */
361	/* XXX check verify targetinfo buffer */
362
363	memset(&type3, 0, sizeof(type3));
364
365	type3.username = ctx->client->username;
366	type3.flags = type2.flags;
367	type3.targetname = type2.targetname;
368	type3.ws = rk_UNCONST("workstation");
369
370	/*
371	 * NTLM Version 1 if no targetinfo buffer.
372	 */
373
374	if (1 || type2.targetinfo.length == 0) {
375	    struct ntlm_buf sessionkey;
376
377	    if (type2.flags & NTLM_NEG_NTLM2_SESSION) {
378		unsigned char nonce[8];
379
380		if (RAND_bytes(nonce, sizeof(nonce)) != 1) {
381		    _gss_ntlm_delete_sec_context(minor_status,
382						 context_handle, NULL);
383		    *minor_status = EINVAL;
384		    return GSS_S_FAILURE;
385		}
386
387		ret = heim_ntlm_calculate_ntlm2_sess(nonce,
388						     type2.challenge,
389						     ctx->client->key.data,
390						     &type3.lm,
391						     &type3.ntlm);
392	    } else {
393		ret = heim_ntlm_calculate_ntlm1(ctx->client->key.data,
394						ctx->client->key.length,
395						type2.challenge,
396						&type3.ntlm);
397
398	    }
399	    if (ret) {
400		_gss_ntlm_delete_sec_context(minor_status,context_handle,NULL);
401		*minor_status = ret;
402		return GSS_S_FAILURE;
403	    }
404
405	    ret = heim_ntlm_build_ntlm1_master(ctx->client->key.data,
406					       ctx->client->key.length,
407					       &sessionkey,
408					       &type3.sessionkey);
409	    if (ret) {
410		if (type3.lm.data)
411		    free(type3.lm.data);
412		if (type3.ntlm.data)
413		    free(type3.ntlm.data);
414		_gss_ntlm_delete_sec_context(minor_status,context_handle,NULL);
415		*minor_status = ret;
416		return GSS_S_FAILURE;
417	    }
418
419	    ret = krb5_data_copy(&ctx->sessionkey,
420				 sessionkey.data, sessionkey.length);
421	    free(sessionkey.data);
422	    if (ret) {
423		if (type3.lm.data)
424		    free(type3.lm.data);
425		if (type3.ntlm.data)
426		    free(type3.ntlm.data);
427		_gss_ntlm_delete_sec_context(minor_status,context_handle,NULL);
428		*minor_status = ret;
429		return GSS_S_FAILURE;
430	    }
431	    ctx->status |= STATUS_SESSIONKEY;
432
433	} else {
434	    struct ntlm_buf sessionkey;
435	    unsigned char ntlmv2[16];
436	    struct ntlm_targetinfo ti;
437
438	    /* verify infotarget */
439
440	    ret = heim_ntlm_decode_targetinfo(&type2.targetinfo, 1, &ti);
441	    if(ret) {
442		_gss_ntlm_delete_sec_context(minor_status,
443					     context_handle, NULL);
444		*minor_status = ret;
445		return GSS_S_FAILURE;
446	    }
447
448	    if (ti.domainname && strcmp(ti.domainname, name->domain) != 0) {
449		_gss_ntlm_delete_sec_context(minor_status,
450					     context_handle, NULL);
451		*minor_status = EINVAL;
452		return GSS_S_FAILURE;
453	    }
454
455	    ret = heim_ntlm_calculate_ntlm2(ctx->client->key.data,
456					    ctx->client->key.length,
457					    ctx->client->username,
458					    name->domain,
459					    type2.challenge,
460					    &type2.targetinfo,
461					    ntlmv2,
462					    &type3.ntlm);
463	    if (ret) {
464		_gss_ntlm_delete_sec_context(minor_status,
465					     context_handle, NULL);
466		*minor_status = ret;
467		return GSS_S_FAILURE;
468	    }
469
470	    ret = heim_ntlm_build_ntlm1_master(ntlmv2, sizeof(ntlmv2),
471					       &sessionkey,
472					       &type3.sessionkey);
473	    memset_s(ntlmv2, sizeof(ntlmv2), 0, sizeof(ntlmv2));
474	    if (ret) {
475		_gss_ntlm_delete_sec_context(minor_status,
476					     context_handle, NULL);
477		*minor_status = ret;
478		return GSS_S_FAILURE;
479	    }
480
481	    ctx->flags |= NTLM_NEG_NTLM2_SESSION;
482
483	    ret = krb5_data_copy(&ctx->sessionkey,
484				 sessionkey.data, sessionkey.length);
485	    free(sessionkey.data);
486	    if (ret) {
487		_gss_ntlm_delete_sec_context(minor_status,
488					     context_handle, NULL);
489		*minor_status = ret;
490		return GSS_S_FAILURE;
491	    }
492	}
493
494	if (ctx->flags & NTLM_NEG_NTLM2_SESSION) {
495	    ctx->status |= STATUS_SESSIONKEY;
496	    _gss_ntlm_set_key(&ctx->u.v2.send, 0, (ctx->flags & NTLM_NEG_KEYEX),
497			      ctx->sessionkey.data,
498			      ctx->sessionkey.length);
499	    _gss_ntlm_set_key(&ctx->u.v2.recv, 1, (ctx->flags & NTLM_NEG_KEYEX),
500			      ctx->sessionkey.data,
501			      ctx->sessionkey.length);
502	} else {
503	    ctx->status |= STATUS_SESSIONKEY;
504	    RC4_set_key(&ctx->u.v1.crypto_recv.key,
505			ctx->sessionkey.length,
506			ctx->sessionkey.data);
507	    RC4_set_key(&ctx->u.v1.crypto_send.key,
508			ctx->sessionkey.length,
509			ctx->sessionkey.data);
510	}
511
512
513
514	ret = heim_ntlm_encode_type3(&type3, &data, NULL);
515	free(type3.sessionkey.data);
516	if (type3.lm.data)
517	    free(type3.lm.data);
518	if (type3.ntlm.data)
519	    free(type3.ntlm.data);
520	if (ret) {
521	    _gss_ntlm_delete_sec_context(minor_status, context_handle, NULL);
522	    *minor_status = ret;
523	    return GSS_S_FAILURE;
524	}
525
526	output_token->length = data.length;
527	output_token->value = data.data;
528
529	if (actual_mech_type)
530	    *actual_mech_type = GSS_NTLM_MECHANISM;
531	if (ret_flags)
532	    *ret_flags = 0;
533	if (time_rec)
534	    *time_rec = GSS_C_INDEFINITE;
535
536	ctx->status |= STATUS_OPEN;
537
538	return GSS_S_COMPLETE;
539    }
540}
541