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