1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/rcache/rc_dfl.c */
4 * This file of the Kerberos V5 software is derived from public-domain code
5 * contributed by Daniel J. Bernstein, <brnstnd@acf10.nyu.edu>.
10 * An implementation for the default replay cache type.
19 * If NOIOSTUFF is defined at compile time, dfl rcaches will be per-process.
25 static int hash(krb5_donot_replay *rep, int hsize)
26 returns hash value of *rep, between 0 and hsize - 1
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()
36 data stored in this cache type, namely "dfl"
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
45 #define HASHSIZE 997 /* a convenient prime */
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.
60 * The rcache will also automatically be expunged when it encounters
61 * more than EXCESSREPS expired entries when recovering a cache in
66 hash(krb5_donot_replay *rep, unsigned int hsize)
68 unsigned int h = rep->cusec + rep->ctime;
75 #define CMP_EXPIRED -2
81 cmp(krb5_donot_replay *old, krb5_donot_replay *new1, krb5_deltat t)
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)
96 alive(krb5_int32 mytime, krb5_donot_replay *new1, krb5_deltat t)
99 return CMP_HOHUM; /* who cares? */
100 /* I hope we don't have to worry about overflow */
101 if (new1->ctime + t < mytime)
109 krb5_deltat lifespan;
123 krb5_donot_replay rep;
128 /* of course, list is backwards from file */
129 /* hash could be forwards since we have to search on match, but naaaah */
132 rc_store(krb5_context context, krb5_rcache id, krb5_donot_replay *rep,
133 krb5_int32 now, krb5_boolean fromfile)
135 struct dfl_data *t = (struct dfl_data *)id->data;
136 unsigned int rephash;
139 rephash = hash(rep, t->hsize);
141 for (ta = t->h[rephash]; ta; ta = ta->nh) {
142 switch(cmp(&ta->rep, rep, t->lifespan))
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.
152 if (!ta->rep.msghash && rep->msghash) {
153 if (!(ta->rep.msghash = strdup(rep->msghash)))
160 if (alive(now, &ta->rep, t->lifespan) == CMP_EXPIRED)
170 if (!(ta = (struct authlist *) malloc(sizeof(struct authlist))))
173 ta->rep.client = ta->rep.server = ta->rep.msghash = NULL;
174 if (!(ta->rep.client = strdup(rep->client)))
176 if (!(ta->rep.server = strdup(rep->server)))
178 if (rep->msghash && !(ta->rep.msghash = strdup(rep->msghash)))
180 ta->na = t->a; t->a = ta;
181 ta->nh = t->h[rephash]; t->h[rephash] = ta;
185 free(ta->rep.client);
187 free(ta->rep.server);
189 free(ta->rep.msghash);
195 krb5_rc_dfl_get_name(krb5_context context, krb5_rcache id)
197 return ((struct dfl_data *) (id->data))->name;
200 krb5_error_code KRB5_CALLCONV
201 krb5_rc_dfl_get_span(krb5_context context, krb5_rcache id,
202 krb5_deltat *lifespan)
207 err = k5_mutex_lock(&id->lock);
210 t = (struct dfl_data *) id->data;
211 *lifespan = t->lifespan;
212 k5_mutex_unlock(&id->lock);
216 static krb5_error_code KRB5_CALLCONV
217 krb5_rc_dfl_init_locked(krb5_context context, krb5_rcache id, krb5_deltat lifespan)
219 struct dfl_data *t = (struct dfl_data *)id->data;
220 krb5_error_code retval;
222 t->lifespan = lifespan ? lifespan : context->clockskew;
223 /* default to clockskew from the context */
225 if ((retval = krb5_rc_io_creat(context, &t->d, &t->name))) {
228 if ((krb5_rc_io_write(context, &t->d,
229 (krb5_pointer) &t->lifespan, sizeof(t->lifespan))
230 || krb5_rc_io_sync(context, &t->d))) {
237 krb5_error_code KRB5_CALLCONV
238 krb5_rc_dfl_init(krb5_context context, krb5_rcache id, krb5_deltat lifespan)
240 krb5_error_code retval;
242 retval = k5_mutex_lock(&id->lock);
245 retval = krb5_rc_dfl_init_locked(context, id, lifespan);
246 k5_mutex_unlock(&id->lock);
250 /* Called with the mutex already locked. */
252 krb5_rc_dfl_close_no_free(krb5_context context, krb5_rcache id)
254 struct dfl_data *t = (struct dfl_data *)id->data;
266 free(q->rep.msghash);
270 (void) krb5_rc_io_close(context, &t->d);
276 krb5_error_code KRB5_CALLCONV
277 krb5_rc_dfl_close(krb5_context context, krb5_rcache id)
279 krb5_error_code retval;
280 retval = k5_mutex_lock(&id->lock);
283 krb5_rc_dfl_close_no_free(context, id);
284 k5_mutex_unlock(&id->lock);
285 k5_mutex_destroy(&id->lock);
290 krb5_error_code KRB5_CALLCONV
291 krb5_rc_dfl_destroy(krb5_context context, krb5_rcache id)
294 if (krb5_rc_io_destroy(context, &((struct dfl_data *) (id->data))->d))
297 return krb5_rc_dfl_close(context, id);
300 krb5_error_code KRB5_CALLCONV
301 krb5_rc_dfl_resolve(krb5_context context, krb5_rcache id, char *name)
303 struct dfl_data *t = 0;
304 krb5_error_code retval;
306 /* allocate id? no */
307 if (!(t = (struct dfl_data *) calloc(1, sizeof(struct dfl_data))))
308 return KRB5_RC_MALLOC;
309 id->data = (krb5_pointer) t;
311 t->name = strdup(name);
313 retval = KRB5_RC_MALLOC;
318 t->numhits = t->nummisses = 0;
319 t->hsize = HASHSIZE; /* no need to store---it's memory-only */
320 t->h = (struct authlist **) malloc(t->hsize*sizeof(struct authlist *));
322 retval = KRB5_RC_MALLOC;
325 memset(t->h, 0, t->hsize*sizeof(struct authlist *));
326 t->a = (struct authlist *) 0;
345 krb5_rc_free_entry(krb5_context context, krb5_donot_replay **rep)
347 krb5_donot_replay *rp = *rep;
366 * Parse a string in the format <len>:<data>, with the length
367 * represented in ASCII decimal. On parse failure, return 0 but set
370 static krb5_error_code
371 parse_counted_string(char **strptr, char **result)
373 char *str = *strptr, *end;
378 /* Parse the length, expecting a ':' afterwards. */
380 len = strtoul(str, &end, 10);
381 if (errno != 0 || *end != ':' || len > strlen(end + 1))
384 /* Allocate space for *result and copy the data. */
385 *result = malloc(len + 1);
387 return KRB5_RC_MALLOC;
388 memcpy(*result, end + 1, len);
389 (*result)[len] = '\0';
390 *strptr = end + 1 + len;
395 * Hash extension records have the format:
396 * client = <empty string>
397 * server = HASH:<msghash> <clientlen>:<client> <serverlen>:<server>
398 * Spaces in the client and server string are represented with
399 * with backslashes. Client and server lengths are represented in
400 * ASCII decimal (which is different from the 32-bit binary we use
401 * elsewhere in the replay cache).
403 * On parse failure, we leave the record unmodified.
405 static krb5_error_code
406 check_hash_extension(krb5_donot_replay *rep)
408 char *msghash = NULL, *client = NULL, *server = NULL, *str, *end;
409 krb5_error_code retval = 0;
411 /* Check if this appears to match the hash extension format. */
414 if (strncmp(rep->server, "HASH:", 5) != 0)
417 /* Parse out the message hash. */
418 str = rep->server + 5;
419 end = strchr(str, ' ');
422 msghash = malloc(end - str + 1);
424 return KRB5_RC_MALLOC;
425 memcpy(msghash, str, end - str);
426 msghash[end - str] = '\0';
429 /* Parse out the client and server. */
430 retval = parse_counted_string(&str, &client);
431 if (retval != 0 || client == NULL)
436 retval = parse_counted_string(&str, &server);
437 if (retval != 0 || server == NULL)
444 rep->client = client;
445 rep->server = server;
446 rep->msghash = msghash;
459 static krb5_error_code
460 krb5_rc_io_fetch(krb5_context context, struct dfl_data *t,
461 krb5_donot_replay *rep, int maxlen)
465 krb5_error_code retval;
467 rep->client = rep->server = rep->msghash = NULL;
469 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &len2,
474 if ((len2 <= 0) || (len2 >= maxlen))
475 return KRB5_RC_IO_EOF;
478 rep->client = malloc (len);
480 return KRB5_RC_MALLOC;
482 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) rep->client, len);
486 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &len2,
491 if ((len2 <= 0) || (len2 >= maxlen)) {
492 retval = KRB5_RC_IO_EOF;
497 rep->server = malloc (len);
499 retval = KRB5_RC_MALLOC;
503 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) rep->server, len);
507 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &rep->cusec,
512 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &rep->ctime,
517 retval = check_hash_extension(rep);
530 rep->client = rep->server = 0;
535 static krb5_error_code
536 krb5_rc_dfl_expunge_locked(krb5_context context, krb5_rcache id);
538 static krb5_error_code
539 krb5_rc_dfl_recover_locked(krb5_context context, krb5_rcache id)
545 struct dfl_data *t = (struct dfl_data *)id->data;
546 krb5_donot_replay *rep = 0;
547 krb5_error_code retval;
549 int expired_entries = 0;
552 if ((retval = krb5_rc_io_open(context, &t->d, t->name))) {
558 max_size = krb5_rc_io_size(context, &t->d);
561 if (krb5_rc_io_read(context, &t->d, (krb5_pointer) &t->lifespan,
562 sizeof(t->lifespan))) {
567 if (!(rep = (krb5_donot_replay *) malloc(sizeof(krb5_donot_replay)))) {
568 retval = KRB5_RC_MALLOC;
571 rep->client = rep->server = rep->msghash = NULL;
573 if (krb5_timeofday(context, &now))
576 /* now read in each auth_replay and insert into table */
578 if (krb5_rc_io_mark(context, &t->d)) {
583 retval = krb5_rc_io_fetch(context, t, rep, (int) max_size);
585 if (retval == KRB5_RC_IO_EOF)
587 else if (retval != 0)
590 if (alive(now, rep, t->lifespan) != CMP_EXPIRED) {
591 if (rc_store(context, id, rep, now, TRUE) == CMP_MALLOC) {
592 retval = KRB5_RC_MALLOC; goto io_fail;
599 * free fields allocated by rc_io_fetch
605 rep->client = rep->server = rep->msghash = NULL;
608 krb5_rc_io_unmark(context, &t->d);
610 * An automatic expunge here could remove the need for
611 * mark/unmark but that would be inefficient.
614 krb5_rc_free_entry(context, &rep);
616 krb5_rc_io_close(context, &t->d);
617 else if (expired_entries > EXCESSREPS)
618 retval = krb5_rc_dfl_expunge_locked(context, id);
625 krb5_error_code KRB5_CALLCONV
626 krb5_rc_dfl_recover(krb5_context context, krb5_rcache id)
629 ret = k5_mutex_lock(&id->lock);
632 ret = krb5_rc_dfl_recover_locked(context, id);
633 k5_mutex_unlock(&id->lock);
637 krb5_error_code KRB5_CALLCONV
638 krb5_rc_dfl_recover_or_init(krb5_context context, krb5_rcache id,
639 krb5_deltat lifespan)
641 krb5_error_code retval;
643 retval = k5_mutex_lock(&id->lock);
646 retval = krb5_rc_dfl_recover_locked(context, id);
648 retval = krb5_rc_dfl_init_locked(context, id, lifespan);
649 k5_mutex_unlock(&id->lock);
653 static krb5_error_code
654 krb5_rc_io_store(krb5_context context, struct dfl_data *t,
655 krb5_donot_replay *rep)
657 size_t clientlen, serverlen;
661 struct k5buf buf, extbuf;
662 char *bufptr, *extstr;
664 clientlen = strlen(rep->client);
665 serverlen = strlen(rep->server);
669 * Write a hash extension record, to be followed by a record
670 * in regular format (without the message hash) for the
671 * benefit of old implementations.
674 /* Format the extension value so we know its length. */
675 krb5int_buf_init_dynamic(&extbuf);
676 krb5int_buf_add_fmt(&extbuf, "HASH:%s %lu:%s %lu:%s", rep->msghash,
677 (unsigned long) clientlen, rep->client,
678 (unsigned long) serverlen, rep->server);
679 extstr = krb5int_buf_data(&extbuf);
681 return KRB5_RC_MALLOC;
684 * Put the extension value into the server field of a
685 * regular-format record, with an empty client field.
687 krb5int_buf_init_dynamic(&buf);
689 krb5int_buf_add_len(&buf, (char *) &len, sizeof(len));
690 krb5int_buf_add_len(&buf, "", 1);
691 len = strlen(extstr) + 1;
692 krb5int_buf_add_len(&buf, (char *) &len, sizeof(len));
693 krb5int_buf_add_len(&buf, extstr, len);
694 krb5int_buf_add_len(&buf, (char *) &rep->cusec, sizeof(rep->cusec));
695 krb5int_buf_add_len(&buf, (char *) &rep->ctime, sizeof(rep->ctime));
697 } else /* No extension record needed. */
698 krb5int_buf_init_dynamic(&buf);
701 krb5int_buf_add_len(&buf, (char *) &len, sizeof(len));
702 krb5int_buf_add_len(&buf, rep->client, len);
704 krb5int_buf_add_len(&buf, (char *) &len, sizeof(len));
705 krb5int_buf_add_len(&buf, rep->server, len);
706 krb5int_buf_add_len(&buf, (char *) &rep->cusec, sizeof(rep->cusec));
707 krb5int_buf_add_len(&buf, (char *) &rep->ctime, sizeof(rep->ctime));
709 bufptr = krb5int_buf_data(&buf);
710 buflen = krb5int_buf_len(&buf);
711 if (bufptr == NULL || buflen < 0)
712 return KRB5_RC_MALLOC;
714 ret = krb5_rc_io_write(context, &t->d, bufptr, buflen);
715 krb5int_free_buf(&buf);
719 static krb5_error_code krb5_rc_dfl_expunge_locked(krb5_context, krb5_rcache);
721 krb5_error_code KRB5_CALLCONV
722 krb5_rc_dfl_store(krb5_context context, krb5_rcache id, krb5_donot_replay *rep)
728 ret = krb5_timeofday(context, &now);
732 ret = k5_mutex_lock(&id->lock);
736 switch(rc_store(context, id, rep, now, FALSE)) {
738 k5_mutex_unlock(&id->lock);
739 return KRB5_RC_MALLOC;
741 k5_mutex_unlock(&id->lock);
742 return KRB5KRB_AP_ERR_REPEAT;
744 default: /* wtf? */ ;
746 t = (struct dfl_data *)id->data;
748 ret = krb5_rc_io_store(context, t, rep);
750 k5_mutex_unlock(&id->lock);
754 /* Shall we automatically expunge? */
755 if (t->nummisses > t->numhits + EXCESSREPS)
757 ret = krb5_rc_dfl_expunge_locked(context, id);
758 k5_mutex_unlock(&id->lock);
764 if (krb5_rc_io_sync(context, &t->d)) {
765 k5_mutex_unlock(&id->lock);
770 k5_mutex_unlock(&id->lock);
774 static krb5_error_code
775 krb5_rc_dfl_expunge_locked(krb5_context context, krb5_rcache id)
777 struct dfl_data *t = (struct dfl_data *)id->data;
781 struct authlist **qt;
786 if (krb5_timestamp(context, &now))
789 for (q = &t->a; *q; q = qt) {
791 if (alive(now, &(*q)->rep, t->lifespan) == CMP_EXPIRED) {
792 free((*q)->rep.client);
793 free((*q)->rep.server);
794 if ((*q)->rep.msghash)
795 free((*q)->rep.msghash);
797 *q = *qt; /* why doesn't this feel right? */
800 for (i = 0; i < t->hsize; i++)
801 t->h[i] = (struct authlist *) 0;
802 for (r = t->a; r; r = r->na) {
803 i = hash(&r->rep, t->hsize);
812 krb5_error_code retval = 0;
814 krb5_deltat lifespan = t->lifespan; /* save original lifespan */
816 if (! t->recovering) {
818 t->name = 0; /* Clear name so it isn't freed */
819 (void) krb5_rc_dfl_close_no_free(context, id);
820 retval = krb5_rc_dfl_resolve(context, id, name);
824 retval = krb5_rc_dfl_recover_locked(context, id);
827 t = (struct dfl_data *)id->data; /* point to recovered cache */
830 retval = krb5_rc_resolve_type(context, &tmp, "dfl");
833 retval = krb5_rc_resolve(context, tmp, 0);
836 retval = krb5_rc_initialize(context, tmp, lifespan);
839 for (q = t->a; q; q = q->na) {
840 if (krb5_rc_io_store(context, (struct dfl_data *)tmp->data, &q->rep)) {
845 /* NOTE: We set retval in case we have an error */
847 if (krb5_rc_io_sync(context, &((struct dfl_data *)tmp->data)->d))
849 if (krb5_rc_io_sync(context, &t->d))
851 if (krb5_rc_io_move(context, &t->d, &((struct dfl_data *)tmp->data)->d))
855 (void) krb5_rc_dfl_close(context, tmp);
860 krb5_error_code KRB5_CALLCONV
861 krb5_rc_dfl_expunge(krb5_context context, krb5_rcache id)
864 ret = k5_mutex_lock(&id->lock);
867 ret = krb5_rc_dfl_expunge_locked(context, id);
868 k5_mutex_unlock(&id->lock);