aecbb37e93799d1422332bc776c87eb126956f22
[krb5.git] / src / lib / krb5 / rcache / rc_dfl.c
1 /*
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 /*
11  * An implementation for the default replay cache type.
12  */
13 #define FREE(x) ((void) free((char *) (x)))
14 #include "rc_base.h"
15 #include "rc_dfl.h"
16 #include "rc_io.h"
17 #include <k5-int.h>
18
19 /*
20 If NOIOSTUFF is defined at compile time, dfl rcaches will be per-process.
21 */
22
23 /*
24 Local stuff:
25
26 static int hash(krb5_donot_replay *rep,int hsize)
27   returns hash value of *rep, between 0 and hsize - 1
28 HASHSIZE
29   size of hash table (constant), can be preset
30 static int cmp(krb5_donot_replay *old,krb5_donot_replay *new,krb5_deltat t)
31   compare old and new; return CMP_REPLAY or CMP_HOHUM
32 static int alive (krb5_context, krb5_donot_replay *new,krb5_deltat t)
33   see if new is still alive; return CMP_EXPIRED or CMP_HOHUM
34 CMP_MALLOC, CMP_EXPIRED, CMP_REPLAY, CMP_HOHUM
35   return codes from cmp(), alive(), and store()
36 struct dfl_data
37   data stored in this cache type, namely "dfl"
38 struct authlist
39   multilinked list of reps
40 static int store(context, krb5_rcache id,krb5_donot_replay *rep)
41   store rep in cache id; return CMP_REPLAY if replay, else CMP_MALLOC/CMP_HOHUM
42
43 */
44
45 #ifndef HASHSIZE
46 #define HASHSIZE 997 /* a convenient prime */
47 #endif
48
49 #ifndef EXCESSREPS
50 #define EXCESSREPS 30
51 #endif
52 /* The rcache will be automatically expunged when the number of expired
53 krb5_donot_replays encountered incidentally in searching exceeds the number
54 of live krb5_donot_replays by EXCESSREPS. With the defaults here, a typical
55 cache might build up some 10K of expired krb5_donot_replays before an automatic
56 expunge, with the waste basically independent of the number of stores per
57 minute. */
58
59 static int hash(rep, hsize)
60 krb5_donot_replay *rep;
61 int hsize;
62 {
63  return (int) ((((rep->cusec + rep->ctime + *rep->server + *rep->client)
64          % hsize) + hsize) % hsize);
65  /* We take this opportunity to once again complain about C's idiotic %. */
66 }
67
68 #define CMP_MALLOC -3
69 #define CMP_EXPIRED -2
70 #define CMP_REPLAY -1
71 #define CMP_HOHUM 0
72
73 /*ARGSUSED*/
74 static int cmp(old, new, t)
75 krb5_donot_replay *old;
76 krb5_donot_replay *new;
77 krb5_deltat t;
78 {
79  if ((old->cusec == new->cusec) && /* most likely to distinguish */
80      (old->ctime == new->ctime) &&
81      (strcmp(old->client,new->client) == 0) &&
82      (strcmp(old->server,new->server) == 0)) /* always true */
83    return CMP_REPLAY;
84  return CMP_HOHUM;
85 }
86
87 static int alive(context, new, t)
88     krb5_context context;
89     krb5_donot_replay *new;
90     krb5_deltat t;
91 {
92  krb5_int32 time;
93
94  if (krb5_timeofday(context, &time))
95    return CMP_HOHUM; /* who cares? */
96  if (new->ctime + t < time) /* I hope we don't have to worry about overflow */
97    return CMP_EXPIRED;
98  return CMP_HOHUM;
99 }
100
101 struct dfl_data
102  {
103   char *name;
104   krb5_deltat lifespan;
105   int hsize;
106   int numhits;
107   int nummisses;
108   struct authlist **h;
109   struct authlist *a;
110 #ifndef NOIOSTUFF
111   krb5_rc_iostuff d;
112 #endif
113  }
114 ;
115
116 struct authlist
117  {
118   krb5_donot_replay rep;
119   struct authlist *na;
120   struct authlist *nh;
121  }
122 ;
123
124 /* of course, list is backwards from file */
125 /* hash could be forwards since we have to search on match, but naaaah */
126
127 static int store(context, id, rep)
128     krb5_context context;
129     krb5_rcache id;
130     krb5_donot_replay *rep;
131 {
132  struct dfl_data *t = (struct dfl_data *)id->data;
133  int rephash;
134  struct authlist *ta;
135
136  rephash = hash(rep,t->hsize);
137
138  for (ta = t->h[rephash];ta;ta = ta->nh)
139    switch(cmp(&ta->rep,rep,t->lifespan))
140     {
141      case CMP_REPLAY: return CMP_REPLAY;
142      case CMP_HOHUM: if (alive(context, &ta->rep,t->lifespan) == CMP_EXPIRED)
143                        t->nummisses++;
144                      else
145                        t->numhits++;
146                      break;
147      default: ; /* wtf? */
148     }
149
150  if (!(ta = (struct authlist *) malloc(sizeof(struct authlist))))
151    return CMP_MALLOC;
152  ta->na = t->a; t->a = ta;
153  ta->nh = t->h[rephash]; t->h[rephash] = ta;
154  ta->rep = *rep;
155  if (!(ta->rep.client = strdup(rep->client))) {
156      FREE(ta);
157      return CMP_MALLOC;
158  }
159  if (!(ta->rep.server = strdup(rep->server))) {
160      FREE(ta->rep.client);
161      FREE(ta);
162      return CMP_MALLOC;
163  }
164
165  return CMP_HOHUM;
166 }
167
168 char * INTERFACE krb5_rc_dfl_get_name(context, id)
169     krb5_context context;
170     krb5_rcache id;
171 {
172  return ((struct dfl_data *) (id->data))->name;
173 }
174
175 krb5_error_code INTERFACE krb5_rc_dfl_get_span(context, id, lifespan)
176     krb5_context context;
177     krb5_rcache id;
178     krb5_deltat *lifespan;
179 {
180  *lifespan = ((struct dfl_data *) (id->data))->lifespan;
181  return 0;
182 }
183
184 krb5_error_code INTERFACE krb5_rc_dfl_init(context, id, lifespan)
185     krb5_context context;
186 krb5_rcache id;
187 krb5_deltat lifespan;
188 {
189     struct dfl_data *t = (struct dfl_data *)id->data;
190     krb5_error_code retval;
191
192     t->lifespan = lifespan;
193 #ifndef NOIOSTUFF
194     if (retval = krb5_rc_io_creat(context, &t->d,&t->name))
195         return retval;
196     if (krb5_rc_io_write(context, &t->d,(krb5_pointer) &t->lifespan,sizeof(t->lifespan))
197         || krb5_rc_io_sync(context, &t->d))
198         return KRB5_RC_IO;
199 #endif
200     return 0;
201 }
202
203 krb5_error_code INTERFACE krb5_rc_dfl_close_no_free(context, id)
204     krb5_context context;
205     krb5_rcache id;
206 {
207  struct dfl_data *t = (struct dfl_data *)id->data;
208  struct authlist *q;
209
210  FREE(t->h);
211  if (t->name)
212      FREE(t->name);
213  while (q = t->a)
214   {
215    t->a = q->na;
216    FREE(q->rep.client);
217    FREE(q->rep.server);
218    FREE(q);
219   }
220 #ifndef NOIOSTUFF
221  if (t->d.fd >= 0)
222     (void) krb5_rc_io_close(context, &t->d);
223 #endif
224  FREE(t);
225  return 0;
226 }
227
228 krb5_error_code INTERFACE krb5_rc_dfl_close(context, id)
229     krb5_context context;
230     krb5_rcache id;
231 {
232     krb5_rc_dfl_close_no_free(context, id);
233     free(id);
234     return 0;
235 }
236
237 krb5_error_code INTERFACE krb5_rc_dfl_destroy(context, id)
238     krb5_context context;
239 krb5_rcache id;
240 {
241 #ifndef NOIOSTUFF
242  if (krb5_rc_io_destroy(context, &((struct dfl_data *) (id->data))->d))
243    return KRB5_RC_IO;
244 #endif
245  return krb5_rc_dfl_close(context, id);
246 }
247
248 krb5_error_code INTERFACE krb5_rc_dfl_resolve(context, id, name)
249     krb5_context context;
250     krb5_rcache id;
251     char *name;
252 {
253     struct dfl_data *t = 0;
254     krb5_error_code retval;
255
256     /* allocate id? no */
257     if (!(t = (struct dfl_data *) malloc(sizeof(struct dfl_data))))
258         return KRB5_RC_MALLOC;
259     id->data = (krb5_pointer) t;
260     memset(t, 0, sizeof(struct dfl_data));
261     if (name) {
262         t->name = malloc(strlen(name)+1);
263         if (!t->name) {
264             retval = KRB5_RC_MALLOC;
265             goto cleanup;
266         }
267         strcpy(t->name, name);
268     } else
269         t->name = 0;
270     t->numhits = t->nummisses = 0;
271     t->hsize = HASHSIZE; /* no need to store---it's memory-only */
272     t->h = (struct authlist **) malloc(t->hsize*sizeof(struct authlist *));
273     if (!t->h) {
274         retval = KRB5_RC_MALLOC;
275         goto cleanup;
276     }
277     memset(t->h, 0, t->hsize*sizeof(struct authlist *));
278     t->a = (struct authlist *) 0;
279 #ifndef NOIOSTUFF
280     t->d.fd = -1;
281 #endif
282     return 0;
283     
284 cleanup:
285     if (t) {
286         if (t->name)
287             krb5_xfree(t->name);
288         if (t->h)
289             krb5_xfree(t->h);
290         krb5_xfree(t);
291     }
292     return retval;
293 }
294
295 void INTERFACE krb5_rc_free_entry (context, rep)
296     krb5_context context;
297     krb5_donot_replay **rep;
298 {
299     krb5_donot_replay *rp = *rep;
300     
301     *rep = NULL;
302     if (rp) 
303     {
304         if (rp->client)
305             free(rp->client);
306
307         if (rp->server)
308             free(rp->server);
309         rp->client = NULL;
310         rp->server = NULL;
311         free(rp);
312     }
313 }
314
315 static krb5_error_code krb5_rc_io_fetch(context, t, rep, maxlen) 
316     krb5_context context;
317     struct dfl_data *t;
318     krb5_donot_replay *rep;
319     int maxlen;
320 {
321     int len;
322     krb5_error_code retval;
323
324     rep->client = rep->server = 0;
325     
326     retval = krb5_rc_io_read (context, &t->d, (krb5_pointer) &len, sizeof(len));
327     if (retval) 
328         return retval;
329     
330     if ((len <= 0) || (len >= maxlen))
331         return KRB5_RC_IO_EOF;
332
333     rep->client = malloc (len);
334     if (!rep->client)
335         return KRB5_RC_MALLOC;
336     
337     retval = krb5_rc_io_read (context, &t->d, (krb5_pointer) rep->client, len);
338     if (retval)
339         goto errout;
340     
341     retval = krb5_rc_io_read (context, &t->d, (krb5_pointer) &len, sizeof(len));
342     if (retval)
343         goto errout;
344     
345     if ((len <= 0) || (len >= maxlen)) {
346         retval = KRB5_RC_IO_EOF;
347         goto errout;
348     }
349
350     rep->server = malloc (len);
351     if (!rep->server) {
352         retval = KRB5_RC_MALLOC;
353         goto errout;
354     }
355     
356     retval = krb5_rc_io_read (context, &t->d, (krb5_pointer) rep->server, len);
357     if (retval)
358         goto errout;
359     
360     retval = krb5_rc_io_read (context, &t->d, (krb5_pointer) &rep->cusec, sizeof(rep->cusec));
361     if (retval)
362         goto errout;
363     
364     retval = krb5_rc_io_read (context, &t->d, (krb5_pointer) &rep->ctime, sizeof(rep->ctime));
365     if (retval)
366         goto errout;
367
368     return 0;
369     
370 errout:
371     if (rep->client)
372         krb5_xfree(rep->client);
373     if (rep->server)
374         krb5_xfree(rep->server);
375     return retval;
376 }
377     
378
379
380 krb5_error_code INTERFACE krb5_rc_dfl_recover(context, id)
381     krb5_context context;
382 krb5_rcache id;
383 {
384 #ifdef NOIOSTUFF
385     return KRB5_RC_NOIO;
386 #else
387
388     struct dfl_data *t = (struct dfl_data *)id->data;
389     krb5_donot_replay *rep;
390     krb5_error_code retval;
391     long max_size;
392
393     if (retval = krb5_rc_io_open(context, &t->d,t->name))
394         return retval;
395  
396     max_size = krb5_rc_io_size(context, &t->d);
397  
398     rep = NULL;
399     if (krb5_rc_io_read(context, &t->d,(krb5_pointer) &t->lifespan,sizeof(t->lifespan))) {
400         retval = KRB5_RC_IO;
401         goto io_fail;
402     }
403
404     /* now read in each auth_replay and insert into table */
405     for (;;) {
406         rep = NULL;
407         if (krb5_rc_io_mark(context, &t->d)) {
408             retval = KRB5_RC_IO;
409             goto io_fail;
410         }
411         
412         if (!(rep = (krb5_donot_replay *) malloc(sizeof(krb5_donot_replay)))) {
413             retval = KRB5_RC_MALLOC;
414             goto io_fail;
415         }
416         rep->client = NULL;
417         rep->server = NULL;
418         
419         retval = krb5_rc_io_fetch (context, t, rep, (int) max_size);
420
421         if (retval == KRB5_RC_IO_EOF)
422             break;
423         else if (retval != 0)
424             goto io_fail;
425
426         
427         if (alive(context, rep,t->lifespan) == CMP_EXPIRED) {
428             krb5_rc_free_entry(context, &rep);
429             continue;
430         }
431
432         if (store(context, id,rep) == CMP_MALLOC) {/* can't be a replay */
433             retval = KRB5_RC_MALLOC; goto io_fail;
434         } 
435         /*
436          *  store() copies the server & client fields to make sure
437          *  they don't get stomped on by other callers, so we need to
438          *  free them
439          */
440         FREE(rep->server);
441         FREE(rep->client);
442         rep = NULL;
443     }
444     retval = 0;
445     krb5_rc_io_unmark(context, &t->d);
446     /*
447      *  An automatic expunge here could remove the need for
448      *  mark/unmark but that would be inefficient.
449      */
450 io_fail:
451     krb5_rc_free_entry(context, &rep);
452     if (retval)
453         krb5_rc_io_close(context, &t->d);
454     return retval;
455     
456 #endif
457 }
458
459 static krb5_error_code
460 krb5_rc_io_store (context, t, rep)
461     krb5_context context;
462     struct dfl_data *t;
463     krb5_donot_replay *rep;
464 {
465     int clientlen, serverlen, len;
466     char *buf, *ptr;
467     unsigned long ret;
468
469     clientlen = strlen (rep->client) + 1;
470     serverlen = strlen (rep->server) + 1;
471     len = sizeof(clientlen) + clientlen + sizeof(serverlen) + serverlen +
472         sizeof(rep->cusec) + sizeof(rep->ctime);
473     buf = malloc (len);
474     if (buf == 0)
475         return KRB5_RC_MALLOC;
476     ptr = buf;
477     memcpy(ptr, &clientlen, sizeof(clientlen)); ptr += sizeof(clientlen);
478     memcpy(ptr, rep->client, clientlen); ptr += clientlen;
479     memcpy(ptr, &serverlen, sizeof(serverlen)); ptr += sizeof(serverlen);
480     memcpy(ptr, rep->server, serverlen); ptr += serverlen;
481     memcpy(ptr, &rep->cusec, sizeof(rep->cusec)); ptr += sizeof(rep->cusec);
482     memcpy(ptr, &rep->ctime, sizeof(rep->ctime)); ptr += sizeof(rep->ctime);
483
484     ret = krb5_rc_io_write(context, &t->d, buf, len);
485     free(buf);
486     return ret;
487 }
488
489 krb5_error_code INTERFACE krb5_rc_dfl_store(context, id, rep)
490     krb5_context context;
491 krb5_rcache id;
492 krb5_donot_replay *rep;
493 {
494     unsigned long ret;
495     struct dfl_data *t = (struct dfl_data *)id->data;
496
497     switch(store(context, id,rep)) {
498     case CMP_MALLOC:
499         return KRB5_RC_MALLOC; 
500     case CMP_REPLAY:
501         return KRB5KRB_AP_ERR_REPEAT; 
502     case 0: break;
503     default: /* wtf? */ ;
504     }
505 #ifndef NOIOSTUFF
506     ret = krb5_rc_io_store (context, t, rep);
507     if (ret)
508         return ret;
509 #endif
510  /* Shall we automatically expunge? */
511  if (t->nummisses > t->numhits + EXCESSREPS)
512     {
513    return krb5_rc_dfl_expunge(context, id);
514     }
515 #ifndef NOIOSTUFF
516     else
517     {
518         if (krb5_rc_io_sync(context, &t->d))
519             return KRB5_RC_IO;
520     }
521 #endif
522  return 0;
523 }
524
525 krb5_error_code INTERFACE krb5_rc_dfl_expunge(context, id)
526     krb5_context context;
527 krb5_rcache id;
528 {
529     struct dfl_data *t = (struct dfl_data *)id->data;
530 #ifdef NOIOSTUFF
531     int i;
532     struct authlist **q;
533     struct authlist **qt;
534     struct authlist *r;
535     struct authlist *rt;
536
537     for (q = &t->a;*q;q = qt) {
538         qt = &(*q)->na;
539         if (alive(context, &(*q)->rep,t->lifespan) == CMP_EXPIRED) {
540             FREE((*q)->rep.client);
541             FREE((*q)->rep.server);
542             FREE(*q);
543             *q = *qt; /* why doesn't this feel right? */
544         }
545     }
546     for (i = 0;i < t->hsize;i++)
547         t->h[i] = (struct authlist *) 0;
548     for (r = t->a;r;r = r->na) {
549         i = hash(&r->rep,t->hsize);
550         rt = t->h[i];
551         t->h[i] = r;
552         r->nh = rt;
553     }
554   
555 #else
556     struct authlist *q;
557     char *name;
558     krb5_error_code retval;
559     krb5_rcache tmp;
560     krb5_deltat lifespan = t->lifespan;  /* save original lifespan */
561
562     name = t->name;
563     t->name = 0;                /* Clear name so it isn't freed */
564     (void) krb5_rc_dfl_close_no_free(context, id);
565     retval = krb5_rc_dfl_resolve(context, id, name);
566     free(name);
567     if (retval)
568         return retval;
569     retval = krb5_rc_dfl_recover(context, id);
570     if (retval)
571         return retval;
572     t = (struct dfl_data *)id->data; /* point to recovered cache */
573     tmp = (krb5_rcache) malloc(sizeof(*tmp));
574     if (!tmp)
575         return ENOMEM;
576     retval = krb5_rc_resolve_type(context, &tmp, "dfl");
577     if (retval)
578         return retval;
579     retval = krb5_rc_resolve(context, tmp, 0);
580     if (retval)
581         return retval;
582     retval = krb5_rc_initialize(context, tmp, lifespan);
583     if (retval)
584         return retval;
585     for (q = t->a;q;q = q->na) {
586         if (krb5_rc_io_store (context, (struct dfl_data *)tmp->data, &q->rep))
587             return KRB5_RC_IO;
588     }
589     if (krb5_rc_io_sync(context, &t->d))
590         return KRB5_RC_IO;
591     if (krb5_rc_io_move(context, &t->d, &((struct dfl_data *)tmp->data)->d))
592         return KRB5_RC_IO;
593      (void) krb5_rc_dfl_close(context, tmp);
594 #endif
595     return 0;
596 }