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