1/* $NetBSD: mcache.c,v 1.3 2023/06/19 21:41:44 christos Exp $ */ 2 3/* 4 * Copyright (c) 1997-2004 Kungliga Tekniska H��gskolan 5 * (Royal Institute of Technology, Stockholm, Sweden). 6 * All rights reserved. 7 * 8 * Portions Copyright (c) 2009 Apple Inc. All rights reserved. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 21 * 3. Neither the name of the Institute nor the names of its contributors 22 * may be used to endorse or promote products derived from this software 23 * without specific prior written permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 28 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 * SUCH DAMAGE. 36 */ 37 38#include "krb5_locl.h" 39 40typedef struct krb5_mcache { 41 char *name; 42 unsigned int refcnt; 43 int dead; 44 krb5_principal primary_principal; 45 struct link { 46 krb5_creds cred; 47 struct link *next; 48 } *creds; 49 struct krb5_mcache *next; 50 time_t mtime; 51 krb5_deltat kdc_offset; 52 HEIMDAL_MUTEX mutex; 53} krb5_mcache; 54 55static HEIMDAL_MUTEX mcc_mutex = HEIMDAL_MUTEX_INITIALIZER; 56static struct krb5_mcache *mcc_head; 57 58#define MCACHE(X) ((krb5_mcache *)(X)->data.data) 59 60#define MISDEAD(X) ((X)->dead) 61 62static const char* KRB5_CALLCONV 63mcc_get_name(krb5_context context, 64 krb5_ccache id) 65{ 66 return MCACHE(id)->name; 67} 68 69static krb5_mcache * KRB5_CALLCONV 70mcc_alloc(const char *name) 71{ 72 krb5_mcache *m, *m_c; 73 int ret = 0; 74 75 ALLOC(m, 1); 76 if(m == NULL) 77 return NULL; 78 if(name == NULL) 79 ret = asprintf(&m->name, "%p", m); 80 else 81 m->name = strdup(name); 82 if(ret < 0 || m->name == NULL) { 83 free(m); 84 return NULL; 85 } 86 /* check for dups first */ 87 HEIMDAL_MUTEX_lock(&mcc_mutex); 88 for (m_c = mcc_head; m_c != NULL; m_c = m_c->next) 89 if (strcmp(m->name, m_c->name) == 0) 90 break; 91 if (m_c) { 92 free(m->name); 93 free(m); 94 HEIMDAL_MUTEX_unlock(&mcc_mutex); 95 return NULL; 96 } 97 98 m->dead = 0; 99 m->refcnt = 1; 100 m->primary_principal = NULL; 101 m->creds = NULL; 102 m->mtime = time(NULL); 103 m->kdc_offset = 0; 104 m->next = mcc_head; 105 HEIMDAL_MUTEX_init(&(m->mutex)); 106 mcc_head = m; 107 HEIMDAL_MUTEX_unlock(&mcc_mutex); 108 return m; 109} 110 111static krb5_error_code KRB5_CALLCONV 112mcc_resolve(krb5_context context, krb5_ccache *id, const char *res) 113{ 114 krb5_mcache *m; 115 116 HEIMDAL_MUTEX_lock(&mcc_mutex); 117 for (m = mcc_head; m != NULL; m = m->next) 118 if (strcmp(m->name, res) == 0) 119 break; 120 HEIMDAL_MUTEX_unlock(&mcc_mutex); 121 122 if (m != NULL) { 123 HEIMDAL_MUTEX_lock(&(m->mutex)); 124 m->refcnt++; 125 HEIMDAL_MUTEX_unlock(&(m->mutex)); 126 (*id)->data.data = m; 127 (*id)->data.length = sizeof(*m); 128 return 0; 129 } 130 131 m = mcc_alloc(res); 132 if (m == NULL) { 133 krb5_set_error_message(context, KRB5_CC_NOMEM, 134 N_("malloc: out of memory", "")); 135 return KRB5_CC_NOMEM; 136 } 137 138 (*id)->data.data = m; 139 (*id)->data.length = sizeof(*m); 140 141 return 0; 142} 143 144 145static krb5_error_code KRB5_CALLCONV 146mcc_gen_new(krb5_context context, krb5_ccache *id) 147{ 148 krb5_mcache *m; 149 150 m = mcc_alloc(NULL); 151 152 if (m == NULL) { 153 krb5_set_error_message(context, KRB5_CC_NOMEM, 154 N_("malloc: out of memory", "")); 155 return KRB5_CC_NOMEM; 156 } 157 158 (*id)->data.data = m; 159 (*id)->data.length = sizeof(*m); 160 161 return 0; 162} 163 164static void KRB5_CALLCONV 165mcc_destroy_internal(krb5_context context, 166 krb5_mcache *m) 167{ 168 struct link *l; 169 170 if (m->primary_principal != NULL) { 171 krb5_free_principal (context, m->primary_principal); 172 m->primary_principal = NULL; 173 } 174 m->dead = 1; 175 176 l = m->creds; 177 while (l != NULL) { 178 struct link *old; 179 180 krb5_free_cred_contents (context, &l->cred); 181 old = l; 182 l = l->next; 183 free (old); 184 } 185 186 m->creds = NULL; 187 return; 188} 189 190static krb5_error_code KRB5_CALLCONV 191mcc_initialize(krb5_context context, 192 krb5_ccache id, 193 krb5_principal primary_principal) 194{ 195 krb5_mcache *m = MCACHE(id); 196 krb5_error_code ret = 0; 197 HEIMDAL_MUTEX_lock(&(m->mutex)); 198 heim_assert(m->refcnt != 0, "resurection released mcache"); 199 /* 200 * It's important to destroy any existing 201 * creds here, that matches the baheviour 202 * of all other backends and also the 203 * MEMORY: backend in MIT. 204 */ 205 mcc_destroy_internal(context, m); 206 m->dead = 0; 207 m->kdc_offset = 0; 208 m->mtime = time(NULL); 209 ret = krb5_copy_principal (context, 210 primary_principal, 211 &m->primary_principal); 212 HEIMDAL_MUTEX_unlock(&(m->mutex)); 213 return ret; 214} 215 216static int 217mcc_close_internal(krb5_mcache *m) 218{ 219 HEIMDAL_MUTEX_lock(&(m->mutex)); 220 heim_assert(m->refcnt != 0, "closed dead cache mcache"); 221 if (--m->refcnt != 0) { 222 HEIMDAL_MUTEX_unlock(&(m->mutex)); 223 return 0; 224 } 225 if (MISDEAD(m)) { 226 free (m->name); 227 HEIMDAL_MUTEX_unlock(&(m->mutex)); 228 return 1; 229 } 230 HEIMDAL_MUTEX_unlock(&(m->mutex)); 231 return 0; 232} 233 234static krb5_error_code KRB5_CALLCONV 235mcc_close(krb5_context context, 236 krb5_ccache id) 237{ 238 krb5_mcache *m = MCACHE(id); 239 240 if (mcc_close_internal(MCACHE(id))) { 241 HEIMDAL_MUTEX_destroy(&(m->mutex)); 242 krb5_data_free(&id->data); 243 } 244 return 0; 245} 246 247static krb5_error_code KRB5_CALLCONV 248mcc_destroy(krb5_context context, 249 krb5_ccache id) 250{ 251 krb5_mcache **n, *m = MCACHE(id); 252 253 HEIMDAL_MUTEX_lock(&mcc_mutex); 254 HEIMDAL_MUTEX_lock(&(m->mutex)); 255 if (m->refcnt == 0) 256 { 257 HEIMDAL_MUTEX_unlock(&(m->mutex)); 258 HEIMDAL_MUTEX_unlock(&mcc_mutex); 259 krb5_abortx(context, "mcc_destroy: refcnt already 0"); 260 } 261 262 if (!MISDEAD(m)) { 263 /* if this is an active mcache, remove it from the linked 264 list, and free all data */ 265 for(n = &mcc_head; n && *n; n = &(*n)->next) { 266 if(m == *n) { 267 *n = m->next; 268 break; 269 } 270 } 271 mcc_destroy_internal(context, m); 272 } 273 HEIMDAL_MUTEX_unlock(&(m->mutex)); 274 HEIMDAL_MUTEX_unlock(&mcc_mutex); 275 return 0; 276} 277 278static krb5_error_code KRB5_CALLCONV 279mcc_store_cred(krb5_context context, 280 krb5_ccache id, 281 krb5_creds *creds) 282{ 283 krb5_mcache *m = MCACHE(id); 284 krb5_error_code ret; 285 struct link *l; 286 287 HEIMDAL_MUTEX_lock(&(m->mutex)); 288 if (MISDEAD(m)) 289 { 290 HEIMDAL_MUTEX_unlock(&(m->mutex)); 291 return ENOENT; 292 } 293 294 l = malloc (sizeof(*l)); 295 if (l == NULL) { 296 krb5_set_error_message(context, KRB5_CC_NOMEM, 297 N_("malloc: out of memory", "")); 298 HEIMDAL_MUTEX_unlock(&(m->mutex)); 299 return KRB5_CC_NOMEM; 300 } 301 l->next = m->creds; 302 m->creds = l; 303 memset (&l->cred, 0, sizeof(l->cred)); 304 ret = krb5_copy_creds_contents (context, creds, &l->cred); 305 if (ret) { 306 m->creds = l->next; 307 free (l); 308 HEIMDAL_MUTEX_unlock(&(m->mutex)); 309 return ret; 310 } 311 m->mtime = time(NULL); 312 HEIMDAL_MUTEX_unlock(&(m->mutex)); 313 return 0; 314} 315 316static krb5_error_code KRB5_CALLCONV 317mcc_get_principal(krb5_context context, 318 krb5_ccache id, 319 krb5_principal *principal) 320{ 321 krb5_mcache *m = MCACHE(id); 322 krb5_error_code ret = 0; 323 324 HEIMDAL_MUTEX_lock(&(m->mutex)); 325 if (MISDEAD(m) || m->primary_principal == NULL) { 326 HEIMDAL_MUTEX_unlock(&(m->mutex)); 327 return ENOENT; 328 } 329 ret = krb5_copy_principal (context, 330 m->primary_principal, 331 principal); 332 HEIMDAL_MUTEX_unlock(&(m->mutex)); 333 return ret; 334} 335 336static krb5_error_code KRB5_CALLCONV 337mcc_get_first (krb5_context context, 338 krb5_ccache id, 339 krb5_cc_cursor *cursor) 340{ 341 krb5_mcache *m = MCACHE(id); 342 343 HEIMDAL_MUTEX_lock(&(m->mutex)); 344 if (MISDEAD(m)) { 345 HEIMDAL_MUTEX_unlock(&(m->mutex)); 346 return ENOENT; 347 } 348 *cursor = m->creds; 349 350 HEIMDAL_MUTEX_unlock(&(m->mutex)); 351 return 0; 352} 353 354static krb5_error_code KRB5_CALLCONV 355mcc_get_next (krb5_context context, 356 krb5_ccache id, 357 krb5_cc_cursor *cursor, 358 krb5_creds *creds) 359{ 360 krb5_mcache *m = MCACHE(id); 361 struct link *l; 362 363 HEIMDAL_MUTEX_lock(&(m->mutex)); 364 if (MISDEAD(m)) { 365 HEIMDAL_MUTEX_unlock(&(m->mutex)); 366 return ENOENT; 367 } 368 HEIMDAL_MUTEX_unlock(&(m->mutex)); 369 370 l = *cursor; 371 if (l != NULL) { 372 *cursor = l->next; 373 return krb5_copy_creds_contents (context, 374 &l->cred, 375 creds); 376 } else 377 return KRB5_CC_END; 378} 379 380static krb5_error_code KRB5_CALLCONV 381mcc_end_get (krb5_context context, 382 krb5_ccache id, 383 krb5_cc_cursor *cursor) 384{ 385 return 0; 386} 387 388static krb5_error_code KRB5_CALLCONV 389mcc_remove_cred(krb5_context context, 390 krb5_ccache id, 391 krb5_flags which, 392 krb5_creds *mcreds) 393{ 394 krb5_mcache *m = MCACHE(id); 395 struct link **q, *p; 396 397 HEIMDAL_MUTEX_lock(&(m->mutex)); 398 399 for(q = &m->creds, p = *q; p; p = *q) { 400 if(krb5_compare_creds(context, which, mcreds, &p->cred)) { 401 *q = p->next; 402 krb5_free_cred_contents(context, &p->cred); 403 free(p); 404 m->mtime = time(NULL); 405 } else 406 q = &p->next; 407 } 408 HEIMDAL_MUTEX_unlock(&(m->mutex)); 409 return 0; 410} 411 412static krb5_error_code KRB5_CALLCONV 413mcc_set_flags(krb5_context context, 414 krb5_ccache id, 415 krb5_flags flags) 416{ 417 return 0; /* XXX */ 418} 419 420struct mcache_iter { 421 krb5_mcache *cache; 422}; 423 424static krb5_error_code KRB5_CALLCONV 425mcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor) 426{ 427 struct mcache_iter *iter; 428 429 iter = calloc(1, sizeof(*iter)); 430 if (iter == NULL) 431 return krb5_enomem(context); 432 433 HEIMDAL_MUTEX_lock(&mcc_mutex); 434 iter->cache = mcc_head; 435 if (iter->cache) { 436 HEIMDAL_MUTEX_lock(&(iter->cache->mutex)); 437 iter->cache->refcnt++; 438 HEIMDAL_MUTEX_unlock(&(iter->cache->mutex)); 439 } 440 HEIMDAL_MUTEX_unlock(&mcc_mutex); 441 442 *cursor = iter; 443 return 0; 444} 445 446static krb5_error_code KRB5_CALLCONV 447mcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id) 448{ 449 struct mcache_iter *iter = cursor; 450 krb5_error_code ret; 451 krb5_mcache *m; 452 453 if (iter->cache == NULL) 454 return KRB5_CC_END; 455 456 HEIMDAL_MUTEX_lock(&mcc_mutex); 457 m = iter->cache; 458 if (m->next) 459 { 460 HEIMDAL_MUTEX_lock(&(m->next->mutex)); 461 m->next->refcnt++; 462 HEIMDAL_MUTEX_unlock(&(m->next->mutex)); 463 } 464 465 iter->cache = m->next; 466 HEIMDAL_MUTEX_unlock(&mcc_mutex); 467 468 ret = _krb5_cc_allocate(context, &krb5_mcc_ops, id); 469 if (ret) 470 return ret; 471 472 (*id)->data.data = m; 473 (*id)->data.length = sizeof(*m); 474 475 return 0; 476} 477 478static krb5_error_code KRB5_CALLCONV 479mcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor) 480{ 481 struct mcache_iter *iter = cursor; 482 483 if (iter->cache) 484 mcc_close_internal(iter->cache); 485 iter->cache = NULL; 486 free(iter); 487 return 0; 488} 489 490static krb5_error_code KRB5_CALLCONV 491mcc_move(krb5_context context, krb5_ccache from, krb5_ccache to) 492{ 493 krb5_mcache *mfrom = MCACHE(from), *mto = MCACHE(to); 494 struct link *creds; 495 krb5_principal principal; 496 krb5_mcache **n; 497 498 HEIMDAL_MUTEX_lock(&mcc_mutex); 499 500 /* drop the from cache from the linked list to avoid lookups */ 501 for(n = &mcc_head; n && *n; n = &(*n)->next) { 502 if(mfrom == *n) { 503 *n = mfrom->next; 504 break; 505 } 506 } 507 508 HEIMDAL_MUTEX_lock(&(mfrom->mutex)); 509 HEIMDAL_MUTEX_lock(&(mto->mutex)); 510 /* swap creds */ 511 creds = mto->creds; 512 mto->creds = mfrom->creds; 513 mfrom->creds = creds; 514 /* swap principal */ 515 principal = mto->primary_principal; 516 mto->primary_principal = mfrom->primary_principal; 517 mfrom->primary_principal = principal; 518 519 mto->mtime = mfrom->mtime = time(NULL); 520 521 HEIMDAL_MUTEX_unlock(&(mfrom->mutex)); 522 HEIMDAL_MUTEX_unlock(&(mto->mutex)); 523 HEIMDAL_MUTEX_unlock(&mcc_mutex); 524 mcc_destroy(context, from); 525 526 return 0; 527} 528 529static krb5_error_code KRB5_CALLCONV 530mcc_default_name(krb5_context context, char **str) 531{ 532 *str = strdup("MEMORY:"); 533 if (*str == NULL) 534 return krb5_enomem(context); 535 return 0; 536} 537 538static krb5_error_code KRB5_CALLCONV 539mcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime) 540{ 541 krb5_mcache *m = MCACHE(id); 542 HEIMDAL_MUTEX_lock(&(m->mutex)); 543 *mtime = m->mtime; 544 HEIMDAL_MUTEX_unlock(&(m->mutex)); 545 return 0; 546} 547 548static krb5_error_code KRB5_CALLCONV 549mcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset) 550{ 551 krb5_mcache *m = MCACHE(id); 552 HEIMDAL_MUTEX_lock(&(m->mutex)); 553 m->kdc_offset = kdc_offset; 554 HEIMDAL_MUTEX_unlock(&(m->mutex)); 555 return 0; 556} 557 558static krb5_error_code KRB5_CALLCONV 559mcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset) 560{ 561 krb5_mcache *m = MCACHE(id); 562 HEIMDAL_MUTEX_lock(&(m->mutex)); 563 *kdc_offset = m->kdc_offset; 564 HEIMDAL_MUTEX_unlock(&(m->mutex)); 565 return 0; 566} 567 568 569/** 570 * Variable containing the MEMORY based credential cache implemention. 571 * 572 * @ingroup krb5_ccache 573 */ 574 575KRB5_LIB_VARIABLE const krb5_cc_ops krb5_mcc_ops = { 576 KRB5_CC_OPS_VERSION, 577 "MEMORY", 578 mcc_get_name, 579 mcc_resolve, 580 mcc_gen_new, 581 mcc_initialize, 582 mcc_destroy, 583 mcc_close, 584 mcc_store_cred, 585 NULL, /* mcc_retrieve */ 586 mcc_get_principal, 587 mcc_get_first, 588 mcc_get_next, 589 mcc_end_get, 590 mcc_remove_cred, 591 mcc_set_flags, 592 NULL, 593 mcc_get_cache_first, 594 mcc_get_cache_next, 595 mcc_end_cache_get, 596 mcc_move, 597 mcc_default_name, 598 NULL, 599 mcc_lastchange, 600 mcc_set_kdc_offset, 601 mcc_get_kdc_offset 602}; 603