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