components/krb5/Solaris/rc_mem.c
author Will Fiveash <will.fiveash@oracle.com>
Wed, 24 Feb 2016 10:43:57 -0600
changeset 5490 9bf0bc57423a
permissions -rw-r--r--
PSARC/2015/144 Kerberos 1.13 Delivery to Userland 19153034 Add MIT Kerberos to the Userland Consolidation

/*
 * Copyright (c) 2004, 2015, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * mech_krb5/krb5/rcache/rc_mem.c
 *
 * This file of the Kerberos V5 software is derived from public-domain code
 * contributed by Daniel J. Bernstein, <[email protected]>.
 */

/*
 * An implementation for the memory only (mem) replay cache type.
 * This file was derived from rc_dfl.c with NOIOSTUFF defined.
 */
#include "rc_mem.h"

#ifndef	HASHSIZE
#define	HASHSIZE 997 /* a convenient prime */
#endif

#define	CMP_MALLOC -3
#define	CMP_EXPIRED -2
#define	CMP_REPLAY -1
#define	CMP_HOHUM 0

struct authlist
{
    krb5_donot_replay rep;
    struct authlist *na;
    struct authlist *nh;
};

/*
 * We want the replay cache to hang around for the entire life span of the
 * process, regardless if the auth_context or acceptor_cred handles are
 * destroyed.
 */
struct global_rcache grcache = {K5_MUTEX_PARTIAL_INITIALIZER, NULL};

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;
}

/*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;
}
/*
 * of course, list is backwards
 * 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)
{
	struct mem_data *t = (struct mem_data *)id->data;
	int rephash;
	struct authlist *ta, *pta = NULL, *head;
	krb5_int32 time;

	rephash = hash(rep, t->hsize);

	/* Calling krb_timeofday() here, once for better perf. */
	krb5_timeofday(context, &time);

	/*
	 * Calling alive() on rep since it doesn't make sense to store
	 * an expired replay.
	 */
	if (alive(time, rep, t->lifespan) == CMP_EXPIRED)
		return (CMP_EXPIRED);

	for (ta = t->h[rephash]; ta; ta = ta->nh) {
		switch (cmp(&ta->rep, rep, t->lifespan)) {
			case CMP_REPLAY:
				return (CMP_REPLAY);
			case CMP_HOHUM:
				if (alive(time, &ta->rep, t->lifespan)
				    == CMP_EXPIRED) {
					free(ta->rep.client);
					free(ta->rep.server);
					free(ta->rep.msghash);
					if (pta) {
						pta->nh = ta->nh;
						free(ta);
						ta = pta;
					} else {
						head = t->h[rephash];
						t->h[rephash] = ta->nh;
						free(head);
					}
					continue;
				}
		}
		pta = ta;
	}

	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 (!(ta->rep.msghash = strdup(rep->msghash)))
		goto error;
	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);
}

/*ARGSUSED*/
char *KRB5_CALLCONV
krb5_rc_mem_get_name(krb5_context context, krb5_rcache id)
{
	return (((struct mem_data *)(id->data))->name);
}

/*ARGSUSED*/
krb5_error_code KRB5_CALLCONV
krb5_rc_mem_get_span(krb5_context context, krb5_rcache id,
    krb5_deltat *lifespan)
{
	struct mem_data *t;

	k5_mutex_lock(&id->lock);
	k5_mutex_lock(&grcache.lock);

	t = (struct mem_data *)id->data;
	*lifespan = t->lifespan;

	k5_mutex_unlock(&grcache.lock);
	k5_mutex_unlock(&id->lock);

	return (0);
}

krb5_error_code KRB5_CALLCONV
krb5_rc_mem_init_locked(krb5_context context, krb5_rcache id,
    krb5_deltat lifespan)
{
	struct mem_data *t = (struct mem_data *)id->data;

	t->lifespan = lifespan ? lifespan : context->clockskew;
	/* default to clockskew from the context */
	return (0);
}

krb5_error_code KRB5_CALLCONV
krb5_rc_mem_init(krb5_context context, krb5_rcache id, krb5_deltat lifespan)
{
	krb5_error_code retval;

	k5_mutex_lock(&id->lock);
	k5_mutex_lock(&grcache.lock);

	retval = krb5_rc_mem_init_locked(context, id, lifespan);

	k5_mutex_unlock(&grcache.lock);
	k5_mutex_unlock(&id->lock);

	return (retval);
}

/*
 * We want the replay cache to be persistent since we can't
 * read from a file to retrieve the rcache, so we must not free
 * here.  Just return success.
 */
krb5_error_code KRB5_CALLCONV
krb5_rc_mem_close(krb5_context context, krb5_rcache id)
{
	return (0);
}

krb5_error_code KRB5_CALLCONV
krb5_rc_mem_destroy(krb5_context context, krb5_rcache id)
{
	return (krb5_rc_mem_close(context, id));
}

/*ARGSUSED*/
krb5_error_code KRB5_CALLCONV
krb5_rc_mem_resolve(krb5_context context, krb5_rcache id, char *name)
{
	struct mem_data *t = 0;
	krb5_error_code retval;

	k5_mutex_lock(&grcache.lock);

	/*
	 * If the global rcache has already been initialized through a prior
	 * call to this function then just set the rcache to point to it for
	 * any subsequent operations.
	 */
	if (grcache.data != NULL) {
		id->data = (krb5_pointer)grcache.data;
		k5_mutex_unlock(&grcache.lock);
		return (0);
	}
	/* allocate id? no */
	if (!(t = (struct mem_data *)malloc(sizeof (struct mem_data)))) {
		k5_mutex_unlock(&grcache.lock);
		return (KRB5_RC_MALLOC);
	}
	grcache.data = id->data = (krb5_pointer)t;
	memset(t, 0, sizeof (struct mem_data));
	if (name) {
		t->name = malloc(strlen(name)+1);
		if (!t->name) {
			retval = KRB5_RC_MALLOC;
			goto cleanup;
		}
		strcpy(t->name, name);
	} else
		t->name = 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 *));
	k5_mutex_unlock(&grcache.lock);
	return (0);

cleanup:
	if (t) {
		if (t->name)
			free(t->name);
		if (t->h)
			free(t->h);
		free(t);
		grcache.data = NULL;
		id->data = NULL;
	}
	k5_mutex_unlock(&grcache.lock);

	return (retval);
}

/*
 * Recovery (retrieval) of the replay cache occurred during
 * krb5_rc_resolve().  So we just return error here.
 */
krb5_error_code KRB5_CALLCONV
krb5_rc_mem_recover(krb5_context context, krb5_rcache id)
{
	return (KRB5_RC_NOIO);
}

krb5_error_code KRB5_CALLCONV
krb5_rc_mem_recover_or_init(krb5_context context, krb5_rcache id,
			    krb5_deltat lifespan)
{
	krb5_error_code retval;

	retval = krb5_rc_mem_recover(context, id);
	if (retval)
		retval = krb5_rc_mem_init(context, id, lifespan);

	return (retval);
}

krb5_error_code KRB5_CALLCONV
krb5_rc_mem_store(krb5_context context, krb5_rcache id, krb5_donot_replay *rep)
{
	k5_mutex_lock(&id->lock);
	k5_mutex_lock(&grcache.lock);

	switch (rc_store(context, id, rep)) {
		case CMP_MALLOC:
			k5_mutex_unlock(&grcache.lock);
			k5_mutex_unlock(&id->lock);
			return (KRB5_RC_MALLOC);
		case CMP_REPLAY:
			k5_mutex_unlock(&grcache.lock);
			k5_mutex_unlock(&id->lock);
			return (KRB5KRB_AP_ERR_REPEAT);
		case CMP_EXPIRED:
			k5_mutex_unlock(&grcache.lock);
			k5_mutex_unlock(&id->lock);
			return (KRB5KRB_AP_ERR_SKEW);
		case CMP_HOHUM:
			break;
	}

	k5_mutex_unlock(&grcache.lock);
	k5_mutex_unlock(&id->lock);

	return (0);
}

const krb5_rc_ops krb5_rc_mem_ops = {
    0,
    "MEMORY",
    krb5_rc_mem_init,
    krb5_rc_mem_recover,
    krb5_rc_mem_recover_or_init,
    krb5_rc_mem_destroy,
    krb5_rc_mem_close,
    krb5_rc_mem_store,
    /* expunging not used in memory rcache type */
    NULL,
    krb5_rc_mem_get_span,
    krb5_rc_mem_get_name,
    krb5_rc_mem_resolve
};