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