1/*	$NetBSD: gssapictx.c,v 1.10 2024/02/21 22:52:06 christos Exp $	*/
2
3/*
4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 *
6 * SPDX-License-Identifier: MPL-2.0
7 *
8 * This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11 *
12 * See the COPYRIGHT file distributed with this work for additional
13 * information regarding copyright ownership.
14 */
15
16#include <ctype.h>
17#include <inttypes.h>
18#include <stdbool.h>
19#include <stdlib.h>
20#include <string.h>
21#include <time.h>
22
23#if HAVE_GSSAPI_GSSAPI_H
24#include <gssapi/gssapi.h>
25#elif HAVE_GSSAPI_H
26#include <gssapi.h>
27#endif
28
29#if HAVE_GSSAPI_GSSAPI_KRB5_H
30#include <gssapi/gssapi_krb5.h>
31#elif HAVE_GSSAPI_KRB5_H
32#include <gssapi_krb5.h>
33#endif
34
35#if HAVE_KRB5_KRB5_H
36#include <krb5/krb5.h>
37#elif HAVE_KRB5_H
38#include <krb5.h>
39#endif
40
41#include <isc/buffer.h>
42#include <isc/dir.h>
43#include <isc/file.h>
44#include <isc/lex.h>
45#include <isc/mem.h>
46#include <isc/once.h>
47#include <isc/print.h>
48#include <isc/random.h>
49#include <isc/result.h>
50#include <isc/string.h>
51#include <isc/time.h>
52#include <isc/util.h>
53
54#include <dns/fixedname.h>
55#include <dns/keyvalues.h>
56#include <dns/log.h>
57#include <dns/name.h>
58#include <dns/rdata.h>
59#include <dns/rdataclass.h>
60#include <dns/types.h>
61
62#include <dst/gssapi.h>
63
64#include "dst_internal.h"
65
66#if HAVE_GSSAPI
67
68#ifndef GSS_KRB5_MECHANISM
69static unsigned char krb5_mech_oid_bytes[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7,
70					       0x12, 0x01, 0x02, 0x02 };
71static gss_OID_desc __gss_krb5_mechanism_oid_desc = {
72	sizeof(krb5_mech_oid_bytes), krb5_mech_oid_bytes
73};
74#define GSS_KRB5_MECHANISM (&__gss_krb5_mechanism_oid_desc)
75#endif /* ifndef GSS_KRB5_MECHANISM */
76
77#ifndef GSS_SPNEGO_MECHANISM
78static unsigned char spnego_mech_oid_bytes[] = { 0x2b, 0x06, 0x01,
79						 0x05, 0x05, 0x02 };
80static gss_OID_desc __gss_spnego_mechanism_oid_desc = {
81	sizeof(spnego_mech_oid_bytes), spnego_mech_oid_bytes
82};
83#define GSS_SPNEGO_MECHANISM (&__gss_spnego_mechanism_oid_desc)
84#endif /* ifndef GSS_SPNEGO_MECHANISM */
85
86#define REGION_TO_GBUFFER(r, gb)          \
87	do {                              \
88		(gb).length = (r).length; \
89		(gb).value = (r).base;    \
90	} while (0)
91
92#define GBUFFER_TO_REGION(gb, r)                        \
93	do {                                            \
94		(r).length = (unsigned int)(gb).length; \
95		(r).base = (gb).value;                  \
96	} while (0)
97
98#define RETERR(x)                            \
99	do {                                 \
100		result = (x);                \
101		if (result != ISC_R_SUCCESS) \
102			goto out;            \
103	} while (0)
104
105static void
106name_to_gbuffer(const dns_name_t *name, isc_buffer_t *buffer,
107		gss_buffer_desc *gbuffer) {
108	dns_name_t tname;
109	const dns_name_t *namep;
110	isc_region_t r;
111	isc_result_t result;
112
113	if (!dns_name_isabsolute(name)) {
114		namep = name;
115	} else {
116		unsigned int labels;
117		dns_name_init(&tname, NULL);
118		labels = dns_name_countlabels(name);
119		dns_name_getlabelsequence(name, 0, labels - 1, &tname);
120		namep = &tname;
121	}
122
123	result = dns_name_toprincipal(namep, buffer);
124	RUNTIME_CHECK(result == ISC_R_SUCCESS);
125	isc_buffer_putuint8(buffer, 0);
126	isc_buffer_usedregion(buffer, &r);
127	REGION_TO_GBUFFER(r, *gbuffer);
128}
129
130static void
131log_cred(const gss_cred_id_t cred) {
132	OM_uint32 gret, minor, lifetime;
133	gss_name_t gname;
134	gss_buffer_desc gbuffer;
135	gss_cred_usage_t usage;
136	const char *usage_text;
137	char buf[1024];
138
139	gret = gss_inquire_cred(&minor, cred, &gname, &lifetime, &usage, NULL);
140	if (gret != GSS_S_COMPLETE) {
141		gss_log(3, "failed gss_inquire_cred: %s",
142			gss_error_tostring(gret, minor, buf, sizeof(buf)));
143		return;
144	}
145
146	gret = gss_display_name(&minor, gname, &gbuffer, NULL);
147	if (gret != GSS_S_COMPLETE) {
148		gss_log(3, "failed gss_display_name: %s",
149			gss_error_tostring(gret, minor, buf, sizeof(buf)));
150	} else {
151		switch (usage) {
152		case GSS_C_BOTH:
153			usage_text = "GSS_C_BOTH";
154			break;
155		case GSS_C_INITIATE:
156			usage_text = "GSS_C_INITIATE";
157			break;
158		case GSS_C_ACCEPT:
159			usage_text = "GSS_C_ACCEPT";
160			break;
161		default:
162			usage_text = "???";
163		}
164		gss_log(3, "gss cred: \"%s\", %s, %lu", (char *)gbuffer.value,
165			usage_text, (unsigned long)lifetime);
166	}
167
168	if (gret == GSS_S_COMPLETE) {
169		if (gbuffer.length != 0U) {
170			gret = gss_release_buffer(&minor, &gbuffer);
171			if (gret != GSS_S_COMPLETE) {
172				gss_log(3, "failed gss_release_buffer: %s",
173					gss_error_tostring(gret, minor, buf,
174							   sizeof(buf)));
175			}
176		}
177	}
178
179	gret = gss_release_name(&minor, &gname);
180	if (gret != GSS_S_COMPLETE) {
181		gss_log(3, "failed gss_release_name: %s",
182			gss_error_tostring(gret, minor, buf, sizeof(buf)));
183	}
184}
185
186/*
187 * check for the most common configuration errors.
188 *
189 * The errors checked for are:
190 *   - tkey-gssapi-credential doesn't start with DNS/
191 *   - the default realm in /etc/krb5.conf and the
192 *     tkey-gssapi-credential bind config option don't match
193 *
194 * Note that if tkey-gssapi-keytab is set then these configure checks
195 * are not performed, and runtime errors from gssapi are used instead
196 */
197static void
198check_config(const char *gss_name) {
199	const char *p;
200	krb5_context krb5_ctx;
201	char *krb5_realm_name = NULL;
202
203	if (strncasecmp(gss_name, "DNS/", 4) != 0) {
204		gss_log(ISC_LOG_ERROR,
205			"tkey-gssapi-credential (%s) "
206			"should start with 'DNS/'",
207			gss_name);
208		return;
209	}
210
211	if (krb5_init_context(&krb5_ctx) != 0) {
212		gss_log(ISC_LOG_ERROR, "Unable to initialise krb5 context");
213		return;
214	}
215	if (krb5_get_default_realm(krb5_ctx, &krb5_realm_name) != 0) {
216		gss_log(ISC_LOG_ERROR, "Unable to get krb5 default realm");
217		krb5_free_context(krb5_ctx);
218		return;
219	}
220	p = strchr(gss_name, '@');
221	if (p == NULL) {
222		gss_log(ISC_LOG_ERROR,
223			"badly formatted "
224			"tkey-gssapi-credentials (%s)",
225			gss_name);
226		krb5_free_context(krb5_ctx);
227		return;
228	}
229	if (strcasecmp(p + 1, krb5_realm_name) != 0) {
230		gss_log(ISC_LOG_ERROR,
231			"default realm from krb5.conf (%s) "
232			"does not match tkey-gssapi-credential (%s)",
233			krb5_realm_name, gss_name);
234		krb5_free_context(krb5_ctx);
235		return;
236	}
237	krb5_free_context(krb5_ctx);
238}
239
240static OM_uint32
241mech_oid_set_create(OM_uint32 *minor, gss_OID_set *mech_oid_set) {
242	OM_uint32 gret;
243
244	gret = gss_create_empty_oid_set(minor, mech_oid_set);
245	if (gret != GSS_S_COMPLETE) {
246		return (gret);
247	}
248
249	gret = gss_add_oid_set_member(minor, GSS_KRB5_MECHANISM, mech_oid_set);
250	if (gret != GSS_S_COMPLETE) {
251		goto release;
252	}
253
254	gret = gss_add_oid_set_member(minor, GSS_SPNEGO_MECHANISM,
255				      mech_oid_set);
256	if (gret != GSS_S_COMPLETE) {
257		goto release;
258	}
259
260release:
261	REQUIRE(gss_release_oid_set(minor, mech_oid_set) == GSS_S_COMPLETE);
262
263	return (gret);
264}
265
266static void
267mech_oid_set_release(gss_OID_set *mech_oid_set) {
268	OM_uint32 minor;
269
270	REQUIRE(gss_release_oid_set(&minor, mech_oid_set) == GSS_S_COMPLETE);
271}
272
273isc_result_t
274dst_gssapi_acquirecred(const dns_name_t *name, bool initiate,
275		       dns_gss_cred_id_t *cred) {
276	isc_result_t result;
277	isc_buffer_t namebuf;
278	gss_name_t gname;
279	gss_buffer_desc gnamebuf;
280	unsigned char array[DNS_NAME_MAXTEXT + 1];
281	OM_uint32 gret, minor;
282	OM_uint32 lifetime;
283	gss_cred_usage_t usage;
284	char buf[1024];
285	gss_OID_set mech_oid_set;
286
287	REQUIRE(cred != NULL && *cred == NULL);
288
289	/*
290	 * XXXSRA In theory we could use GSS_C_NT_HOSTBASED_SERVICE
291	 * here when we're in the acceptor role, which would let us
292	 * default the hostname and use a compiled in default service
293	 * name of "DNS", giving one less thing to configure in
294	 * named.conf.	Unfortunately, this creates a circular
295	 * dependency due to DNS-based realm lookup in at least one
296	 * GSSAPI implementation (Heimdal).  Oh well.
297	 */
298	if (name != NULL) {
299		isc_buffer_init(&namebuf, array, sizeof(array));
300		name_to_gbuffer(name, &namebuf, &gnamebuf);
301		gret = gss_import_name(&minor, &gnamebuf, GSS_C_NO_OID, &gname);
302		if (gret != GSS_S_COMPLETE) {
303			check_config((char *)array);
304
305			gss_log(3, "failed gss_import_name: %s",
306				gss_error_tostring(gret, minor, buf,
307						   sizeof(buf)));
308			return (ISC_R_FAILURE);
309		}
310	} else {
311		gname = NULL;
312	}
313
314	/* Get the credentials. */
315	if (gname != NULL) {
316		gss_log(3, "acquiring credentials for %s",
317			(char *)gnamebuf.value);
318	} else {
319		/* XXXDCL does this even make any sense? */
320		gss_log(3, "acquiring credentials for ?");
321	}
322
323	if (initiate) {
324		usage = GSS_C_INITIATE;
325	} else {
326		usage = GSS_C_ACCEPT;
327	}
328
329	gret = mech_oid_set_create(&minor, &mech_oid_set);
330	if (gret != GSS_S_COMPLETE) {
331		gss_log(3, "failed to create OID_set: %s",
332			gss_error_tostring(gret, minor, buf, sizeof(buf)));
333		return (ISC_R_FAILURE);
334	}
335
336	gret = gss_acquire_cred(&minor, gname, GSS_C_INDEFINITE, mech_oid_set,
337				usage, (gss_cred_id_t *)cred, NULL, &lifetime);
338
339	if (gret != GSS_S_COMPLETE) {
340		gss_log(3, "failed to acquire %s credentials for %s: %s",
341			initiate ? "initiate" : "accept",
342			(gname != NULL) ? (char *)gnamebuf.value : "?",
343			gss_error_tostring(gret, minor, buf, sizeof(buf)));
344		if (gname != NULL) {
345			check_config((char *)array);
346		}
347		result = ISC_R_FAILURE;
348		goto cleanup;
349	}
350
351	gss_log(4, "acquired %s credentials for %s",
352		initiate ? "initiate" : "accept",
353		(gname != NULL) ? (char *)gnamebuf.value : "?");
354
355	log_cred(*cred);
356	result = ISC_R_SUCCESS;
357
358cleanup:
359	mech_oid_set_release(&mech_oid_set);
360
361	if (gname != NULL) {
362		gret = gss_release_name(&minor, &gname);
363		if (gret != GSS_S_COMPLETE) {
364			gss_log(3, "failed gss_release_name: %s",
365				gss_error_tostring(gret, minor, buf,
366						   sizeof(buf)));
367		}
368	}
369
370	return (result);
371}
372
373bool
374dst_gssapi_identitymatchesrealmkrb5(const dns_name_t *signer,
375				    const dns_name_t *name,
376				    const dns_name_t *realm, bool subdomain) {
377	char sbuf[DNS_NAME_FORMATSIZE];
378	char rbuf[DNS_NAME_FORMATSIZE];
379	char *sname;
380	char *rname;
381	isc_buffer_t buffer;
382	isc_result_t result;
383
384	/*
385	 * It is far, far easier to write the names we are looking at into
386	 * a string, and do string operations on them.
387	 */
388	isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
389	result = dns_name_toprincipal(signer, &buffer);
390	RUNTIME_CHECK(result == ISC_R_SUCCESS);
391	isc_buffer_putuint8(&buffer, 0);
392	dns_name_format(realm, rbuf, sizeof(rbuf));
393
394	/*
395	 * Find the realm portion.  This is the part after the @.  If it
396	 * does not exist, we don't have something we like, so we fail our
397	 * compare.
398	 */
399	rname = strchr(sbuf, '@');
400	if (rname == NULL) {
401		return (false);
402	}
403	*rname = '\0';
404	rname++;
405
406	if (strcmp(rname, rbuf) != 0) {
407		return (false);
408	}
409
410	/*
411	 * Find the host portion of the signer's name.	We do this by
412	 * searching for the first / character.  We then check to make
413	 * certain the instance name is "host"
414	 *
415	 * This will work for
416	 *    host/example.com@EXAMPLE.COM
417	 */
418	sname = strchr(sbuf, '/');
419	if (sname == NULL) {
420		return (false);
421	}
422	*sname = '\0';
423	sname++;
424	if (strcmp(sbuf, "host") != 0) {
425		return (false);
426	}
427
428	/*
429	 * If name is non NULL check that it matches against the
430	 * machine name as expected.
431	 */
432	if (name != NULL) {
433		dns_fixedname_t fixed;
434		dns_name_t *machine;
435
436		machine = dns_fixedname_initname(&fixed);
437		result = dns_name_fromstring(machine, sname, 0, NULL);
438		if (result != ISC_R_SUCCESS) {
439			return (false);
440		}
441		if (subdomain) {
442			return (dns_name_issubdomain(name, machine));
443		}
444		return (dns_name_equal(name, machine));
445	}
446
447	return (true);
448}
449
450bool
451dst_gssapi_identitymatchesrealmms(const dns_name_t *signer,
452				  const dns_name_t *name,
453				  const dns_name_t *realm, bool subdomain) {
454	char sbuf[DNS_NAME_FORMATSIZE];
455	char rbuf[DNS_NAME_FORMATSIZE];
456	char *sname;
457	char *rname;
458	isc_buffer_t buffer;
459	isc_result_t result;
460
461	/*
462	 * It is far, far easier to write the names we are looking at into
463	 * a string, and do string operations on them.
464	 */
465	isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
466	result = dns_name_toprincipal(signer, &buffer);
467	RUNTIME_CHECK(result == ISC_R_SUCCESS);
468	isc_buffer_putuint8(&buffer, 0);
469	dns_name_format(realm, rbuf, sizeof(rbuf));
470
471	/*
472	 * Find the realm portion.  This is the part after the @.  If it
473	 * does not exist, we don't have something we like, so we fail our
474	 * compare.
475	 */
476	rname = strchr(sbuf, '@');
477	if (rname == NULL) {
478		return (false);
479	}
480	sname = strchr(sbuf, '$');
481	if (sname == NULL) {
482		return (false);
483	}
484
485	/*
486	 * Verify that the $ and @ follow one another.
487	 */
488	if (rname - sname != 1) {
489		return (false);
490	}
491
492	/*
493	 * Find the host portion of the signer's name.	Zero out the $ so
494	 * it terminates the signer's name, and skip past the @ for
495	 * the realm.
496	 *
497	 * All service principals in Microsoft format seem to be in
498	 *    machinename$@EXAMPLE.COM
499	 * format.
500	 */
501	rname++;
502	*sname = '\0';
503
504	if (strcmp(rname, rbuf) != 0) {
505		return (false);
506	}
507
508	/*
509	 * Now, we check that the realm matches (case sensitive) and that
510	 * 'name' matches against 'machinename' qualified with 'realm'.
511	 */
512	if (name != NULL) {
513		dns_fixedname_t fixed;
514		dns_name_t *machine;
515
516		machine = dns_fixedname_initname(&fixed);
517		result = dns_name_fromstring2(machine, sbuf, realm, 0, NULL);
518		if (result != ISC_R_SUCCESS) {
519			return (false);
520		}
521		if (subdomain) {
522			return (dns_name_issubdomain(name, machine));
523		}
524		return (dns_name_equal(name, machine));
525	}
526
527	return (true);
528}
529
530isc_result_t
531dst_gssapi_releasecred(dns_gss_cred_id_t *cred) {
532	OM_uint32 gret, minor;
533	char buf[1024];
534
535	REQUIRE(cred != NULL && *cred != NULL);
536
537	gret = gss_release_cred(&minor, (gss_cred_id_t *)cred);
538	if (gret != GSS_S_COMPLETE) {
539		/* Log the error, but still free the credential's memory */
540		gss_log(3, "failed releasing credential: %s",
541			gss_error_tostring(gret, minor, buf, sizeof(buf)));
542	}
543	*cred = NULL;
544
545	return (ISC_R_SUCCESS);
546}
547
548/*
549 * Format a gssapi error message info into a char ** on the given memory
550 * context. This is used to return gssapi error messages back up the
551 * call chain for reporting to the user.
552 */
553static void
554gss_err_message(isc_mem_t *mctx, uint32_t major, uint32_t minor,
555		char **err_message) {
556	char buf[1024];
557	char *estr;
558
559	if (err_message == NULL || mctx == NULL) {
560		/* the caller doesn't want any error messages */
561		return;
562	}
563
564	estr = gss_error_tostring(major, minor, buf, sizeof(buf));
565	if (estr != NULL) {
566		(*err_message) = isc_mem_strdup(mctx, estr);
567	}
568}
569
570isc_result_t
571dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken,
572		   isc_buffer_t *outtoken, dns_gss_ctx_id_t *gssctx,
573		   isc_mem_t *mctx, char **err_message) {
574	isc_region_t r;
575	isc_buffer_t namebuf;
576	gss_name_t gname;
577	OM_uint32 gret, minor, ret_flags, flags;
578	gss_buffer_desc gintoken, *gintokenp, gouttoken = GSS_C_EMPTY_BUFFER;
579	isc_result_t result;
580	gss_buffer_desc gnamebuf;
581	unsigned char array[DNS_NAME_MAXTEXT + 1];
582
583	/* Client must pass us a valid gss_ctx_id_t here */
584	REQUIRE(gssctx != NULL);
585	REQUIRE(mctx != NULL);
586
587	isc_buffer_init(&namebuf, array, sizeof(array));
588	name_to_gbuffer(name, &namebuf, &gnamebuf);
589
590	/* Get the name as a GSS name */
591	gret = gss_import_name(&minor, &gnamebuf, GSS_C_NO_OID, &gname);
592	if (gret != GSS_S_COMPLETE) {
593		gss_err_message(mctx, gret, minor, err_message);
594		result = ISC_R_FAILURE;
595		goto out;
596	}
597
598	if (intoken != NULL) {
599		/* Don't call gss_release_buffer for gintoken! */
600		REGION_TO_GBUFFER(*intoken, gintoken);
601		gintokenp = &gintoken;
602	} else {
603		gintokenp = NULL;
604	}
605
606	/*
607	 * Note that we don't set GSS_C_SEQUENCE_FLAG as Windows DNS
608	 * servers don't like it.
609	 */
610	flags = GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG;
611
612	gret = gss_init_sec_context(
613		&minor, GSS_C_NO_CREDENTIAL, (gss_ctx_id_t *)gssctx, gname,
614		GSS_SPNEGO_MECHANISM, flags, 0, NULL, gintokenp, NULL,
615		&gouttoken, &ret_flags, NULL);
616
617	if (gret != GSS_S_COMPLETE && gret != GSS_S_CONTINUE_NEEDED) {
618		gss_err_message(mctx, gret, minor, err_message);
619		if (err_message != NULL && *err_message != NULL) {
620			gss_log(3, "Failure initiating security context: %s",
621				*err_message);
622		} else {
623			gss_log(3, "Failure initiating security context");
624		}
625
626		result = ISC_R_FAILURE;
627		goto out;
628	}
629
630	/*
631	 * XXXSRA Not handled yet: RFC 3645 3.1.1: check ret_flags
632	 * MUTUAL and INTEG flags, fail if either not set.
633	 */
634
635	/*
636	 * RFC 2744 states the a valid output token has a non-zero length.
637	 */
638	if (gouttoken.length != 0U) {
639		GBUFFER_TO_REGION(gouttoken, r);
640		RETERR(isc_buffer_copyregion(outtoken, &r));
641	}
642
643	if (gret == GSS_S_COMPLETE) {
644		result = ISC_R_SUCCESS;
645	} else {
646		result = DNS_R_CONTINUE;
647	}
648
649out:
650	if (gouttoken.length != 0U) {
651		(void)gss_release_buffer(&minor, &gouttoken);
652	}
653	(void)gss_release_name(&minor, &gname);
654	return (result);
655}
656
657isc_result_t
658dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab,
659		     isc_region_t *intoken, isc_buffer_t **outtoken,
660		     dns_gss_ctx_id_t *ctxout, dns_name_t *principal,
661		     isc_mem_t *mctx) {
662	isc_region_t r;
663	isc_buffer_t namebuf;
664	gss_buffer_desc gnamebuf = GSS_C_EMPTY_BUFFER, gintoken,
665			gouttoken = GSS_C_EMPTY_BUFFER;
666	OM_uint32 gret, minor;
667	gss_ctx_id_t context = GSS_C_NO_CONTEXT;
668	gss_name_t gname = NULL;
669	isc_result_t result;
670	char buf[1024];
671
672	REQUIRE(outtoken != NULL && *outtoken == NULL);
673
674	REGION_TO_GBUFFER(*intoken, gintoken);
675
676	if (*ctxout == NULL) {
677		context = GSS_C_NO_CONTEXT;
678	} else {
679		context = *ctxout;
680	}
681
682	if (gssapi_keytab != NULL) {
683#if HAVE_GSSAPI_GSSAPI_KRB5_H || HAVE_GSSAPI_KRB5_H
684		gret = gsskrb5_register_acceptor_identity(gssapi_keytab);
685		if (gret != GSS_S_COMPLETE) {
686			gss_log(3,
687				"failed "
688				"gsskrb5_register_acceptor_identity(%s): %s",
689				gssapi_keytab,
690				gss_error_tostring(gret, 0, buf, sizeof(buf)));
691			return (DNS_R_INVALIDTKEY);
692		}
693#else
694		/*
695		 * Minimize memory leakage by only setting KRB5_KTNAME
696		 * if it needs to change.
697		 */
698		const char *old = getenv("KRB5_KTNAME");
699		if (old == NULL || strcmp(old, gssapi_keytab) != 0) {
700			size_t size;
701			char *kt;
702
703			size = strlen(gssapi_keytab) + 13;
704			kt = malloc(size);
705			if (kt == NULL) {
706				return (ISC_R_NOMEMORY);
707			}
708			snprintf(kt, size, "KRB5_KTNAME=%s", gssapi_keytab);
709			if (putenv(kt) != 0) {
710				return (ISC_R_NOMEMORY);
711			}
712		}
713#endif
714	}
715
716	log_cred(cred);
717
718	gret = gss_accept_sec_context(&minor, &context, cred, &gintoken,
719				      GSS_C_NO_CHANNEL_BINDINGS, &gname, NULL,
720				      &gouttoken, NULL, NULL, NULL);
721
722	result = ISC_R_FAILURE;
723
724	switch (gret) {
725	case GSS_S_COMPLETE:
726	case GSS_S_CONTINUE_NEEDED:
727		break;
728	case GSS_S_DEFECTIVE_TOKEN:
729	case GSS_S_DEFECTIVE_CREDENTIAL:
730	case GSS_S_BAD_SIG:
731	case GSS_S_DUPLICATE_TOKEN:
732	case GSS_S_OLD_TOKEN:
733	case GSS_S_NO_CRED:
734	case GSS_S_CREDENTIALS_EXPIRED:
735	case GSS_S_BAD_BINDINGS:
736	case GSS_S_NO_CONTEXT:
737	case GSS_S_BAD_MECH:
738	case GSS_S_FAILURE:
739		result = DNS_R_INVALIDTKEY;
740	/* fall through */
741	default:
742		gss_log(3, "failed gss_accept_sec_context: %s",
743			gss_error_tostring(gret, minor, buf, sizeof(buf)));
744		if (gouttoken.length > 0U) {
745			(void)gss_release_buffer(&minor, &gouttoken);
746		}
747		return (result);
748	}
749
750	if (gouttoken.length > 0U) {
751		isc_buffer_allocate(mctx, outtoken,
752				    (unsigned int)gouttoken.length);
753		GBUFFER_TO_REGION(gouttoken, r);
754		RETERR(isc_buffer_copyregion(*outtoken, &r));
755		(void)gss_release_buffer(&minor, &gouttoken);
756	}
757
758	if (gret == GSS_S_COMPLETE) {
759		gret = gss_display_name(&minor, gname, &gnamebuf, NULL);
760		if (gret != GSS_S_COMPLETE) {
761			gss_log(3, "failed gss_display_name: %s",
762				gss_error_tostring(gret, minor, buf,
763						   sizeof(buf)));
764			RETERR(ISC_R_FAILURE);
765		}
766
767		/*
768		 * Compensate for a bug in Solaris8's implementation
769		 * of gss_display_name().  Should be harmless in any
770		 * case, since principal names really should not
771		 * contain null characters.
772		 */
773		if (gnamebuf.length > 0U &&
774		    ((char *)gnamebuf.value)[gnamebuf.length - 1] == '\0')
775		{
776			gnamebuf.length--;
777		}
778
779		gss_log(3, "gss-api source name (accept) is %.*s",
780			(int)gnamebuf.length, (char *)gnamebuf.value);
781
782		GBUFFER_TO_REGION(gnamebuf, r);
783		isc_buffer_init(&namebuf, r.base, r.length);
784		isc_buffer_add(&namebuf, r.length);
785
786		RETERR(dns_name_fromtext(principal, &namebuf, dns_rootname, 0,
787					 NULL));
788
789		if (gnamebuf.length != 0U) {
790			gret = gss_release_buffer(&minor, &gnamebuf);
791			if (gret != GSS_S_COMPLETE) {
792				gss_log(3, "failed gss_release_buffer: %s",
793					gss_error_tostring(gret, minor, buf,
794							   sizeof(buf)));
795			}
796		}
797	} else {
798		result = DNS_R_CONTINUE;
799	}
800
801	*ctxout = context;
802
803out:
804	if (gname != NULL) {
805		gret = gss_release_name(&minor, &gname);
806		if (gret != GSS_S_COMPLETE) {
807			gss_log(3, "failed gss_release_name: %s",
808				gss_error_tostring(gret, minor, buf,
809						   sizeof(buf)));
810		}
811	}
812
813	return (result);
814}
815
816isc_result_t
817dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx) {
818	OM_uint32 gret, minor;
819	char buf[1024];
820
821	UNUSED(mctx);
822
823	REQUIRE(gssctx != NULL && *gssctx != NULL);
824
825	/* Delete the context from the GSS provider */
826	gret = gss_delete_sec_context(&minor, (gss_ctx_id_t *)gssctx,
827				      GSS_C_NO_BUFFER);
828	if (gret != GSS_S_COMPLETE) {
829		/* Log the error, but still free the context's memory */
830		gss_log(3, "Failure deleting security context %s",
831			gss_error_tostring(gret, minor, buf, sizeof(buf)));
832	}
833	return (ISC_R_SUCCESS);
834}
835
836char *
837gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen) {
838	gss_buffer_desc msg_minor = GSS_C_EMPTY_BUFFER,
839			msg_major = GSS_C_EMPTY_BUFFER;
840	OM_uint32 msg_ctx, minor_stat;
841
842	/* Handle major status */
843	msg_ctx = 0;
844	(void)gss_display_status(&minor_stat, major, GSS_C_GSS_CODE,
845				 GSS_C_NULL_OID, &msg_ctx, &msg_major);
846
847	/* Handle minor status */
848	msg_ctx = 0;
849	(void)gss_display_status(&minor_stat, minor, GSS_C_MECH_CODE,
850				 GSS_C_NULL_OID, &msg_ctx, &msg_minor);
851
852	snprintf(buf, buflen, "GSSAPI error: Major = %s, Minor = %s.",
853		 (char *)msg_major.value, (char *)msg_minor.value);
854
855	if (msg_major.length != 0U) {
856		(void)gss_release_buffer(&minor_stat, &msg_major);
857	}
858	if (msg_minor.length != 0U) {
859		(void)gss_release_buffer(&minor_stat, &msg_minor);
860	}
861	return (buf);
862}
863
864#else
865
866isc_result_t
867dst_gssapi_acquirecred(const dns_name_t *name, bool initiate,
868		       dns_gss_cred_id_t *cred) {
869	REQUIRE(cred != NULL && *cred == NULL);
870
871	UNUSED(name);
872	UNUSED(initiate);
873	UNUSED(cred);
874
875	return (ISC_R_NOTIMPLEMENTED);
876}
877
878bool
879dst_gssapi_identitymatchesrealmkrb5(const dns_name_t *signer,
880				    const dns_name_t *name,
881				    const dns_name_t *realm, bool subdomain) {
882	UNUSED(signer);
883	UNUSED(name);
884	UNUSED(realm);
885	UNUSED(subdomain);
886
887	return (false);
888}
889
890bool
891dst_gssapi_identitymatchesrealmms(const dns_name_t *signer,
892				  const dns_name_t *name,
893				  const dns_name_t *realm, bool subdomain) {
894	UNUSED(signer);
895	UNUSED(name);
896	UNUSED(realm);
897	UNUSED(subdomain);
898
899	return (false);
900}
901
902isc_result_t
903dst_gssapi_releasecred(dns_gss_cred_id_t *cred) {
904	UNUSED(cred);
905
906	return (ISC_R_NOTIMPLEMENTED);
907}
908
909isc_result_t
910dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken,
911		   isc_buffer_t *outtoken, dns_gss_ctx_id_t *gssctx,
912		   isc_mem_t *mctx, char **err_message) {
913	UNUSED(name);
914	UNUSED(intoken);
915	UNUSED(outtoken);
916	UNUSED(gssctx);
917	UNUSED(mctx);
918	UNUSED(err_message);
919
920	return (ISC_R_NOTIMPLEMENTED);
921}
922
923isc_result_t
924dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab,
925		     isc_region_t *intoken, isc_buffer_t **outtoken,
926		     dns_gss_ctx_id_t *ctxout, dns_name_t *principal,
927		     isc_mem_t *mctx) {
928	UNUSED(cred);
929	UNUSED(gssapi_keytab);
930	UNUSED(intoken);
931	UNUSED(outtoken);
932	UNUSED(ctxout);
933	UNUSED(principal);
934	UNUSED(mctx);
935
936	return (ISC_R_NOTIMPLEMENTED);
937}
938
939isc_result_t
940dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx) {
941	UNUSED(mctx);
942	UNUSED(gssctx);
943	return (ISC_R_NOTIMPLEMENTED);
944}
945
946char *
947gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen) {
948	snprintf(buf, buflen, "GSSAPI error: Major = %u, Minor = %u.", major,
949		 minor);
950
951	return (buf);
952}
953
954#endif
955
956void
957gss_log(int level, const char *fmt, ...) {
958	va_list ap;
959
960	va_start(ap, fmt);
961	isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_TKEY,
962		       ISC_LOG_DEBUG(level), fmt, ap);
963	va_end(ap);
964}
965