]> CyberLeo.Net >> Repos - FreeBSD/releng/9.0.git/blob - contrib/bind9/lib/dns/gssapictx.c
Copy stable/9 to releng/9.0 as part of the FreeBSD 9.0-RELEASE release
[FreeBSD/releng/9.0.git] / contrib / bind9 / lib / dns / gssapictx.c
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.12.2 2011-04-07 23:05:01 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
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         isc_buffer_putuint8(buffer, 0);
139         isc_buffer_usedregion(buffer, &r);
140         REGION_TO_GBUFFER(r, *gbuffer);
141 }
142
143 static void
144 log_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 != 0U) {
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  */
210 static void
211 check_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
249 isc_result_t
250 dst_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
334 isc_boolean_t
335 dst_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
405 isc_boolean_t
406 dst_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
494 isc_result_t
495 dst_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  */
524 static void
525 gss_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
542 isc_result_t
543 dst_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 != 0U) {
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
633 isc_result_t
634 dst_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         REGION_TO_GBUFFER(*intoken, gintoken);
654
655         if (*ctxout == NULL)
656                 context = GSS_C_NO_CONTEXT;
657         else
658                 context = *ctxout;
659
660         if (gssapi_keytab != NULL) {
661 #ifdef ISC_PLATFORM_GSSAPI_KRB5_HEADER
662                 gret = gsskrb5_register_acceptor_identity(gssapi_keytab);
663                 if (gret != GSS_S_COMPLETE) {
664                         gss_log(3, "failed "
665                                 "gsskrb5_register_acceptor_identity(%s): %s",
666                                 gssapi_keytab,
667                                 gss_error_tostring(gret, minor,
668                                                    buf, sizeof(buf)));
669                         return (DNS_R_INVALIDTKEY);
670                 }
671 #else
672                 /*
673                  * Minimize memory leakage by only setting KRB5_KTNAME
674                  * if it needs to change.
675                  */
676                 const char *old = getenv("KRB5_KTNAME");
677                 if (old == NULL || strcmp(old, gssapi_keytab) != 0) {
678                         char *kt = malloc(strlen(gssapi_keytab) + 13);
679                         if (kt == NULL)
680                                 return (ISC_R_NOMEMORY);
681                         sprintf(kt, "KRB5_KTNAME=%s", gssapi_keytab);
682                         if (putenv(kt) != 0)
683                                 return (ISC_R_NOMEMORY);
684                 }
685 #endif
686         }
687
688         log_cred(cred);
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 > 0U) {
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 > 0U &&
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 != 0U) {
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
792 isc_result_t
793 dst_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
818 char *
819 gss_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 != 0U)
840                 (void)gss_release_buffer(&minor_stat, &msg_major);
841         if (msg_minor.length != 0U)
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
852 void
853 gss_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 */