/*
 * Implementation of the userspace access vector cache (AVC).
 *
 * Author : Eamon Walsh <ewalsh@epoch.ncsc.mil>
 *
 * Derived from the kernel AVC implementation by
 * Stephen Smalley <sds@epoch.ncsc.mil> and 
 * James Morris <jmorris@redhat.com>.
 */
#include <selinux/avc.h>
#include "selinux_internal.h"
#include "avc_sidtab.h"
#include "avc_internal.h"

#define AVC_CACHE_SLOTS		512
#define AVC_CACHE_MAXNODES	410

struct avc_entry {
	security_id_t ssid;
	security_id_t tsid;
	security_class_t tclass;
	struct av_decision avd;
	security_id_t	create_sid;
	int used;		/* used recently */
};

struct avc_node {
	struct avc_entry ae;
	struct avc_node *next;
};

struct avc_cache {
	struct avc_node *slots[AVC_CACHE_SLOTS];
	uint32_t lru_hint;	/* LRU hint for reclaim scan */
	uint32_t active_nodes;
	uint32_t latest_notif;	/* latest revocation notification */
};

struct avc_callback_node {
	int (*callback) (uint32_t event, security_id_t ssid,
			 security_id_t tsid,
			 security_class_t tclass, access_vector_t perms,
			 access_vector_t * out_retained);
	uint32_t events;
	security_id_t ssid;
	security_id_t tsid;
	security_class_t tclass;
	access_vector_t perms;
	struct avc_callback_node *next;
};

static void *avc_netlink_thread = NULL;
static void *avc_lock = NULL;
static void *avc_log_lock = NULL;
static struct avc_node *avc_node_freelist = NULL;
static struct avc_cache avc_cache;
static char *avc_audit_buf = NULL;
static struct avc_cache_stats cache_stats;
static struct avc_callback_node *avc_callbacks = NULL;
static struct sidtab avc_sidtab;

static inline int avc_hash(security_id_t ssid,
			   security_id_t tsid, security_class_t tclass)
{
	return ((uintptr_t) ssid ^ ((uintptr_t) tsid << 2) ^ tclass)
	    & (AVC_CACHE_SLOTS - 1);
}

int avc_context_to_sid(const char * ctx, security_id_t * sid)
{
	int rc;
	avc_get_lock(avc_lock);
	rc = sidtab_context_to_sid(&avc_sidtab, ctx, sid);
	avc_release_lock(avc_lock);
	return rc;
}

int avc_sid_to_context(security_id_t sid, char ** ctx)
{
	int rc;
	*ctx = NULL;
	avc_get_lock(avc_lock);
	*ctx = strdup(sid->ctx);	/* caller must free via freecon */
	rc = *ctx ? 0 : -1;
	avc_release_lock(avc_lock);
	return rc;
}

int avc_get_initial_sid(const char * name, security_id_t * sid)
{
	int rc;
	char * con;

	rc = security_get_initial_context(name, &con);
	if (rc < 0)
		return rc;
	rc = avc_context_to_sid(con, sid);

	freecon(con);

	return rc;
}

int avc_open(struct selinux_opt *opts, unsigned nopts)
{
	avc_setenforce = 0;

	while (nopts--)
		switch(opts[nopts].type) {
		case AVC_OPT_SETENFORCE:
			avc_setenforce = 1;
			avc_enforcing = !!opts[nopts].value;
			break;
		}

	return avc_init("avc", NULL, NULL, NULL, NULL);
}

int avc_init(const char *prefix,
	     const struct avc_memory_callback *mem_cb,
	     const struct avc_log_callback *log_cb,
	     const struct avc_thread_callback *thread_cb,
	     const struct avc_lock_callback *lock_cb)
{
	struct avc_node *new;
	int i, rc = 0;

	if (prefix)
		strncpy(avc_prefix, prefix, AVC_PREFIX_SIZE - 1);

	set_callbacks(mem_cb, log_cb, thread_cb, lock_cb);

	avc_lock = avc_alloc_lock();
	avc_log_lock = avc_alloc_lock();

	memset(&cache_stats, 0, sizeof(cache_stats));

	for (i = 0; i < AVC_CACHE_SLOTS; i++)
		avc_cache.slots[i] = 0;
	avc_cache.lru_hint = 0;
	avc_cache.active_nodes = 0;
	avc_cache.latest_notif = 0;

	rc = sidtab_init(&avc_sidtab);
	if (rc) {
		avc_log(SELINUX_ERROR,
			"%s:  unable to initialize SID table\n",
			avc_prefix);
		goto out;
	}

	avc_audit_buf = (char *)avc_malloc(AVC_AUDIT_BUFSIZE);
	if (!avc_audit_buf) {
		avc_log(SELINUX_ERROR,
			"%s:  unable to allocate audit buffer\n",
			avc_prefix);
		rc = -1;
		goto out;
	}

	for (i = 0; i < AVC_CACHE_MAXNODES; i++) {
		new = avc_malloc(sizeof(*new));
		if (!new) {
			avc_log(SELINUX_WARNING,
				"%s:  warning: only got %d av entries\n",
				avc_prefix, i);
			break;
		}
		memset(new, 0, sizeof(*new));
		new->next = avc_node_freelist;
		avc_node_freelist = new;
	}

	if (!avc_setenforce) {
		rc = security_getenforce();
		if (rc < 0) {
			avc_log(SELINUX_ERROR,
				"%s:  could not determine enforcing mode: %s\n",
				avc_prefix,
				strerror(errno));
			goto out;
		}
		avc_enforcing = rc;
	}

	rc = avc_netlink_open(0);
	if (rc < 0) {
		avc_log(SELINUX_ERROR,
			"%s:  can't open netlink socket: %d (%s)\n",
			avc_prefix, errno, strerror(errno));
		goto out;
	}
	if (avc_using_threads) {
		avc_netlink_thread = avc_create_thread(&avc_netlink_loop);
		avc_netlink_trouble = 0;
	}
	avc_running = 1;
      out:
	return rc;
}

void avc_cache_stats(struct avc_cache_stats *p)
{
	memcpy(p, &cache_stats, sizeof(cache_stats));
}

void avc_sid_stats(void)
{
	avc_get_lock(avc_log_lock);
	avc_get_lock(avc_lock);
	sidtab_sid_stats(&avc_sidtab, avc_audit_buf, AVC_AUDIT_BUFSIZE);
	avc_release_lock(avc_lock);
	avc_log(SELINUX_INFO, "%s", avc_audit_buf);
	avc_release_lock(avc_log_lock);
}

void avc_av_stats(void)
{
	int i, chain_len, max_chain_len, slots_used;
	struct avc_node *node;

	avc_get_lock(avc_lock);

	slots_used = 0;
	max_chain_len = 0;
	for (i = 0; i < AVC_CACHE_SLOTS; i++) {
		node = avc_cache.slots[i];
		if (node) {
			slots_used++;
			chain_len = 0;
			while (node) {
				chain_len++;
				node = node->next;
			}
			if (chain_len > max_chain_len)
				max_chain_len = chain_len;
		}
	}

	avc_release_lock(avc_lock);

	avc_log(SELINUX_INFO, "%s:  %d AV entries and %d/%d buckets used, "
		"longest chain length %d\n", avc_prefix,
		avc_cache.active_nodes,
		slots_used, AVC_CACHE_SLOTS, max_chain_len);
}

hidden_def(avc_av_stats)

static inline struct avc_node *avc_reclaim_node(void)
{
	struct avc_node *prev, *cur;
	int try;
	uint32_t hvalue;

	hvalue = avc_cache.lru_hint;
	for (try = 0; try < 2; try++) {
		do {
			prev = NULL;
			cur = avc_cache.slots[hvalue];
			while (cur) {
				if (!cur->ae.used)
					goto found;

				cur->ae.used = 0;

				prev = cur;
				cur = cur->next;
			}
			hvalue = (hvalue + 1) & (AVC_CACHE_SLOTS - 1);
		} while (hvalue != avc_cache.lru_hint);
	}

	errno = ENOMEM;		/* this was a panic in the kernel... */
	return NULL;

      found:
	avc_cache.lru_hint = hvalue;

	if (prev == NULL)
		avc_cache.slots[hvalue] = cur->next;
	else
		prev->next = cur->next;

	return cur;
}

static inline void avc_clear_avc_entry(struct avc_entry *ae)
{
	memset(ae, 0, sizeof *ae);
}

static inline struct avc_node *avc_claim_node(security_id_t ssid,
					      security_id_t tsid,
					      security_class_t tclass)
{
	struct avc_node *new;
	int hvalue;

	if (!avc_node_freelist)
		avc_cleanup();

	if (avc_node_freelist) {
		new = avc_node_freelist;
		avc_node_freelist = avc_node_freelist->next;
		avc_cache.active_nodes++;
	} else {
		new = avc_reclaim_node();
		if (!new)
			goto out;
	}

	hvalue = avc_hash(ssid, tsid, tclass);
	avc_clear_avc_entry(&new->ae);
	new->ae.used = 1;
	new->ae.ssid = ssid;
	new->ae.tsid = tsid;
	new->ae.tclass = tclass;
	new->next = avc_cache.slots[hvalue];
	avc_cache.slots[hvalue] = new;

      out:
	return new;
}

static inline struct avc_node *avc_search_node(security_id_t ssid,
					       security_id_t tsid,
					       security_class_t tclass,
					       int *probes)
{
	struct avc_node *cur;
	int hvalue;
	int tprobes = 1;

	hvalue = avc_hash(ssid, tsid, tclass);
	cur = avc_cache.slots[hvalue];
	while (cur != NULL &&
	       (ssid != cur->ae.ssid ||
		tclass != cur->ae.tclass || tsid != cur->ae.tsid)) {
		tprobes++;
		cur = cur->next;
	}

	if (cur == NULL) {
		/* cache miss */
		goto out;
	}

	/* cache hit */
	if (probes)
		*probes = tprobes;

	cur->ae.used = 1;

      out:
	return cur;
}

/**
 * avc_lookup - Look up an AVC entry.
 * @ssid: source security identifier
 * @tsid: target security identifier
 * @tclass: target security class
 * @requested: requested permissions, interpreted based on @tclass
 * @aeref:  AVC entry reference
 *
 * Look up an AVC entry that is valid for the
 * @requested permissions between the SID pair
 * (@ssid, @tsid), interpreting the permissions
 * based on @tclass.  If a valid AVC entry exists,
 * then this function updates @aeref to refer to the
 * entry and returns %0.  Otherwise, -1 is returned.
 */
static int avc_lookup(security_id_t ssid, security_id_t tsid,
		      security_class_t tclass,
		      access_vector_t requested, struct avc_entry_ref *aeref)
{
	struct avc_node *node;
	int probes, rc = 0;

	avc_cache_stats_incr(cav_lookups);
	node = avc_search_node(ssid, tsid, tclass, &probes);

	if (node && ((node->ae.avd.decided & requested) == requested)) {
		avc_cache_stats_incr(cav_hits);
		avc_cache_stats_add(cav_probes, probes);
		aeref->ae = &node->ae;
		goto out;
	}

	avc_cache_stats_incr(cav_misses);
	rc = -1;
      out:
	return rc;
}

/**
 * avc_insert - Insert an AVC entry.
 * @ssid: source security identifier
 * @tsid: target security identifier
 * @tclass: target security class
 * @ae: AVC entry
 * @aeref:  AVC entry reference
 *
 * Insert an AVC entry for the SID pair
 * (@ssid, @tsid) and class @tclass.
 * The access vectors and the sequence number are
 * normally provided by the security server in
 * response to a security_compute_av() call.  If the
 * sequence number @ae->avd.seqno is not less than the latest
 * revocation notification, then the function copies
 * the access vectors into a cache entry, updates
 * @aeref to refer to the entry, and returns %0.
 * Otherwise, this function returns -%1 with @errno set to %EAGAIN.
 */
static int avc_insert(security_id_t ssid, security_id_t tsid,
		      security_class_t tclass,
		      struct avc_entry *ae, struct avc_entry_ref *aeref)
{
	struct avc_node *node;
	int rc = 0;

	if (ae->avd.seqno < avc_cache.latest_notif) {
		avc_log(SELINUX_WARNING,
			"%s:  seqno %d < latest_notif %d\n", avc_prefix,
			ae->avd.seqno, avc_cache.latest_notif);
		errno = EAGAIN;
		rc = -1;
		goto out;
	}

	node = avc_claim_node(ssid, tsid, tclass);
	if (!node) {
		rc = -1;
		goto out;
	}

	memcpy(&node->ae.avd, &ae->avd, sizeof ae->avd);
	aeref->ae = &node->ae;
      out:
	return rc;
}

void avc_cleanup(void)
{
}

hidden_def(avc_cleanup)

int avc_reset(void)
{
	struct avc_callback_node *c;
	int i, ret, rc = 0, errsave = 0;
	struct avc_node *node, *tmp;
	errno = 0;

	if (!avc_running)
		return 0;

	avc_get_lock(avc_lock);

	for (i = 0; i < AVC_CACHE_SLOTS; i++) {
		node = avc_cache.slots[i];
		while (node) {
			tmp = node;
			node = node->next;
			avc_clear_avc_entry(&tmp->ae);
			tmp->next = avc_node_freelist;
			avc_node_freelist = tmp;
			avc_cache.active_nodes--;
		}
		avc_cache.slots[i] = 0;
	}
	avc_cache.lru_hint = 0;

	avc_release_lock(avc_lock);

	memset(&cache_stats, 0, sizeof(cache_stats));

	for (c = avc_callbacks; c; c = c->next) {
		if (c->events & AVC_CALLBACK_RESET) {
			ret = c->callback(AVC_CALLBACK_RESET, 0, 0, 0, 0, 0);
			if (ret && !rc) {
				rc = ret;
				errsave = errno;
			}
		}
	}
	errno = errsave;
	return rc;
}

hidden_def(avc_reset)

void avc_destroy(void)
{
	struct avc_callback_node *c;
	struct avc_node *node, *tmp;
	int i;

	avc_get_lock(avc_lock);

	if (avc_using_threads)
		avc_stop_thread(avc_netlink_thread);
	avc_netlink_close();

	for (i = 0; i < AVC_CACHE_SLOTS; i++) {
		node = avc_cache.slots[i];
		while (node) {
			tmp = node;
			node = node->next;
			avc_free(tmp);
		}
	}
	while (avc_node_freelist) {
		tmp = avc_node_freelist;
		avc_node_freelist = tmp->next;
		avc_free(tmp);
	}
	avc_release_lock(avc_lock);

	while (avc_callbacks) {
		c = avc_callbacks;
		avc_callbacks = c->next;
		avc_free(c);
	}
	sidtab_destroy(&avc_sidtab);
	avc_free_lock(avc_lock);
	avc_free_lock(avc_log_lock);
	avc_free(avc_audit_buf);
	avc_running = 0;
}

/* ratelimit stuff put aside for now --EFW */
#if 0
/*
 * Copied from net/core/utils.c:net_ratelimit and modified for
 * use by the AVC audit facility.
 */
#define AVC_MSG_COST	5*HZ
#define AVC_MSG_BURST	10*5*HZ

/*
 * This enforces a rate limit: not more than one kernel message
 * every 5secs to make a denial-of-service attack impossible.
 */
static int avc_ratelimit(void)
{
	static unsigned long toks = 10 * 5 * HZ;
	static unsigned long last_msg;
	static int missed, rc = 0;
	unsigned long now = jiffies;
	void *ratelimit_lock = avc_alloc_lock();

	avc_get_lock(ratelimit_lock);
	toks += now - last_msg;
	last_msg = now;
	if (toks > AVC_MSG_BURST)
		toks = AVC_MSG_BURST;
	if (toks >= AVC_MSG_COST) {
		int lost = missed;
		missed = 0;
		toks -= AVC_MSG_COST;
		avc_release_lock(ratelimit_lock);
		if (lost) {
			avc_log(SELINUX_WARNING,
				"%s:  %d messages suppressed.\n", avc_prefix,
				lost);
		}
		rc = 1;
		goto out;
	}
	missed++;
	avc_release_lock(ratelimit_lock);
      out:
	avc_free_lock(ratelimit_lock);
	return rc;
}

static inline int check_avc_ratelimit(void)
{
	if (avc_enforcing)
		return avc_ratelimit();
	else {
		/* If permissive, then never suppress messages. */
		return 1;
	}
}
#endif				/* ratelimit stuff */

/**
 * avc_dump_av - Display an access vector in human-readable form.
 * @tclass: target security class
 * @av: access vector
 */
static void avc_dump_av(security_class_t tclass, access_vector_t av)
{
	const char *permstr;
	access_vector_t bit = 1;

	if (av == 0) {
		log_append(avc_audit_buf, " null");
		return;
	}

	log_append(avc_audit_buf, " {");

	while (av) {
		if (av & bit) {
			permstr = security_av_perm_to_string(tclass, bit);
			if (!permstr)
				break;
			log_append(avc_audit_buf, " %s", permstr);
			av &= ~bit;
		}
		bit <<= 1;
	}

	if (av)
		log_append(avc_audit_buf, " 0x%x", av);
	log_append(avc_audit_buf, " }");
}

/**
 * avc_dump_query - Display a SID pair and a class in human-readable form.
 * @ssid: source security identifier
 * @tsid: target security identifier
 * @tclass: target security class
 */
static void avc_dump_query(security_id_t ssid, security_id_t tsid,
			   security_class_t tclass)
{
	avc_get_lock(avc_lock);

	log_append(avc_audit_buf, "scontext=%s tcontext=%s",
		   ssid->ctx, tsid->ctx);

	avc_release_lock(avc_lock);
	log_append(avc_audit_buf, " tclass=%s",
		   security_class_to_string(tclass));
}

void avc_audit(security_id_t ssid, security_id_t tsid,
	       security_class_t tclass, access_vector_t requested,
	       struct av_decision *avd, int result, void *a)
{
	access_vector_t denied, audited;

	denied = requested & ~avd->allowed;
	if (denied)
		audited = denied & avd->auditdeny;
	else if (!requested || result)
		audited = denied = requested;
	else
		audited = requested & avd->auditallow;
	if (!audited)
		return;
#if 0
	if (!check_avc_ratelimit())
		return;
#endif
	/* prevent overlapping buffer writes */
	avc_get_lock(avc_log_lock);
	snprintf(avc_audit_buf, AVC_AUDIT_BUFSIZE,
		 "%s:  %s ", avc_prefix, (denied || !requested) ? "denied" : "granted");
	avc_dump_av(tclass, audited);
	log_append(avc_audit_buf, " for ");

	/* get any extra information printed by the callback */
	avc_suppl_audit(a, tclass, avc_audit_buf + strlen(avc_audit_buf),
			AVC_AUDIT_BUFSIZE - strlen(avc_audit_buf));

	log_append(avc_audit_buf, " ");
	avc_dump_query(ssid, tsid, tclass);
	log_append(avc_audit_buf, "\n");
	avc_log(SELINUX_AVC, "%s", avc_audit_buf);

	avc_release_lock(avc_log_lock);
}

hidden_def(avc_audit)

int avc_has_perm_noaudit(security_id_t ssid,
			 security_id_t tsid,
			 security_class_t tclass,
			 access_vector_t requested,
			 struct avc_entry_ref *aeref, struct av_decision *avd)
{
	struct avc_entry *ae;
	int rc = 0;
	struct avc_entry entry;
	access_vector_t denied;
	struct avc_entry_ref ref;

	if (!avc_using_threads && !avc_app_main_loop) {
		(void)avc_netlink_check_nb();
	}

	if (!aeref) {
		avc_entry_ref_init(&ref);
		aeref = &ref;
	}

	avc_get_lock(avc_lock);
	avc_cache_stats_incr(entry_lookups);
	ae = aeref->ae;
	if (ae) {
		if (ae->ssid == ssid &&
		    ae->tsid == tsid &&
		    ae->tclass == tclass &&
		    ((ae->avd.decided & requested) == requested)) {
			avc_cache_stats_incr(entry_hits);
			ae->used = 1;
		} else {
			avc_cache_stats_incr(entry_discards);
			ae = 0;
		}
	}

	if (!ae) {
		avc_cache_stats_incr(entry_misses);
		rc = avc_lookup(ssid, tsid, tclass, requested, aeref);
		if (rc) {
			rc = security_compute_av(ssid->ctx, tsid->ctx,
						 tclass, requested,
						 &entry.avd);
			if (rc)
				goto out;
			rc = avc_insert(ssid, tsid, tclass, &entry, aeref);
			if (rc)
				goto out;
		}
		ae = aeref->ae;
	}

	if (avd)
		memcpy(avd, &ae->avd, sizeof(*avd));

	denied = requested & ~(ae->avd.allowed);

	if (!requested || denied) {
		if (!avc_enforcing ||
		    (ae->avd.flags & SELINUX_AVD_FLAGS_PERMISSIVE))
			ae->avd.allowed |= requested;
		else {
			errno = EACCES;
			rc = -1;
		}
	}

      out:
	avc_release_lock(avc_lock);
	return rc;
}

hidden_def(avc_has_perm_noaudit)

int avc_has_perm(security_id_t ssid, security_id_t tsid,
		 security_class_t tclass, access_vector_t requested,
		 struct avc_entry_ref *aeref, void *auditdata)
{
	struct av_decision avd;
	int errsave, rc;

	memset(&avd, 0, sizeof(avd));

	rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested, aeref, &avd);
	errsave = errno;
	avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata);
	errno = errsave;
	return rc;
}

int avc_compute_create(security_id_t ssid,  security_id_t tsid,
		       security_class_t tclass, security_id_t *newsid)
{
	int rc;
	struct avc_entry_ref aeref;
	struct avc_entry entry;
	char * ctx;

	*newsid = NULL;
	avc_entry_ref_init(&aeref);

	avc_get_lock(avc_lock);

	/* check for a cached entry */
	rc = avc_lookup(ssid, tsid, tclass, 0, &aeref);
	if (rc) {
		/* need to make a cache entry for this tuple */
		rc = security_compute_av(ssid->ctx, tsid->ctx,
					 tclass, 0, &entry.avd);
		if (rc)
			goto out;
		rc = avc_insert(ssid, tsid, tclass, &entry, &aeref);
		if (rc)
			goto out;
	}

	/* check for a saved compute_create value */
	if (!aeref.ae->create_sid) {
		/* need to query the kernel policy */
		rc = security_compute_create(ssid->ctx, tsid->ctx, tclass,
						 &ctx);
		if (rc)
			goto out;
		rc = sidtab_context_to_sid(&avc_sidtab, ctx, newsid);
		freecon(ctx);
		if (rc)
			goto out;

		aeref.ae->create_sid = *newsid;
	} else {
		/* found saved value */
		*newsid = aeref.ae->create_sid;
	}

	rc = 0;
out:
	avc_release_lock(avc_lock);
	return rc;
}

int avc_add_callback(int (*callback) (uint32_t event, security_id_t ssid,
				      security_id_t tsid,
				      security_class_t tclass,
				      access_vector_t perms,
				      access_vector_t * out_retained),
		     uint32_t events, security_id_t ssid,
		     security_id_t tsid,
		     security_class_t tclass, access_vector_t perms)
{
	struct avc_callback_node *c;
	int rc = 0;

	c = avc_malloc(sizeof(*c));
	if (!c) {
		rc = -1;
		goto out;
	}

	c->callback = callback;
	c->events = events;
	c->ssid = ssid;
	c->tsid = tsid;
	c->tclass = tclass;
	c->perms = perms;
	c->next = avc_callbacks;
	avc_callbacks = c;
      out:
	return rc;
}

static inline int avc_sidcmp(security_id_t x, security_id_t y)
{
	return (x == y || x == SECSID_WILD || y == SECSID_WILD);
}

static inline void avc_update_node(uint32_t event, struct avc_node *node,
				   access_vector_t perms)
{
	switch (event) {
	case AVC_CALLBACK_GRANT:
		node->ae.avd.allowed |= perms;
		break;
	case AVC_CALLBACK_TRY_REVOKE:
	case AVC_CALLBACK_REVOKE:
		node->ae.avd.allowed &= ~perms;
		break;
	case AVC_CALLBACK_AUDITALLOW_ENABLE:
		node->ae.avd.auditallow |= perms;
		break;
	case AVC_CALLBACK_AUDITALLOW_DISABLE:
		node->ae.avd.auditallow &= ~perms;
		break;
	case AVC_CALLBACK_AUDITDENY_ENABLE:
		node->ae.avd.auditdeny |= perms;
		break;
	case AVC_CALLBACK_AUDITDENY_DISABLE:
		node->ae.avd.auditdeny &= ~perms;
		break;
	}
}

static int avc_update_cache(uint32_t event, security_id_t ssid,
			    security_id_t tsid, security_class_t tclass,
			    access_vector_t perms)
{
	struct avc_node *node;
	int i;

	avc_get_lock(avc_lock);

	if (ssid == SECSID_WILD || tsid == SECSID_WILD) {
		/* apply to all matching nodes */
		for (i = 0; i < AVC_CACHE_SLOTS; i++) {
			for (node = avc_cache.slots[i]; node; node = node->next) {
				if (avc_sidcmp(ssid, node->ae.ssid) &&
				    avc_sidcmp(tsid, node->ae.tsid) &&
				    tclass == node->ae.tclass) {
					avc_update_node(event, node, perms);
				}
			}
		}
	} else {
		/* apply to one node */
		node = avc_search_node(ssid, tsid, tclass, 0);
		if (node) {
			avc_update_node(event, node, perms);
		}
	}

	avc_release_lock(avc_lock);

	return 0;
}

/* avc_control - update cache and call callbacks
 *
 * This should not be called directly; use the individual event
 * functions instead.
 */
static int avc_control(uint32_t event, security_id_t ssid,
		       security_id_t tsid, security_class_t tclass,
		       access_vector_t perms,
		       uint32_t seqno, access_vector_t * out_retained)
{
	struct avc_callback_node *c;
	access_vector_t tretained = 0, cretained = 0;
	int ret, rc = 0, errsave = 0;
	errno = 0;

	/*
	 * try_revoke only removes permissions from the cache
	 * state if they are not retained by the object manager.
	 * Hence, try_revoke must wait until after the callbacks have
	 * been invoked to update the cache state.
	 */
	if (event != AVC_CALLBACK_TRY_REVOKE)
		avc_update_cache(event, ssid, tsid, tclass, perms);

	for (c = avc_callbacks; c; c = c->next) {
		if ((c->events & event) &&
		    avc_sidcmp(c->ssid, ssid) &&
		    avc_sidcmp(c->tsid, tsid) &&
		    c->tclass == tclass && (c->perms & perms)) {
			cretained = 0;
			ret = c->callback(event, ssid, tsid, tclass,
					  (c->perms & perms), &cretained);
			if (ret && !rc) {
				rc = ret;
				errsave = errno;
			}
			if (!ret)
				tretained |= cretained;
		}
	}

	if (event == AVC_CALLBACK_TRY_REVOKE) {
		/* revoke any unretained permissions */
		perms &= ~tretained;
		avc_update_cache(event, ssid, tsid, tclass, perms);
		*out_retained = tretained;
	}

	avc_get_lock(avc_lock);
	if (seqno > avc_cache.latest_notif)
		avc_cache.latest_notif = seqno;
	avc_release_lock(avc_lock);

	errno = errsave;
	return rc;
}

/**
 * avc_ss_grant - Grant previously denied permissions.
 * @ssid: source security identifier or %SECSID_WILD
 * @tsid: target security identifier or %SECSID_WILD
 * @tclass: target security class
 * @perms: permissions to grant
 * @seqno: policy sequence number
 */
int avc_ss_grant(security_id_t ssid, security_id_t tsid,
		 security_class_t tclass, access_vector_t perms,
		 uint32_t seqno)
{
	return avc_control(AVC_CALLBACK_GRANT,
			   ssid, tsid, tclass, perms, seqno, 0);
}

/**
 * avc_ss_try_revoke - Try to revoke previously granted permissions.
 * @ssid: source security identifier or %SECSID_WILD
 * @tsid: target security identifier or %SECSID_WILD
 * @tclass: target security class
 * @perms: permissions to grant
 * @seqno: policy sequence number
 * @out_retained: subset of @perms that are retained
 *
 * Try to revoke previously granted permissions, but
 * only if they are not retained as migrated permissions.
 * Return the subset of permissions that are retained via @out_retained.
 */
int avc_ss_try_revoke(security_id_t ssid, security_id_t tsid,
		      security_class_t tclass,
		      access_vector_t perms, uint32_t seqno,
		      access_vector_t * out_retained)
{
	return avc_control(AVC_CALLBACK_TRY_REVOKE,
			   ssid, tsid, tclass, perms, seqno, out_retained);
}

/**
 * avc_ss_revoke - Revoke previously granted permissions.
 * @ssid: source security identifier or %SECSID_WILD
 * @tsid: target security identifier or %SECSID_WILD
 * @tclass: target security class
 * @perms: permissions to grant
 * @seqno: policy sequence number
 *
 * Revoke previously granted permissions, even if
 * they are retained as migrated permissions.
 */
int avc_ss_revoke(security_id_t ssid, security_id_t tsid,
		  security_class_t tclass, access_vector_t perms,
		  uint32_t seqno)
{
	return avc_control(AVC_CALLBACK_REVOKE,
			   ssid, tsid, tclass, perms, seqno, 0);
}

/**
 * avc_ss_reset - Flush the cache and revalidate migrated permissions.
 * @seqno: policy sequence number
 */
int avc_ss_reset(uint32_t seqno)
{
	int rc;

	rc = avc_reset();

	avc_get_lock(avc_lock);
	if (seqno > avc_cache.latest_notif)
		avc_cache.latest_notif = seqno;
	avc_release_lock(avc_lock);

	return rc;
}

/**
 * avc_ss_set_auditallow - Enable or disable auditing of granted permissions.
 * @ssid: source security identifier or %SECSID_WILD
 * @tsid: target security identifier or %SECSID_WILD
 * @tclass: target security class
 * @perms: permissions to grant
 * @seqno: policy sequence number
 * @enable: enable flag.
 */
int avc_ss_set_auditallow(security_id_t ssid, security_id_t tsid,
			  security_class_t tclass, access_vector_t perms,
			  uint32_t seqno, uint32_t enable)
{
	if (enable)
		return avc_control(AVC_CALLBACK_AUDITALLOW_ENABLE,
				   ssid, tsid, tclass, perms, seqno, 0);
	else
		return avc_control(AVC_CALLBACK_AUDITALLOW_DISABLE,
				   ssid, tsid, tclass, perms, seqno, 0);
}

/**
 * avc_ss_set_auditdeny - Enable or disable auditing of denied permissions.
 * @ssid: source security identifier or %SECSID_WILD
 * @tsid: target security identifier or %SECSID_WILD
 * @tclass: target security class
 * @perms: permissions to grant
 * @seqno: policy sequence number
 * @enable: enable flag.
 */
int avc_ss_set_auditdeny(security_id_t ssid, security_id_t tsid,
			 security_class_t tclass, access_vector_t perms,
			 uint32_t seqno, uint32_t enable)
{
	if (enable)
		return avc_control(AVC_CALLBACK_AUDITDENY_ENABLE,
				   ssid, tsid, tclass, perms, seqno, 0);
	else
		return avc_control(AVC_CALLBACK_AUDITDENY_DISABLE,
				   ssid, tsid, tclass, perms, seqno, 0);
}
