1 /* 2 * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights 3 * reserved. 4 */ 5 6 7 /* 8 * lib/krb5/rcache/rc_file.c 9 * 10 * This file of the Kerberos V5 software is derived from public-domain code 11 * contributed by Daniel J. Bernstein, <brnstnd@acf10.nyu.edu>. 12 * 13 */ 14 15 16 /* 17 * An implementation for the default replay cache type. 18 */ 19 /* Solaris Kerberos */ 20 #define FREE_RC(x) ((void) free((char *) (x))) 21 #include "rc_common.h" 22 #include "rc_file.h" 23 24 /* Solaris Kerberos */ 25 #include <kstat.h> 26 #include <atomic.h> 27 #include <assert.h> 28 #include <syslog.h> 29 30 /* 31 * Solaris: The NOIOSTUFF macro has been taken out for the Solaris version 32 * of this module, because this has been split into a separate mem rcache. 33 */ 34 35 /* of course, list is backwards from file */ 36 /* hash could be forwards since we have to search on match, but naaaah */ 37 38 static int 39 rc_store(krb5_context context, krb5_rcache id, krb5_donot_replay *rep) 40 { 41 struct file_data *t = (struct file_data *)id->data; 42 int rephash; 43 struct authlist *ta; 44 krb5_int32 time; 45 46 rephash = hash(rep, t->hsize); 47 48 /* Solaris: calling krb_timeofday() here, once for better perf. */ 49 krb5_timeofday(context, &time); 50 51 /* Solaris: calling alive() on rep since it doesn't make sense to store an 52 * expired replay. 53 */ 54 if (alive(context, rep, t->lifespan, time) == CMP_EXPIRED){ 55 return CMP_EXPIRED; 56 } 57 58 for (ta = t->h[rephash]; ta; ta = ta->nh) { 59 switch(cmp(&ta->rep, rep)) 60 { 61 case CMP_REPLAY: 62 return CMP_REPLAY; 63 case CMP_HOHUM: 64 if (alive(context, &ta->rep, t->lifespan, time) == CMP_EXPIRED) 65 t->nummisses++; 66 else 67 t->numhits++; 68 break; 69 default: 70 ; /* wtf? */ 71 } 72 } 73 74 if (!(ta = (struct authlist *) malloc(sizeof(struct authlist)))) 75 return CMP_MALLOC; 76 ta->rep = *rep; 77 if (!(ta->rep.client = strdup(rep->client))) { 78 FREE_RC(ta); 79 return CMP_MALLOC; 80 } 81 if (!(ta->rep.server = strdup(rep->server))) { 82 FREE_RC(ta->rep.client); 83 FREE_RC(ta); 84 return CMP_MALLOC; 85 } 86 ta->na = t->a; t->a = ta; 87 ta->nh = t->h[rephash]; t->h[rephash] = ta; 88 89 return CMP_HOHUM; 90 } 91 92 /*ARGSUSED*/ 93 char * KRB5_CALLCONV 94 krb5_rc_file_get_name(krb5_context context, krb5_rcache id) 95 { 96 return ((struct file_data *) (id->data))->name; 97 } 98 99 /*ARGSUSED*/ 100 krb5_error_code KRB5_CALLCONV 101 krb5_rc_file_get_span(krb5_context context, krb5_rcache id, 102 krb5_deltat *lifespan) 103 { 104 krb5_error_code err; 105 struct file_data *t; 106 107 err = k5_mutex_lock(&id->lock); 108 if (err) 109 return err; 110 t = (struct file_data *) id->data; 111 *lifespan = t->lifespan; 112 k5_mutex_unlock(&id->lock); 113 return 0; 114 } 115 116 static krb5_error_code KRB5_CALLCONV 117 krb5_rc_file_init_locked(krb5_context context, krb5_rcache id, krb5_deltat lifespan) 118 { 119 struct file_data *t = (struct file_data *)id->data; 120 krb5_error_code retval; 121 122 t->lifespan = lifespan ? lifespan : context->clockskew; 123 /* default to clockskew from the context */ 124 if ((retval = krb5_rc_io_creat(context, &t->d, &t->name))) { 125 return retval; 126 } 127 if ((krb5_rc_io_write(context, &t->d, 128 (krb5_pointer) &t->lifespan, sizeof(t->lifespan)) 129 || krb5_rc_io_sync(context, &t->d))) { 130 return KRB5_RC_IO; 131 } 132 return 0; 133 } 134 135 krb5_error_code KRB5_CALLCONV 136 krb5_rc_file_init(krb5_context context, krb5_rcache id, krb5_deltat lifespan) 137 { 138 krb5_error_code retval; 139 140 retval = k5_mutex_lock(&id->lock); 141 if (retval) 142 return retval; 143 retval = krb5_rc_file_init_locked(context, id, lifespan); 144 k5_mutex_unlock(&id->lock); 145 return retval; 146 } 147 148 /* Called with the mutex already locked. */ 149 krb5_error_code 150 krb5_rc_file_close_no_free(krb5_context context, krb5_rcache id) 151 { 152 struct file_data *t = (struct file_data *)id->data; 153 struct authlist *q; 154 155 if (t->h) 156 FREE_RC(t->h); 157 if (t->name) 158 FREE_RC(t->name); 159 while ((q = t->a)) 160 { 161 t->a = q->na; 162 FREE_RC(q->rep.client); 163 FREE_RC(q->rep.server); 164 FREE_RC(q); 165 } 166 if (t->d.fd >= 0) 167 (void) krb5_rc_io_close(context, &t->d); 168 FREE_RC(t); 169 id->data = NULL; 170 return 0; 171 } 172 173 krb5_error_code KRB5_CALLCONV 174 krb5_rc_file_close(krb5_context context, krb5_rcache id) 175 { 176 krb5_error_code retval; 177 retval = k5_mutex_lock(&id->lock); 178 if (retval) 179 return retval; 180 krb5_rc_file_close_no_free(context, id); 181 k5_mutex_unlock(&id->lock); 182 k5_mutex_destroy(&id->lock); 183 free(id); 184 return 0; 185 } 186 187 krb5_error_code KRB5_CALLCONV 188 krb5_rc_file_destroy(krb5_context context, krb5_rcache id) 189 { 190 if (krb5_rc_io_destroy(context, &((struct file_data *) (id->data))->d)) 191 return KRB5_RC_IO; 192 return krb5_rc_file_close(context, id); 193 } 194 195 /*ARGSUSED*/ 196 krb5_error_code KRB5_CALLCONV 197 krb5_rc_file_resolve(krb5_context context, krb5_rcache id, char *name) 198 { 199 struct file_data *t = 0; 200 krb5_error_code retval; 201 202 /* allocate id? no */ 203 if (!(t = (struct file_data *) malloc(sizeof(struct file_data)))) 204 return KRB5_RC_MALLOC; 205 id->data = (krb5_pointer) t; 206 memset(t, 0, sizeof(struct file_data)); 207 if (name) { 208 t->name = malloc(strlen(name)+1); 209 if (!t->name) { 210 retval = KRB5_RC_MALLOC; 211 goto cleanup; 212 } 213 strcpy(t->name, name); 214 } else 215 t->name = 0; 216 t->numhits = t->nummisses = 0; 217 t->hsize = HASHSIZE; /* no need to store---it's memory-only */ 218 t->h = (struct authlist **) malloc(t->hsize*sizeof(struct authlist *)); 219 if (!t->h) { 220 retval = KRB5_RC_MALLOC; 221 goto cleanup; 222 } 223 memset(t->h, 0, t->hsize*sizeof(struct authlist *)); 224 t->a = (struct authlist *) 0; 225 t->d.fd = -1; 226 t->recovering = 0; 227 return 0; 228 229 cleanup: 230 if (t) { 231 if (t->name) 232 krb5_xfree(t->name); 233 if (t->h) 234 krb5_xfree(t->h); 235 krb5_xfree(t); 236 id->data = NULL; 237 } 238 return retval; 239 } 240 241 /*ARGSUSED*/ 242 void 243 krb5_rc_free_entry(krb5_context context, krb5_donot_replay **rep) 244 { 245 krb5_donot_replay *rp = *rep; 246 247 *rep = NULL; 248 if (rp) 249 { 250 if (rp->client) 251 free(rp->client); 252 253 if (rp->server) 254 free(rp->server); 255 rp->client = NULL; 256 rp->server = NULL; 257 free(rp); 258 } 259 } 260 261 static krb5_error_code 262 krb5_rc_io_fetch(krb5_context context, struct file_data *t, 263 krb5_donot_replay *rep, int maxlen) 264 { 265 unsigned int len; 266 krb5_error_code retval; 267 268 rep->client = rep->server = 0; 269 270 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &len, 271 sizeof(len)); 272 if (retval) 273 return retval; 274 275 if ((len <= 0) || (len >= maxlen)) 276 return KRB5_RC_IO_EOF; 277 278 rep->client = malloc (len); 279 if (!rep->client) 280 return KRB5_RC_MALLOC; 281 282 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) rep->client, len); 283 if (retval) 284 goto errout; 285 286 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &len, 287 sizeof(len)); 288 if (retval) 289 goto errout; 290 291 if ((len <= 0) || (len >= maxlen)) { 292 retval = KRB5_RC_IO_EOF; 293 goto errout; 294 } 295 296 rep->server = malloc (len); 297 if (!rep->server) { 298 retval = KRB5_RC_MALLOC; 299 goto errout; 300 } 301 302 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) rep->server, len); 303 if (retval) 304 goto errout; 305 306 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &rep->cusec, 307 sizeof(rep->cusec)); 308 if (retval) 309 goto errout; 310 311 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &rep->ctime, 312 sizeof(rep->ctime)); 313 if (retval) 314 goto errout; 315 316 return 0; 317 318 errout: 319 if (rep->client) 320 krb5_xfree(rep->client); 321 if (rep->server) 322 krb5_xfree(rep->server); 323 rep->client = rep->server = 0; 324 return retval; 325 } 326 327 328 static krb5_error_code 329 krb5_rc_file_expunge_locked(krb5_context context, krb5_rcache id); 330 331 static krb5_error_code 332 krb5_rc_file_recover_locked(krb5_context context, krb5_rcache id) 333 { 334 struct file_data *t = (struct file_data *)id->data; 335 krb5_donot_replay *rep = 0; 336 krb5_error_code retval; 337 long max_size; 338 int expired_entries = 0; 339 340 if ((retval = krb5_rc_io_open(context, &t->d, t->name))) { 341 return retval; 342 } 343 344 t->recovering = 1; 345 346 max_size = krb5_rc_io_size(context, &t->d); 347 348 rep = NULL; 349 if (krb5_rc_io_read(context, &t->d, (krb5_pointer) &t->lifespan, 350 sizeof(t->lifespan))) { 351 retval = KRB5_RC_IO; 352 goto io_fail; 353 } 354 355 if (!(rep = (krb5_donot_replay *) malloc(sizeof(krb5_donot_replay)))) { 356 retval = KRB5_RC_MALLOC; 357 goto io_fail; 358 } 359 rep->client = NULL; 360 rep->server = NULL; 361 362 /* now read in each auth_replay and insert into table */ 363 for (;;) { 364 if (krb5_rc_io_mark(context, &t->d)) { 365 retval = KRB5_RC_IO; 366 goto io_fail; 367 } 368 369 retval = krb5_rc_io_fetch (context, t, rep, (int) max_size); 370 371 if (retval == KRB5_RC_IO_EOF) 372 break; 373 else if (retval != 0) 374 goto io_fail; 375 376 /* Solaris: made the change below for better perf. */ 377 switch (rc_store(context, id, rep)) { 378 case CMP_EXPIRED: 379 expired_entries++; 380 break; 381 case CMP_MALLOC: 382 retval = KRB5_RC_MALLOC; 383 goto io_fail; 384 break; 385 } 386 /* 387 * free fields allocated by rc_io_fetch 388 */ 389 FREE_RC(rep->server); 390 FREE_RC(rep->client); 391 rep->server = 0; 392 rep->client = 0; 393 } 394 retval = 0; 395 krb5_rc_io_unmark(context, &t->d); 396 /* 397 * An automatic expunge here could remove the need for 398 * mark/unmark but that would be inefficient. 399 */ 400 io_fail: 401 krb5_rc_free_entry(context, &rep); 402 if (retval) 403 krb5_rc_io_close(context, &t->d); 404 else if (expired_entries > EXCESSREPS) 405 retval = krb5_rc_file_expunge_locked(context, id); 406 t->recovering = 0; 407 return retval; 408 } 409 410 krb5_error_code KRB5_CALLCONV 411 krb5_rc_file_recover(krb5_context context, krb5_rcache id) 412 { 413 krb5_error_code ret; 414 ret = k5_mutex_lock(&id->lock); 415 if (ret) 416 return ret; 417 ret = krb5_rc_file_recover_locked(context, id); 418 k5_mutex_unlock(&id->lock); 419 return ret; 420 } 421 422 krb5_error_code KRB5_CALLCONV 423 krb5_rc_file_recover_or_init(krb5_context context, krb5_rcache id, 424 krb5_deltat lifespan) 425 { 426 krb5_error_code retval; 427 428 retval = k5_mutex_lock(&id->lock); 429 if (retval) 430 return retval; 431 retval = krb5_rc_file_recover_locked(context, id); 432 if (retval) 433 retval = krb5_rc_file_init_locked(context, id, lifespan); 434 k5_mutex_unlock(&id->lock); 435 return retval; 436 } 437 438 static krb5_error_code 439 krb5_rc_io_store(krb5_context context, struct file_data *t, 440 krb5_donot_replay *rep) 441 { 442 int clientlen, serverlen, len; 443 char *buf, *ptr; 444 krb5_error_code ret; 445 446 clientlen = strlen(rep->client) + 1; 447 serverlen = strlen(rep->server) + 1; 448 len = sizeof(clientlen) + clientlen + sizeof(serverlen) + serverlen + 449 sizeof(rep->cusec) + sizeof(rep->ctime); 450 buf = malloc(len); 451 if (buf == 0) 452 return KRB5_RC_MALLOC; 453 ptr = buf; 454 memcpy(ptr, &clientlen, sizeof(clientlen)); ptr += sizeof(clientlen); 455 memcpy(ptr, rep->client, clientlen); ptr += clientlen; 456 memcpy(ptr, &serverlen, sizeof(serverlen)); ptr += sizeof(serverlen); 457 memcpy(ptr, rep->server, serverlen); ptr += serverlen; 458 memcpy(ptr, &rep->cusec, sizeof(rep->cusec)); ptr += sizeof(rep->cusec); 459 memcpy(ptr, &rep->ctime, sizeof(rep->ctime)); ptr += sizeof(rep->ctime); 460 461 ret = krb5_rc_io_write(context, &t->d, buf, len); 462 free(buf); 463 return ret; 464 } 465 466 static krb5_error_code krb5_rc_file_expunge_locked(krb5_context, krb5_rcache); 467 468 /* 469 * Solaris Kerberos 470 * 471 * Get time of boot. This is needed for fsync()-less operation. See below. 472 * 473 * Cstyle note: MIT style used here. 474 */ 475 static 476 krb5_timestamp 477 get_boot_time(krb5_timestamp now) 478 { 479 krb5_timestamp bt; 480 kstat_ctl_t *kc; 481 kstat_t *k; 482 kstat_named_t *kn; 483 kid_t rc; 484 485 /* 486 * We use the boot_time kstat from the "unix" module. 487 * 488 * It's hard to determine the interface stability of kstats. To be safe 489 * we treat boot_time with extra care: if it disappears or is renamed, 490 * or if its type changes, or if its value appears to be in the future, 491 * then we fail to get boot time and the rcache falls back on slow 492 * behavior (fsync()ing at every write). If this kstat should produce a 493 * time less than the actual boot time then this increases the chance of 494 * post-crash replays of Authenticators whose rcache entries were not 495 * fsync()ed and were lost. 496 * 497 * We consider it extremely unlikely that this kstat will ever change at 498 * all however, much less to change in such a way that it will return 499 * the wrong boot time as an unsigned 32-bit integer. If we fail to 500 * find the kstat we expect we log loudly even though the rcache remains 501 * functional. 502 */ 503 if ((kc = kstat_open()) == NULL || 504 (k = kstat_lookup(kc, "unix", 0, "system_misc")) == NULL || 505 (rc = kstat_read(kc, k, NULL)) == -1 || 506 (kn = kstat_data_lookup(k, "boot_time")) == NULL || 507 /* check that the kstat's type hasn't changed */ 508 kn->data_type != KSTAT_DATA_UINT32 || 509 /* boot_time value sanity check */ 510 kn->value.i32 > now || 511 /* krb5_timestamp is int32_t, this kstat is uint32_t; 2038 problem! */ 512 kn->value.i32 < 0) { 513 514 /* Return boot time to 1 to indicate failure to get actual boot time */ 515 bt = 1; 516 syslog(LOG_ALERT, "Alert: Unable to determine boot_time (boot_time " 517 "kstat removed or changed?); rcache will be functional, but slow"); 518 } else { 519 bt = kn->value.i32; 520 } 521 522 if (kc != NULL) 523 (void) kstat_close(kc); 524 525 return (bt); 526 } 527 528 /* 529 * Solaris Kerberos 530 * 531 * We optimize the rcache by foregoing fsync() in the most common cases. 532 * Foregoing fsync() requires an early boot procedure to ensure that we 533 * never accept an authenticator that could be a replay of one whose 534 * rcache entry we've lost. 535 * 536 * We do this by picking an arbitrary, small time delta such that 537 * storing any krb5_donot_replays whose ctime is further into the future 538 * than now + that small delta causes an fsync() of the rcache. Early 539 * after booting we must reject all krb5_donot_replays whose ctime falls 540 * before time of boot + that delta. 541 * 542 * This works well as long as client clocks are reasonably synchronized 543 * or as long as they use kdc_timesync. Clients with clocks faster than 544 * this delta will find their AP exchanges are slower than clients with 545 * good or slow clocks. Clients with very slow clocks will find that 546 * their AP-REQs are rejected by servers that have just booted. In all 547 * other cases clients will notice only that AP exchanges are much 548 * faster as a result of the missing fsync()s. 549 * 550 * KRB5_RC_FSYNCLESS_FAST_SKEW is that time delta, in seconds. Five 551 * seconds seems like a reasonable delta. If it takes more than five 552 * seconds from the time the kernel initializes itself to the time when 553 * a kerberized system starts, and clients have good clocks or use 554 * kdc_timesync, then no authenticators will be rejected. 555 */ 556 #define KRB5_RC_FSYNCLESS_FAST_SKEW 5 557 558 krb5_error_code KRB5_CALLCONV 559 krb5_rc_file_store(krb5_context context, krb5_rcache id, krb5_donot_replay *rep) 560 { 561 krb5_error_code ret; 562 struct file_data *t; 563 static krb5_timestamp boot_time = 0; 564 krb5_timestamp now; 565 566 if (krb5_timeofday(context, &now) != 0) 567 /* No time of day -> broken rcache */ 568 return KRB5KRB_AP_ERR_REPEAT; 569 570 /* 571 * Solaris Kerberos 572 * 573 * if boot_time <= 1 -> we always fsync() (see below) 574 * if boot_time == 1 -> don't bother trying to get it again (as it could be 575 * a slow operation) 576 */ 577 if (boot_time == 0) { 578 krb5_timestamp btime = get_boot_time(now); 579 580 assert(sizeof (boot_time) == sizeof (krb5_int32) && sizeof (krb5_int32) == sizeof (uint32_t)); 581 (void) atomic_cas_32((uint32_t *)&boot_time, 0, btime); 582 } 583 584 /* 585 * Solaris Kerberos 586 * 587 * fsync()-less-ness requires safety. If we just booted then we want to 588 * reject all Authenticators whose timestamps are old enough that we might 589 * not have fsync()ed rcache entries for them prior to booting. See 590 * comment above where KRB5_RC_FSYNCLESS_FAST_SKEW is defined. See 591 * also below, where krb5_rc_io_sync() is called. 592 * 593 * If we could tell here the time of the last system crash then we 594 * could do better because we could know that the rcache has been 595 * synced to disk. But there's no reliable way to detect past 596 * crashes in this code; getting the time of boot is hard enough. 597 */ 598 if (boot_time > 1 && 599 rep->ctime < (boot_time + KRB5_RC_FSYNCLESS_FAST_SKEW)) 600 /* 601 * A better error code would be nice; clients might then know 602 * that nothing's necessarily wrong with their (or our) clocks 603 * and that they should just wait a while (or even set their 604 * clock offset slow so that their timestamps then appear into 605 * the future, where we'd accept them. 606 * 607 * KRB5KRB_AP_ERR_SKEW will just have to do. 608 */ 609 return KRB5KRB_AP_ERR_SKEW; 610 611 ret = k5_mutex_lock(&id->lock); 612 if (ret) 613 return ret; 614 615 t = (struct file_data *)id->data; 616 617 switch(rc_store(context, id,rep)) { 618 case CMP_MALLOC: 619 k5_mutex_unlock(&id->lock); 620 return KRB5_RC_MALLOC; 621 case CMP_REPLAY: 622 k5_mutex_unlock(&id->lock); 623 return KRB5KRB_AP_ERR_REPEAT; 624 case CMP_EXPIRED: 625 k5_mutex_unlock(&id->lock); 626 return KRB5KRB_AP_ERR_SKEW; 627 case CMP_HOHUM: break; 628 default: /* wtf? */ ; 629 } 630 ret = krb5_rc_io_store (context, t, rep); 631 if (ret) { 632 k5_mutex_unlock(&id->lock); 633 return ret; 634 } 635 /* Shall we automatically expunge? */ 636 if (t->nummisses > t->numhits + EXCESSREPS) 637 { 638 /* Expunge calls krb5_rc_io_sync() */ 639 ret = krb5_rc_file_expunge_locked(context, id); 640 k5_mutex_unlock(&id->lock); 641 return ret; 642 } 643 /* Solaris Kerberos */ 644 else if (boot_time <= 1 || rep->ctime > (now + KRB5_RC_FSYNCLESS_FAST_SKEW)) 645 { 646 /* 647 * fsync() only when necessary: 648 * 649 * - on expunge (see above) 650 * - if we don't know when we booted 651 * - if rep->ctime is too far into the future 652 */ 653 if (krb5_rc_io_sync(context, &t->d)) { 654 k5_mutex_unlock(&id->lock); 655 return KRB5_RC_IO; 656 } 657 } 658 k5_mutex_unlock(&id->lock); 659 return 0; 660 } 661 662 static krb5_error_code 663 krb5_rc_file_expunge_locked(krb5_context context, krb5_rcache id) 664 { 665 struct file_data *t = (struct file_data *)id->data; 666 struct authlist *q; 667 char *name; 668 krb5_error_code retval = 0; 669 krb5_rcache tmp; 670 krb5_deltat lifespan = t->lifespan; /* save original lifespan */ 671 672 if (! t->recovering) { 673 name = t->name; 674 t->name = 0; /* Clear name so it isn't freed */ 675 (void) krb5_rc_file_close_no_free(context, id); 676 retval = krb5_rc_file_resolve(context, id, name); 677 free(name); 678 if (retval) 679 return retval; 680 retval = krb5_rc_file_recover_locked(context, id); 681 if (retval) 682 return retval; 683 t = (struct file_data *)id->data; /* point to recovered cache */ 684 } 685 686 tmp = (krb5_rcache) malloc(sizeof(*tmp)); 687 if (!tmp) 688 return ENOMEM; 689 690 retval = k5_mutex_init(&tmp->lock); 691 if (retval) { 692 free(tmp); 693 return retval; 694 } 695 696 tmp->ops = &krb5_rc_file_ops; 697 if ((retval = krb5_rc_file_resolve(context, tmp, 0)) != 0) 698 goto out; 699 if ((retval = krb5_rc_initialize(context, tmp, lifespan)) != 0) 700 goto out; 701 for (q = t->a;q;q = q->na) { 702 if (krb5_rc_io_store (context, (struct file_data *)tmp->data, &q->rep)) { 703 retval = KRB5_RC_IO; 704 goto out; 705 } 706 } 707 if (krb5_rc_io_sync(context, &t->d)) { 708 retval = KRB5_RC_IO; 709 goto out; 710 } 711 if (krb5_rc_io_move(context, &t->d, &((struct file_data *)tmp->data)->d)) 712 retval = KRB5_RC_IO; 713 714 out: 715 /* 716 * krb5_rc_file_close() will free the tmp struct and it's members that the 717 * previous functions had allocated. 718 */ 719 (void) krb5_rc_file_close(context, tmp); 720 721 return (retval); 722 } 723 724 krb5_error_code KRB5_CALLCONV 725 krb5_rc_file_expunge(krb5_context context, krb5_rcache id) 726 { 727 krb5_error_code ret; 728 ret = k5_mutex_lock(&id->lock); 729 if (ret) 730 return ret; 731 ret = krb5_rc_file_expunge_locked(context, id); 732 k5_mutex_unlock(&id->lock); 733 return ret; 734 } 735