1 /* $NetBSD: gssapictx.c,v 1.11 2025/01/26 16:25:22 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/random.h> 48 #include <isc/result.h> 49 #include <isc/string.h> 50 #include <isc/time.h> 51 #include <isc/util.h> 52 53 #include <dns/fixedname.h> 54 #include <dns/keyvalues.h> 55 #include <dns/log.h> 56 #include <dns/name.h> 57 #include <dns/rdata.h> 58 #include <dns/rdataclass.h> 59 #include <dns/types.h> 60 61 #include <dst/gssapi.h> 62 63 #include "dst_internal.h" 64 65 #if HAVE_GSSAPI 66 67 #ifndef GSS_KRB5_MECHANISM 68 static unsigned char krb5_mech_oid_bytes[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 69 0x12, 0x01, 0x02, 0x02 }; 70 static gss_OID_desc __gss_krb5_mechanism_oid_desc = { 71 sizeof(krb5_mech_oid_bytes), krb5_mech_oid_bytes 72 }; 73 #define GSS_KRB5_MECHANISM (&__gss_krb5_mechanism_oid_desc) 74 #endif /* ifndef GSS_KRB5_MECHANISM */ 75 76 #ifndef GSS_SPNEGO_MECHANISM 77 static unsigned char spnego_mech_oid_bytes[] = { 0x2b, 0x06, 0x01, 78 0x05, 0x05, 0x02 }; 79 static gss_OID_desc __gss_spnego_mechanism_oid_desc = { 80 sizeof(spnego_mech_oid_bytes), spnego_mech_oid_bytes 81 }; 82 #define GSS_SPNEGO_MECHANISM (&__gss_spnego_mechanism_oid_desc) 83 #endif /* ifndef GSS_SPNEGO_MECHANISM */ 84 85 #define REGION_TO_GBUFFER(r, gb) \ 86 do { \ 87 (gb).length = (r).length; \ 88 (gb).value = (r).base; \ 89 } while (0) 90 91 #define GBUFFER_TO_REGION(gb, r) \ 92 do { \ 93 (r).length = (unsigned int)(gb).length; \ 94 (r).base = (gb).value; \ 95 } while (0) 96 97 #define RETERR(x) \ 98 do { \ 99 result = (x); \ 100 if (result != ISC_R_SUCCESS) \ 101 goto out; \ 102 } while (0) 103 104 static void 105 name_to_gbuffer(const dns_name_t *name, isc_buffer_t *buffer, 106 gss_buffer_desc *gbuffer) { 107 dns_name_t tname; 108 const dns_name_t *namep; 109 isc_region_t r; 110 isc_result_t result; 111 112 if (!dns_name_isabsolute(name)) { 113 namep = name; 114 } else { 115 unsigned int labels; 116 dns_name_init(&tname, NULL); 117 labels = dns_name_countlabels(name); 118 dns_name_getlabelsequence(name, 0, labels - 1, &tname); 119 namep = &tname; 120 } 121 122 result = dns_name_totext( 123 namep, DNS_NAME_OMITFINALDOT | DNS_NAME_PRINCIPAL, 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 130 static void 131 log_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 */ 197 static void 198 check_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 240 static OM_uint32 241 mech_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 260 release: 261 REQUIRE(gss_release_oid_set(minor, mech_oid_set) == GSS_S_COMPLETE); 262 263 return gret; 264 } 265 266 static void 267 mech_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 273 isc_result_t 274 dst_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 358 cleanup: 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 373 bool 374 dst_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_totext( 390 signer, DNS_NAME_OMITFINALDOT | DNS_NAME_PRINCIPAL, &buffer); 391 RUNTIME_CHECK(result == ISC_R_SUCCESS); 392 isc_buffer_putuint8(&buffer, 0); 393 dns_name_format(realm, rbuf, sizeof(rbuf)); 394 395 /* 396 * Find the realm portion. This is the part after the @. If it 397 * does not exist, we don't have something we like, so we fail our 398 * compare. 399 */ 400 rname = strchr(sbuf, '@'); 401 if (rname == NULL) { 402 return false; 403 } 404 *rname = '\0'; 405 rname++; 406 407 if (strcmp(rname, rbuf) != 0) { 408 return false; 409 } 410 411 /* 412 * Find the host portion of the signer's name. We do this by 413 * searching for the first / character. We then check to make 414 * certain the instance name is "host" 415 * 416 * This will work for 417 * host/example.com@EXAMPLE.COM 418 */ 419 sname = strchr(sbuf, '/'); 420 if (sname == NULL) { 421 return false; 422 } 423 *sname = '\0'; 424 sname++; 425 if (strcmp(sbuf, "host") != 0) { 426 return false; 427 } 428 429 /* 430 * If name is non NULL check that it matches against the 431 * machine name as expected. 432 */ 433 if (name != NULL) { 434 dns_fixedname_t fixed; 435 dns_name_t *machine; 436 437 machine = dns_fixedname_initname(&fixed); 438 result = dns_name_fromstring(machine, sname, dns_rootname, 0, 439 NULL); 440 if (result != ISC_R_SUCCESS) { 441 return false; 442 } 443 if (subdomain) { 444 return dns_name_issubdomain(name, machine); 445 } 446 return dns_name_equal(name, machine); 447 } 448 449 return true; 450 } 451 452 bool 453 dst_gssapi_identitymatchesrealmms(const dns_name_t *signer, 454 const dns_name_t *name, 455 const dns_name_t *realm, bool subdomain) { 456 char sbuf[DNS_NAME_FORMATSIZE]; 457 char rbuf[DNS_NAME_FORMATSIZE]; 458 char *sname; 459 char *rname; 460 isc_buffer_t buffer; 461 isc_result_t result; 462 463 /* 464 * It is far, far easier to write the names we are looking at into 465 * a string, and do string operations on them. 466 */ 467 isc_buffer_init(&buffer, sbuf, sizeof(sbuf)); 468 result = dns_name_totext( 469 signer, DNS_NAME_OMITFINALDOT | DNS_NAME_PRINCIPAL, &buffer); 470 RUNTIME_CHECK(result == ISC_R_SUCCESS); 471 isc_buffer_putuint8(&buffer, 0); 472 dns_name_format(realm, rbuf, sizeof(rbuf)); 473 474 /* 475 * Find the realm portion. This is the part after the @. If it 476 * does not exist, we don't have something we like, so we fail our 477 * compare. 478 */ 479 rname = strchr(sbuf, '@'); 480 if (rname == NULL) { 481 return false; 482 } 483 sname = strchr(sbuf, '$'); 484 if (sname == NULL) { 485 return false; 486 } 487 488 /* 489 * Verify that the $ and @ follow one another. 490 */ 491 if (rname - sname != 1) { 492 return false; 493 } 494 495 /* 496 * Find the host portion of the signer's name. Zero out the $ so 497 * it terminates the signer's name, and skip past the @ for 498 * the realm. 499 * 500 * All service principals in Microsoft format seem to be in 501 * machinename$@EXAMPLE.COM 502 * format. 503 */ 504 rname++; 505 *sname = '\0'; 506 507 if (strcmp(rname, rbuf) != 0) { 508 return false; 509 } 510 511 /* 512 * Now, we check that the realm matches (case sensitive) and that 513 * 'name' matches against 'machinename' qualified with 'realm'. 514 */ 515 if (name != NULL) { 516 dns_fixedname_t fixed; 517 dns_name_t *machine; 518 519 machine = dns_fixedname_initname(&fixed); 520 result = dns_name_fromstring(machine, sbuf, realm, 0, NULL); 521 if (result != ISC_R_SUCCESS) { 522 return false; 523 } 524 if (subdomain) { 525 return dns_name_issubdomain(name, machine); 526 } 527 return dns_name_equal(name, machine); 528 } 529 530 return true; 531 } 532 533 isc_result_t 534 dst_gssapi_releasecred(dns_gss_cred_id_t *cred) { 535 OM_uint32 gret, minor; 536 char buf[1024]; 537 538 REQUIRE(cred != NULL && *cred != NULL); 539 540 gret = gss_release_cred(&minor, (gss_cred_id_t *)cred); 541 if (gret != GSS_S_COMPLETE) { 542 /* Log the error, but still free the credential's memory */ 543 gss_log(3, "failed releasing credential: %s", 544 gss_error_tostring(gret, minor, buf, sizeof(buf))); 545 } 546 *cred = NULL; 547 548 return ISC_R_SUCCESS; 549 } 550 551 /* 552 * Format a gssapi error message info into a char ** on the given memory 553 * context. This is used to return gssapi error messages back up the 554 * call chain for reporting to the user. 555 */ 556 static void 557 gss_err_message(isc_mem_t *mctx, uint32_t major, uint32_t minor, 558 char **err_message) { 559 char buf[1024]; 560 char *estr; 561 562 if (err_message == NULL || mctx == NULL) { 563 /* the caller doesn't want any error messages */ 564 return; 565 } 566 567 estr = gss_error_tostring(major, minor, buf, sizeof(buf)); 568 if (estr != NULL) { 569 (*err_message) = isc_mem_strdup(mctx, estr); 570 } 571 } 572 573 isc_result_t 574 dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken, 575 isc_buffer_t *outtoken, dns_gss_ctx_id_t *gssctx, 576 isc_mem_t *mctx, char **err_message) { 577 isc_region_t r; 578 isc_buffer_t namebuf; 579 gss_name_t gname; 580 OM_uint32 gret, minor, ret_flags, flags; 581 gss_buffer_desc gintoken, *gintokenp, gouttoken = GSS_C_EMPTY_BUFFER; 582 isc_result_t result; 583 gss_buffer_desc gnamebuf; 584 unsigned char array[DNS_NAME_MAXTEXT + 1]; 585 586 /* Client must pass us a valid gss_ctx_id_t here */ 587 REQUIRE(gssctx != NULL); 588 REQUIRE(mctx != NULL); 589 590 isc_buffer_init(&namebuf, array, sizeof(array)); 591 name_to_gbuffer(name, &namebuf, &gnamebuf); 592 593 /* Get the name as a GSS name */ 594 gret = gss_import_name(&minor, &gnamebuf, GSS_C_NO_OID, &gname); 595 if (gret != GSS_S_COMPLETE) { 596 gss_err_message(mctx, gret, minor, err_message); 597 result = ISC_R_FAILURE; 598 goto out; 599 } 600 601 if (intoken != NULL) { 602 /* Don't call gss_release_buffer for gintoken! */ 603 REGION_TO_GBUFFER(*intoken, gintoken); 604 gintokenp = &gintoken; 605 } else { 606 gintokenp = NULL; 607 } 608 609 /* 610 * Note that we don't set GSS_C_SEQUENCE_FLAG as Windows DNS 611 * servers don't like it. 612 */ 613 flags = GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG; 614 615 gret = gss_init_sec_context( 616 &minor, GSS_C_NO_CREDENTIAL, (gss_ctx_id_t *)gssctx, gname, 617 GSS_SPNEGO_MECHANISM, flags, 0, NULL, gintokenp, NULL, 618 &gouttoken, &ret_flags, NULL); 619 620 if (gret != GSS_S_COMPLETE && gret != GSS_S_CONTINUE_NEEDED) { 621 gss_err_message(mctx, gret, minor, err_message); 622 if (err_message != NULL && *err_message != NULL) { 623 gss_log(3, "Failure initiating security context: %s", 624 *err_message); 625 } else { 626 gss_log(3, "Failure initiating security context"); 627 } 628 629 result = ISC_R_FAILURE; 630 goto out; 631 } 632 633 /* 634 * XXXSRA Not handled yet: RFC 3645 3.1.1: check ret_flags 635 * MUTUAL and INTEG flags, fail if either not set. 636 */ 637 638 /* 639 * RFC 2744 states the a valid output token has a non-zero length. 640 */ 641 if (gouttoken.length != 0U) { 642 GBUFFER_TO_REGION(gouttoken, r); 643 RETERR(isc_buffer_copyregion(outtoken, &r)); 644 } 645 646 if (gret == GSS_S_COMPLETE) { 647 result = ISC_R_SUCCESS; 648 } else { 649 result = DNS_R_CONTINUE; 650 } 651 652 out: 653 if (gouttoken.length != 0U) { 654 (void)gss_release_buffer(&minor, &gouttoken); 655 } 656 (void)gss_release_name(&minor, &gname); 657 return result; 658 } 659 660 isc_result_t 661 dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab, 662 isc_region_t *intoken, isc_buffer_t **outtoken, 663 dns_gss_ctx_id_t *ctxout, dns_name_t *principal, 664 isc_mem_t *mctx) { 665 isc_region_t r; 666 isc_buffer_t namebuf; 667 gss_buffer_desc gnamebuf = GSS_C_EMPTY_BUFFER, gintoken, 668 gouttoken = GSS_C_EMPTY_BUFFER; 669 OM_uint32 gret, minor; 670 gss_ctx_id_t context = GSS_C_NO_CONTEXT; 671 gss_name_t gname = NULL; 672 isc_result_t result; 673 char buf[1024]; 674 675 REQUIRE(outtoken != NULL && *outtoken == NULL); 676 677 REGION_TO_GBUFFER(*intoken, gintoken); 678 679 if (*ctxout == NULL) { 680 context = GSS_C_NO_CONTEXT; 681 } else { 682 context = *ctxout; 683 } 684 685 if (gssapi_keytab != NULL) { 686 #if HAVE_GSSAPI_GSSAPI_KRB5_H || HAVE_GSSAPI_KRB5_H 687 gret = gsskrb5_register_acceptor_identity(gssapi_keytab); 688 if (gret != GSS_S_COMPLETE) { 689 gss_log(3, 690 "failed " 691 "gsskrb5_register_acceptor_identity(%s): %s", 692 gssapi_keytab, 693 gss_error_tostring(gret, 0, buf, sizeof(buf))); 694 return DNS_R_INVALIDTKEY; 695 } 696 #else 697 /* 698 * Minimize memory leakage by only setting KRB5_KTNAME 699 * if it needs to change. 700 */ 701 const char *old = getenv("KRB5_KTNAME"); 702 if (old == NULL || strcmp(old, gssapi_keytab) != 0) { 703 size_t size; 704 char *kt; 705 706 size = strlen(gssapi_keytab) + 13; 707 kt = malloc(size); 708 if (kt == NULL) { 709 return ISC_R_NOMEMORY; 710 } 711 snprintf(kt, size, "KRB5_KTNAME=%s", gssapi_keytab); 712 if (putenv(kt) != 0) { 713 return ISC_R_NOMEMORY; 714 } 715 } 716 #endif 717 } 718 719 log_cred(cred); 720 721 gret = gss_accept_sec_context(&minor, &context, cred, &gintoken, 722 GSS_C_NO_CHANNEL_BINDINGS, &gname, NULL, 723 &gouttoken, NULL, NULL, NULL); 724 725 result = ISC_R_FAILURE; 726 727 switch (gret) { 728 case GSS_S_COMPLETE: 729 case GSS_S_CONTINUE_NEEDED: 730 break; 731 case GSS_S_DEFECTIVE_TOKEN: 732 case GSS_S_DEFECTIVE_CREDENTIAL: 733 case GSS_S_BAD_SIG: 734 case GSS_S_DUPLICATE_TOKEN: 735 case GSS_S_OLD_TOKEN: 736 case GSS_S_NO_CRED: 737 case GSS_S_CREDENTIALS_EXPIRED: 738 case GSS_S_BAD_BINDINGS: 739 case GSS_S_NO_CONTEXT: 740 case GSS_S_BAD_MECH: 741 case GSS_S_FAILURE: 742 result = DNS_R_INVALIDTKEY; 743 /* fall through */ 744 default: 745 gss_log(3, "failed gss_accept_sec_context: %s", 746 gss_error_tostring(gret, minor, buf, sizeof(buf))); 747 if (gouttoken.length > 0U) { 748 (void)gss_release_buffer(&minor, &gouttoken); 749 } 750 return result; 751 } 752 753 if (gouttoken.length > 0U) { 754 isc_buffer_allocate(mctx, outtoken, 755 (unsigned int)gouttoken.length); 756 GBUFFER_TO_REGION(gouttoken, r); 757 RETERR(isc_buffer_copyregion(*outtoken, &r)); 758 (void)gss_release_buffer(&minor, &gouttoken); 759 } 760 761 if (gret == GSS_S_COMPLETE) { 762 gret = gss_display_name(&minor, gname, &gnamebuf, NULL); 763 if (gret != GSS_S_COMPLETE) { 764 gss_log(3, "failed gss_display_name: %s", 765 gss_error_tostring(gret, minor, buf, 766 sizeof(buf))); 767 RETERR(ISC_R_FAILURE); 768 } 769 770 /* 771 * Compensate for a bug in Solaris8's implementation 772 * of gss_display_name(). Should be harmless in any 773 * case, since principal names really should not 774 * contain null characters. 775 */ 776 if (gnamebuf.length > 0U && 777 ((char *)gnamebuf.value)[gnamebuf.length - 1] == '\0') 778 { 779 gnamebuf.length--; 780 } 781 782 gss_log(3, "gss-api source name (accept) is %.*s", 783 (int)gnamebuf.length, (char *)gnamebuf.value); 784 785 GBUFFER_TO_REGION(gnamebuf, r); 786 isc_buffer_init(&namebuf, r.base, r.length); 787 isc_buffer_add(&namebuf, r.length); 788 789 RETERR(dns_name_fromtext(principal, &namebuf, dns_rootname, 0, 790 NULL)); 791 792 if (gnamebuf.length != 0U) { 793 gret = gss_release_buffer(&minor, &gnamebuf); 794 if (gret != GSS_S_COMPLETE) { 795 gss_log(3, "failed gss_release_buffer: %s", 796 gss_error_tostring(gret, minor, buf, 797 sizeof(buf))); 798 } 799 } 800 } else { 801 result = DNS_R_CONTINUE; 802 } 803 804 *ctxout = context; 805 806 out: 807 if (gname != NULL) { 808 gret = gss_release_name(&minor, &gname); 809 if (gret != GSS_S_COMPLETE) { 810 gss_log(3, "failed gss_release_name: %s", 811 gss_error_tostring(gret, minor, buf, 812 sizeof(buf))); 813 } 814 } 815 816 return result; 817 } 818 819 isc_result_t 820 dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx) { 821 OM_uint32 gret, minor; 822 char buf[1024]; 823 824 UNUSED(mctx); 825 826 REQUIRE(gssctx != NULL && *gssctx != NULL); 827 828 /* Delete the context from the GSS provider */ 829 gret = gss_delete_sec_context(&minor, (gss_ctx_id_t *)gssctx, 830 GSS_C_NO_BUFFER); 831 if (gret != GSS_S_COMPLETE) { 832 /* Log the error, but still free the context's memory */ 833 gss_log(3, "Failure deleting security context %s", 834 gss_error_tostring(gret, minor, buf, sizeof(buf))); 835 } 836 return ISC_R_SUCCESS; 837 } 838 839 char * 840 gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen) { 841 gss_buffer_desc msg_minor = GSS_C_EMPTY_BUFFER, 842 msg_major = GSS_C_EMPTY_BUFFER; 843 OM_uint32 msg_ctx, minor_stat; 844 845 /* Handle major status */ 846 msg_ctx = 0; 847 (void)gss_display_status(&minor_stat, major, GSS_C_GSS_CODE, 848 GSS_C_NULL_OID, &msg_ctx, &msg_major); 849 850 /* Handle minor status */ 851 msg_ctx = 0; 852 (void)gss_display_status(&minor_stat, minor, GSS_C_MECH_CODE, 853 GSS_C_NULL_OID, &msg_ctx, &msg_minor); 854 855 snprintf(buf, buflen, "GSSAPI error: Major = %s, Minor = %s.", 856 (char *)msg_major.value, (char *)msg_minor.value); 857 858 if (msg_major.length != 0U) { 859 (void)gss_release_buffer(&minor_stat, &msg_major); 860 } 861 if (msg_minor.length != 0U) { 862 (void)gss_release_buffer(&minor_stat, &msg_minor); 863 } 864 return buf; 865 } 866 867 #else 868 869 isc_result_t 870 dst_gssapi_acquirecred(const dns_name_t *name, bool initiate, 871 dns_gss_cred_id_t *cred) { 872 REQUIRE(cred != NULL && *cred == NULL); 873 874 UNUSED(name); 875 UNUSED(initiate); 876 UNUSED(cred); 877 878 return ISC_R_NOTIMPLEMENTED; 879 } 880 881 bool 882 dst_gssapi_identitymatchesrealmkrb5(const dns_name_t *signer, 883 const dns_name_t *name, 884 const dns_name_t *realm, bool subdomain) { 885 UNUSED(signer); 886 UNUSED(name); 887 UNUSED(realm); 888 UNUSED(subdomain); 889 890 return false; 891 } 892 893 bool 894 dst_gssapi_identitymatchesrealmms(const dns_name_t *signer, 895 const dns_name_t *name, 896 const dns_name_t *realm, bool subdomain) { 897 UNUSED(signer); 898 UNUSED(name); 899 UNUSED(realm); 900 UNUSED(subdomain); 901 902 return false; 903 } 904 905 isc_result_t 906 dst_gssapi_releasecred(dns_gss_cred_id_t *cred) { 907 UNUSED(cred); 908 909 return ISC_R_NOTIMPLEMENTED; 910 } 911 912 isc_result_t 913 dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken, 914 isc_buffer_t *outtoken, dns_gss_ctx_id_t *gssctx, 915 isc_mem_t *mctx, char **err_message) { 916 UNUSED(name); 917 UNUSED(intoken); 918 UNUSED(outtoken); 919 UNUSED(gssctx); 920 UNUSED(mctx); 921 UNUSED(err_message); 922 923 return ISC_R_NOTIMPLEMENTED; 924 } 925 926 isc_result_t 927 dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab, 928 isc_region_t *intoken, isc_buffer_t **outtoken, 929 dns_gss_ctx_id_t *ctxout, dns_name_t *principal, 930 isc_mem_t *mctx) { 931 UNUSED(cred); 932 UNUSED(gssapi_keytab); 933 UNUSED(intoken); 934 UNUSED(outtoken); 935 UNUSED(ctxout); 936 UNUSED(principal); 937 UNUSED(mctx); 938 939 return ISC_R_NOTIMPLEMENTED; 940 } 941 942 isc_result_t 943 dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx) { 944 UNUSED(mctx); 945 UNUSED(gssctx); 946 return ISC_R_NOTIMPLEMENTED; 947 } 948 949 char * 950 gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen) { 951 snprintf(buf, buflen, "GSSAPI error: Major = %u, Minor = %u.", major, 952 minor); 953 954 return buf; 955 } 956 957 #endif 958 959 void 960 gss_log(int level, const char *fmt, ...) { 961 va_list ap; 962 963 va_start(ap, fmt); 964 isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_TKEY, 965 ISC_LOG_DEBUG(level), fmt, ap); 966 va_end(ap); 967 } 968