1/* $NetBSD: cache.c,v 1.6 2023/06/19 21:41:41 christos Exp $ */ 2 3/* 4 * Copyright (c) 2005, PADL Software Pty Ltd. 5 * All rights reserved. 6 * 7 * Portions Copyright (c) 2009 Apple Inc. All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 20 * 3. Neither the name of PADL Software nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37#include "kcm_locl.h" 38 39HEIMDAL_MUTEX ccache_mutex = HEIMDAL_MUTEX_INITIALIZER; 40kcm_ccache_data *ccache_head = NULL; 41static unsigned int ccache_nextid = 0; 42 43char *kcm_ccache_nextid(pid_t pid, uid_t uid, gid_t gid) 44{ 45 unsigned n; 46 char *name; 47 int ret; 48 49 HEIMDAL_MUTEX_lock(&ccache_mutex); 50 n = ++ccache_nextid; 51 HEIMDAL_MUTEX_unlock(&ccache_mutex); 52 53 ret = asprintf(&name, "%ld:%u", (long)uid, n); 54 if (ret == -1) 55 return NULL; 56 57 return name; 58} 59 60krb5_error_code 61kcm_ccache_resolve(krb5_context context, 62 const char *name, 63 kcm_ccache *ccache) 64{ 65 kcm_ccache p; 66 krb5_error_code ret; 67 68 *ccache = NULL; 69 70 ret = KRB5_FCC_NOFILE; 71 72 HEIMDAL_MUTEX_lock(&ccache_mutex); 73 74 for (p = ccache_head; p != NULL; p = p->next) { 75 if ((p->flags & KCM_FLAGS_VALID) == 0) 76 continue; 77 if (strcmp(p->name, name) == 0) { 78 ret = 0; 79 break; 80 } 81 } 82 83 if (ret == 0) { 84 kcm_retain_ccache(context, p); 85 *ccache = p; 86 } 87 88 HEIMDAL_MUTEX_unlock(&ccache_mutex); 89 90 return ret; 91} 92 93krb5_error_code 94kcm_ccache_resolve_by_uuid(krb5_context context, 95 kcmuuid_t uuid, 96 kcm_ccache *ccache) 97{ 98 kcm_ccache p; 99 krb5_error_code ret; 100 101 *ccache = NULL; 102 103 ret = KRB5_FCC_NOFILE; 104 105 HEIMDAL_MUTEX_lock(&ccache_mutex); 106 107 for (p = ccache_head; p != NULL; p = p->next) { 108 if ((p->flags & KCM_FLAGS_VALID) == 0) 109 continue; 110 if (memcmp(p->uuid, uuid, sizeof(kcmuuid_t)) == 0) { 111 ret = 0; 112 break; 113 } 114 } 115 116 if (ret == 0) { 117 kcm_retain_ccache(context, p); 118 *ccache = p; 119 } 120 121 HEIMDAL_MUTEX_unlock(&ccache_mutex); 122 123 return ret; 124} 125 126krb5_error_code 127kcm_ccache_get_uuids(krb5_context context, kcm_client *client, kcm_operation opcode, krb5_storage *sp) 128{ 129 krb5_error_code ret; 130 kcm_ccache p; 131 132 ret = KRB5_FCC_NOFILE; 133 134 HEIMDAL_MUTEX_lock(&ccache_mutex); 135 136 for (p = ccache_head; p != NULL; p = p->next) { 137 if ((p->flags & KCM_FLAGS_VALID) == 0) 138 continue; 139 ret = kcm_access(context, client, opcode, p); 140 if (ret) { 141 ret = 0; 142 continue; 143 } 144 krb5_storage_write(sp, p->uuid, sizeof(p->uuid)); 145 } 146 147 HEIMDAL_MUTEX_unlock(&ccache_mutex); 148 149 return ret; 150} 151 152 153krb5_error_code kcm_debug_ccache(krb5_context context) 154{ 155 kcm_ccache p; 156 157 for (p = ccache_head; p != NULL; p = p->next) { 158 char *cpn = NULL, *spn = NULL; 159 int ncreds = 0; 160 struct kcm_creds *k; 161 162 if ((p->flags & KCM_FLAGS_VALID) == 0) { 163 kcm_log(7, "cache %08x: empty slot"); 164 continue; 165 } 166 167 KCM_ASSERT_VALID(p); 168 169 for (k = p->creds; k != NULL; k = k->next) 170 ncreds++; 171 172 if (p->client != NULL) 173 krb5_unparse_name(context, p->client, &cpn); 174 if (p->server != NULL) 175 krb5_unparse_name(context, p->server, &spn); 176 177 kcm_log(7, "cache %08x: name %s refcnt %d flags %04x mode %04o " 178 "uid %d gid %d client %s server %s ncreds %d", 179 p, p->name, p->refcnt, p->flags, p->mode, p->uid, p->gid, 180 (cpn == NULL) ? "<none>" : cpn, 181 (spn == NULL) ? "<none>" : spn, 182 ncreds); 183 184 if (cpn != NULL) 185 free(cpn); 186 if (spn != NULL) 187 free(spn); 188 } 189 190 return 0; 191} 192 193static void 194kcm_free_ccache_data_internal(krb5_context context, 195 kcm_ccache_data *cache) 196{ 197 KCM_ASSERT_VALID(cache); 198 199 if (cache->name != NULL) { 200 free(cache->name); 201 cache->name = NULL; 202 } 203 204 if (cache->flags & KCM_FLAGS_USE_KEYTAB) { 205 krb5_kt_close(context, cache->key.keytab); 206 cache->key.keytab = NULL; 207 } else if (cache->flags & KCM_FLAGS_USE_CACHED_KEY) { 208 krb5_free_keyblock_contents(context, &cache->key.keyblock); 209 krb5_keyblock_zero(&cache->key.keyblock); 210 } 211 212 cache->flags = 0; 213 cache->mode = 0; 214 cache->uid = -1; 215 cache->gid = -1; 216 cache->session = -1; 217 218 kcm_zero_ccache_data_internal(context, cache); 219 220 cache->tkt_life = 0; 221 cache->renew_life = 0; 222 223 cache->next = NULL; 224 cache->refcnt = 0; 225 226 HEIMDAL_MUTEX_unlock(&cache->mutex); 227 HEIMDAL_MUTEX_destroy(&cache->mutex); 228} 229 230 231krb5_error_code 232kcm_ccache_destroy(krb5_context context, const char *name) 233{ 234 kcm_ccache *p, ccache; 235 krb5_error_code ret; 236 237 ret = KRB5_FCC_NOFILE; 238 239 HEIMDAL_MUTEX_lock(&ccache_mutex); 240 for (p = &ccache_head; *p != NULL; p = &(*p)->next) { 241 if (((*p)->flags & KCM_FLAGS_VALID) == 0) 242 continue; 243 if (strcmp((*p)->name, name) == 0) { 244 ret = 0; 245 break; 246 } 247 } 248 if (ret) 249 goto out; 250 251 if ((*p)->refcnt != 1) { 252 ret = EAGAIN; 253 goto out; 254 } 255 256 ccache = *p; 257 *p = (*p)->next; 258 kcm_free_ccache_data_internal(context, ccache); 259 free(ccache); 260 261out: 262 HEIMDAL_MUTEX_unlock(&ccache_mutex); 263 264 return ret; 265} 266 267static krb5_error_code 268kcm_ccache_alloc(krb5_context context, 269 const char *name, 270 kcm_ccache *ccache) 271{ 272 kcm_ccache slot = NULL, p; 273 krb5_error_code ret; 274 int new_slot = 0; 275 276 *ccache = NULL; 277 278 /* First, check for duplicates */ 279 HEIMDAL_MUTEX_lock(&ccache_mutex); 280 ret = 0; 281 for (p = ccache_head; p != NULL; p = p->next) { 282 if (p->flags & KCM_FLAGS_VALID) { 283 if (strcmp(p->name, name) == 0) { 284 ret = KRB5_CC_WRITE; 285 break; 286 } 287 } else if (slot == NULL) 288 slot = p; 289 } 290 291 if (ret) 292 goto out; 293 294 /* 295 * Create an enpty slot for us. 296 */ 297 if (slot == NULL) { 298 slot = (kcm_ccache_data *)malloc(sizeof(*slot)); 299 if (slot == NULL) { 300 ret = KRB5_CC_NOMEM; 301 goto out; 302 } 303 slot->next = ccache_head; 304 HEIMDAL_MUTEX_init(&slot->mutex); 305 new_slot = 1; 306 } 307 308 RAND_bytes(slot->uuid, sizeof(slot->uuid)); 309 310 slot->name = strdup(name); 311 if (slot->name == NULL) { 312 ret = KRB5_CC_NOMEM; 313 goto out; 314 } 315 316 slot->refcnt = 1; 317 slot->flags = KCM_FLAGS_VALID; 318 slot->mode = S_IRUSR | S_IWUSR; 319 slot->uid = -1; 320 slot->gid = -1; 321 slot->client = NULL; 322 slot->server = NULL; 323 slot->creds = NULL; 324 slot->key.keytab = NULL; 325 slot->tkt_life = 0; 326 slot->renew_life = 0; 327 slot->kdc_offset = 0; 328 329 if (new_slot) 330 ccache_head = slot; 331 332 *ccache = slot; 333 334 HEIMDAL_MUTEX_unlock(&ccache_mutex); 335 return 0; 336 337out: 338 HEIMDAL_MUTEX_unlock(&ccache_mutex); 339 if (new_slot && slot != NULL) { 340 HEIMDAL_MUTEX_destroy(&slot->mutex); 341 free(slot); 342 } 343 return ret; 344} 345 346krb5_error_code 347kcm_ccache_remove_creds_internal(krb5_context context, 348 kcm_ccache ccache) 349{ 350 struct kcm_creds *k; 351 352 k = ccache->creds; 353 while (k != NULL) { 354 struct kcm_creds *old; 355 356 krb5_free_cred_contents(context, &k->cred); 357 old = k; 358 k = k->next; 359 free(old); 360 } 361 ccache->creds = NULL; 362 363 return 0; 364} 365 366krb5_error_code 367kcm_ccache_remove_creds(krb5_context context, 368 kcm_ccache ccache) 369{ 370 krb5_error_code ret; 371 372 KCM_ASSERT_VALID(ccache); 373 374 HEIMDAL_MUTEX_lock(&ccache->mutex); 375 ret = kcm_ccache_remove_creds_internal(context, ccache); 376 HEIMDAL_MUTEX_unlock(&ccache->mutex); 377 378 return ret; 379} 380 381krb5_error_code 382kcm_zero_ccache_data_internal(krb5_context context, 383 kcm_ccache_data *cache) 384{ 385 if (cache->client != NULL) { 386 krb5_free_principal(context, cache->client); 387 cache->client = NULL; 388 } 389 390 if (cache->server != NULL) { 391 krb5_free_principal(context, cache->server); 392 cache->server = NULL; 393 } 394 395 kcm_ccache_remove_creds_internal(context, cache); 396 397 return 0; 398} 399 400krb5_error_code 401kcm_zero_ccache_data(krb5_context context, 402 kcm_ccache cache) 403{ 404 krb5_error_code ret; 405 406 KCM_ASSERT_VALID(cache); 407 408 HEIMDAL_MUTEX_lock(&cache->mutex); 409 ret = kcm_zero_ccache_data_internal(context, cache); 410 HEIMDAL_MUTEX_unlock(&cache->mutex); 411 412 return ret; 413} 414 415krb5_error_code 416kcm_retain_ccache(krb5_context context, 417 kcm_ccache ccache) 418{ 419 KCM_ASSERT_VALID(ccache); 420 421 HEIMDAL_MUTEX_lock(&ccache->mutex); 422 ccache->refcnt++; 423 HEIMDAL_MUTEX_unlock(&ccache->mutex); 424 425 return 0; 426} 427 428krb5_error_code 429kcm_release_ccache(krb5_context context, kcm_ccache c) 430{ 431 krb5_error_code ret = 0; 432 433 KCM_ASSERT_VALID(c); 434 435 HEIMDAL_MUTEX_lock(&c->mutex); 436 if (c->refcnt == 1) { 437 kcm_free_ccache_data_internal(context, c); 438 free(c); 439 } else { 440 c->refcnt--; 441 HEIMDAL_MUTEX_unlock(&c->mutex); 442 } 443 444 return ret; 445} 446 447krb5_error_code 448kcm_ccache_gen_new(krb5_context context, 449 pid_t pid, 450 uid_t uid, 451 gid_t gid, 452 kcm_ccache *ccache) 453{ 454 krb5_error_code ret; 455 char *name; 456 457 name = kcm_ccache_nextid(pid, uid, gid); 458 if (name == NULL) { 459 return KRB5_CC_NOMEM; 460 } 461 462 ret = kcm_ccache_new(context, name, ccache); 463 464 free(name); 465 return ret; 466} 467 468krb5_error_code 469kcm_ccache_new(krb5_context context, 470 const char *name, 471 kcm_ccache *ccache) 472{ 473 krb5_error_code ret; 474 475 ret = kcm_ccache_alloc(context, name, ccache); 476 if (ret == 0) { 477 /* 478 * one reference is held by the linked list, 479 * one by the caller 480 */ 481 kcm_retain_ccache(context, *ccache); 482 } 483 484 return ret; 485} 486 487krb5_error_code 488kcm_ccache_destroy_if_empty(krb5_context context, 489 kcm_ccache ccache) 490{ 491 krb5_error_code ret; 492 493 KCM_ASSERT_VALID(ccache); 494 495 if (ccache->creds == NULL) { 496 ret = kcm_ccache_destroy(context, ccache->name); 497 } else 498 ret = 0; 499 500 return ret; 501} 502 503krb5_error_code 504kcm_ccache_store_cred(krb5_context context, 505 kcm_ccache ccache, 506 krb5_creds *creds, 507 int copy) 508{ 509 krb5_error_code ret; 510 krb5_creds *tmp; 511 512 KCM_ASSERT_VALID(ccache); 513 514 HEIMDAL_MUTEX_lock(&ccache->mutex); 515 ret = kcm_ccache_store_cred_internal(context, ccache, creds, copy, &tmp); 516 HEIMDAL_MUTEX_unlock(&ccache->mutex); 517 518 return ret; 519} 520 521struct kcm_creds * 522kcm_ccache_find_cred_uuid(krb5_context context, 523 kcm_ccache ccache, 524 kcmuuid_t uuid) 525{ 526 struct kcm_creds *c; 527 528 for (c = ccache->creds; c != NULL; c = c->next) 529 if (memcmp(c->uuid, uuid, sizeof(c->uuid)) == 0) 530 return c; 531 532 return NULL; 533} 534 535 536 537krb5_error_code 538kcm_ccache_store_cred_internal(krb5_context context, 539 kcm_ccache ccache, 540 krb5_creds *creds, 541 int copy, 542 krb5_creds **credp) 543{ 544 struct kcm_creds **c; 545 krb5_error_code ret; 546 547 for (c = &ccache->creds; *c != NULL; c = &(*c)->next) 548 ; 549 550 *c = (struct kcm_creds *)calloc(1, sizeof(**c)); 551 if (*c == NULL) 552 return KRB5_CC_NOMEM; 553 554 RAND_bytes((*c)->uuid, sizeof((*c)->uuid)); 555 556 *credp = &(*c)->cred; 557 558 if (copy) { 559 ret = krb5_copy_creds_contents(context, creds, *credp); 560 if (ret) { 561 free(*c); 562 *c = NULL; 563 } 564 } else { 565 **credp = *creds; 566 ret = 0; 567 } 568 569 return ret; 570} 571 572krb5_error_code 573kcm_ccache_remove_cred_internal(krb5_context context, 574 kcm_ccache ccache, 575 krb5_flags whichfields, 576 const krb5_creds *mcreds) 577{ 578 krb5_error_code ret; 579 struct kcm_creds **c; 580 581 ret = KRB5_CC_NOTFOUND; 582 583 for (c = &ccache->creds; *c != NULL; c = &(*c)->next) { 584 if (krb5_compare_creds(context, whichfields, mcreds, &(*c)->cred)) { 585 struct kcm_creds *cred = *c; 586 587 *c = cred->next; 588 krb5_free_cred_contents(context, &cred->cred); 589 free(cred); 590 ret = 0; 591 if (*c == NULL) 592 break; 593 } 594 } 595 596 return ret; 597} 598 599krb5_error_code 600kcm_ccache_remove_cred(krb5_context context, 601 kcm_ccache ccache, 602 krb5_flags whichfields, 603 const krb5_creds *mcreds) 604{ 605 krb5_error_code ret; 606 607 KCM_ASSERT_VALID(ccache); 608 609 HEIMDAL_MUTEX_lock(&ccache->mutex); 610 ret = kcm_ccache_remove_cred_internal(context, ccache, whichfields, mcreds); 611 HEIMDAL_MUTEX_unlock(&ccache->mutex); 612 613 return ret; 614} 615 616krb5_error_code 617kcm_ccache_retrieve_cred_internal(krb5_context context, 618 kcm_ccache ccache, 619 krb5_flags whichfields, 620 const krb5_creds *mcreds, 621 krb5_creds **creds) 622{ 623 krb5_boolean match; 624 struct kcm_creds *c; 625 krb5_error_code ret; 626 627 memset(creds, 0, sizeof(*creds)); 628 629 ret = KRB5_CC_END; 630 631 match = FALSE; 632 for (c = ccache->creds; c != NULL; c = c->next) { 633 match = krb5_compare_creds(context, whichfields, mcreds, &c->cred); 634 if (match) 635 break; 636 } 637 638 if (match) { 639 ret = 0; 640 *creds = &c->cred; 641 } 642 643 return ret; 644} 645 646krb5_error_code 647kcm_ccache_retrieve_cred(krb5_context context, 648 kcm_ccache ccache, 649 krb5_flags whichfields, 650 const krb5_creds *mcreds, 651 krb5_creds **credp) 652{ 653 krb5_error_code ret; 654 655 KCM_ASSERT_VALID(ccache); 656 657 HEIMDAL_MUTEX_lock(&ccache->mutex); 658 ret = kcm_ccache_retrieve_cred_internal(context, ccache, 659 whichfields, mcreds, credp); 660 HEIMDAL_MUTEX_unlock(&ccache->mutex); 661 662 return ret; 663} 664 665char * 666kcm_ccache_first_name(kcm_client *client) 667{ 668 kcm_ccache p; 669 char *name = NULL; 670 671 HEIMDAL_MUTEX_lock(&ccache_mutex); 672 673 for (p = ccache_head; p != NULL; p = p->next) { 674 if (kcm_is_same_session(client, p->uid, p->session)) 675 break; 676 } 677 if (p) 678 name = strdup(p->name); 679 HEIMDAL_MUTEX_unlock(&ccache_mutex); 680 return name; 681} 682