xref: /netbsd-src/external/mpl/bind/dist/lib/dns/gssapictx.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
1 /*	$NetBSD: gssapictx.c,v 1.11 2025/01/26 16:25:22 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/random.h>
48 #include <isc/result.h>
49 #include <isc/string.h>
50 #include <isc/time.h>
51 #include <isc/util.h>
52 
53 #include <dns/fixedname.h>
54 #include <dns/keyvalues.h>
55 #include <dns/log.h>
56 #include <dns/name.h>
57 #include <dns/rdata.h>
58 #include <dns/rdataclass.h>
59 #include <dns/types.h>
60 
61 #include <dst/gssapi.h>
62 
63 #include "dst_internal.h"
64 
65 #if HAVE_GSSAPI
66 
67 #ifndef GSS_KRB5_MECHANISM
68 static unsigned char krb5_mech_oid_bytes[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7,
69 					       0x12, 0x01, 0x02, 0x02 };
70 static gss_OID_desc __gss_krb5_mechanism_oid_desc = {
71 	sizeof(krb5_mech_oid_bytes), krb5_mech_oid_bytes
72 };
73 #define GSS_KRB5_MECHANISM (&__gss_krb5_mechanism_oid_desc)
74 #endif /* ifndef GSS_KRB5_MECHANISM */
75 
76 #ifndef GSS_SPNEGO_MECHANISM
77 static unsigned char spnego_mech_oid_bytes[] = { 0x2b, 0x06, 0x01,
78 						 0x05, 0x05, 0x02 };
79 static gss_OID_desc __gss_spnego_mechanism_oid_desc = {
80 	sizeof(spnego_mech_oid_bytes), spnego_mech_oid_bytes
81 };
82 #define GSS_SPNEGO_MECHANISM (&__gss_spnego_mechanism_oid_desc)
83 #endif /* ifndef GSS_SPNEGO_MECHANISM */
84 
85 #define REGION_TO_GBUFFER(r, gb)          \
86 	do {                              \
87 		(gb).length = (r).length; \
88 		(gb).value = (r).base;    \
89 	} while (0)
90 
91 #define GBUFFER_TO_REGION(gb, r)                        \
92 	do {                                            \
93 		(r).length = (unsigned int)(gb).length; \
94 		(r).base = (gb).value;                  \
95 	} while (0)
96 
97 #define RETERR(x)                            \
98 	do {                                 \
99 		result = (x);                \
100 		if (result != ISC_R_SUCCESS) \
101 			goto out;            \
102 	} while (0)
103 
104 static void
105 name_to_gbuffer(const dns_name_t *name, isc_buffer_t *buffer,
106 		gss_buffer_desc *gbuffer) {
107 	dns_name_t tname;
108 	const dns_name_t *namep;
109 	isc_region_t r;
110 	isc_result_t result;
111 
112 	if (!dns_name_isabsolute(name)) {
113 		namep = name;
114 	} else {
115 		unsigned int labels;
116 		dns_name_init(&tname, NULL);
117 		labels = dns_name_countlabels(name);
118 		dns_name_getlabelsequence(name, 0, labels - 1, &tname);
119 		namep = &tname;
120 	}
121 
122 	result = dns_name_totext(
123 		namep, DNS_NAME_OMITFINALDOT | DNS_NAME_PRINCIPAL, 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 
130 static void
131 log_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  */
197 static void
198 check_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 
240 static OM_uint32
241 mech_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 
260 release:
261 	REQUIRE(gss_release_oid_set(minor, mech_oid_set) == GSS_S_COMPLETE);
262 
263 	return gret;
264 }
265 
266 static void
267 mech_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 
273 isc_result_t
274 dst_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 
358 cleanup:
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 
373 bool
374 dst_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_totext(
390 		signer, DNS_NAME_OMITFINALDOT | DNS_NAME_PRINCIPAL, &buffer);
391 	RUNTIME_CHECK(result == ISC_R_SUCCESS);
392 	isc_buffer_putuint8(&buffer, 0);
393 	dns_name_format(realm, rbuf, sizeof(rbuf));
394 
395 	/*
396 	 * Find the realm portion.  This is the part after the @.  If it
397 	 * does not exist, we don't have something we like, so we fail our
398 	 * compare.
399 	 */
400 	rname = strchr(sbuf, '@');
401 	if (rname == NULL) {
402 		return false;
403 	}
404 	*rname = '\0';
405 	rname++;
406 
407 	if (strcmp(rname, rbuf) != 0) {
408 		return false;
409 	}
410 
411 	/*
412 	 * Find the host portion of the signer's name.	We do this by
413 	 * searching for the first / character.  We then check to make
414 	 * certain the instance name is "host"
415 	 *
416 	 * This will work for
417 	 *    host/example.com@EXAMPLE.COM
418 	 */
419 	sname = strchr(sbuf, '/');
420 	if (sname == NULL) {
421 		return false;
422 	}
423 	*sname = '\0';
424 	sname++;
425 	if (strcmp(sbuf, "host") != 0) {
426 		return false;
427 	}
428 
429 	/*
430 	 * If name is non NULL check that it matches against the
431 	 * machine name as expected.
432 	 */
433 	if (name != NULL) {
434 		dns_fixedname_t fixed;
435 		dns_name_t *machine;
436 
437 		machine = dns_fixedname_initname(&fixed);
438 		result = dns_name_fromstring(machine, sname, dns_rootname, 0,
439 					     NULL);
440 		if (result != ISC_R_SUCCESS) {
441 			return false;
442 		}
443 		if (subdomain) {
444 			return dns_name_issubdomain(name, machine);
445 		}
446 		return dns_name_equal(name, machine);
447 	}
448 
449 	return true;
450 }
451 
452 bool
453 dst_gssapi_identitymatchesrealmms(const dns_name_t *signer,
454 				  const dns_name_t *name,
455 				  const dns_name_t *realm, bool subdomain) {
456 	char sbuf[DNS_NAME_FORMATSIZE];
457 	char rbuf[DNS_NAME_FORMATSIZE];
458 	char *sname;
459 	char *rname;
460 	isc_buffer_t buffer;
461 	isc_result_t result;
462 
463 	/*
464 	 * It is far, far easier to write the names we are looking at into
465 	 * a string, and do string operations on them.
466 	 */
467 	isc_buffer_init(&buffer, sbuf, sizeof(sbuf));
468 	result = dns_name_totext(
469 		signer, DNS_NAME_OMITFINALDOT | DNS_NAME_PRINCIPAL, &buffer);
470 	RUNTIME_CHECK(result == ISC_R_SUCCESS);
471 	isc_buffer_putuint8(&buffer, 0);
472 	dns_name_format(realm, rbuf, sizeof(rbuf));
473 
474 	/*
475 	 * Find the realm portion.  This is the part after the @.  If it
476 	 * does not exist, we don't have something we like, so we fail our
477 	 * compare.
478 	 */
479 	rname = strchr(sbuf, '@');
480 	if (rname == NULL) {
481 		return false;
482 	}
483 	sname = strchr(sbuf, '$');
484 	if (sname == NULL) {
485 		return false;
486 	}
487 
488 	/*
489 	 * Verify that the $ and @ follow one another.
490 	 */
491 	if (rname - sname != 1) {
492 		return false;
493 	}
494 
495 	/*
496 	 * Find the host portion of the signer's name.	Zero out the $ so
497 	 * it terminates the signer's name, and skip past the @ for
498 	 * the realm.
499 	 *
500 	 * All service principals in Microsoft format seem to be in
501 	 *    machinename$@EXAMPLE.COM
502 	 * format.
503 	 */
504 	rname++;
505 	*sname = '\0';
506 
507 	if (strcmp(rname, rbuf) != 0) {
508 		return false;
509 	}
510 
511 	/*
512 	 * Now, we check that the realm matches (case sensitive) and that
513 	 * 'name' matches against 'machinename' qualified with 'realm'.
514 	 */
515 	if (name != NULL) {
516 		dns_fixedname_t fixed;
517 		dns_name_t *machine;
518 
519 		machine = dns_fixedname_initname(&fixed);
520 		result = dns_name_fromstring(machine, sbuf, realm, 0, NULL);
521 		if (result != ISC_R_SUCCESS) {
522 			return false;
523 		}
524 		if (subdomain) {
525 			return dns_name_issubdomain(name, machine);
526 		}
527 		return dns_name_equal(name, machine);
528 	}
529 
530 	return true;
531 }
532 
533 isc_result_t
534 dst_gssapi_releasecred(dns_gss_cred_id_t *cred) {
535 	OM_uint32 gret, minor;
536 	char buf[1024];
537 
538 	REQUIRE(cred != NULL && *cred != NULL);
539 
540 	gret = gss_release_cred(&minor, (gss_cred_id_t *)cred);
541 	if (gret != GSS_S_COMPLETE) {
542 		/* Log the error, but still free the credential's memory */
543 		gss_log(3, "failed releasing credential: %s",
544 			gss_error_tostring(gret, minor, buf, sizeof(buf)));
545 	}
546 	*cred = NULL;
547 
548 	return ISC_R_SUCCESS;
549 }
550 
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 	char buf[1024];
560 	char *estr;
561 
562 	if (err_message == NULL || mctx == NULL) {
563 		/* the caller doesn't want any error messages */
564 		return;
565 	}
566 
567 	estr = gss_error_tostring(major, minor, buf, sizeof(buf));
568 	if (estr != NULL) {
569 		(*err_message) = isc_mem_strdup(mctx, estr);
570 	}
571 }
572 
573 isc_result_t
574 dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken,
575 		   isc_buffer_t *outtoken, dns_gss_ctx_id_t *gssctx,
576 		   isc_mem_t *mctx, char **err_message) {
577 	isc_region_t r;
578 	isc_buffer_t namebuf;
579 	gss_name_t gname;
580 	OM_uint32 gret, minor, ret_flags, flags;
581 	gss_buffer_desc gintoken, *gintokenp, gouttoken = GSS_C_EMPTY_BUFFER;
582 	isc_result_t result;
583 	gss_buffer_desc gnamebuf;
584 	unsigned char array[DNS_NAME_MAXTEXT + 1];
585 
586 	/* Client must pass us a valid gss_ctx_id_t here */
587 	REQUIRE(gssctx != NULL);
588 	REQUIRE(mctx != NULL);
589 
590 	isc_buffer_init(&namebuf, array, sizeof(array));
591 	name_to_gbuffer(name, &namebuf, &gnamebuf);
592 
593 	/* Get the name as a GSS name */
594 	gret = gss_import_name(&minor, &gnamebuf, GSS_C_NO_OID, &gname);
595 	if (gret != GSS_S_COMPLETE) {
596 		gss_err_message(mctx, gret, minor, err_message);
597 		result = ISC_R_FAILURE;
598 		goto out;
599 	}
600 
601 	if (intoken != NULL) {
602 		/* Don't call gss_release_buffer for gintoken! */
603 		REGION_TO_GBUFFER(*intoken, gintoken);
604 		gintokenp = &gintoken;
605 	} else {
606 		gintokenp = NULL;
607 	}
608 
609 	/*
610 	 * Note that we don't set GSS_C_SEQUENCE_FLAG as Windows DNS
611 	 * servers don't like it.
612 	 */
613 	flags = GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG;
614 
615 	gret = gss_init_sec_context(
616 		&minor, GSS_C_NO_CREDENTIAL, (gss_ctx_id_t *)gssctx, gname,
617 		GSS_SPNEGO_MECHANISM, flags, 0, NULL, gintokenp, NULL,
618 		&gouttoken, &ret_flags, NULL);
619 
620 	if (gret != GSS_S_COMPLETE && gret != GSS_S_CONTINUE_NEEDED) {
621 		gss_err_message(mctx, gret, minor, err_message);
622 		if (err_message != NULL && *err_message != NULL) {
623 			gss_log(3, "Failure initiating security context: %s",
624 				*err_message);
625 		} else {
626 			gss_log(3, "Failure initiating security context");
627 		}
628 
629 		result = ISC_R_FAILURE;
630 		goto out;
631 	}
632 
633 	/*
634 	 * XXXSRA Not handled yet: RFC 3645 3.1.1: check ret_flags
635 	 * MUTUAL and INTEG flags, fail if either not set.
636 	 */
637 
638 	/*
639 	 * RFC 2744 states the a valid output token has a non-zero length.
640 	 */
641 	if (gouttoken.length != 0U) {
642 		GBUFFER_TO_REGION(gouttoken, r);
643 		RETERR(isc_buffer_copyregion(outtoken, &r));
644 	}
645 
646 	if (gret == GSS_S_COMPLETE) {
647 		result = ISC_R_SUCCESS;
648 	} else {
649 		result = DNS_R_CONTINUE;
650 	}
651 
652 out:
653 	if (gouttoken.length != 0U) {
654 		(void)gss_release_buffer(&minor, &gouttoken);
655 	}
656 	(void)gss_release_name(&minor, &gname);
657 	return result;
658 }
659 
660 isc_result_t
661 dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab,
662 		     isc_region_t *intoken, isc_buffer_t **outtoken,
663 		     dns_gss_ctx_id_t *ctxout, dns_name_t *principal,
664 		     isc_mem_t *mctx) {
665 	isc_region_t r;
666 	isc_buffer_t namebuf;
667 	gss_buffer_desc gnamebuf = GSS_C_EMPTY_BUFFER, gintoken,
668 			gouttoken = GSS_C_EMPTY_BUFFER;
669 	OM_uint32 gret, minor;
670 	gss_ctx_id_t context = GSS_C_NO_CONTEXT;
671 	gss_name_t gname = NULL;
672 	isc_result_t result;
673 	char buf[1024];
674 
675 	REQUIRE(outtoken != NULL && *outtoken == NULL);
676 
677 	REGION_TO_GBUFFER(*intoken, gintoken);
678 
679 	if (*ctxout == NULL) {
680 		context = GSS_C_NO_CONTEXT;
681 	} else {
682 		context = *ctxout;
683 	}
684 
685 	if (gssapi_keytab != NULL) {
686 #if HAVE_GSSAPI_GSSAPI_KRB5_H || HAVE_GSSAPI_KRB5_H
687 		gret = gsskrb5_register_acceptor_identity(gssapi_keytab);
688 		if (gret != GSS_S_COMPLETE) {
689 			gss_log(3,
690 				"failed "
691 				"gsskrb5_register_acceptor_identity(%s): %s",
692 				gssapi_keytab,
693 				gss_error_tostring(gret, 0, buf, sizeof(buf)));
694 			return DNS_R_INVALIDTKEY;
695 		}
696 #else
697 		/*
698 		 * Minimize memory leakage by only setting KRB5_KTNAME
699 		 * if it needs to change.
700 		 */
701 		const char *old = getenv("KRB5_KTNAME");
702 		if (old == NULL || strcmp(old, gssapi_keytab) != 0) {
703 			size_t size;
704 			char *kt;
705 
706 			size = strlen(gssapi_keytab) + 13;
707 			kt = malloc(size);
708 			if (kt == NULL) {
709 				return ISC_R_NOMEMORY;
710 			}
711 			snprintf(kt, size, "KRB5_KTNAME=%s", gssapi_keytab);
712 			if (putenv(kt) != 0) {
713 				return ISC_R_NOMEMORY;
714 			}
715 		}
716 #endif
717 	}
718 
719 	log_cred(cred);
720 
721 	gret = gss_accept_sec_context(&minor, &context, cred, &gintoken,
722 				      GSS_C_NO_CHANNEL_BINDINGS, &gname, NULL,
723 				      &gouttoken, NULL, NULL, NULL);
724 
725 	result = ISC_R_FAILURE;
726 
727 	switch (gret) {
728 	case GSS_S_COMPLETE:
729 	case GSS_S_CONTINUE_NEEDED:
730 		break;
731 	case GSS_S_DEFECTIVE_TOKEN:
732 	case GSS_S_DEFECTIVE_CREDENTIAL:
733 	case GSS_S_BAD_SIG:
734 	case GSS_S_DUPLICATE_TOKEN:
735 	case GSS_S_OLD_TOKEN:
736 	case GSS_S_NO_CRED:
737 	case GSS_S_CREDENTIALS_EXPIRED:
738 	case GSS_S_BAD_BINDINGS:
739 	case GSS_S_NO_CONTEXT:
740 	case GSS_S_BAD_MECH:
741 	case GSS_S_FAILURE:
742 		result = DNS_R_INVALIDTKEY;
743 	/* fall through */
744 	default:
745 		gss_log(3, "failed gss_accept_sec_context: %s",
746 			gss_error_tostring(gret, minor, buf, sizeof(buf)));
747 		if (gouttoken.length > 0U) {
748 			(void)gss_release_buffer(&minor, &gouttoken);
749 		}
750 		return result;
751 	}
752 
753 	if (gouttoken.length > 0U) {
754 		isc_buffer_allocate(mctx, outtoken,
755 				    (unsigned int)gouttoken.length);
756 		GBUFFER_TO_REGION(gouttoken, r);
757 		RETERR(isc_buffer_copyregion(*outtoken, &r));
758 		(void)gss_release_buffer(&minor, &gouttoken);
759 	}
760 
761 	if (gret == GSS_S_COMPLETE) {
762 		gret = gss_display_name(&minor, gname, &gnamebuf, NULL);
763 		if (gret != GSS_S_COMPLETE) {
764 			gss_log(3, "failed gss_display_name: %s",
765 				gss_error_tostring(gret, minor, buf,
766 						   sizeof(buf)));
767 			RETERR(ISC_R_FAILURE);
768 		}
769 
770 		/*
771 		 * Compensate for a bug in Solaris8's implementation
772 		 * of gss_display_name().  Should be harmless in any
773 		 * case, since principal names really should not
774 		 * contain null characters.
775 		 */
776 		if (gnamebuf.length > 0U &&
777 		    ((char *)gnamebuf.value)[gnamebuf.length - 1] == '\0')
778 		{
779 			gnamebuf.length--;
780 		}
781 
782 		gss_log(3, "gss-api source name (accept) is %.*s",
783 			(int)gnamebuf.length, (char *)gnamebuf.value);
784 
785 		GBUFFER_TO_REGION(gnamebuf, r);
786 		isc_buffer_init(&namebuf, r.base, r.length);
787 		isc_buffer_add(&namebuf, r.length);
788 
789 		RETERR(dns_name_fromtext(principal, &namebuf, dns_rootname, 0,
790 					 NULL));
791 
792 		if (gnamebuf.length != 0U) {
793 			gret = gss_release_buffer(&minor, &gnamebuf);
794 			if (gret != GSS_S_COMPLETE) {
795 				gss_log(3, "failed gss_release_buffer: %s",
796 					gss_error_tostring(gret, minor, buf,
797 							   sizeof(buf)));
798 			}
799 		}
800 	} else {
801 		result = DNS_R_CONTINUE;
802 	}
803 
804 	*ctxout = context;
805 
806 out:
807 	if (gname != NULL) {
808 		gret = gss_release_name(&minor, &gname);
809 		if (gret != GSS_S_COMPLETE) {
810 			gss_log(3, "failed gss_release_name: %s",
811 				gss_error_tostring(gret, minor, buf,
812 						   sizeof(buf)));
813 		}
814 	}
815 
816 	return result;
817 }
818 
819 isc_result_t
820 dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx) {
821 	OM_uint32 gret, minor;
822 	char buf[1024];
823 
824 	UNUSED(mctx);
825 
826 	REQUIRE(gssctx != NULL && *gssctx != NULL);
827 
828 	/* Delete the context from the GSS provider */
829 	gret = gss_delete_sec_context(&minor, (gss_ctx_id_t *)gssctx,
830 				      GSS_C_NO_BUFFER);
831 	if (gret != GSS_S_COMPLETE) {
832 		/* Log the error, but still free the context's memory */
833 		gss_log(3, "Failure deleting security context %s",
834 			gss_error_tostring(gret, minor, buf, sizeof(buf)));
835 	}
836 	return ISC_R_SUCCESS;
837 }
838 
839 char *
840 gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen) {
841 	gss_buffer_desc msg_minor = GSS_C_EMPTY_BUFFER,
842 			msg_major = GSS_C_EMPTY_BUFFER;
843 	OM_uint32 msg_ctx, minor_stat;
844 
845 	/* Handle major status */
846 	msg_ctx = 0;
847 	(void)gss_display_status(&minor_stat, major, GSS_C_GSS_CODE,
848 				 GSS_C_NULL_OID, &msg_ctx, &msg_major);
849 
850 	/* Handle minor status */
851 	msg_ctx = 0;
852 	(void)gss_display_status(&minor_stat, minor, GSS_C_MECH_CODE,
853 				 GSS_C_NULL_OID, &msg_ctx, &msg_minor);
854 
855 	snprintf(buf, buflen, "GSSAPI error: Major = %s, Minor = %s.",
856 		 (char *)msg_major.value, (char *)msg_minor.value);
857 
858 	if (msg_major.length != 0U) {
859 		(void)gss_release_buffer(&minor_stat, &msg_major);
860 	}
861 	if (msg_minor.length != 0U) {
862 		(void)gss_release_buffer(&minor_stat, &msg_minor);
863 	}
864 	return buf;
865 }
866 
867 #else
868 
869 isc_result_t
870 dst_gssapi_acquirecred(const dns_name_t *name, bool initiate,
871 		       dns_gss_cred_id_t *cred) {
872 	REQUIRE(cred != NULL && *cred == NULL);
873 
874 	UNUSED(name);
875 	UNUSED(initiate);
876 	UNUSED(cred);
877 
878 	return ISC_R_NOTIMPLEMENTED;
879 }
880 
881 bool
882 dst_gssapi_identitymatchesrealmkrb5(const dns_name_t *signer,
883 				    const dns_name_t *name,
884 				    const dns_name_t *realm, bool subdomain) {
885 	UNUSED(signer);
886 	UNUSED(name);
887 	UNUSED(realm);
888 	UNUSED(subdomain);
889 
890 	return false;
891 }
892 
893 bool
894 dst_gssapi_identitymatchesrealmms(const dns_name_t *signer,
895 				  const dns_name_t *name,
896 				  const dns_name_t *realm, bool subdomain) {
897 	UNUSED(signer);
898 	UNUSED(name);
899 	UNUSED(realm);
900 	UNUSED(subdomain);
901 
902 	return false;
903 }
904 
905 isc_result_t
906 dst_gssapi_releasecred(dns_gss_cred_id_t *cred) {
907 	UNUSED(cred);
908 
909 	return ISC_R_NOTIMPLEMENTED;
910 }
911 
912 isc_result_t
913 dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken,
914 		   isc_buffer_t *outtoken, dns_gss_ctx_id_t *gssctx,
915 		   isc_mem_t *mctx, char **err_message) {
916 	UNUSED(name);
917 	UNUSED(intoken);
918 	UNUSED(outtoken);
919 	UNUSED(gssctx);
920 	UNUSED(mctx);
921 	UNUSED(err_message);
922 
923 	return ISC_R_NOTIMPLEMENTED;
924 }
925 
926 isc_result_t
927 dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab,
928 		     isc_region_t *intoken, isc_buffer_t **outtoken,
929 		     dns_gss_ctx_id_t *ctxout, dns_name_t *principal,
930 		     isc_mem_t *mctx) {
931 	UNUSED(cred);
932 	UNUSED(gssapi_keytab);
933 	UNUSED(intoken);
934 	UNUSED(outtoken);
935 	UNUSED(ctxout);
936 	UNUSED(principal);
937 	UNUSED(mctx);
938 
939 	return ISC_R_NOTIMPLEMENTED;
940 }
941 
942 isc_result_t
943 dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx) {
944 	UNUSED(mctx);
945 	UNUSED(gssctx);
946 	return ISC_R_NOTIMPLEMENTED;
947 }
948 
949 char *
950 gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen) {
951 	snprintf(buf, buflen, "GSSAPI error: Major = %u, Minor = %u.", major,
952 		 minor);
953 
954 	return buf;
955 }
956 
957 #endif
958 
959 void
960 gss_log(int level, const char *fmt, ...) {
961 	va_list ap;
962 
963 	va_start(ap, fmt);
964 	isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_TKEY,
965 		       ISC_LOG_DEBUG(level), fmt, ap);
966 	va_end(ap);
967 }
968