"Fossies" - the Fresh Open Source Software Archive

Member "bind-9.16.7/lib/dns/gssapictx.c" (4 Sep 2020, 22983 Bytes) of package /linux/misc/dns/bind9/9.16.7/bind-9.16.7.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "gssapictx.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 9.17.3_vs_9.17.4.

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