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_timestamp mytime, krb5_donot_replay *new1, krb5_deltat t)
99 return CMP_HOHUM; /* who cares? */
100 if (ts_after(mytime, ts_incr(new1->ctime, t)))
108 krb5_deltat lifespan;
122 krb5_donot_replay rep;
127 /* of course, list is backwards from file */
128 /* hash could be forwards since we have to search on match, but naaaah */
131 rc_store(krb5_context context, krb5_rcache id, krb5_donot_replay *rep,
132 krb5_timestamp now, krb5_boolean fromfile)
134 struct dfl_data *t = (struct dfl_data *)id->data;
135 unsigned int rephash;
138 rephash = hash(rep, t->hsize);
140 for (ta = t->h[rephash]; ta; ta = ta->nh) {
141 switch(cmp(&ta->rep, rep, t->lifespan))
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.
151 if (!ta->rep.msghash && rep->msghash) {
152 if (!(ta->rep.msghash = strdup(rep->msghash)))
159 if (alive(now, &ta->rep, t->lifespan) == CMP_EXPIRED)
169 if (!(ta = (struct authlist *) malloc(sizeof(struct authlist))))
172 ta->rep.client = ta->rep.server = ta->rep.msghash = NULL;
173 if (!(ta->rep.client = strdup(rep->client)))
175 if (!(ta->rep.server = strdup(rep->server)))
177 if (rep->msghash && !(ta->rep.msghash = strdup(rep->msghash)))
179 ta->na = t->a; t->a = ta;
180 ta->nh = t->h[rephash]; t->h[rephash] = ta;
184 free(ta->rep.client);
186 free(ta->rep.server);
188 free(ta->rep.msghash);
194 krb5_rc_dfl_get_name(krb5_context context, krb5_rcache id)
196 return ((struct dfl_data *) (id->data))->name;
199 krb5_error_code KRB5_CALLCONV
200 krb5_rc_dfl_get_span(krb5_context context, krb5_rcache id,
201 krb5_deltat *lifespan)
205 k5_mutex_lock(&id->lock);
206 t = (struct dfl_data *) id->data;
207 *lifespan = t->lifespan;
208 k5_mutex_unlock(&id->lock);
212 static krb5_error_code KRB5_CALLCONV
213 krb5_rc_dfl_init_locked(krb5_context context, krb5_rcache id, krb5_deltat lifespan)
215 struct dfl_data *t = (struct dfl_data *)id->data;
216 krb5_error_code retval;
218 t->lifespan = lifespan ? lifespan : context->clockskew;
219 /* default to clockskew from the context */
221 if ((retval = krb5_rc_io_creat(context, &t->d, &t->name))) {
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))) {
233 krb5_error_code KRB5_CALLCONV
234 krb5_rc_dfl_init(krb5_context context, krb5_rcache id, krb5_deltat lifespan)
236 krb5_error_code retval;
238 k5_mutex_lock(&id->lock);
239 retval = krb5_rc_dfl_init_locked(context, id, lifespan);
240 k5_mutex_unlock(&id->lock);
244 /* Called with the mutex already locked. */
246 krb5_rc_dfl_close_no_free(krb5_context context, krb5_rcache id)
248 struct dfl_data *t = (struct dfl_data *)id->data;
260 free(q->rep.msghash);
264 (void) krb5_rc_io_close(context, &t->d);
270 krb5_error_code KRB5_CALLCONV
271 krb5_rc_dfl_close(krb5_context context, krb5_rcache id)
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);
281 krb5_error_code KRB5_CALLCONV
282 krb5_rc_dfl_destroy(krb5_context context, krb5_rcache id)
285 if (krb5_rc_io_destroy(context, &((struct dfl_data *) (id->data))->d))
288 return krb5_rc_dfl_close(context, id);
291 krb5_error_code KRB5_CALLCONV
292 krb5_rc_dfl_resolve(krb5_context context, krb5_rcache id, char *name)
294 struct dfl_data *t = 0;
295 krb5_error_code retval;
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;
302 t->name = strdup(name);
304 retval = KRB5_RC_MALLOC;
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 *));
313 retval = KRB5_RC_MALLOC;
316 memset(t->h, 0, t->hsize*sizeof(struct authlist *));
317 t->a = (struct authlist *) 0;
336 krb5_rc_free_entry(krb5_context context, krb5_donot_replay **rep)
338 krb5_donot_replay *rp = *rep;
357 * Parse a string in the format <len>:<data>, with the length
358 * represented in ASCII decimal. On parse failure, return 0 but set
361 static krb5_error_code
362 parse_counted_string(char **strptr, char **result)
364 char *str = *strptr, *end;
369 /* Parse the length, expecting a ':' afterwards. */
371 len = strtoul(str, &end, 10);
372 if (errno != 0 || *end != ':' || len > strlen(end + 1))
375 /* Allocate space for *result and copy the data. */
376 *result = malloc(len + 1);
378 return KRB5_RC_MALLOC;
379 memcpy(*result, end + 1, len);
380 (*result)[len] = '\0';
381 *strptr = end + 1 + len;
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).
394 * On parse failure, we leave the record unmodified.
396 static krb5_error_code
397 check_hash_extension(krb5_donot_replay *rep)
399 char *msghash = NULL, *client = NULL, *server = NULL, *str, *end;
400 krb5_error_code retval = 0;
402 /* Check if this appears to match the hash extension format. */
405 if (strncmp(rep->server, "SHA256:", 7) != 0)
408 /* Parse out the message hash. */
409 str = rep->server + 7;
410 end = strchr(str, ' ');
413 msghash = k5memdup0(str, end - str, &retval);
415 return KRB5_RC_MALLOC;
418 /* Parse out the client and server. */
419 retval = parse_counted_string(&str, &client);
420 if (retval != 0 || client == NULL)
425 retval = parse_counted_string(&str, &server);
426 if (retval != 0 || server == NULL)
433 rep->client = client;
434 rep->server = server;
435 rep->msghash = msghash;
448 static krb5_error_code
449 krb5_rc_io_fetch(krb5_context context, struct dfl_data *t,
450 krb5_donot_replay *rep, int maxlen)
454 krb5_error_code retval;
456 rep->client = rep->server = rep->msghash = NULL;
458 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &len2,
463 if ((len2 <= 0) || (len2 >= maxlen))
464 return KRB5_RC_IO_EOF;
467 rep->client = malloc (len);
469 return KRB5_RC_MALLOC;
471 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) rep->client, len);
475 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &len2,
480 if ((len2 <= 0) || (len2 >= maxlen)) {
481 retval = KRB5_RC_IO_EOF;
486 rep->server = malloc (len);
488 retval = KRB5_RC_MALLOC;
492 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) rep->server, len);
496 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &rep->cusec,
501 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &rep->ctime,
506 retval = check_hash_extension(rep);
519 rep->client = rep->server = rep->msghash = NULL;
524 static krb5_error_code
525 krb5_rc_dfl_expunge_locked(krb5_context context, krb5_rcache id);
527 static krb5_error_code
528 krb5_rc_dfl_recover_locked(krb5_context context, krb5_rcache id)
534 struct dfl_data *t = (struct dfl_data *)id->data;
535 krb5_donot_replay *rep = 0;
536 krb5_error_code retval;
538 int expired_entries = 0;
541 if ((retval = krb5_rc_io_open(context, &t->d, t->name))) {
547 max_size = krb5_rc_io_size(context, &t->d);
550 if (krb5_rc_io_read(context, &t->d, (krb5_pointer) &t->lifespan,
551 sizeof(t->lifespan))) {
556 if (!(rep = (krb5_donot_replay *) malloc(sizeof(krb5_donot_replay)))) {
557 retval = KRB5_RC_MALLOC;
560 rep->client = rep->server = rep->msghash = NULL;
562 if (krb5_timeofday(context, &now))
565 /* now read in each auth_replay and insert into table */
567 if (krb5_rc_io_mark(context, &t->d)) {
572 retval = krb5_rc_io_fetch(context, t, rep, (int) max_size);
574 if (retval == KRB5_RC_IO_EOF)
576 else if (retval != 0)
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;
588 * free fields allocated by rc_io_fetch
594 rep->client = rep->server = rep->msghash = NULL;
597 krb5_rc_io_unmark(context, &t->d);
599 * An automatic expunge here could remove the need for
600 * mark/unmark but that would be inefficient.
603 krb5_rc_free_entry(context, &rep);
605 krb5_rc_io_close(context, &t->d);
606 else if (expired_entries > EXCESSREPS)
607 retval = krb5_rc_dfl_expunge_locked(context, id);
614 krb5_error_code KRB5_CALLCONV
615 krb5_rc_dfl_recover(krb5_context context, krb5_rcache id)
619 k5_mutex_lock(&id->lock);
620 ret = krb5_rc_dfl_recover_locked(context, id);
621 k5_mutex_unlock(&id->lock);
625 krb5_error_code KRB5_CALLCONV
626 krb5_rc_dfl_recover_or_init(krb5_context context, krb5_rcache id,
627 krb5_deltat lifespan)
629 krb5_error_code retval;
631 k5_mutex_lock(&id->lock);
632 retval = krb5_rc_dfl_recover_locked(context, id);
634 retval = krb5_rc_dfl_init_locked(context, id, lifespan);
635 k5_mutex_unlock(&id->lock);
639 static krb5_error_code
640 krb5_rc_io_store(krb5_context context, struct dfl_data *t,
641 krb5_donot_replay *rep)
643 size_t clientlen, serverlen;
646 struct k5buf buf, extbuf;
649 clientlen = strlen(rep->client);
650 serverlen = strlen(rep->server);
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.
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;
669 * Put the extension value into the server field of a
670 * regular-format record, with an empty client field.
672 k5_buf_init_dynamic(&buf);
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));
682 } else /* No extension record needed. */
683 k5_buf_init_dynamic(&buf);
686 k5_buf_add_len(&buf, (char *)&len, sizeof(len));
687 k5_buf_add_len(&buf, rep->client, len);
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));
694 if (k5_buf_status(&buf) != 0)
695 return KRB5_RC_MALLOC;
697 ret = krb5_rc_io_write(context, &t->d, buf.data, buf.len);
702 static krb5_error_code krb5_rc_dfl_expunge_locked(krb5_context, krb5_rcache);
704 krb5_error_code KRB5_CALLCONV
705 krb5_rc_dfl_store(krb5_context context, krb5_rcache id, krb5_donot_replay *rep)
711 ret = krb5_timeofday(context, &now);
715 k5_mutex_lock(&id->lock);
717 switch(rc_store(context, id, rep, now, FALSE)) {
719 k5_mutex_unlock(&id->lock);
720 return KRB5_RC_MALLOC;
722 k5_mutex_unlock(&id->lock);
723 return KRB5KRB_AP_ERR_REPEAT;
725 default: /* wtf? */ ;
727 t = (struct dfl_data *)id->data;
729 ret = krb5_rc_io_store(context, t, rep);
731 k5_mutex_unlock(&id->lock);
735 /* Shall we automatically expunge? */
736 if (t->nummisses > t->numhits + EXCESSREPS)
738 ret = krb5_rc_dfl_expunge_locked(context, id);
739 k5_mutex_unlock(&id->lock);
745 if (krb5_rc_io_sync(context, &t->d)) {
746 k5_mutex_unlock(&id->lock);
751 k5_mutex_unlock(&id->lock);
755 static krb5_error_code
756 krb5_rc_dfl_expunge_locked(krb5_context context, krb5_rcache id)
758 struct dfl_data *t = (struct dfl_data *)id->data;
762 struct authlist **qt;
767 if (krb5_timestamp(context, &now))
770 for (q = &t->a; *q; q = qt) {
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);
778 *q = *qt; /* why doesn't this feel right? */
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);
793 krb5_error_code retval = 0;
795 krb5_deltat lifespan = t->lifespan; /* save original lifespan */
797 if (! t->recovering) {
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);
805 retval = krb5_rc_dfl_recover_locked(context, id);
808 t = (struct dfl_data *)id->data; /* point to recovered cache */
811 retval = krb5_rc_resolve_type(context, &tmp, "dfl");
814 retval = krb5_rc_resolve(context, tmp, 0);
817 retval = krb5_rc_initialize(context, tmp, lifespan);
820 for (q = t->a; q; q = q->na) {
821 if (krb5_rc_io_store(context, (struct dfl_data *)tmp->data, &q->rep)) {
826 /* NOTE: We set retval in case we have an error */
828 if (krb5_rc_io_sync(context, &((struct dfl_data *)tmp->data)->d))
830 if (krb5_rc_io_sync(context, &t->d))
832 if (krb5_rc_io_move(context, &t->d, &((struct dfl_data *)tmp->data)->d))
836 (void) krb5_rc_dfl_close(context, tmp);
841 krb5_error_code KRB5_CALLCONV
842 krb5_rc_dfl_expunge(krb5_context context, krb5_rcache id)
846 k5_mutex_lock(&id->lock);
847 ret = krb5_rc_dfl_expunge_locked(context, id);
848 k5_mutex_unlock(&id->lock);