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