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