/*
 * Policy management for QI meta-API.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <syslog.h>
#include <errno.h>
#include "qick.h"

static char rcsid[] = "$Id: policy.c,v 2.3 2000/08/18 19:27:34 dgc Exp $";


/*
 * Construct a new policy (optionally named "name").
 */
qip_t *
qip_new(qick_t *qick, char *name)
{
	qip_t	*qp;
	list_t	**listp;

	if ((qp = malloc(sizeof(qip_t))) == NULL)
		return NULL;
	qp->qip_first = qp->qip_last = NULL;
	qip_error(qp, "");

	qip_name(qp, name);

	/* Add new policy to policy chain, so we can find by name. */
	LIST_END(listp, qick->q_policies);
	if ((*listp = LIST_NEW) == NULL) {
		syslog(LOG_ERR, "policy.c:qip_new() cannot append policy chain: %s", strerror(errno));
	} else {
		LIST_SET(*listp, qp);
		LIST_LINK(*listp, NULL);
	}

	return qp;
}

/*
 * Recursively destroy a policy tree.
 */
void
qip_free(qip_t *qp)
{
	qit_t	*qt, *tqt;

	qt = qp->qip_first;
	while (qt != NULL) {
		tqt = qt->qit_next;
		qit_free(qt);
		qt = tqt;
	}

	free(qp);
}

/*
 * Create a new policy term.
 */
qit_t *
qit_new(void)
{
	qit_t	*qt;

	qt = malloc(sizeof(qit_t));
	qt->qit_type = QI_POLICY_OP;
	qt->qit_op = QI_POLICY_NOOP;
	qt->qit_top = NULL;
	qt->qit_next = NULL;
	return qt;
}

/*
 * Destroy a single policy term.
 */
void
qit_free(qit_t *qit)
{
	if (qit->qit_type == QI_POLICY_OP)
		/* nothing */ ;
	else if (qit->qit_type == QI_POLICY_LINK && qit->qit_link != NULL)
		/* nothing */ ;
	else if (qit->qit_type == QI_POLICY_QUERY && qit->qit_query != NULL)
		free(qit->qit_query);
	else if (qit->qit_type == QI_POLICY_ERE && qit->qit_ere != NULL) {
		regfree(&qit->qit_ere->rx_regex);
		if (qit->qit_ere->rx_field != NULL)
			free(qit->qit_ere->rx_field);
		if (qit->qit_ere->rx_ere != NULL)
			free(qit->qit_ere->rx_ere);
		free(qit->qit_ere);
	}

	free(qit);
	return;
}

/*
 * Get/set a policy name.
 */
char *
qip_name(qip_t *qip, char *name)
{
	if (name == NULL)
		return qip->qip_name;

	if (qip->qip_name != NULL)
		free(qip->qip_name);

	qip->qip_name = strdup(name);

	qi_debug("= assigning policy 0x%08x name \"%s\"\n",
		qip, qip->qip_name);

	return qip->qip_name;
}

/*
 * Set an operation on a policy term.
 */
qit_t *
qit_setop(qit_t *qit, int op)
{
	qit->qit_type = QI_POLICY_OP;
	qit->qit_op = op;

	qi_debug("= assigned policy term 0x%08x op %d\n",
		qit, qit->qit_op);

	return qit;
}

/*
 * Set query expression for a policy term.
 */
qit_t *
qit_setquery(qit_t *qit, char *query)
{
	qit->qit_type = QI_POLICY_QUERY;
	qit->qit_query = strdup(query);

	qi_debug("= assigned policy term 0x%08x query \"%s\"\n",
		qit, qit->qit_query);

	return qit;
}

/*
 * Set ERE matching expression for a policy term.
 */
qit_t *
qit_setmatch(qit_t *qit, char *field, char *ere, int flags, char *err, int elen)
{
	int	 rc, rcflags;

	qit->qit_type = QI_POLICY_ERE;
	if ((qit->qit_ere = malloc(sizeof(qi_regex_t))) == NULL)
		return NULL;

	qit->qit_ere->rx_field = strdup(field);
	qit->qit_ere->rx_ere = strdup(ere);
	qit->qit_ere->rx_flags = flags;

	if ((rc = regcomp(&qit->qit_ere->rx_regex, ere, flags)) != REG_OK) {
		if (err != NULL)
			regerror(rc, &qit->qit_ere->rx_regex, err, elen);
		return NULL;
	}

	qi_debug("= assigned policy term 0x%08x match \"%s\" ~ /%s/%s\n",
		qit, qit->qit_ere->rx_field, qit->qit_ere->rx_ere,
		qit->qit_ere->rx_flags & QI_POLICY_RE_ICASE ? "i" : "");

	return qit;
}

/*
 * Link one policy term to another policy, as a gloppy term.
 */
qit_t *
qit_setlink(qit_t *qit, qip_t *link)
{
	qit->qit_type = QI_POLICY_LINK;
	qit->qit_link = link;

	qi_debug("= assigned policy 0x%08x link %08x\n",
		qit, qit->qit_link);

	return qit;
}

/*
 * Connect a policy or term to the next term in sequence.
 */
qit_t *
qit_cat(qit_t *qit, qit_t *next)
{
	qit_t	*saved;

	saved = qit->qit_next;
	qit->qit_next = next;
	next->qit_next = saved;

	return next;
}

/*
 * Attach a new term to a policy.  If term is NULL, create a new, empty one.
 */
qit_t *
qip_extend(qip_t *qip, qit_t *qit)
{
	if (qit == NULL) {
		if ((qit = qit_new()) == NULL)
			return NULL;
	}

	qi_debug("= extending policy 0x%08x with term 0x%08x\n",
		qip, qit);

	if (qip->qip_first == NULL) {
		return qip->qip_first = qip->qip_last = qit;
	}

	qit_cat(qip->qip_last, qit);
	qip->qip_last = qit;
	return qit;
}

/*
 * Stack trace for debugging policy evaluation.
 */
static void
_dump_stack(int *stack, int depth)
{
	int	 i;

	if (depth >= 0) {
		fprintf(stderr, "@ %02d [", depth);
		for (i = 0; i <= depth; ++i)
			fprintf(stderr, " %d", stack[i]);
		fprintf(stderr, " ]\n");
#if 0
	} else {
		fprintf(stderr, "PSTACK [ ]\n");
#endif
	}
}

/*
 * Guts of the policy evaluator -- for qip() and qip_trace().
 */
int
_qip_exec(qip_t *qip, qir_t *qir, int trace)
{
	int	 stack[QI_POLICY_STACK_DEPTH];
	int	 i, top;
	char	*qv;
	qit_t	*qt;

	i = -1;
	if (trace) {
		_dump_stack(stack, i);
		fprintf(stderr, "@ policy: 0x%08x [%s]\n",
			qip, S(qip->qip_name));
	}

	qt = qip->qip_first;
	/*while (qt != NULL && qt != qip->qip_last) {*/
	while (qt != NULL) {
		if (trace)
			fprintf(stderr, "@ term: 0x%08x [%d]\n",
				qt, qt->qit_type);
		switch (qt->qit_type) {
			case QI_POLICY_QUERY:
				/* No can do yet. */
				stack[++i] = QI_POLICY_FALSE;
				if (trace)
					fprintf(stderr, "@ query: %s\n",
						S(qt->qit_query));
				break;
			case QI_POLICY_LINK:
				if (++i > QI_POLICY_STACK_DEPTH)
					return QI_POLICY_OVERFLOW;
				if (trace)
					fprintf(stderr, "@ link: %s\n",
						S(qt->qit_link->qip_name));
				stack[i] = _qip_exec(qt->qit_link, qir, trace);
				if (trace)
					fprintf(stderr, "@ (return)\n");
				break;
			case QI_POLICY_ERE:
				if (++i > QI_POLICY_STACK_DEPTH)
					return QI_POLICY_OVERFLOW;
				qv = qi_field(qir, qt->qit_ere->rx_field);
				if (qv == NULL)
					stack[i] = QI_POLICY_FALSE;
				else if (regexec(&qt->qit_ere->rx_regex,
					 qv, 0, NULL, 0) == 0)
					stack[i] = QI_POLICY_TRUE;
				else
					stack[i] = QI_POLICY_FALSE;
				if (trace)
					fprintf(stderr, "@ match: %s: \"%s\" ~ /%s/%s\n",
						S(qt->qit_ere->rx_field),
						S(qv),
						S(qt->qit_ere->rx_ere),
						qt->qit_ere->rx_flags & QI_POLICY_RE_ICASE ? "i" : "");
				break;
			case QI_POLICY_OP:
				if (trace)
					fprintf(stderr, "@ op [%d] ",
						qt->qit_op);
				if (qt->qit_op == QI_POLICY_AND) {
					if (i == 0)
						return QI_POLICY_UNDERFLOW;
					top = stack[i--];
					stack[i] = stack[i] && top;
					if (trace)
						fprintf(stderr, "and:\n");
				} else if (qt->qit_op == QI_POLICY_OR) {
					if (i == 0)
						return QI_POLICY_UNDERFLOW;
					top = stack[i--];
					stack[i] = stack[i] || top;
					if (trace)
						fprintf(stderr, "or:\n");
				} else if (qt->qit_op == QI_POLICY_XOR) {
					if (i == 0)
						return QI_POLICY_UNDERFLOW;
					top = stack[i--];
					if ((stack[i] && top) || (!stack[i] && !top))
						stack[i] = QI_POLICY_FALSE;
					else
						stack[i] = QI_POLICY_FALSE;
					if (trace)
						fprintf(stderr, "xor:\n");
				} else if (qt->qit_op == QI_POLICY_NOT) {
					stack[i] = !stack[i];
					if (trace)
						fprintf(stderr, "not:\n");
				} else {
					/* QI_POLICY_NOOP */
					if (trace)
						fprintf(stderr, "noop:\n");
				}
				break;
			default:
				/* noop */
				if (trace)
					fprintf(stderr, "@ default:\n");
				break;
		}

		if (trace)
			_dump_stack(stack, i);
		qt = qt->qit_next;
	}

	return stack[i];
}

/*
 * Evaluate ("execute") ap policy against a given qi response.  The
 * response (in qir_t *qir), must already contain all parameters that
 * the policy will check.  For best results, pass it the results of
 * a "query ... return all" query.
 */
int
qip_exec(qip_t *qp, qir_t *qir)
{
#ifdef DEBUG
	return _qip_exec(qp, qir, 1);
#else
	return _qip_exec(qp, qir, 0);
#endif
}

/*
 * Evaluate a policy as above, but include debugging output and
 * stack traces.
 */
int
qip_trace(qip_t *qp, qir_t *qir)
{
	return _qip_exec(qp, qir, 1);
}

/*
 * Default policy.
 */
qip_t *
qip_default(qick_t *qick)
{
	static qip_t	*dflt = NULL;
	qit_t		*qt;

	if (dflt != NULL)
		return dflt;

	/* Anchor the policy with a name. */
	dflt = qip_new(qick, "default");

	/* First term: NOT field "locked" with any value. */
	qt = qip_extend(dflt, NULL);
	qit_setmatch(qt, "locked", ".", QI_POLICY_RE_BASIC, NULL, 0);

	/* Second term: invert the previous. */
	qt = qip_extend(dflt, NULL);
	qit_setop(qt, QI_POLICY_NOT);

	/* Third term: field "type" matching "^p" */
	qt = qip_extend(dflt, NULL);
	qit_setmatch(qt, "type", "^p", QI_POLICY_RE_BASIC | QI_POLICY_RE_ICASE,
			NULL, 0);

	/* Last term: unify */
	qt = qip_extend(dflt, NULL);
	qit_setop(qt, QI_POLICY_AND);

	/* Good enough for government work. */
	return dflt;
}

char *
qip_error(qip_t *qip, char *set)
{
	if (set != NULL)
		strncpy(qip->qip_err, set, sizeof(qip->qip_err)-1);
	return S(qip->qip_err);
}

qip_t *
qip_find(qick_t *qick, char *name)
{
	list_t	*l;

	if (!strcmp(name, "default"))
		return qip_default(qick);

	LIST_FOREACH(l, qick->q_policies) {
		if (strcmp(((qip_t *)LIST_DATUM(l))->qip_name, name) == 0) {
			return (qip_t *)(LIST_DATUM(l));
		}
	}

	return NULL;
}

