1e0cb22c94e25872548dac880862c7646c097ae5
[platform/upstream/krb5.git] / src / lib / krb5 / rcache / rc_dfl.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/rcache/rc_dfl.c */
3 /*
4  * This file of the Kerberos V5 software is derived from public-domain code
5  * contributed by Daniel J. Bernstein, <brnstnd@acf10.nyu.edu>.
6  *
7  */
8
9 /*
10  * An implementation for the default replay cache type.
11  */
12 #include "k5-int.h"
13 #include "rc_base.h"
14 #include "rc_dfl.h"
15 #include "rc_io.h"
16 #include "rc-int.h"
17
18 /*
19  * If NOIOSTUFF is defined at compile time, dfl rcaches will be per-process.
20  */
21
22 /*
23   Local stuff:
24
25   static int hash(krb5_donot_replay *rep, int hsize)
26   returns hash value of *rep, between 0 and hsize - 1
27   HASHSIZE
28   size of hash table (constant), can be preset
29   static int cmp(krb5_donot_replay *old, krb5_donot_replay *new, krb5_deltat t)
30   compare old and new; return CMP_REPLAY or CMP_HOHUM
31   static int alive(krb5_context, krb5_donot_replay *new, krb5_deltat t)
32   see if new is still alive; return CMP_EXPIRED or CMP_HOHUM
33   CMP_MALLOC, CMP_EXPIRED, CMP_REPLAY, CMP_HOHUM
34   return codes from cmp(), alive(), and store()
35   struct dfl_data
36   data stored in this cache type, namely "dfl"
37   struct authlist
38   multilinked list of reps
39   static int rc_store(context, krb5_rcache id, krb5_donot_replay *rep)
40   store rep in cache id; return CMP_REPLAY if replay, else CMP_MALLOC/CMP_HOHUM
41
42 */
43
44 #ifndef HASHSIZE
45 #define HASHSIZE 997 /* a convenient prime */
46 #endif
47
48 #ifndef EXCESSREPS
49 #define EXCESSREPS 30
50 #endif
51
52 /*
53  * The rcache will be automatically expunged when the number of
54  * expired krb5_donot_replays encountered incidentally in searching
55  * exceeds the number of live krb5_donot_replays by EXCESSREPS. With
56  * the defaults here, a typical cache might build up some 10K of
57  * expired krb5_donot_replays before an automatic expunge, with the
58  * waste basically independent of the number of stores per minute.
59  *
60  * The rcache will also automatically be expunged when it encounters
61  * more than EXCESSREPS expired entries when recovering a cache in
62  * dfl_recover.
63  */
64
65 static unsigned int
66 hash(krb5_donot_replay *rep, unsigned int hsize)
67 {
68     unsigned int h = rep->cusec + rep->ctime;
69     h += *rep->server;
70     h += *rep->client;
71     return h % hsize;
72 }
73
74 #define CMP_MALLOC -3
75 #define CMP_EXPIRED -2
76 #define CMP_REPLAY -1
77 #define CMP_HOHUM 0
78
79 /*ARGSUSED*/
80 static int
81 cmp(krb5_donot_replay *old, krb5_donot_replay *new1, krb5_deltat t)
82 {
83     if ((old->cusec == new1->cusec) && /* most likely to distinguish */
84         (old->ctime == new1->ctime) &&
85         (strcmp(old->client, new1->client) == 0) &&
86         (strcmp(old->server, new1->server) == 0)) { /* always true */
87         /* If both records include message hashes, compare them as well. */
88         if (old->msghash == NULL || new1->msghash == NULL ||
89             strcmp(old->msghash, new1->msghash) == 0)
90             return CMP_REPLAY;
91     }
92     return CMP_HOHUM;
93 }
94
95 static int
96 alive(krb5_timestamp mytime, krb5_donot_replay *new1, krb5_deltat t)
97 {
98     if (mytime == 0)
99         return CMP_HOHUM; /* who cares? */
100     if (ts_after(mytime, ts_incr(new1->ctime, t)))
101         return CMP_EXPIRED;
102     return CMP_HOHUM;
103 }
104
105 struct dfl_data
106 {
107     char *name;
108     krb5_deltat lifespan;
109     unsigned int hsize;
110     int numhits;
111     int nummisses;
112     struct authlist **h;
113     struct authlist *a;
114 #ifndef NOIOSTUFF
115     krb5_rc_iostuff d;
116 #endif
117     char recovering;
118 };
119
120 struct authlist
121 {
122     krb5_donot_replay rep;
123     struct authlist *na;
124     struct authlist *nh;
125 };
126
127 /* of course, list is backwards from file */
128 /* hash could be forwards since we have to search on match, but naaaah */
129
130 static int
131 rc_store(krb5_context context, krb5_rcache id, krb5_donot_replay *rep,
132          krb5_timestamp now, krb5_boolean fromfile)
133 {
134     struct dfl_data *t = (struct dfl_data *)id->data;
135     unsigned int rephash;
136     struct authlist *ta;
137
138     rephash = hash(rep, t->hsize);
139
140     for (ta = t->h[rephash]; ta; ta = ta->nh) {
141         switch(cmp(&ta->rep, rep, t->lifespan))
142         {
143         case CMP_REPLAY:
144             if (fromfile) {
145                 /*
146                  * This is an expected collision between a hash
147                  * extension record and a normal-format record.  Make
148                  * sure the message hash is included in the stored
149                  * record and carry on.
150                  */
151                 if (!ta->rep.msghash && rep->msghash) {
152                     if (!(ta->rep.msghash = strdup(rep->msghash)))
153                         return CMP_MALLOC;
154                 }
155                 return CMP_HOHUM;
156             } else
157                 return CMP_REPLAY;
158         case CMP_HOHUM:
159             if (alive(now, &ta->rep, t->lifespan) == CMP_EXPIRED)
160                 t->nummisses++;
161             else
162                 t->numhits++;
163             break;
164         default:
165             ; /* wtf? */
166         }
167     }
168
169     if (!(ta = (struct authlist *) malloc(sizeof(struct authlist))))
170         return CMP_MALLOC;
171     ta->rep = *rep;
172     ta->rep.client = ta->rep.server = ta->rep.msghash = NULL;
173     if (!(ta->rep.client = strdup(rep->client)))
174         goto error;
175     if (!(ta->rep.server = strdup(rep->server)))
176         goto error;
177     if (rep->msghash && !(ta->rep.msghash = strdup(rep->msghash)))
178         goto error;
179     ta->na = t->a; t->a = ta;
180     ta->nh = t->h[rephash]; t->h[rephash] = ta;
181     return CMP_HOHUM;
182 error:
183     if (ta->rep.client)
184         free(ta->rep.client);
185     if (ta->rep.server)
186         free(ta->rep.server);
187     if (ta->rep.msghash)
188         free(ta->rep.msghash);
189     free(ta);
190     return CMP_MALLOC;
191 }
192
193 char * KRB5_CALLCONV
194 krb5_rc_dfl_get_name(krb5_context context, krb5_rcache id)
195 {
196     return ((struct dfl_data *) (id->data))->name;
197 }
198
199 krb5_error_code KRB5_CALLCONV
200 krb5_rc_dfl_get_span(krb5_context context, krb5_rcache id,
201                      krb5_deltat *lifespan)
202 {
203     struct dfl_data *t;
204
205     k5_mutex_lock(&id->lock);
206     t = (struct dfl_data *) id->data;
207     *lifespan = t->lifespan;
208     k5_mutex_unlock(&id->lock);
209     return 0;
210 }
211
212 static krb5_error_code KRB5_CALLCONV
213 krb5_rc_dfl_init_locked(krb5_context context, krb5_rcache id, krb5_deltat lifespan)
214 {
215     struct dfl_data *t = (struct dfl_data *)id->data;
216     krb5_error_code retval;
217
218     t->lifespan = lifespan ? lifespan : context->clockskew;
219     /* default to clockskew from the context */
220 #ifndef NOIOSTUFF
221     if ((retval = krb5_rc_io_creat(context, &t->d, &t->name))) {
222         return retval;
223     }
224     if ((krb5_rc_io_write(context, &t->d,
225                           (krb5_pointer) &t->lifespan, sizeof(t->lifespan))
226          || krb5_rc_io_sync(context, &t->d))) {
227         return KRB5_RC_IO;
228     }
229 #endif
230     return 0;
231 }
232
233 krb5_error_code KRB5_CALLCONV
234 krb5_rc_dfl_init(krb5_context context, krb5_rcache id, krb5_deltat lifespan)
235 {
236     krb5_error_code retval;
237
238     k5_mutex_lock(&id->lock);
239     retval = krb5_rc_dfl_init_locked(context, id, lifespan);
240     k5_mutex_unlock(&id->lock);
241     return retval;
242 }
243
244 /* Called with the mutex already locked.  */
245 krb5_error_code
246 krb5_rc_dfl_close_no_free(krb5_context context, krb5_rcache id)
247 {
248     struct dfl_data *t = (struct dfl_data *)id->data;
249     struct authlist *q;
250
251     free(t->h);
252     if (t->name)
253         free(t->name);
254     while ((q = t->a))
255     {
256         t->a = q->na;
257         free(q->rep.client);
258         free(q->rep.server);
259         if (q->rep.msghash)
260             free(q->rep.msghash);
261         free(q);
262     }
263 #ifndef NOIOSTUFF
264     (void) krb5_rc_io_close(context, &t->d);
265 #endif
266     free(t);
267     return 0;
268 }
269
270 krb5_error_code KRB5_CALLCONV
271 krb5_rc_dfl_close(krb5_context context, krb5_rcache id)
272 {
273     k5_mutex_lock(&id->lock);
274     krb5_rc_dfl_close_no_free(context, id);
275     k5_mutex_unlock(&id->lock);
276     k5_mutex_destroy(&id->lock);
277     free(id);
278     return 0;
279 }
280
281 krb5_error_code KRB5_CALLCONV
282 krb5_rc_dfl_destroy(krb5_context context, krb5_rcache id)
283 {
284 #ifndef NOIOSTUFF
285     if (krb5_rc_io_destroy(context, &((struct dfl_data *) (id->data))->d))
286         return KRB5_RC_IO;
287 #endif
288     return krb5_rc_dfl_close(context, id);
289 }
290
291 krb5_error_code KRB5_CALLCONV
292 krb5_rc_dfl_resolve(krb5_context context, krb5_rcache id, char *name)
293 {
294     struct dfl_data *t = 0;
295     krb5_error_code retval;
296
297     /* allocate id? no */
298     if (!(t = (struct dfl_data *) calloc(1, sizeof(struct dfl_data))))
299         return KRB5_RC_MALLOC;
300     id->data = (krb5_pointer) t;
301     if (name) {
302         t->name = strdup(name);
303         if (!t->name) {
304             retval = KRB5_RC_MALLOC;
305             goto cleanup;
306         }
307     } else
308         t->name = 0;
309     t->numhits = t->nummisses = 0;
310     t->hsize = HASHSIZE; /* no need to store---it's memory-only */
311     t->h = (struct authlist **) malloc(t->hsize*sizeof(struct authlist *));
312     if (!t->h) {
313         retval = KRB5_RC_MALLOC;
314         goto cleanup;
315     }
316     memset(t->h, 0, t->hsize*sizeof(struct authlist *));
317     t->a = (struct authlist *) 0;
318 #ifndef NOIOSTUFF
319     t->d.fd = -1;
320 #endif
321     t->recovering = 0;
322     return 0;
323
324 cleanup:
325     if (t) {
326         if (t->name)
327             free(t->name);
328         if (t->h)
329             free(t->h);
330         free(t);
331     }
332     return retval;
333 }
334
335 void
336 krb5_rc_free_entry(krb5_context context, krb5_donot_replay **rep)
337 {
338     krb5_donot_replay *rp = *rep;
339
340     *rep = NULL;
341     if (rp)
342     {
343         if (rp->client)
344             free(rp->client);
345         if (rp->server)
346             free(rp->server);
347         if (rp->msghash)
348             free(rp->msghash);
349         rp->client = NULL;
350         rp->server = NULL;
351         rp->msghash = NULL;
352         free(rp);
353     }
354 }
355
356 /*
357  * Parse a string in the format <len>:<data>, with the length
358  * represented in ASCII decimal.  On parse failure, return 0 but set
359  * *result to NULL.
360  */
361 static krb5_error_code
362 parse_counted_string(char **strptr, char **result)
363 {
364     char *str = *strptr, *end;
365     unsigned long len;
366
367     *result = NULL;
368
369     /* Parse the length, expecting a ':' afterwards. */
370     errno = 0;
371     len = strtoul(str, &end, 10);
372     if (errno != 0 || *end != ':' || len > strlen(end + 1))
373         return 0;
374
375     /* Allocate space for *result and copy the data. */
376     *result = malloc(len + 1);
377     if (!*result)
378         return KRB5_RC_MALLOC;
379     memcpy(*result, end + 1, len);
380     (*result)[len] = '\0';
381     *strptr = end + 1 + len;
382     return 0;
383 }
384
385 /*
386  * Hash extension records have the format:
387  *  client = <empty string>
388  *  server = SHA256:<msghash> <clientlen>:<client> <serverlen>:<server>
389  * Spaces in the client and server string are represented with
390  * with backslashes.  Client and server lengths are represented in
391  * ASCII decimal (which is different from the 32-bit binary we use
392  * elsewhere in the replay cache).
393  *
394  * On parse failure, we leave the record unmodified.
395  */
396 static krb5_error_code
397 check_hash_extension(krb5_donot_replay *rep)
398 {
399     char *msghash = NULL, *client = NULL, *server = NULL, *str, *end;
400     krb5_error_code retval = 0;
401
402     /* Check if this appears to match the hash extension format. */
403     if (*rep->client)
404         return 0;
405     if (strncmp(rep->server, "SHA256:", 7) != 0)
406         return 0;
407
408     /* Parse out the message hash. */
409     str = rep->server + 7;
410     end = strchr(str, ' ');
411     if (!end)
412         return 0;
413     msghash = k5memdup0(str, end - str, &retval);
414     if (!msghash)
415         return KRB5_RC_MALLOC;
416     str = end + 1;
417
418     /* Parse out the client and server. */
419     retval = parse_counted_string(&str, &client);
420     if (retval != 0 || client == NULL)
421         goto error;
422     if (*str != ' ')
423         goto error;
424     str++;
425     retval = parse_counted_string(&str, &server);
426     if (retval != 0 || server == NULL)
427         goto error;
428     if (*str)
429         goto error;
430
431     free(rep->client);
432     free(rep->server);
433     rep->client = client;
434     rep->server = server;
435     rep->msghash = msghash;
436     return 0;
437
438 error:
439     if (msghash)
440         free(msghash);
441     if (client)
442         free(client);
443     if (server)
444         free(server);
445     return retval;
446 }
447
448 static krb5_error_code
449 krb5_rc_io_fetch(krb5_context context, struct dfl_data *t,
450                  krb5_donot_replay *rep, int maxlen)
451 {
452     int len2;
453     unsigned int len;
454     krb5_error_code retval;
455
456     rep->client = rep->server = rep->msghash = NULL;
457
458     retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &len2,
459                              sizeof(len2));
460     if (retval)
461         return retval;
462
463     if ((len2 <= 0) || (len2 >= maxlen))
464         return KRB5_RC_IO_EOF;
465
466     len = len2;
467     rep->client = malloc (len);
468     if (!rep->client)
469         return KRB5_RC_MALLOC;
470
471     retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) rep->client, len);
472     if (retval)
473         goto errout;
474
475     retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &len2,
476                              sizeof(len2));
477     if (retval)
478         goto errout;
479
480     if ((len2 <= 0) || (len2 >= maxlen)) {
481         retval = KRB5_RC_IO_EOF;
482         goto errout;
483     }
484     len = len2;
485
486     rep->server = malloc (len);
487     if (!rep->server) {
488         retval = KRB5_RC_MALLOC;
489         goto errout;
490     }
491
492     retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) rep->server, len);
493     if (retval)
494         goto errout;
495
496     retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &rep->cusec,
497                              sizeof(rep->cusec));
498     if (retval)
499         goto errout;
500
501     retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &rep->ctime,
502                              sizeof(rep->ctime));
503     if (retval)
504         goto errout;
505
506     retval = check_hash_extension(rep);
507     if (retval)
508         goto errout;
509
510     return 0;
511
512 errout:
513     if (rep->client)
514         free(rep->client);
515     if (rep->server)
516         free(rep->server);
517     if (rep->msghash)
518         free(rep->msghash);
519     rep->client = rep->server = rep->msghash = NULL;
520     return retval;
521 }
522
523
524 static krb5_error_code
525 krb5_rc_dfl_expunge_locked(krb5_context context, krb5_rcache id);
526
527 static krb5_error_code
528 krb5_rc_dfl_recover_locked(krb5_context context, krb5_rcache id)
529 {
530 #ifdef NOIOSTUFF
531     return KRB5_RC_NOIO;
532 #else
533
534     struct dfl_data *t = (struct dfl_data *)id->data;
535     krb5_donot_replay *rep = 0;
536     krb5_error_code retval;
537     long max_size;
538     int expired_entries = 0;
539     krb5_timestamp now;
540
541     if ((retval = krb5_rc_io_open(context, &t->d, t->name))) {
542         return retval;
543     }
544
545     t->recovering = 1;
546
547     max_size = krb5_rc_io_size(context, &t->d);
548
549     rep = NULL;
550     if (krb5_rc_io_read(context, &t->d, (krb5_pointer) &t->lifespan,
551                         sizeof(t->lifespan))) {
552         retval = KRB5_RC_IO;
553         goto io_fail;
554     }
555
556     if (!(rep = (krb5_donot_replay *) malloc(sizeof(krb5_donot_replay)))) {
557         retval = KRB5_RC_MALLOC;
558         goto io_fail;
559     }
560     rep->client = rep->server = rep->msghash = NULL;
561
562     if (krb5_timeofday(context, &now))
563         now = 0;
564
565     /* now read in each auth_replay and insert into table */
566     for (;;) {
567         if (krb5_rc_io_mark(context, &t->d)) {
568             retval = KRB5_RC_IO;
569             goto io_fail;
570         }
571
572         retval = krb5_rc_io_fetch(context, t, rep, (int) max_size);
573
574         if (retval == KRB5_RC_IO_EOF)
575             break;
576         else if (retval != 0)
577             goto io_fail;
578
579         if (alive(now, rep, t->lifespan) != CMP_EXPIRED) {
580             if (rc_store(context, id, rep, now, TRUE) == CMP_MALLOC) {
581                 retval = KRB5_RC_MALLOC; goto io_fail;
582             }
583         } else {
584             expired_entries++;
585         }
586
587         /*
588          *  free fields allocated by rc_io_fetch
589          */
590         free(rep->server);
591         free(rep->client);
592         if (rep->msghash)
593             free(rep->msghash);
594         rep->client = rep->server = rep->msghash = NULL;
595     }
596     retval = 0;
597     krb5_rc_io_unmark(context, &t->d);
598     /*
599      *  An automatic expunge here could remove the need for
600      *  mark/unmark but that would be inefficient.
601      */
602 io_fail:
603     krb5_rc_free_entry(context, &rep);
604     if (retval)
605         krb5_rc_io_close(context, &t->d);
606     else if (expired_entries > EXCESSREPS)
607         retval = krb5_rc_dfl_expunge_locked(context, id);
608     t->recovering = 0;
609     return retval;
610
611 #endif
612 }
613
614 krb5_error_code KRB5_CALLCONV
615 krb5_rc_dfl_recover(krb5_context context, krb5_rcache id)
616 {
617     krb5_error_code ret;
618
619     k5_mutex_lock(&id->lock);
620     ret = krb5_rc_dfl_recover_locked(context, id);
621     k5_mutex_unlock(&id->lock);
622     return ret;
623 }
624
625 krb5_error_code KRB5_CALLCONV
626 krb5_rc_dfl_recover_or_init(krb5_context context, krb5_rcache id,
627                             krb5_deltat lifespan)
628 {
629     krb5_error_code retval;
630
631     k5_mutex_lock(&id->lock);
632     retval = krb5_rc_dfl_recover_locked(context, id);
633     if (retval)
634         retval = krb5_rc_dfl_init_locked(context, id, lifespan);
635     k5_mutex_unlock(&id->lock);
636     return retval;
637 }
638
639 static krb5_error_code
640 krb5_rc_io_store(krb5_context context, struct dfl_data *t,
641                  krb5_donot_replay *rep)
642 {
643     size_t clientlen, serverlen;
644     unsigned int len;
645     krb5_error_code ret;
646     struct k5buf buf, extbuf;
647     char *extstr;
648
649     clientlen = strlen(rep->client);
650     serverlen = strlen(rep->server);
651
652     if (rep->msghash) {
653         /*
654          * Write a hash extension record, to be followed by a record
655          * in regular format (without the message hash) for the
656          * benefit of old implementations.
657          */
658
659         /* Format the extension value so we know its length. */
660         k5_buf_init_dynamic(&extbuf);
661         k5_buf_add_fmt(&extbuf, "SHA256:%s %lu:%s %lu:%s", rep->msghash,
662                        (unsigned long)clientlen, rep->client,
663                        (unsigned long)serverlen, rep->server);
664         if (k5_buf_status(&extbuf) != 0)
665             return KRB5_RC_MALLOC;
666         extstr = extbuf.data;
667
668         /*
669          * Put the extension value into the server field of a
670          * regular-format record, with an empty client field.
671          */
672         k5_buf_init_dynamic(&buf);
673         len = 1;
674         k5_buf_add_len(&buf, (char *)&len, sizeof(len));
675         k5_buf_add_len(&buf, "", 1);
676         len = strlen(extstr) + 1;
677         k5_buf_add_len(&buf, (char *)&len, sizeof(len));
678         k5_buf_add_len(&buf, extstr, len);
679         k5_buf_add_len(&buf, (char *)&rep->cusec, sizeof(rep->cusec));
680         k5_buf_add_len(&buf, (char *)&rep->ctime, sizeof(rep->ctime));
681         free(extstr);
682     } else  /* No extension record needed. */
683         k5_buf_init_dynamic(&buf);
684
685     len = clientlen + 1;
686     k5_buf_add_len(&buf, (char *)&len, sizeof(len));
687     k5_buf_add_len(&buf, rep->client, len);
688     len = serverlen + 1;
689     k5_buf_add_len(&buf, (char *)&len, sizeof(len));
690     k5_buf_add_len(&buf, rep->server, len);
691     k5_buf_add_len(&buf, (char *)&rep->cusec, sizeof(rep->cusec));
692     k5_buf_add_len(&buf, (char *)&rep->ctime, sizeof(rep->ctime));
693
694     if (k5_buf_status(&buf) != 0)
695         return KRB5_RC_MALLOC;
696
697     ret = krb5_rc_io_write(context, &t->d, buf.data, buf.len);
698     k5_buf_free(&buf);
699     return ret;
700 }
701
702 static krb5_error_code krb5_rc_dfl_expunge_locked(krb5_context, krb5_rcache);
703
704 krb5_error_code KRB5_CALLCONV
705 krb5_rc_dfl_store(krb5_context context, krb5_rcache id, krb5_donot_replay *rep)
706 {
707     krb5_error_code ret;
708     struct dfl_data *t;
709     krb5_timestamp now;
710
711     ret = krb5_timeofday(context, &now);
712     if (ret)
713         return ret;
714
715     k5_mutex_lock(&id->lock);
716
717     switch(rc_store(context, id, rep, now, FALSE)) {
718     case CMP_MALLOC:
719         k5_mutex_unlock(&id->lock);
720         return KRB5_RC_MALLOC;
721     case CMP_REPLAY:
722         k5_mutex_unlock(&id->lock);
723         return KRB5KRB_AP_ERR_REPEAT;
724     case 0: break;
725     default: /* wtf? */ ;
726     }
727     t = (struct dfl_data *)id->data;
728 #ifndef NOIOSTUFF
729     ret = krb5_rc_io_store(context, t, rep);
730     if (ret) {
731         k5_mutex_unlock(&id->lock);
732         return ret;
733     }
734 #endif
735     /* Shall we automatically expunge? */
736     if (t->nummisses > t->numhits + EXCESSREPS)
737     {
738         ret = krb5_rc_dfl_expunge_locked(context, id);
739         k5_mutex_unlock(&id->lock);
740         return ret;
741     }
742 #ifndef NOIOSTUFF
743     else
744     {
745         if (krb5_rc_io_sync(context, &t->d)) {
746             k5_mutex_unlock(&id->lock);
747             return KRB5_RC_IO;
748         }
749     }
750 #endif
751     k5_mutex_unlock(&id->lock);
752     return 0;
753 }
754
755 static krb5_error_code
756 krb5_rc_dfl_expunge_locked(krb5_context context, krb5_rcache id)
757 {
758     struct dfl_data *t = (struct dfl_data *)id->data;
759 #ifdef NOIOSTUFF
760     unsigned int i;
761     struct authlist **q;
762     struct authlist **qt;
763     struct authlist *r;
764     struct authlist *rt;
765     krb5_timestamp now;
766
767     if (krb5_timestamp(context, &now))
768         now = 0;
769
770     for (q = &t->a; *q; q = qt) {
771         qt = &(*q)->na;
772         if (alive(now, &(*q)->rep, t->lifespan) == CMP_EXPIRED) {
773             free((*q)->rep.client);
774             free((*q)->rep.server);
775             if ((*q)->rep.msghash)
776                 free((*q)->rep.msghash);
777             free(*q);
778             *q = *qt; /* why doesn't this feel right? */
779         }
780     }
781     for (i = 0; i < t->hsize; i++)
782         t->h[i] = (struct authlist *) 0;
783     for (r = t->a; r; r = r->na) {
784         i = hash(&r->rep, t->hsize);
785         rt = t->h[i];
786         t->h[i] = r;
787         r->nh = rt;
788     }
789     return 0;
790 #else
791     struct authlist *q;
792     char *name;
793     krb5_error_code retval = 0;
794     krb5_rcache tmp;
795     krb5_deltat lifespan = t->lifespan;  /* save original lifespan */
796
797     if (! t->recovering) {
798         name = t->name;
799         t->name = 0;            /* Clear name so it isn't freed */
800         (void) krb5_rc_dfl_close_no_free(context, id);
801         retval = krb5_rc_dfl_resolve(context, id, name);
802         free(name);
803         if (retval)
804             return retval;
805         retval = krb5_rc_dfl_recover_locked(context, id);
806         if (retval)
807             return retval;
808         t = (struct dfl_data *)id->data; /* point to recovered cache */
809     }
810
811     retval = krb5_rc_resolve_type(context, &tmp, "dfl");
812     if (retval)
813         return retval;
814     retval = krb5_rc_resolve(context, tmp, 0);
815     if (retval)
816         goto cleanup;
817     retval = krb5_rc_initialize(context, tmp, lifespan);
818     if (retval)
819         goto cleanup;
820     for (q = t->a; q; q = q->na) {
821         if (krb5_rc_io_store(context, (struct dfl_data *)tmp->data, &q->rep)) {
822             retval = KRB5_RC_IO;
823             goto cleanup;
824         }
825     }
826     /* NOTE: We set retval in case we have an error */
827     retval = KRB5_RC_IO;
828     if (krb5_rc_io_sync(context, &((struct dfl_data *)tmp->data)->d))
829         goto cleanup;
830     if (krb5_rc_io_sync(context, &t->d))
831         goto cleanup;
832     if (krb5_rc_io_move(context, &t->d, &((struct dfl_data *)tmp->data)->d))
833         goto cleanup;
834     retval = 0;
835 cleanup:
836     (void) krb5_rc_dfl_close(context, tmp);
837     return retval;
838 #endif
839 }
840
841 krb5_error_code KRB5_CALLCONV
842 krb5_rc_dfl_expunge(krb5_context context, krb5_rcache id)
843 {
844     krb5_error_code ret;
845
846     k5_mutex_lock(&id->lock);
847     ret = krb5_rc_dfl_expunge_locked(context, id);
848     k5_mutex_unlock(&id->lock);
849     return ret;
850 }