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