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