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