/**
 *  @file
 *  Main driver for the seaudit application.  This file also
 *  implements the main class seaudit_t.
 *
 *  @author Jeremy A. Mowery jmowery@tresys.com
 *  @author Jason Tang jtang@tresys.com
 *  @author Jeremy Solt jsolt@tresys.com
 *
 *  Copyright (C) 2003-2007 Tresys Technology, LLC
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <config.h>

#include "seaudit.h"
#include "toplevel.h"

#include <apol/util.h>
#include <seaudit/model.h>
#include <seaudit/parse.h>
#include <seaudit/util.h>

#include <errno.h>
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <glade/glade.h>
#include <glib.h>
#include <gtk/gtk.h>

struct seaudit
{
	preferences_t *prefs;
	apol_policy_t *policy;
	apol_policy_path_t *policy_path;
	seaudit_log_t *log;
	FILE *file;
	char *log_path;
	size_t num_log_messages;
	const struct tm *first, *last;
	toplevel_t *top;
};

static struct option const opts[] = {
	{"log", required_argument, NULL, 'l'},
	{"help", no_argument, NULL, 'h'},
	{"version", no_argument, NULL, 'V'},
	{NULL, 0, NULL, 0}
};

preferences_t *seaudit_get_prefs(seaudit_t * s)
{
	return s->prefs;
}

void seaudit_set_policy(seaudit_t * s, apol_policy_t * policy, apol_policy_path_t * path)
{
	if (policy != NULL) {
		if (preferences_add_recent_policy(s->prefs, path) < 0) {
			toplevel_ERR(s->top, "%s", strerror(errno));
			apol_policy_destroy(&policy);
			return;
		}
		apol_policy_destroy(&s->policy);
		s->policy = policy;
		if (path != s->policy_path) {
			apol_policy_path_destroy(&s->policy_path);
		}
		s->policy_path = path;
	} else {
		apol_policy_destroy(&s->policy);
		apol_policy_path_destroy(&s->policy_path);
	}
}

apol_policy_t *seaudit_get_policy(seaudit_t * s)
{
	return s->policy;
}

apol_policy_path_t *seaudit_get_policy_path(seaudit_t * s)
{
	return s->policy_path;
}

void seaudit_set_log(seaudit_t * s, seaudit_log_t * log, FILE * f, const char *filename)
{
	if (s->file != NULL) {
		fclose(s->file);
		s->file = NULL;
	}
	if (log != NULL) {
		seaudit_model_t *model = NULL;
		apol_vector_t *messages = NULL;
		char *t = NULL;
		if ((model = seaudit_model_create(NULL, log)) == NULL ||
		    (messages = seaudit_model_get_messages(log, model)) == NULL ||
		    (t = strdup(filename)) == NULL || preferences_add_recent_log(s->prefs, filename) < 0) {
			toplevel_ERR(s->top, "%s", strerror(errno));
			seaudit_log_destroy(&log);
			seaudit_model_destroy(&model);
			apol_vector_destroy(&messages);
			free(t);
			return;
		}
		/* do it in this order, for filename could be pointing to
		 * s->log_path */
		seaudit_log_destroy(&s->log);
		s->log = log;
		s->file = f;
		free(s->log_path);
		s->log_path = t;
		s->num_log_messages = apol_vector_get_size(messages);
		if (s->num_log_messages == 0) {
			s->first = s->last = NULL;
		} else {
			seaudit_message_t *message = apol_vector_get_element(messages, 0);
			s->first = seaudit_message_get_time(message);
			message = apol_vector_get_element(messages, s->num_log_messages - 1);
			s->last = seaudit_message_get_time(message);
		}
		seaudit_model_destroy(&model);
		apol_vector_destroy(&messages);
	} else {
		seaudit_log_destroy(&s->log);
		free(s->log_path);
		s->log_path = NULL;
		s->num_log_messages = 0;
		s->first = s->last = NULL;
	}
}

int seaudit_parse_log(seaudit_t * s)
{
	return seaudit_log_parse(s->log, s->file);
}

seaudit_log_t *seaudit_get_log(seaudit_t * s)
{
	return s->log;
}

char *seaudit_get_log_path(seaudit_t * s)
{
	return s->log_path;
}

apol_vector_t *seaudit_get_log_users(seaudit_t * s)
{
	if (s->log == NULL) {
		return NULL;
	} else {
		return seaudit_log_get_users(s->log);
	}
}

apol_vector_t *seaudit_get_log_roles(seaudit_t * s)
{
	if (s->log == NULL) {
		return NULL;
	} else {
		return seaudit_log_get_roles(s->log);
	}
}

apol_vector_t *seaudit_get_log_types(seaudit_t * s)
{
	if (s->log == NULL) {
		return NULL;
	} else {
		return seaudit_log_get_types(s->log);
	}
}

apol_vector_t *seaudit_get_log_mls_lvl(seaudit_t * s)
{
	if (s->log == NULL) {
		return NULL;
	} else {
		return seaudit_log_get_mls_lvl(s->log);
	}
}

apol_vector_t *seaudit_get_log_mls_clr(seaudit_t * s)
{
	if (s->log == NULL) {
		return NULL;
	} else {
		return seaudit_log_get_mls_clr(s->log);
	}
}

apol_vector_t *seaudit_get_log_classes(seaudit_t * s)
{
	if (s->log == NULL) {
		return NULL;
	} else {
		return seaudit_log_get_classes(s->log);
	}
}

size_t seaudit_get_num_log_messages(seaudit_t * s)
{
	return s->num_log_messages;
}

const struct tm *seaudit_get_log_first(seaudit_t * s)
{
	return s->first;
}

const struct tm *seaudit_get_log_last(seaudit_t * s)
{
	return s->last;
}

static seaudit_t *seaudit_create(preferences_t * prefs)
{
	seaudit_t *s = calloc(1, sizeof(*s));
	if (s != NULL) {
		s->prefs = prefs;
	}
	return s;
}

static void seaudit_destroy(seaudit_t ** s)
{
	if (s != NULL && *s != NULL) {
		apol_policy_destroy(&(*s)->policy);
		seaudit_log_destroy(&(*s)->log);
		if ((*s)->file != NULL) {
			fclose((*s)->file);
		}
		preferences_destroy(&(*s)->prefs);
		toplevel_destroy(&(*s)->top);
		free((*s)->policy_path);
		free((*s)->log_path);
		free(*s);
		*s = NULL;
	}
}

static void print_version_info(void)
{
	printf("seaudit %s\n%s\n", VERSION, COPYRIGHT_INFO);
}

static void print_usage_info(const char *program_name, int brief)
{
	printf("Usage: %s [OPTIONS] [POLICY ...]\n\n", program_name);
	if (brief) {
		printf("\tTry %s --help for more help.\n\n", program_name);
		return;
	}
	printf("Audit Log analysis tool for Security Enhanced Linux.\n\n");
	printf("   -l FILE, --log=FILE     open the log FILE\n");
	printf("   -h, --help              print this help text and exit\n");
	printf("   -V, --version           print version information and exit\n\n");
}

static void seaudit_parse_command_line(seaudit_t * seaudit, int argc, char **argv, const char **log, apol_policy_path_t ** policy)
{
	int optc;
	*log = NULL;
	*policy = NULL;
	apol_policy_path_type_e path_type = APOL_POLICY_PATH_TYPE_MONOLITHIC;
	char *primary_path = NULL;
	apol_vector_t *modules = NULL;
	while ((optc = getopt_long(argc, argv, "l:hV", opts, NULL)) != -1) {
		switch (optc) {
		case 'l':
		{
			*log = optarg;
			break;
		}
		case 'h':
		{
			print_usage_info(argv[0], 0);
			seaudit_destroy(&seaudit);
			exit(EXIT_SUCCESS);
		}
		case 'V':
		{
			print_version_info();
			seaudit_destroy(&seaudit);
			exit(EXIT_SUCCESS);
		}
		default:
		{
			/* unrecognized argument give full usage */
			print_usage_info(argv[0], 1);
			seaudit_destroy(&seaudit);
			exit(EXIT_FAILURE);
		}
		}
	}
	if (optind < argc) {	       /* modules */
		if ((modules = apol_vector_create(NULL)) == NULL) {
			ERR(NULL, "%s", strerror(ENOMEM));
			seaudit_destroy(&seaudit);
			exit(EXIT_FAILURE);
		}
		path_type = APOL_POLICY_PATH_TYPE_MONOLITHIC;
		primary_path = argv[optind++];
		while (argc - optind) {
			if (apol_vector_append(modules, argv[optind])) {
				ERR(NULL, "%s", strerror(ENOMEM));
				seaudit_destroy(&seaudit);
				exit(EXIT_FAILURE);
			}
			path_type = APOL_POLICY_PATH_TYPE_MODULAR;
			optind++;
		}
	}
	if (*log == NULL) {
		*log = preferences_get_log(seaudit->prefs);
	}
	if (primary_path != NULL && strcmp(primary_path, "") != 0) {
		if (apol_file_is_policy_path_list(primary_path)) {
			if ((*policy = apol_policy_path_create_from_file(primary_path)) == NULL) {
				ERR(NULL, "%s", "invalid policy list");
				seaudit_destroy(&seaudit);
				exit(EXIT_FAILURE);
			}
		} else {
			if ((*policy = apol_policy_path_create(path_type, primary_path, modules)) == NULL) {
				ERR(NULL, "%s", strerror(ENOMEM));
				seaudit_destroy(&seaudit);
				exit(EXIT_FAILURE);
			}
		}
	} else {
		const apol_policy_path_t *path = preferences_get_policy(seaudit->prefs);
		if (path != NULL && (*policy = apol_policy_path_create_from_policy_path(path)) == NULL) {
			ERR(NULL, "%s", strerror(ENOMEM));
			seaudit_destroy(&seaudit);
			exit(EXIT_FAILURE);
		}
	}
	apol_vector_destroy(&modules);
}

/*
 * We don't want to do the heavy work of loading and displaying the
 * log and policy before the main loop has started because it will
 * freeze the gui for too long.  To solve this, the function is called
 * from an idle callback set-up in main.
 */
struct delay_file_data
{
	toplevel_t *top;
	const char *log_filename;
	apol_policy_path_t *policy_path;
};

static gboolean delayed_main(gpointer data)
{
	struct delay_file_data *dfd = (struct delay_file_data *)data;
	if (dfd->log_filename != NULL && strcmp(dfd->log_filename, "") != 0) {
		toplevel_open_log(dfd->top, dfd->log_filename);
	}
	if (dfd->policy_path != NULL) {
		toplevel_open_policy(dfd->top, dfd->policy_path);
	}
	return FALSE;
}

int main(int argc, char **argv)
{
	preferences_t *prefs;
	seaudit_t *app;
	const char *log;
	apol_policy_path_t *policy;
	struct delay_file_data file_data;

	gtk_init(&argc, &argv);
	glade_init();
	if (!g_thread_supported())
		g_thread_init(NULL);
	if ((prefs = preferences_create()) == NULL) {
		ERR(NULL, "%s", strerror(ENOMEM));
		exit(EXIT_FAILURE);
	}
	if ((app = seaudit_create(prefs)) == NULL) {
		ERR(NULL, "%s", strerror(ENOMEM));
		exit(EXIT_FAILURE);
	}
	seaudit_parse_command_line(app, argc, argv, &log, &policy);
	if ((app->top = toplevel_create(app)) == NULL) {
		ERR(NULL, "%s", strerror(ENOMEM));
		seaudit_destroy(&app);
		exit(EXIT_FAILURE);
	}
	file_data.top = app->top;
	file_data.log_filename = log;
	file_data.policy_path = policy;
	g_idle_add(&delayed_main, &file_data);
	gtk_main();
	if (preferences_write_to_conf_file(app->prefs) < 0) {
		ERR(NULL, "%s", strerror(ENOMEM));
	}
	seaudit_destroy(&app);
	exit(EXIT_SUCCESS);
}
