#include "k5-int.h"
#include "k5-queue.h"
#include "k5-hashtab.h"
#include "kdc_util.h"
#include "extern.h"
#ifndef NOCACHE
struct entry {
K5_TAILQ_ENTRY(entry) links;
int num_hits;
krb5_timestamp timein;
krb5_data req_packet;
krb5_data reply_packet;
};
#ifndef LOOKASIDE_MAX_SIZE
#define LOOKASIDE_MAX_SIZE (10 * 1024 * 1024)
#endif
K5_LIST_HEAD(entry_list, entry);
K5_TAILQ_HEAD(entry_queue, entry);
static struct k5_hashtab *hash_table;
static struct entry_queue expiration_queue;
static int hits = 0;
static int calls = 0;
static int max_hits_per_entry = 0;
static int num_entries = 0;
static size_t total_size = 0;
#define STALE_TIME (2*60)
#define STALE(ptr, now) (ts_after(now, ts_incr((ptr)->timein, STALE_TIME)))
static size_t
entry_size(const krb5_data *req, const krb5_data *rep)
{
return sizeof(struct entry) + req->length +
((rep == NULL) ? 0 : rep->length);
}
static struct entry *
insert_entry(krb5_context context, krb5_data *req, krb5_data *rep,
krb5_timestamp time)
{
krb5_error_code ret;
struct entry *entry;
size_t esize = entry_size(req, rep);
entry = calloc(1, sizeof(*entry));
if (entry == NULL)
goto error;
entry->timein = time;
ret = krb5int_copy_data_contents(context, req, &entry->req_packet);
if (ret)
goto error;
if (rep != NULL) {
ret = krb5int_copy_data_contents(context, rep, &entry->reply_packet);
if (ret)
goto error;
}
ret = k5_hashtab_add(hash_table, entry->req_packet.data,
entry->req_packet.length, entry);
if (ret)
goto error;
K5_TAILQ_INSERT_TAIL(&expiration_queue, entry, links);
num_entries++;
total_size += esize;
return entry;
error:
if (entry != NULL) {
krb5_free_data_contents(context, &entry->req_packet);
krb5_free_data_contents(context, &entry->reply_packet);
free(entry);
}
return NULL;
}
static void
discard_entry(krb5_context context, struct entry *entry)
{
total_size -= entry_size(&entry->req_packet, &entry->reply_packet);
num_entries--;
k5_hashtab_remove(hash_table, entry->req_packet.data,
entry->req_packet.length);
K5_TAILQ_REMOVE(&expiration_queue, entry, links);
krb5_free_data_contents(context, &entry->req_packet);
krb5_free_data_contents(context, &entry->reply_packet);
free(entry);
}
krb5_error_code
kdc_init_lookaside(krb5_context context)
{
krb5_error_code ret;
uint8_t seed[K5_HASH_SEED_LEN];
krb5_data d = make_data(seed, sizeof(seed));
ret = krb5_c_random_make_octets(context, &d);
if (ret)
return ret;
ret = k5_hashtab_create(seed, 8192, &hash_table);
if (ret)
return ret;
K5_TAILQ_INIT(&expiration_queue);
return 0;
}
void
kdc_remove_lookaside(krb5_context kcontext, krb5_data *req_packet)
{
struct entry *e;
e = k5_hashtab_get(hash_table, req_packet->data, req_packet->length);
if (e != NULL)
discard_entry(kcontext, e);
}
krb5_boolean
kdc_check_lookaside(krb5_context kcontext, krb5_data *req_packet,
krb5_data **reply_packet_out)
{
struct entry *e;
*reply_packet_out = NULL;
calls++;
e = k5_hashtab_get(hash_table, req_packet->data, req_packet->length);
if (e == NULL)
return FALSE;
e->num_hits++;
hits++;
if (e->reply_packet.length == 0)
return TRUE;
return (krb5_copy_data(kcontext, &e->reply_packet,
reply_packet_out) == 0);
}
void
kdc_insert_lookaside(krb5_context kcontext, krb5_data *req_packet,
krb5_data *reply_packet)
{
struct entry *e, *next;
krb5_timestamp timenow;
size_t esize = entry_size(req_packet, reply_packet);
if (krb5_timeofday(kcontext, &timenow))
return;
K5_TAILQ_FOREACH_SAFE(e, &expiration_queue, links, next) {
if (!STALE(e, timenow) && total_size + esize <= LOOKASIDE_MAX_SIZE)
break;
max_hits_per_entry = max(max_hits_per_entry, e->num_hits);
discard_entry(kcontext, e);
}
insert_entry(kcontext, req_packet, reply_packet, timenow);
return;
}
void
kdc_free_lookaside(krb5_context kcontext)
{
struct entry *e, *next;
K5_TAILQ_FOREACH_SAFE(e, &expiration_queue, links, next) {
discard_entry(kcontext, e);
}
k5_hashtab_free(hash_table);
}
#endif