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