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