xref: /titanic_51/usr/src/lib/gss_mechs/mech_krb5/krb5/rcache/rc_file.c (revision 16d62afbe67d641c4a1c953eca69066fec82f3d9)
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