/**
 *  @file
 *  Implementation of seaudit_model_t.
 *
 *  @author Jeremy A. Mowery jmowery@tresys.com
 *  @author Jason Tang jtang@tresys.com
 *
 *  Copyright (C) 2004-2007 Tresys Technology, LLC
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "seaudit_internal.h"

#include <apol/bst.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <libxml/uri.h>

#define DEFAULT_MODEL_NAME "Untitled"

struct seaudit_model
{
	char *name;
	/** vector of seaudit_log_t pointers; this model will get
	 * messages from these logs */
	apol_vector_t *logs;
	/** vector of seaudit_message_t pointers; these point into
	 * messages from the watched logs (only valid if dirty == 0) */
	apol_vector_t *messages;
	/** vector of char * pointers; these point into malformed
	 * messages from the watched logs (only valid if dirty == 0) */
	apol_vector_t *malformed_messages;
	/** list of messages to hide */
	apol_bst_t *hidden_messages;
	/** vector of seaudit_filter_t */
	apol_vector_t *filters;
	/** if more than one filter is being applied, then accept
         * messages if any match or if all match */
	seaudit_filter_match_e match;
	/** if a filter is being applied, then either show/hide
         * messages selected by filter */
	seaudit_filter_visible_e visible;
	/** vector of seaudit_sort_t, order from highest priority to lowest */
	apol_vector_t *sorts;
	/** number of allow messages in the model (only valid if dirty == 0) */
	size_t num_allows;
	/** number of deny messages in the model (only valid if dirty == 0) */
	size_t num_denies;
	/** number of boolean changes in the model (only valid if dirty == 0) */
	size_t num_bools;
	/** number of policy loads in the model (only valid if dirty == 0) */
	size_t num_loads;
	/** non-zero whenever this model needs to be recalculated */
	int dirty;
};

/**
 * Apply all of the model's filters to the message.
 *
 * @param model Model containing filters to apply.
 * @param m Message to check.
 *
 * @return Non-zero if the message is accepted by the filters, 0 if not.
 */
static int model_filter_message(seaudit_model_t * model, const seaudit_message_t * m)
{
	size_t i;
	int compval, filters_passed = 0;
	if (apol_vector_get_size(model->filters) == 0) {
		return 1;
	}
	for (i = 0; i < apol_vector_get_size(model->filters); i++) {
		seaudit_filter_t *f = apol_vector_get_element(model->filters, i);
		compval = filter_is_accepted(f, m);
		if (compval) {
			if (model->match == SEAUDIT_FILTER_MATCH_ANY) {
				return 1;
			}
			filters_passed++;
		} else {
			if (model->match == SEAUDIT_FILTER_MATCH_ALL) {
				return 0;
			}
		}
	}
	if (model->match == SEAUDIT_FILTER_MATCH_ANY) {
		/* if got here, then no filters were met */
		return 0;
	}
	/* if got here, then all criteria were met */
	if (filters_passed) {
		return 1;
	}
	return 0;
}

/**
 * Callback for sorting the model's messages vector.
 *
 * @param a First message to compare.
 * @param b Second message to compare.
 * @param data Pointer to the model being sorted.
 *
 * @return 0 if the messages are equivalent, < 0 if a is first, > 0 if
 * b is first.
 */
static int message_comp(const void *a, const void *b, void *data)
{
	const seaudit_message_t *m1 = a;
	const seaudit_message_t *m2 = b;
	seaudit_model_t *model = data;
	size_t i;
	seaudit_sort_t *s;
	int compval, s1, s2;
	for (i = 0; i < apol_vector_get_size(model->sorts); i++) {
		s = apol_vector_get_element(model->sorts, i);
		s1 = sort_is_supported(s, m1);
		s2 = sort_is_supported(s, m2);
		if (!s1 && !s2) {
			continue;
		}
		if (!s2) {
			return -1;
		}
		if (!s1) {
			return 1;
		}
		if ((compval = sort_comp(s, m1, m2)) != 0) {
			return compval;
		}
	}
	return 0;
}

/**
 * Sort the model's messages.  Create two temporary vectors.  The
 * first holds messages that are sortable, according to the list of
 * sort objects.  Sort them in their priority order.  The second
 * vector holds messages that are not sortable; append those messages
 * to the end of the first (now sorted) vector.
 *
 * @param log Error handling log.
 * @param model Model to sort.
 *
 * @return 0 on successful sort, < 0 on error.
 */
static int model_sort(const seaudit_log_t * log, seaudit_model_t * model)
{
	size_t i, j, num_messages = apol_vector_get_size(model->messages);
	apol_vector_t *sup = NULL, *unsup = NULL;
	seaudit_message_t *m;
	seaudit_sort_t *s;
	int supported = 0, retval = -1, error = 0;
	if (apol_vector_get_size(model->sorts) == 0) {
		retval = 0;
		goto cleanup;
	}

	if ((sup = apol_vector_create_with_capacity(num_messages, NULL)) == NULL ||
	    (unsup = apol_vector_create_with_capacity(num_messages, NULL)) == NULL) {
		error = errno;
		ERR(log, "%s", strerror(error));
		goto cleanup;
	}
	for (i = 0; i < num_messages; i++) {
		m = apol_vector_get_element(model->messages, i);
		supported = 0;
		for (j = 0; j < apol_vector_get_size(model->sorts); j++) {
			s = apol_vector_get_element(model->sorts, j);
			if ((supported = sort_is_supported(s, m)) != 0) {
				break;
			}
		}
		if ((supported && apol_vector_append(sup, m) < 0) || (!supported && apol_vector_append(unsup, m) < 0)) {
			error = errno;
			ERR(log, "%s", strerror(error));
			goto cleanup;
		}
	}
	apol_vector_sort(sup, message_comp, model);
	if (apol_vector_cat(sup, unsup) < 0) {
		error = errno;
		ERR(log, "%s", strerror(error));
		goto cleanup;
	}
	apol_vector_destroy(&model->messages);
	model->messages = sup;
	sup = NULL;
	retval = 0;
      cleanup:
	apol_vector_destroy(&sup);
	apol_vector_destroy(&unsup);
	if (retval != 0) {
		errno = error;
	}
	return retval;
}

/**
 * Iterate through the model's messages and recalculate the number of
 * each type of message is stored within.
 *
 * @param model Model to recalculate.
 */
static void model_recalc_stats(seaudit_model_t * model)
{
	size_t i;
	seaudit_message_t *msg;
	seaudit_message_type_e type;
	void *v;
	seaudit_avc_message_t *avc;
	model->num_allows = model->num_denies = model->num_bools = model->num_loads = 0;
	for (i = 0; i < apol_vector_get_size(model->messages); i++) {
		msg = apol_vector_get_element(model->messages, i);
		v = seaudit_message_get_data(msg, &type);
		if (type == SEAUDIT_MESSAGE_TYPE_AVC) {
			avc = (seaudit_avc_message_t *) v;
			if (avc->msg == SEAUDIT_AVC_DENIED) {
				model->num_denies++;
			} else if (avc->msg == SEAUDIT_AVC_GRANTED) {
				model->num_allows++;
			}
		} else if (type == SEAUDIT_MESSAGE_TYPE_BOOL) {
			model->num_bools++;
		} else if (type == SEAUDIT_MESSAGE_TYPE_LOAD) {
			model->num_loads++;
		}
	}
}

/**
 * Recalculate all of the messages associated with a particular model,
 * based upon that model's criteria.  If the model is marked as not
 * dirty then do nothing and return success.
 *
 * @param log Log to which report error messages.
 * @param model Model whose messages list to refresh.
 *
 * @return 0 on success, < 0 on error.
 */
static int model_refresh(const seaudit_log_t * log, seaudit_model_t * model)
{
	size_t i, j;
	seaudit_log_t *l;
	const apol_vector_t *v;
	seaudit_message_t *message;
	void *result;
	int error, filter_match;

	if (!model->dirty) {
		return 0;
	}
	apol_vector_destroy(&model->messages);
	apol_vector_destroy(&model->malformed_messages);
	if ((model->messages = apol_vector_create(NULL)) == NULL || (model->malformed_messages = apol_vector_create(NULL)) == NULL) {
		error = errno;
		ERR(log, "%s", strerror(error));
		errno = error;
		return -1;
	}
	for (i = 0; i < apol_vector_get_size(model->logs); i++) {
		l = apol_vector_get_element(model->logs, i);
		v = log_get_messages(l);
		for (j = 0; j < apol_vector_get_size(v); j++) {
			message = apol_vector_get_element(v, j);
			if (apol_bst_get_element(model->hidden_messages, message, NULL, &result) == 0) {
				continue;
			}
			filter_match = model_filter_message(model, message);
			if (((filter_match && model->visible == SEAUDIT_FILTER_VISIBLE_SHOW) ||
			     (!filter_match && model->visible == SEAUDIT_FILTER_VISIBLE_HIDE)) &&
			    apol_vector_append(model->messages, message) < 0) {
				error = errno;
				ERR(log, "%s", strerror(error));
				errno = error;
				return -1;
			}
		}
		v = log_get_malformed_messages(l);
		if (apol_vector_cat(model->malformed_messages, v) < 0) {
			error = errno;
			ERR(log, "%s", strerror(error));
			errno = error;
			return -1;
		}
	}
	if (model_sort(log, model) < 0) {
		return -1;
	}
	model_recalc_stats(model);
	model->dirty = 0;
	return 0;
}

/**
 * Callback invoked when free()ing a vector of filters.
 *
 * @param v Filter object to free.
 */
static void filter_free(void *v)
{
	seaudit_filter_t *f = v;
	seaudit_filter_destroy(&f);
}

/**
 * Callback invoked when free()ing a vector of sort objects.
 *
 * @param v Sort object to free.
 */
static void sort_free(void *v)
{
	seaudit_sort_t *sort = v;
	seaudit_sort_destroy(&sort);
}

seaudit_model_t *seaudit_model_create(const char *name, seaudit_log_t * log)
{
	seaudit_model_t *m = NULL;
	int error;
	if ((m = calloc(1, sizeof(*m))) == NULL) {
		error = errno;
		ERR(log, "%s", strerror(error));
		errno = error;
		return NULL;
	}
	if (name == NULL) {
		name = DEFAULT_MODEL_NAME;
	}
	if ((m->name = strdup(name)) == NULL ||
	    (m->logs = apol_vector_create_with_capacity(1, NULL)) == NULL ||
	    (m->hidden_messages = apol_bst_create(NULL, NULL)) == NULL ||
	    (m->filters = apol_vector_create_with_capacity(1, filter_free)) == NULL ||
	    (m->sorts = apol_vector_create_with_capacity(1, sort_free)) == NULL) {
		error = errno;
		seaudit_model_destroy(&m);
		ERR(log, "%s", strerror(error));
		errno = error;
		return NULL;
	}
	if (log != NULL) {
		if (apol_vector_append(m->logs, log) < 0 || log_append_model(log, m)) {
			error = errno;
			seaudit_model_destroy(&m);
			ERR(log, "%s", strerror(error));
			errno = error;
			return NULL;
		}
	}
	m->dirty = 1;
	return m;
}

static void *model_filter_dup(const void *elem, void *data)
{
	const seaudit_filter_t *filter = elem;
	seaudit_model_t *model = data;
	seaudit_filter_t *f;
	if ((f = seaudit_filter_create_from_filter(filter)) == NULL) {
		return NULL;
	}
	filter_set_model(f, model);
	return f;
}

static void *model_sort_dup(const void *elem, void *data __attribute__ ((unused)))
{
	const seaudit_sort_t *sort = elem;
	seaudit_model_t *model = data;
	seaudit_sort_t *s;
	if ((s = sort_create_from_sort(sort)) == NULL) {
		return NULL;
	}
	if (seaudit_model_append_sort(model, s) < 0) {
		seaudit_sort_destroy(&s);
		return NULL;
	}
	return s;
}

seaudit_model_t *seaudit_model_create_from_model(const seaudit_model_t * model)
{
	seaudit_model_t *m = NULL;
	int error = 0;
	size_t i;
	const char *name;

	if (model == NULL) {
		error = EINVAL;
		goto cleanup;
	}
	if ((m = calloc(1, sizeof(*m))) == NULL) {
		error = errno;
		goto cleanup;
	}
	if ((name = model->name) == NULL) {
		name = "Untitled";
	}
	if ((m->name = strdup(name)) == NULL) {
		error = errno;
		goto cleanup;
	}
	m->dirty = 1;
	if ((m->logs = apol_vector_create_from_vector(model->logs, NULL, NULL, NULL)) == NULL) {
		error = errno;
		goto cleanup;
	}
	if ((m->filters = apol_vector_create_from_vector(model->filters, model_filter_dup, (void *)m, filter_free)) == NULL) {
		error = errno;
		goto cleanup;
	}
	if ((m->sorts = apol_vector_create_from_vector(model->sorts, model_sort_dup, (void *)m, sort_free)) == NULL) {
		error = errno;
		goto cleanup;
	}
	m->match = model->match;
	m->visible = model->visible;
	/* link this new model to the old model's logs */
	for (i = 0; i < apol_vector_get_size(m->logs); i++) {
		seaudit_log_t *log = apol_vector_get_element(m->logs, i);
		if (log_append_model(log, m) < 0) {
			error = errno;
			goto cleanup;
		}
	}
      cleanup:
	if (error != 0) {
		seaudit_model_destroy(&m);
		errno = error;
		return NULL;
	}
	return m;
}

seaudit_model_t *seaudit_model_create_from_file(const char *filename)
{
	struct filter_parse_state state;
	int retval, error;
	seaudit_model_t *m;
	memset(&state, 0, sizeof(state));
	if ((state.filters = apol_vector_create(filter_free)) == NULL) {
		return NULL;
	}
	retval = filter_parse_xml(&state, filename);
	if (retval < 0) {
		error = errno;
		free(state.view_name);
		apol_vector_destroy(&state.filters);
		errno = errno;
		return NULL;
	}
	if ((m = seaudit_model_create(state.view_name, NULL)) == NULL) {
		error = errno;
		free(state.view_name);
		apol_vector_destroy(&state.filters);
		errno = error;
		return NULL;
	}
	free(state.view_name);
	apol_vector_destroy(&m->filters);
	m->filters = state.filters;
	state.filters = NULL;
	seaudit_model_set_filter_match(m, state.view_match);
	seaudit_model_set_filter_visible(m, state.view_visible);
	return m;
}

void seaudit_model_destroy(seaudit_model_t ** model)
{
	size_t i;
	if (model == NULL || *model == NULL) {
		return;
	}
	for (i = 0; i < apol_vector_get_size((*model)->logs); i++) {
		seaudit_log_t *l = apol_vector_get_element((*model)->logs, i);
		log_remove_model(l, *model);
	}
	free((*model)->name);
	apol_vector_destroy(&(*model)->logs);
	apol_vector_destroy(&(*model)->filters);
	apol_vector_destroy(&(*model)->sorts);
	apol_vector_destroy(&(*model)->messages);
	apol_vector_destroy(&(*model)->malformed_messages);
	apol_bst_destroy(&(*model)->hidden_messages);
	free(*model);
	*model = NULL;
}

int seaudit_model_save_to_file(const seaudit_model_t * model, const char *filename)
{
	FILE *file;
	const char *XML_VER = "<?xml version=\"1.0\"?>\n";
	seaudit_filter_t *filter;
	size_t i;

	if (model == NULL || filename == NULL) {
		errno = EINVAL;
		return -1;
	}
	if ((file = fopen(filename, "w")) == NULL) {
		return -1;
	}
	fprintf(file, XML_VER);
	fprintf(file, "<view xmlns=\"http://oss.tresys.com/projects/setools/seaudit-%s/\" name=\"%s\" match=\"%s\" show=\"%s\">\n",
		FILTER_FILE_FORMAT_VERSION, model->name,
		model->match == SEAUDIT_FILTER_MATCH_ALL ? "all" : "any",
		model->visible == SEAUDIT_FILTER_VISIBLE_SHOW ? "true" : "false");
	for (i = 0; i < apol_vector_get_size(model->filters); i++) {
		filter = apol_vector_get_element(model->filters, i);
		filter_append_to_file(filter, file, 1);
	}
	fprintf(file, "</view>\n");
	fclose(file);
	return 0;
}

const char *seaudit_model_get_name(const seaudit_model_t * model)
{
	if (model == NULL) {
		errno = EINVAL;
		return NULL;
	}
	return model->name;
}

int seaudit_model_set_name(seaudit_model_t * model, const char *name)
{
	char *s;
	if (model == NULL) {
		errno = EINVAL;
		return -1;
	}
	if (name == NULL) {
		name = DEFAULT_MODEL_NAME;
	}
	if ((s = strdup(name)) == NULL) {
		return -1;
	}
	free(model->name);
	model->name = s;
	return 0;
}

int seaudit_model_append_log(seaudit_model_t * model, seaudit_log_t * log)
{
	if (model == NULL || log == NULL) {
		ERR(log, "%s", strerror(EINVAL));
		errno = EINVAL;
		return -1;
	}
	if (apol_vector_append(model->logs, log) < 0 || log_append_model(log, model) < 0) {
		int error = errno;
		ERR(log, "%s", strerror(error));
		errno = error;
		return -1;
	}
	model->dirty = 1;
	return 0;
}

int seaudit_model_append_filter(seaudit_model_t * model, seaudit_filter_t * filter)
{
	if (model == NULL || filter == NULL) {
		errno = EINVAL;
		return -1;
	}
	if (apol_vector_append(model->filters, filter) < 0) {
		return -1;
	}
	filter_set_model(filter, model);
	model->dirty = 1;
	return 0;
}

const apol_vector_t *seaudit_model_get_filters(const seaudit_model_t * model)
{
	if (model == NULL) {
		errno = EINVAL;
		return NULL;
	}
	return model->filters;
}

int seaudit_model_remove_filter(seaudit_model_t * model, seaudit_filter_t * filter)
{
	size_t i;
	if (model == NULL || filter == NULL) {
		errno = EINVAL;
		return -1;
	}
	if (apol_vector_get_index(model->filters, filter, NULL, NULL, &i) < 0) {
		errno = EINVAL;
		return -1;
	}
	seaudit_filter_destroy(&filter);
	apol_vector_remove(model->filters, i);
	model->dirty = 1;
	return 0;
}

int seaudit_model_set_filter_match(seaudit_model_t * model, seaudit_filter_match_e match)
{
	if (model == NULL) {
		errno = EINVAL;
		return -1;
	}
	model->match = match;
	model->dirty = 1;
	return 0;
}

seaudit_filter_match_e seaudit_model_get_filter_match(const seaudit_model_t * model)
{
	if (model == NULL) {
		errno = EINVAL;
		return SEAUDIT_FILTER_MATCH_ALL;
	}
	return model->match;
}

int seaudit_model_set_filter_visible(seaudit_model_t * model, seaudit_filter_visible_e visible)
{
	if (model == NULL) {
		errno = EINVAL;
		return -1;
	}
	model->visible = visible;
	model->dirty = 1;
	return 0;
}

seaudit_filter_visible_e seaudit_model_get_filter_visible(const seaudit_model_t * model)
{
	if (model == NULL) {
		errno = EINVAL;
		return SEAUDIT_FILTER_VISIBLE_SHOW;
	}
	return model->visible;
}

int seaudit_model_append_sort(seaudit_model_t * model, seaudit_sort_t * sort)
{
	if (model == NULL || sort == NULL) {
		errno = EINVAL;
		return -1;
	}
	if (apol_vector_append(model->sorts, sort) < 0) {
		return -1;
	}
	model->dirty = 1;
	return 0;
}

int seaudit_model_clear_sorts(seaudit_model_t * model)
{
	if (model == NULL) {
		errno = EINVAL;
		return -1;
	}
	apol_vector_destroy(&model->sorts);
	if ((model->sorts = apol_vector_create_with_capacity(1, sort_free)) == NULL) {
		return -1;
	}
	model->dirty = 1;
	return 0;
}

int seaudit_model_is_changed(const seaudit_model_t * model)
{
	if (model == NULL) {
		errno = EINVAL;
		return -1;
	}
	return model->dirty;
}

apol_vector_t *seaudit_model_get_messages(const seaudit_log_t * log, seaudit_model_t * model)
{
	if (log == NULL || model == NULL) {
		ERR(log, "%s", strerror(EINVAL));
		errno = EINVAL;
		return NULL;
	}
	if (model_refresh(log, model) < 0) {
		return NULL;
	}
	return apol_vector_create_from_vector(model->messages, NULL, NULL, NULL);
}

apol_vector_t *seaudit_model_get_malformed_messages(const seaudit_log_t * log, seaudit_model_t * model)
{
	if (log == NULL || model == NULL) {
		ERR(log, "%s", strerror(EINVAL));
		errno = EINVAL;
		return NULL;
	}
	if (model_refresh(log, model) < 0) {
		return NULL;
	}
	return apol_vector_create_from_vector(model->malformed_messages, NULL, NULL, NULL);
}

void seaudit_model_hide_message(seaudit_model_t * model, const seaudit_message_t * message)
{
	if (model == NULL) {
		errno = EINVAL;
		return;
	}
	if (message == NULL) {
		return;
	}
	if (apol_bst_insert(model->hidden_messages, (seaudit_message_t *) message, NULL) == 0) {
		model->dirty = 1;
	}
}

size_t seaudit_model_get_num_allows(const seaudit_log_t * log, seaudit_model_t * model)
{
	if (log == NULL || model == NULL) {
		ERR(log, "%s", strerror(EINVAL));
		errno = EINVAL;
		return 0;
	}
	if (model_refresh(log, model) < 0) {
		return 0;
	}
	return model->num_allows;
}

size_t seaudit_model_get_num_denies(const seaudit_log_t * log, seaudit_model_t * model)
{
	if (log == NULL || model == NULL) {
		ERR(log, "%s", strerror(EINVAL));
		errno = EINVAL;
		return 0;
	}
	if (model_refresh(log, model) < 0) {
		return 0;
	}
	return model->num_denies;
}

size_t seaudit_model_get_num_bools(const seaudit_log_t * log, seaudit_model_t * model)
{
	if (log == NULL || model == NULL) {
		ERR(log, "%s", strerror(EINVAL));
		errno = EINVAL;
		return 0;
	}
	if (model_refresh(log, model) < 0) {
		return 0;
	}
	return model->num_bools;
}

size_t seaudit_model_get_num_loads(const seaudit_log_t * log, seaudit_model_t * model)
{
	if (log == NULL || model == NULL) {
		ERR(log, "%s", strerror(EINVAL));
		errno = EINVAL;
		return 0;
	}
	if (model_refresh(log, model) < 0) {
		return 0;
	}
	return model->num_loads;
}

/******************** protected functions below ********************/

void model_remove_log(seaudit_model_t * model, seaudit_log_t * log)
{
	size_t i;
	if (apol_vector_get_index(model->logs, log, NULL, NULL, &i) == 0) {
		apol_vector_remove(model->logs, i);
		model->dirty = 1;
	}
}

void model_notify_log_changed(seaudit_model_t * model, seaudit_log_t * log)
{
	size_t i;
	if (apol_vector_get_index(model->logs, log, NULL, NULL, &i) == 0) {
		model->dirty = 1;
	}
}

void model_notify_filter_changed(seaudit_model_t * model, seaudit_filter_t * filter)
{
	size_t i;
	if (apol_vector_get_index(model->filters, filter, NULL, NULL, &i) == 0) {
		model->dirty = 1;
	}
}
