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