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