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)
206 k5_mutex_lock(&id->lock);
207 t = (struct dfl_data *) id->data;
208 *lifespan = t->lifespan;
209 k5_mutex_unlock(&id->lock);
213 static krb5_error_code KRB5_CALLCONV
214 krb5_rc_dfl_init_locked(krb5_context context, krb5_rcache id, krb5_deltat lifespan)
216 struct dfl_data *t = (struct dfl_data *)id->data;
217 krb5_error_code retval;
219 t->lifespan = lifespan ? lifespan : context->clockskew;
220 /* default to clockskew from the context */
222 if ((retval = krb5_rc_io_creat(context, &t->d, &t->name))) {
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))) {
234 krb5_error_code KRB5_CALLCONV
235 krb5_rc_dfl_init(krb5_context context, krb5_rcache id, krb5_deltat lifespan)
237 krb5_error_code retval;
239 k5_mutex_lock(&id->lock);
240 retval = krb5_rc_dfl_init_locked(context, id, lifespan);
241 k5_mutex_unlock(&id->lock);
245 /* Called with the mutex already locked. */
247 krb5_rc_dfl_close_no_free(krb5_context context, krb5_rcache id)
249 struct dfl_data *t = (struct dfl_data *)id->data;
261 free(q->rep.msghash);
265 (void) krb5_rc_io_close(context, &t->d);
271 krb5_error_code KRB5_CALLCONV
272 krb5_rc_dfl_close(krb5_context context, krb5_rcache id)
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);
282 krb5_error_code KRB5_CALLCONV
283 krb5_rc_dfl_destroy(krb5_context context, krb5_rcache id)
286 if (krb5_rc_io_destroy(context, &((struct dfl_data *) (id->data))->d))
289 return krb5_rc_dfl_close(context, id);
292 krb5_error_code KRB5_CALLCONV
293 krb5_rc_dfl_resolve(krb5_context context, krb5_rcache id, char *name)
295 struct dfl_data *t = 0;
296 krb5_error_code retval;
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;
303 t->name = strdup(name);
305 retval = KRB5_RC_MALLOC;
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 *));
314 retval = KRB5_RC_MALLOC;
317 memset(t->h, 0, t->hsize*sizeof(struct authlist *));
318 t->a = (struct authlist *) 0;
337 krb5_rc_free_entry(krb5_context context, krb5_donot_replay **rep)
339 krb5_donot_replay *rp = *rep;
358 * Parse a string in the format <len>:<data>, with the length
359 * represented in ASCII decimal. On parse failure, return 0 but set
362 static krb5_error_code
363 parse_counted_string(char **strptr, char **result)
365 char *str = *strptr, *end;
370 /* Parse the length, expecting a ':' afterwards. */
372 len = strtoul(str, &end, 10);
373 if (errno != 0 || *end != ':' || len > strlen(end + 1))
376 /* Allocate space for *result and copy the data. */
377 *result = malloc(len + 1);
379 return KRB5_RC_MALLOC;
380 memcpy(*result, end + 1, len);
381 (*result)[len] = '\0';
382 *strptr = end + 1 + len;
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).
395 * On parse failure, we leave the record unmodified.
397 static krb5_error_code
398 check_hash_extension(krb5_donot_replay *rep)
400 char *msghash = NULL, *client = NULL, *server = NULL, *str, *end;
401 krb5_error_code retval = 0;
403 /* Check if this appears to match the hash extension format. */
406 if (strncmp(rep->server, "SHA256:", 7) != 0)
409 /* Parse out the message hash. */
410 str = rep->server + 7;
411 end = strchr(str, ' ');
414 msghash = k5memdup0(str, end - str, &retval);
416 return KRB5_RC_MALLOC;
419 /* Parse out the client and server. */
420 retval = parse_counted_string(&str, &client);
421 if (retval != 0 || client == NULL)
426 retval = parse_counted_string(&str, &server);
427 if (retval != 0 || server == NULL)
434 rep->client = client;
435 rep->server = server;
436 rep->msghash = msghash;
449 static krb5_error_code
450 krb5_rc_io_fetch(krb5_context context, struct dfl_data *t,
451 krb5_donot_replay *rep, int maxlen)
455 krb5_error_code retval;
457 rep->client = rep->server = rep->msghash = NULL;
459 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &len2,
464 if ((len2 <= 0) || (len2 >= maxlen))
465 return KRB5_RC_IO_EOF;
468 rep->client = malloc (len);
470 return KRB5_RC_MALLOC;
472 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) rep->client, len);
476 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &len2,
481 if ((len2 <= 0) || (len2 >= maxlen)) {
482 retval = KRB5_RC_IO_EOF;
487 rep->server = malloc (len);
489 retval = KRB5_RC_MALLOC;
493 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) rep->server, len);
497 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &rep->cusec,
502 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &rep->ctime,
507 retval = check_hash_extension(rep);
520 rep->client = rep->server = 0;
525 static krb5_error_code
526 krb5_rc_dfl_expunge_locked(krb5_context context, krb5_rcache id);
528 static krb5_error_code
529 krb5_rc_dfl_recover_locked(krb5_context context, krb5_rcache id)
535 struct dfl_data *t = (struct dfl_data *)id->data;
536 krb5_donot_replay *rep = 0;
537 krb5_error_code retval;
539 int expired_entries = 0;
542 if ((retval = krb5_rc_io_open(context, &t->d, t->name))) {
548 max_size = krb5_rc_io_size(context, &t->d);
551 if (krb5_rc_io_read(context, &t->d, (krb5_pointer) &t->lifespan,
552 sizeof(t->lifespan))) {
557 if (!(rep = (krb5_donot_replay *) malloc(sizeof(krb5_donot_replay)))) {
558 retval = KRB5_RC_MALLOC;
561 rep->client = rep->server = rep->msghash = NULL;
563 if (krb5_timeofday(context, &now))
566 /* now read in each auth_replay and insert into table */
568 if (krb5_rc_io_mark(context, &t->d)) {
573 retval = krb5_rc_io_fetch(context, t, rep, (int) max_size);
575 if (retval == KRB5_RC_IO_EOF)
577 else if (retval != 0)
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;
589 * free fields allocated by rc_io_fetch
595 rep->client = rep->server = rep->msghash = NULL;
598 krb5_rc_io_unmark(context, &t->d);
600 * An automatic expunge here could remove the need for
601 * mark/unmark but that would be inefficient.
604 krb5_rc_free_entry(context, &rep);
606 krb5_rc_io_close(context, &t->d);
607 else if (expired_entries > EXCESSREPS)
608 retval = krb5_rc_dfl_expunge_locked(context, id);
615 krb5_error_code KRB5_CALLCONV
616 krb5_rc_dfl_recover(krb5_context context, krb5_rcache id)
620 k5_mutex_lock(&id->lock);
621 ret = krb5_rc_dfl_recover_locked(context, id);
622 k5_mutex_unlock(&id->lock);
626 krb5_error_code KRB5_CALLCONV
627 krb5_rc_dfl_recover_or_init(krb5_context context, krb5_rcache id,
628 krb5_deltat lifespan)
630 krb5_error_code retval;
632 k5_mutex_lock(&id->lock);
633 retval = krb5_rc_dfl_recover_locked(context, id);
635 retval = krb5_rc_dfl_init_locked(context, id, lifespan);
636 k5_mutex_unlock(&id->lock);
640 static krb5_error_code
641 krb5_rc_io_store(krb5_context context, struct dfl_data *t,
642 krb5_donot_replay *rep)
644 size_t clientlen, serverlen;
647 struct k5buf buf, extbuf;
650 clientlen = strlen(rep->client);
651 serverlen = strlen(rep->server);
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.
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;
670 * Put the extension value into the server field of a
671 * regular-format record, with an empty client field.
673 k5_buf_init_dynamic(&buf);
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));
683 } else /* No extension record needed. */
684 k5_buf_init_dynamic(&buf);
687 k5_buf_add_len(&buf, (char *)&len, sizeof(len));
688 k5_buf_add_len(&buf, rep->client, len);
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));
695 if (k5_buf_status(&buf) != 0)
696 return KRB5_RC_MALLOC;
698 ret = krb5_rc_io_write(context, &t->d, buf.data, buf.len);
703 static krb5_error_code krb5_rc_dfl_expunge_locked(krb5_context, krb5_rcache);
705 krb5_error_code KRB5_CALLCONV
706 krb5_rc_dfl_store(krb5_context context, krb5_rcache id, krb5_donot_replay *rep)
712 ret = krb5_timeofday(context, &now);
716 k5_mutex_lock(&id->lock);
718 switch(rc_store(context, id, rep, now, FALSE)) {
720 k5_mutex_unlock(&id->lock);
721 return KRB5_RC_MALLOC;
723 k5_mutex_unlock(&id->lock);
724 return KRB5KRB_AP_ERR_REPEAT;
726 default: /* wtf? */ ;
728 t = (struct dfl_data *)id->data;
730 ret = krb5_rc_io_store(context, t, rep);
732 k5_mutex_unlock(&id->lock);
736 /* Shall we automatically expunge? */
737 if (t->nummisses > t->numhits + EXCESSREPS)
739 ret = krb5_rc_dfl_expunge_locked(context, id);
740 k5_mutex_unlock(&id->lock);
746 if (krb5_rc_io_sync(context, &t->d)) {
747 k5_mutex_unlock(&id->lock);
752 k5_mutex_unlock(&id->lock);
756 static krb5_error_code
757 krb5_rc_dfl_expunge_locked(krb5_context context, krb5_rcache id)
759 struct dfl_data *t = (struct dfl_data *)id->data;
763 struct authlist **qt;
768 if (krb5_timestamp(context, &now))
771 for (q = &t->a; *q; q = qt) {
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);
779 *q = *qt; /* why doesn't this feel right? */
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);
794 krb5_error_code retval = 0;
796 krb5_deltat lifespan = t->lifespan; /* save original lifespan */
798 if (! t->recovering) {
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);
806 retval = krb5_rc_dfl_recover_locked(context, id);
809 t = (struct dfl_data *)id->data; /* point to recovered cache */
812 retval = krb5_rc_resolve_type(context, &tmp, "dfl");
815 retval = krb5_rc_resolve(context, tmp, 0);
818 retval = krb5_rc_initialize(context, tmp, lifespan);
821 for (q = t->a; q; q = q->na) {
822 if (krb5_rc_io_store(context, (struct dfl_data *)tmp->data, &q->rep)) {
827 /* NOTE: We set retval in case we have an error */
829 if (krb5_rc_io_sync(context, &((struct dfl_data *)tmp->data)->d))
831 if (krb5_rc_io_sync(context, &t->d))
833 if (krb5_rc_io_move(context, &t->d, &((struct dfl_data *)tmp->data)->d))
837 (void) krb5_rc_dfl_close(context, tmp);
842 krb5_error_code KRB5_CALLCONV
843 krb5_rc_dfl_expunge(krb5_context context, krb5_rcache id)
847 k5_mutex_lock(&id->lock);
848 ret = krb5_rc_dfl_expunge_locked(context, id);
849 k5_mutex_unlock(&id->lock);