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