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