/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* * lib/krb5/rcache/rc_dfl.c * * This file of the Kerberos V5 software is derived from public-domain code * contributed by Daniel J. Bernstein, . * */ /* * An implementation for the default replay cache type. */ #include "rc_base.h" #include "rc_dfl.h" #include "rc_io.h" #include "k5-int.h" #include "rc-int.h" /* * If NOIOSTUFF is defined at compile time, dfl rcaches will be per-process. */ /* Local stuff: static int hash(krb5_donot_replay *rep, int hsize) returns hash value of *rep, between 0 and hsize - 1 HASHSIZE size of hash table (constant), can be preset static int cmp(krb5_donot_replay *old, krb5_donot_replay *new, krb5_deltat t) compare old and new; return CMP_REPLAY or CMP_HOHUM static int alive(krb5_context, krb5_donot_replay *new, krb5_deltat t) see if new is still alive; return CMP_EXPIRED or CMP_HOHUM CMP_MALLOC, CMP_EXPIRED, CMP_REPLAY, CMP_HOHUM return codes from cmp(), alive(), and store() struct dfl_data data stored in this cache type, namely "dfl" struct authlist multilinked list of reps static int rc_store(context, krb5_rcache id, krb5_donot_replay *rep) store rep in cache id; return CMP_REPLAY if replay, else CMP_MALLOC/CMP_HOHUM */ #ifndef HASHSIZE #define HASHSIZE 997 /* a convenient prime */ #endif #ifndef EXCESSREPS #define EXCESSREPS 30 #endif /* * The rcache will be automatically expunged when the number of * expired krb5_donot_replays encountered incidentally in searching * exceeds the number of live krb5_donot_replays by EXCESSREPS. With * the defaults here, a typical cache might build up some 10K of * expired krb5_donot_replays before an automatic expunge, with the * waste basically independent of the number of stores per minute. * * The rcache will also automatically be expunged when it encounters * more than EXCESSREPS expired entries when recovering a cache in * dfl_recover. */ static unsigned int hash(krb5_donot_replay *rep, unsigned int hsize) { unsigned int h = rep->cusec + rep->ctime; h += *rep->server; h += *rep->client; return h % hsize; } #define CMP_MALLOC -3 #define CMP_EXPIRED -2 #define CMP_REPLAY -1 #define CMP_HOHUM 0 /*ARGSUSED*/ static int cmp(krb5_donot_replay *old, krb5_donot_replay *new1, krb5_deltat t) { if ((old->cusec == new1->cusec) && /* most likely to distinguish */ (old->ctime == new1->ctime) && (strcmp(old->client, new1->client) == 0) && (strcmp(old->server, new1->server) == 0)) { /* always true */ /* If both records include message hashes, compare them as well. */ if (old->msghash == NULL || new1->msghash == NULL || strcmp(old->msghash, new1->msghash) == 0) return CMP_REPLAY; } return CMP_HOHUM; } static int alive(krb5_int32 mytime, krb5_donot_replay *new1, krb5_deltat t) { if (mytime == 0) return CMP_HOHUM; /* who cares? */ /* I hope we don't have to worry about overflow */ if (new1->ctime + t < mytime) return CMP_EXPIRED; return CMP_HOHUM; } struct dfl_data { char *name; krb5_deltat lifespan; unsigned int hsize; int numhits; int nummisses; struct authlist **h; struct authlist *a; #ifndef NOIOSTUFF krb5_rc_iostuff d; #endif char recovering; }; struct authlist { krb5_donot_replay rep; struct authlist *na; struct authlist *nh; }; /* of course, list is backwards from file */ /* hash could be forwards since we have to search on match, but naaaah */ static int rc_store(krb5_context context, krb5_rcache id, krb5_donot_replay *rep, krb5_int32 now, krb5_boolean fromfile) { struct dfl_data *t = (struct dfl_data *)id->data; unsigned int rephash; struct authlist *ta; rephash = hash(rep, t->hsize); for (ta = t->h[rephash]; ta; ta = ta->nh) { switch(cmp(&ta->rep, rep, t->lifespan)) { case CMP_REPLAY: if (fromfile) { /* * This is an expected collision between a hash * extension record and a normal-format record. Make * sure the message hash is included in the stored * record and carry on. */ if (!ta->rep.msghash && rep->msghash) { if (!(ta->rep.msghash = strdup(rep->msghash))) return CMP_MALLOC; } return CMP_HOHUM; } else return CMP_REPLAY; case CMP_HOHUM: if (alive(now, &ta->rep, t->lifespan) == CMP_EXPIRED) t->nummisses++; else t->numhits++; break; default: ; /* wtf? */ } } if (!(ta = (struct authlist *) malloc(sizeof(struct authlist)))) return CMP_MALLOC; ta->rep = *rep; ta->rep.client = ta->rep.server = ta->rep.msghash = NULL; if (!(ta->rep.client = strdup(rep->client))) goto error; if (!(ta->rep.server = strdup(rep->server))) goto error; if (rep->msghash && !(ta->rep.msghash = strdup(rep->msghash))) goto error; ta->na = t->a; t->a = ta; ta->nh = t->h[rephash]; t->h[rephash] = ta; return CMP_HOHUM; error: if (ta->rep.client) free(ta->rep.client); if (ta->rep.server) free(ta->rep.server); if (ta->rep.msghash) free(ta->rep.msghash); free(ta); return CMP_MALLOC; } char * KRB5_CALLCONV krb5_rc_dfl_get_name(krb5_context context, krb5_rcache id) { return ((struct dfl_data *) (id->data))->name; } krb5_error_code KRB5_CALLCONV krb5_rc_dfl_get_span(krb5_context context, krb5_rcache id, krb5_deltat *lifespan) { krb5_error_code err; struct dfl_data *t; err = k5_mutex_lock(&id->lock); if (err) return err; t = (struct dfl_data *) id->data; *lifespan = t->lifespan; k5_mutex_unlock(&id->lock); return 0; } static krb5_error_code KRB5_CALLCONV krb5_rc_dfl_init_locked(krb5_context context, krb5_rcache id, krb5_deltat lifespan) { struct dfl_data *t = (struct dfl_data *)id->data; krb5_error_code retval; t->lifespan = lifespan ? lifespan : context->clockskew; /* default to clockskew from the context */ #ifndef NOIOSTUFF if ((retval = krb5_rc_io_creat(context, &t->d, &t->name))) { return retval; } if ((krb5_rc_io_write(context, &t->d, (krb5_pointer) &t->lifespan, sizeof(t->lifespan)) || krb5_rc_io_sync(context, &t->d))) { return KRB5_RC_IO; } #endif return 0; } krb5_error_code KRB5_CALLCONV krb5_rc_dfl_init(krb5_context context, krb5_rcache id, krb5_deltat lifespan) { krb5_error_code retval; retval = k5_mutex_lock(&id->lock); if (retval) return retval; retval = krb5_rc_dfl_init_locked(context, id, lifespan); k5_mutex_unlock(&id->lock); return retval; } /* Called with the mutex already locked. */ krb5_error_code krb5_rc_dfl_close_no_free(krb5_context context, krb5_rcache id) { struct dfl_data *t = (struct dfl_data *)id->data; struct authlist *q; free(t->h); if (t->name) free(t->name); while ((q = t->a)) { t->a = q->na; free(q->rep.client); free(q->rep.server); if (q->rep.msghash) free(q->rep.msghash); free(q); } #ifndef NOIOSTUFF (void) krb5_rc_io_close(context, &t->d); #endif free(t); return 0; } krb5_error_code KRB5_CALLCONV krb5_rc_dfl_close(krb5_context context, krb5_rcache id) { krb5_error_code retval; retval = k5_mutex_lock(&id->lock); if (retval) return retval; krb5_rc_dfl_close_no_free(context, id); k5_mutex_unlock(&id->lock); k5_mutex_destroy(&id->lock); free(id); return 0; } krb5_error_code KRB5_CALLCONV krb5_rc_dfl_destroy(krb5_context context, krb5_rcache id) { #ifndef NOIOSTUFF if (krb5_rc_io_destroy(context, &((struct dfl_data *) (id->data))->d)) return KRB5_RC_IO; #endif return krb5_rc_dfl_close(context, id); } krb5_error_code KRB5_CALLCONV krb5_rc_dfl_resolve(krb5_context context, krb5_rcache id, char *name) { struct dfl_data *t = 0; krb5_error_code retval; /* allocate id? no */ if (!(t = (struct dfl_data *) calloc(1, sizeof(struct dfl_data)))) return KRB5_RC_MALLOC; id->data = (krb5_pointer) t; if (name) { t->name = strdup(name); if (!t->name) { retval = KRB5_RC_MALLOC; goto cleanup; } } else t->name = 0; t->numhits = t->nummisses = 0; t->hsize = HASHSIZE; /* no need to store---it's memory-only */ t->h = (struct authlist **) malloc(t->hsize*sizeof(struct authlist *)); if (!t->h) { retval = KRB5_RC_MALLOC; goto cleanup; } memset(t->h, 0, t->hsize*sizeof(struct authlist *)); t->a = (struct authlist *) 0; #ifndef NOIOSTUFF t->d.fd = -1; #endif t->recovering = 0; return 0; cleanup: if (t) { if (t->name) free(t->name); if (t->h) free(t->h); free(t); } return retval; } void krb5_rc_free_entry(krb5_context context, krb5_donot_replay **rep) { krb5_donot_replay *rp = *rep; *rep = NULL; if (rp) { if (rp->client) free(rp->client); if (rp->server) free(rp->server); if (rp->msghash) free(rp->msghash); rp->client = NULL; rp->server = NULL; rp->msghash = NULL; free(rp); } } /* * Parse a string in the format :, with the length * represented in ASCII decimal. On parse failure, return 0 but set * *result to NULL. */ static krb5_error_code parse_counted_string(char **strptr, char **result) { char *str = *strptr, *end; unsigned long len; *result = NULL; /* Parse the length, expecting a ':' afterwards. */ errno = 0; len = strtoul(str, &end, 10); if (errno != 0 || *end != ':' || len > strlen(end + 1)) return 0; /* Allocate space for *result and copy the data. */ *result = malloc(len + 1); if (!*result) return KRB5_RC_MALLOC; memcpy(*result, end + 1, len); (*result)[len] = '\0'; *strptr = end + 1 + len; return 0; } /* * Hash extension records have the format: * client = * server = HASH: : : * Spaces in the client and server string are represented with * with backslashes. Client and server lengths are represented in * ASCII decimal (which is different from the 32-bit binary we use * elsewhere in the replay cache). * * On parse failure, we leave the record unmodified. */ static krb5_error_code check_hash_extension(krb5_donot_replay *rep) { char *msghash = NULL, *client = NULL, *server = NULL, *str, *end; krb5_error_code retval = 0; /* Check if this appears to match the hash extension format. */ if (*rep->client) return 0; if (strncmp(rep->server, "HASH:", 5) != 0) return 0; /* Parse out the message hash. */ str = rep->server + 5; end = strchr(str, ' '); if (!end) return 0; msghash = malloc(end - str + 1); if (!msghash) return KRB5_RC_MALLOC; memcpy(msghash, str, end - str); msghash[end - str] = '\0'; str = end + 1; /* Parse out the client and server. */ retval = parse_counted_string(&str, &client); if (retval != 0 || client == NULL) goto error; if (*str != ' ') goto error; str++; retval = parse_counted_string(&str, &server); if (retval != 0 || server == NULL) goto error; if (*str) goto error; free(rep->client); free(rep->server); rep->client = client; rep->server = server; rep->msghash = msghash; return 0; error: if (msghash) free(msghash); if (client) free(client); if (server) free(server); return retval; } static krb5_error_code krb5_rc_io_fetch(krb5_context context, struct dfl_data *t, krb5_donot_replay *rep, int maxlen) { int len2; unsigned int len; krb5_error_code retval; rep->client = rep->server = rep->msghash = NULL; retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &len2, sizeof(len2)); if (retval) return retval; if ((len2 <= 0) || (len2 >= maxlen)) return KRB5_RC_IO_EOF; len = len2; rep->client = malloc (len); if (!rep->client) return KRB5_RC_MALLOC; retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) rep->client, len); if (retval) goto errout; retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &len2, sizeof(len2)); if (retval) goto errout; if ((len2 <= 0) || (len2 >= maxlen)) { retval = KRB5_RC_IO_EOF; goto errout; } len = len2; rep->server = malloc (len); if (!rep->server) { retval = KRB5_RC_MALLOC; goto errout; } retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) rep->server, len); if (retval) goto errout; retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &rep->cusec, sizeof(rep->cusec)); if (retval) goto errout; retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &rep->ctime, sizeof(rep->ctime)); if (retval) goto errout; retval = check_hash_extension(rep); if (retval) goto errout; return 0; errout: if (rep->client) free(rep->client); if (rep->server) free(rep->server); if (rep->msghash) free(rep->msghash); rep->client = rep->server = 0; return retval; } static krb5_error_code krb5_rc_dfl_expunge_locked(krb5_context context, krb5_rcache id); static krb5_error_code krb5_rc_dfl_recover_locked(krb5_context context, krb5_rcache id) { #ifdef NOIOSTUFF return KRB5_RC_NOIO; #else struct dfl_data *t = (struct dfl_data *)id->data; krb5_donot_replay *rep = 0; krb5_error_code retval; long max_size; int expired_entries = 0; krb5_int32 now; if ((retval = krb5_rc_io_open(context, &t->d, t->name))) { return retval; } t->recovering = 1; max_size = krb5_rc_io_size(context, &t->d); rep = NULL; if (krb5_rc_io_read(context, &t->d, (krb5_pointer) &t->lifespan, sizeof(t->lifespan))) { retval = KRB5_RC_IO; goto io_fail; } if (!(rep = (krb5_donot_replay *) malloc(sizeof(krb5_donot_replay)))) { retval = KRB5_RC_MALLOC; goto io_fail; } rep->client = rep->server = rep->msghash = NULL; if (krb5_timeofday(context, &now)) now = 0; /* now read in each auth_replay and insert into table */ for (;;) { if (krb5_rc_io_mark(context, &t->d)) { retval = KRB5_RC_IO; goto io_fail; } retval = krb5_rc_io_fetch(context, t, rep, (int) max_size); if (retval == KRB5_RC_IO_EOF) break; else if (retval != 0) goto io_fail; if (alive(now, rep, t->lifespan) != CMP_EXPIRED) { if (rc_store(context, id, rep, now, TRUE) == CMP_MALLOC) { retval = KRB5_RC_MALLOC; goto io_fail; } } else { expired_entries++; } /* * free fields allocated by rc_io_fetch */ free(rep->server); free(rep->client); if (rep->msghash) free(rep->msghash); rep->client = rep->server = rep->msghash = NULL; } retval = 0; krb5_rc_io_unmark(context, &t->d); /* * An automatic expunge here could remove the need for * mark/unmark but that would be inefficient. */ io_fail: krb5_rc_free_entry(context, &rep); if (retval) krb5_rc_io_close(context, &t->d); else if (expired_entries > EXCESSREPS) retval = krb5_rc_dfl_expunge_locked(context, id); t->recovering = 0; return retval; #endif } krb5_error_code KRB5_CALLCONV krb5_rc_dfl_recover(krb5_context context, krb5_rcache id) { krb5_error_code ret; ret = k5_mutex_lock(&id->lock); if (ret) return ret; ret = krb5_rc_dfl_recover_locked(context, id); k5_mutex_unlock(&id->lock); return ret; } krb5_error_code KRB5_CALLCONV krb5_rc_dfl_recover_or_init(krb5_context context, krb5_rcache id, krb5_deltat lifespan) { krb5_error_code retval; retval = k5_mutex_lock(&id->lock); if (retval) return retval; retval = krb5_rc_dfl_recover_locked(context, id); if (retval) retval = krb5_rc_dfl_init_locked(context, id, lifespan); k5_mutex_unlock(&id->lock); return retval; } static krb5_error_code krb5_rc_io_store(krb5_context context, struct dfl_data *t, krb5_donot_replay *rep) { size_t clientlen, serverlen; ssize_t buflen; unsigned int len; krb5_error_code ret; struct k5buf buf, extbuf; char *bufptr, *extstr; clientlen = strlen(rep->client); serverlen = strlen(rep->server); if (rep->msghash) { /* * Write a hash extension record, to be followed by a record * in regular format (without the message hash) for the * benefit of old implementations. */ /* Format the extension value so we know its length. */ krb5int_buf_init_dynamic(&extbuf); krb5int_buf_add_fmt(&extbuf, "HASH:%s %lu:%s %lu:%s", rep->msghash, (unsigned long) clientlen, rep->client, (unsigned long) serverlen, rep->server); extstr = krb5int_buf_data(&extbuf); if (!extstr) return KRB5_RC_MALLOC; /* * Put the extension value into the server field of a * regular-format record, with an empty client field. */ krb5int_buf_init_dynamic(&buf); len = 1; krb5int_buf_add_len(&buf, (char *) &len, sizeof(len)); krb5int_buf_add_len(&buf, "", 1); len = strlen(extstr) + 1; krb5int_buf_add_len(&buf, (char *) &len, sizeof(len)); krb5int_buf_add_len(&buf, extstr, len); krb5int_buf_add_len(&buf, (char *) &rep->cusec, sizeof(rep->cusec)); krb5int_buf_add_len(&buf, (char *) &rep->ctime, sizeof(rep->ctime)); free(extstr); } else /* No extension record needed. */ krb5int_buf_init_dynamic(&buf); len = clientlen + 1; krb5int_buf_add_len(&buf, (char *) &len, sizeof(len)); krb5int_buf_add_len(&buf, rep->client, len); len = serverlen + 1; krb5int_buf_add_len(&buf, (char *) &len, sizeof(len)); krb5int_buf_add_len(&buf, rep->server, len); krb5int_buf_add_len(&buf, (char *) &rep->cusec, sizeof(rep->cusec)); krb5int_buf_add_len(&buf, (char *) &rep->ctime, sizeof(rep->ctime)); bufptr = krb5int_buf_data(&buf); buflen = krb5int_buf_len(&buf); if (bufptr == NULL || buflen < 0) return KRB5_RC_MALLOC; ret = krb5_rc_io_write(context, &t->d, bufptr, buflen); krb5int_free_buf(&buf); return ret; } static krb5_error_code krb5_rc_dfl_expunge_locked(krb5_context, krb5_rcache); krb5_error_code KRB5_CALLCONV krb5_rc_dfl_store(krb5_context context, krb5_rcache id, krb5_donot_replay *rep) { krb5_error_code ret; struct dfl_data *t; krb5_int32 now; ret = krb5_timeofday(context, &now); if (ret) return ret; ret = k5_mutex_lock(&id->lock); if (ret) return ret; switch(rc_store(context, id, rep, now, FALSE)) { case CMP_MALLOC: k5_mutex_unlock(&id->lock); return KRB5_RC_MALLOC; case CMP_REPLAY: k5_mutex_unlock(&id->lock); return KRB5KRB_AP_ERR_REPEAT; case 0: break; default: /* wtf? */ ; } t = (struct dfl_data *)id->data; #ifndef NOIOSTUFF ret = krb5_rc_io_store(context, t, rep); if (ret) { k5_mutex_unlock(&id->lock); return ret; } #endif /* Shall we automatically expunge? */ if (t->nummisses > t->numhits + EXCESSREPS) { ret = krb5_rc_dfl_expunge_locked(context, id); k5_mutex_unlock(&id->lock); return ret; } #ifndef NOIOSTUFF else { if (krb5_rc_io_sync(context, &t->d)) { k5_mutex_unlock(&id->lock); return KRB5_RC_IO; } } #endif k5_mutex_unlock(&id->lock); return 0; } static krb5_error_code krb5_rc_dfl_expunge_locked(krb5_context context, krb5_rcache id) { struct dfl_data *t = (struct dfl_data *)id->data; #ifdef NOIOSTUFF unsigned int i; struct authlist **q; struct authlist **qt; struct authlist *r; struct authlist *rt; krb5_int32 now; if (krb5_timestamp(context, &now)) now = 0; for (q = &t->a; *q; q = qt) { qt = &(*q)->na; if (alive(now, &(*q)->rep, t->lifespan) == CMP_EXPIRED) { free((*q)->rep.client); free((*q)->rep.server); if ((*q)->rep.msghash) free((*q)->rep.msghash); free(*q); *q = *qt; /* why doesn't this feel right? */ } } for (i = 0; i < t->hsize; i++) t->h[i] = (struct authlist *) 0; for (r = t->a; r; r = r->na) { i = hash(&r->rep, t->hsize); rt = t->h[i]; t->h[i] = r; r->nh = rt; } return 0; #else struct authlist *q; char *name; krb5_error_code retval = 0; krb5_rcache tmp; krb5_deltat lifespan = t->lifespan; /* save original lifespan */ if (! t->recovering) { name = t->name; t->name = 0; /* Clear name so it isn't freed */ (void) krb5_rc_dfl_close_no_free(context, id); retval = krb5_rc_dfl_resolve(context, id, name); free(name); if (retval) return retval; retval = krb5_rc_dfl_recover_locked(context, id); if (retval) return retval; t = (struct dfl_data *)id->data; /* point to recovered cache */ } retval = krb5_rc_resolve_type(context, &tmp, "dfl"); if (retval) return retval; retval = krb5_rc_resolve(context, tmp, 0); if (retval) goto cleanup; retval = krb5_rc_initialize(context, tmp, lifespan); if (retval) goto cleanup; for (q = t->a; q; q = q->na) { if (krb5_rc_io_store(context, (struct dfl_data *)tmp->data, &q->rep)) { retval = KRB5_RC_IO; goto cleanup; } } /* NOTE: We set retval in case we have an error */ retval = KRB5_RC_IO; if (krb5_rc_io_sync(context, &((struct dfl_data *)tmp->data)->d)) goto cleanup; if (krb5_rc_io_sync(context, &t->d)) goto cleanup; if (krb5_rc_io_move(context, &t->d, &((struct dfl_data *)tmp->data)->d)) goto cleanup; retval = 0; cleanup: (void) krb5_rc_dfl_close(context, tmp); return retval; #endif } krb5_error_code KRB5_CALLCONV krb5_rc_dfl_expunge(krb5_context context, krb5_rcache id) { krb5_error_code ret; ret = k5_mutex_lock(&id->lock); if (ret) return ret; ret = krb5_rc_dfl_expunge_locked(context, id); k5_mutex_unlock(&id->lock); return ret; }