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