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