]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - contrib/bind9/lib/dns/gssapictx.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / contrib / bind9 / lib / dns / gssapictx.c
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
74 static unsigned char krb5_mech_oid_bytes[] = {
75         0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02
76 };
77
78 #ifndef USE_ISC_SPNEGO
79 static unsigned char spnego_mech_oid_bytes[] = {
80         0x2b, 0x06, 0x01, 0x05, 0x05, 0x02
81 };
82 #endif
83
84 static 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
91 static 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
118 static inline void
119 name_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
144 static void
145 log_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  */
211 static void
212 check_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
250 isc_result_t
251 dst_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
337 isc_boolean_t
338 dst_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
410 isc_boolean_t
411 dst_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
501 isc_result_t
502 dst_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  */
531 static void
532 gss_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
549 isc_result_t
550 dst_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
640 isc_result_t
641 dst_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
798 isc_result_t
799 dst_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
824 char *
825 gss_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
858 void
859 gss_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 */