/*
 * auvirt.c - A tool to extract data related to virtualization.
 * Copyright (c) 2011 IBM Corp.
 * All Rights Reserved.
 *
 * This software may be freely redistributed and/or modified under the
 * terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2, 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; see the file COPYING. If not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor 
 * Boston, MA 02110-1335, USA.
 *
 * Authors:
 *   Marcelo Henrique Cerri <mhcerri@br.ibm.com>
 */

#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <locale.h>
#include <string.h>
#include <regex.h>
#include <time.h>
#include <sys/types.h>
#include <unistd.h>
#include "auparse.h"
#include "libaudit.h"
#include "ausearch-time.h"
#include "auvirt-list.h"

/* Command line parameters */
static int help_flag = 0;
static int stdin_flag = 0;
static int summary_flag = 0;
static int all_events_flag = 0;
static int uuid_flag = 0;
static int proof_flag = 0;
static const char *vm = NULL;
static const char *uuid = NULL;
static const char *file = NULL;
static int debug = 0;
/*
 * The start time and end time given in the command line is stored respectively
 * in the variables start_time and end_time that are declared/defined in the
 * files ausearch-time.h and ausearch-time.c. These files are reused from the
 * ausearch tool source code:
 *
 *	time_t start_time = 0;
 *	time_t end_time = 0;
 */

/* List of events */
enum event_type {
	ET_NONE = 0, ET_START, ET_STOP, ET_MACHINE_ID, ET_AVC, ET_RES, ET_ANOM
};
struct record_id {
	time_t time;
	unsigned int milli;
	unsigned long serial;
};
struct event {
	enum event_type type;
	time_t start;
	time_t end;
	const char *user;
	char *uuid;
	char *name;
	int success;
	pid_t pid;
	/* Fields specific for resource events: */
	char *reason;
	char *res_type;
	char *res;
	/* Fields specific for cgroup resources */
	char *cgroup_class;
	char *cgroup_detail;
	char *cgroup_acl;
	/* Fields specific for machine id events: */
	char *seclevel;
	/* Fields specific for avc events: */
	char *avc_result;
	char *avc_operation;
	char *target;
	char *comm;
	char *context;
	/* Fields to print proof information: */
	struct record_id proof[4];
};
static list_t *events = NULL;


/* Auxiliary functions to allocate and to free events. */
static struct event *event_alloc(void)
{
	struct event *event = malloc(sizeof(struct event));
	if (event) {
		/* The new event is initialized with values that represents
		 * unset values: -1 for user and pid and 0 (or NULL) for numbers
		 * and pointers. For example, event->end = 0 represents an
		 * unfinished event.
		 */
		memset(event, 0, sizeof(struct event));
		event->pid = -1;
	}
	return event;
}

static void event_free(struct event *event)
{
	if (event) {
		free((void *)event->user);
		free(event->uuid);
		free(event->name);
		free(event->reason);
		free(event->res_type);
		free(event->res);
		free(event->avc_result);
		free(event->avc_operation);
		free(event->seclevel);
		free(event->target);
		free(event->comm);
		free(event->cgroup_class);
		free(event->cgroup_detail);
		free(event->cgroup_acl);
		free(event->context);
		free(event);
	}
}

#define copy_str( str ) (str) ? strdup(str) : NULL


static void usage(FILE *output)
{
	fprintf(output, "usage: auvirt [--stdin] [--all-events] [--summary] "
			"[--start start-date [start-time]] "
			"[--end end-date [end-time]] [--file file-name] "
			"[--show-uuid] [--proof] [--debug] "
			"[--uuid uuid] [--vm vm-name]\n");
}

/* Parse and check command line arguments */
static int parse_args(int argc, char **argv)
{
	/* Based on http://www.ietf.org/rfc/rfc4122.txt */
	const char *uuid_pattern = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-"
		"[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$";
	int i, rc = 0;
	regex_t uuid_regex;

	if (regcomp(&uuid_regex, uuid_pattern, REG_EXTENDED)) {
		fprintf(stderr, "Failed to initialize program.\n");
		return 1;
	}

	for (i = 1; i < argc; i++) {
		const char *opt = argv[i];
		if (opt[0] != '-') {
			fprintf(stderr, "Argument not expected: %s\n", opt);
			goto error;
		} else if (strcmp("--vm", opt) == 0 ||
			   strcmp("-v", opt) == 0) {
			if ((i + 1) >= argc || argv[i + 1][0] == '-') {
				fprintf(stderr, "\"%s\" option requires "
						"an argument.\n", opt);
				goto error;
			}
			vm = argv[++i];
		} else if (strcmp("--uuid", opt) == 0 ||
			   strcmp("-u", opt) == 0) {
			if ((i + 1) >= argc || argv[i + 1][0] == '-') {
				fprintf(stderr, "\"%s\" option requires "
						"an argument.\n", opt);
				goto error;
			}
			if (regexec(&uuid_regex, argv[i + 1], 0, NULL, 0)) {
				fprintf(stderr, "Invalid uuid: %s\n",
						argv[i + 1]);
				goto error;
			}
			uuid = argv[++i];
		} else if (strcmp("--all-events", opt) == 0 ||
		           strcmp("-a", opt) == 0) {
			all_events_flag = 1;
		} else if (strcmp("--summary", opt) == 0 ||
			   strcmp("-s", opt) == 0) {
			summary_flag = 1;
		} else if (strcmp("--file", opt) == 0 ||
			   strcmp("-f", opt) == 0) {
			if ((i + 1) >= argc || argv[i + 1][0] == '-') {
				fprintf(stderr, "\"%s\" option requires "
						"an argument.\n", opt);
				goto error;
			}
			file = argv[++i];
		} else if (strcmp("--show-uuid", opt) == 0) {
			uuid_flag = 1;
		} else if (strcmp("--stdin", opt) == 0) {
			stdin_flag = 1;
		} else if (strcmp("--proof", opt) == 0) {
			proof_flag = 1;
		} else if (strcmp("--help", opt) == 0 ||
			   strcmp("-h", opt) == 0) {
			help_flag = 1;
			goto exit;
		} else if (strcmp("--start", opt) == 0 ||
			   strcmp("-ts", opt) == 0) {
			const char *date, *time = NULL;
			if ((i + 1) >= argc || argv[i + 1][0] == '-') {
				fprintf(stderr, "\"%s\" option requires at "
						"least one argument.\n", opt);
				goto error;
			}
			date = argv[++i];
			if ((i + 1) < argc && argv[i + 1][0] != '-')
				time = argv[++i];
			else
				time = "00:00:00";
			/* This will set start_time */
			if (ausearch_time_start(date, time))
				goto error;
		} else if (strcmp("--end", opt) == 0 ||
			   strcmp("-te", opt) == 0) {
			const char *date, *time = NULL;
			if ((i + 1) >= argc || argv[i + 1][0] == '-') {
				fprintf(stderr, "\"%s\" option requires at "
						"least one argument.\n", opt);
				goto error;
			}
			date = argv[++i];
			if ((i + 1) < argc && argv[i + 1][0] != '-')
				time = argv[++i];
			else
				time = "00:00:00";
			/* This will set end_time */
			if (ausearch_time_end(date, time))
				goto error;
		} else if (strcmp("--debug", opt) == 0) {
			debug = 1;
		} else {
			fprintf(stderr, "Unknown option \"%s\".\n", opt);
			goto error;
		}
	}

	/* Validate conflicting options */
	if (stdin_flag && file) {
		fprintf(stderr, "\"--sdtin\" and \"--file\" options "
				"must not be specified together.\n");
		goto error;
	}

	if (debug) {
		fprintf(stderr, "help_flag='%i'\n", help_flag);
		fprintf(stderr, "stdin_flag='%i'\n", stdin_flag);
		fprintf(stderr, "all_events_flag='%i'\n", all_events_flag);
		fprintf(stderr, "summary_flag='%i'\n", summary_flag);
		fprintf(stderr, "uuid='%s'\n", uuid ? uuid : "(null)");
		fprintf(stderr, "vm='%s'\n", vm ? vm : "(null)");
		fprintf(stderr, "file='%s'\n", file ? file : "(null)");
		fprintf(stderr, "start_time='%-.16s'\n", (start_time == 0L) ?
				"" : ctime(&start_time));
		fprintf(stderr, "end_time='%-.16s'\n", (end_time == 0L) ?
				"" : ctime(&end_time));
	}

exit:
	regfree(&uuid_regex);
	return rc;
error:
	rc = 1;
	goto exit;
}

/* Initialize an auparse_state_t with the correct log source. */
static auparse_state_t *init_auparse(void)
{
	auparse_state_t *au = NULL;
	if (stdin_flag) {
		au = auparse_init(AUSOURCE_FILE_POINTER, stdin);
	} else if (file) {
		au = auparse_init(AUSOURCE_FILE, file);
	} else {
		au = auparse_init(AUSOURCE_LOGS, NULL);
	}
	if (au == NULL) {
		fprintf(stderr, "Error: %s\n", strerror(errno));
	}
	return au;
}

/* Extract the most common fields from virtualization-related records. */
static int extract_virt_fields(auparse_state_t *au, const char **p_uuid,
		const char **p_user, time_t *p_time, const char **p_name,
		int *p_suc)
{
	const char *field = NULL;
	auparse_first_record(au);

	/* Order matters */
	if (p_user) {
		const char *t;
		if (!auparse_find_field(au, field = "uid"))
			goto error;
		t = auparse_interpret_field(au);
		if (t)
			*p_user = strdup(t);
	}
	if (p_name) {
		if (!auparse_find_field(au, field = "vm"))
			goto error;
		*p_name = auparse_interpret_field(au);
	}
	if (p_uuid) {
		if (!auparse_find_field(au, field = "uuid"))
			goto error;
		*p_uuid = auparse_get_field_str(au);
	}
	if (p_suc) {
		const char *res = auparse_find_field(au, field = "res");
		if (res == NULL)
			goto error;
		*p_suc = (strcmp("success", res) == 0) ? 1 : 0;
	}
	if (p_time) {
		*p_time = auparse_get_time(au);
	}
	return 0;

error:
	if (debug) {
		fprintf(stderr, "Failed to get field \"%s\" for record "
				"%ld.%03u:%lu\n", field,
				auparse_get_time(au),
				auparse_get_milli(au),
				auparse_get_serial(au));
	}
	if (p_user)
		free((void *) *p_user);
	return 1;
}

/* Return label and categories from a security context. */
static const char *get_seclevel(const char *seclabel)
{
	/*
	 * system_u:system_r:svirt_t:s0:c107,c434
	 *                           \____ _____/
	 *                                '
	 *                           level + cat
	 */
	int c = 0;
	for (;seclabel && *seclabel; seclabel++) {
		if (*seclabel == ':')
			c += 1;
		if (c == 3)
			return seclabel + 1;
	}
	return NULL;
}

static int add_proof(struct event *event, auparse_state_t *au)
{
	if (!proof_flag)
		return 0;

	size_t i, proof_len = sizeof(event->proof)/sizeof(event->proof[0]);
	for (i = 0; i < proof_len; i++) {
		if (event->proof[i].time == 0)
			break;
	}
	if (i == proof_len) {
		if (debug)
			fprintf(stderr, "Failed to add proof.\n");
		return 1;
	}

	event->proof[i].time = auparse_get_time(au);
	event->proof[i].milli = auparse_get_milli(au);
	event->proof[i].serial = auparse_get_serial(au);
	return 0;
}

// This returns -1 if we don't want the event and 0 if we do
static int filter_event(auparse_state_t *au)
{
	extern time_t start_time, end_time;
	time_t current = auparse_get_time(au);

	if (start_time == 0 || current >= start_time) {
		if (end_time == 0 || current <= end_time) {

			if (vm) {
				const char *v = auparse_find_field(au, "vm");
				if (v) {
					const char *v_text =
						auparse_interpret_field(au);
					if (v_text && strcmp(vm, v_text))
						return -1;
				}
			}
			if (uuid) {
				const char *u = auparse_find_field(au, "uuid");
				if (u) {
					const char *u_text =
						auparse_interpret_field(au);
					if (u_text && strcmp(uuid, u_text))
						return -1;
				}
			}
			auparse_first_record(au);
			return 0;
		}
	}
	return -1;
}

/*
 * machine_id records are used to get the selinux context associated to a
 * guest.
 */
static int process_machine_id_event(auparse_state_t *au)
{
	time_t time;
	const char *seclevel, *model, *uuid, *name, *user = NULL;
	struct event *event;
	int success;

	if (filter_event(au))
		return 0;

	seclevel = get_seclevel(auparse_find_field(au, "vm-ctx"));
	if (seclevel == NULL) {
		if (debug)
			fprintf(stderr, "Security context not found for "
					"MACHINE_ID event.\n");
	}

	// We only need to collect seclevel if model is selinux	
	model = auparse_find_field(au, "model");
	if (model && strcmp(model, "dac") == 0)
		return 0;

	if (extract_virt_fields(au, &uuid, &user, &time, &name, &success))
		return 0;

	event = event_alloc();
	if (event == NULL) {
		free((void *)user);
		return 1;
	}
	event->type = ET_MACHINE_ID;
	event->uuid = copy_str(uuid);
	event->name = copy_str(name);
	event->success = success;
	event->seclevel = copy_str(seclevel);
	event->user = user;
	event->start = time;
	add_proof(event, au);
	if (list_append(events, event) == NULL) {
		event_free(event);
		return 1;
	}
	return 0;
}

static int add_start_guest_event(auparse_state_t *au)
{
	struct event *start;
	time_t time;
	const char *uuid, *name, *user = NULL;
	int success;
	list_node_t *it;

	/* Just skip this record if it failed to get some of the fields */
	if (extract_virt_fields(au, &uuid, &user, &time, &name, &success))
		return 0;

	/* On failure, loop backwards to update all the resources associated to
	 * the last session of this guest. When a machine_id or a stop event is
	 * found the loop can be broken because a machine_id is created at the
	 * beginning of a session and a stop event indicates a previous
	 * session.
	 */
	if (!success) {
		for (it = events->tail; it; it = it->prev) {
			struct event *event = it->data;
			if (event->success && event->uuid &&
			    strcmp(uuid, event->uuid) == 0) {
				if (event->type == ET_STOP ||
				    event->type == ET_MACHINE_ID) {
					/* An old session found. */
					break;
				} else if (event->type == ET_RES &&
				           event->end == 0) {
					event->end = time;
					add_proof(event, au);
				}
			}
		}
	}

	start = event_alloc();
	if (start == NULL) {
		free((void *)user);
		return 1;
	}
	start->type = ET_START;
	start->uuid = copy_str(uuid);
	start->name = copy_str(name);
	start->success = success;
	start->user = user;
	start->start = time;
	auparse_first_record(au);
	if (auparse_find_field(au, "vm-pid"))
		start->pid = auparse_get_field_int(au);
	add_proof(start, au);
	if (list_append(events, start) == NULL) {
		event_free(start);
		return 1;
	}
	return 0;
}

static int add_stop_guest_event(auparse_state_t *au)
{
	list_node_t *it;
	struct event *stop, *start = NULL, *event = NULL;
	time_t time;
	const char *uuid, *name, *user = NULL;
	int success;

	/* Just skip this record if it failed to get some of the fields */
	if (extract_virt_fields(au, &uuid, &user, &time, &name, &success))
		return 0;

	/* Loop backwards to find the last start event for the uuid and
	 * update all resource records related to that guest session.
	 */
	for (it = events->tail; it; it = it->prev) {
		event = it->data;
		if (event->success && event->uuid &&
		    strcmp(uuid, event->uuid) == 0) {
			if (event->type == ET_START) {
				/* If an old session is found it's no longer
				 * necessary to update the resource records.
				 */
				if (event->end || start)
					break;
				/* This is the start event related to the
				 * current session. */
				start = event;
			} else if (event->type == ET_STOP ||
				   event->type == ET_MACHINE_ID) {
				/* Old session found. */
				break;
			} else if (event->type == ET_RES && event->end == 0) {
				/* Update the resource assignments. */
				event->end = time;
				add_proof(event, au);
			}
		}
	}
	if (start == NULL) {
		if (debug) {
			fprintf(stderr, "Couldn't find the correlated start "
					"record to the stop event.\n");
		}
		free((void *)user);
		return 0;
	}

	/* Create a new stop event */
	stop = event_alloc();
	if (stop == NULL) {
		free((void *)user);
		return 1;
	}
	stop->type = ET_STOP;
	stop->uuid = copy_str(uuid);
	stop->name = copy_str(name);
	stop->success = success;
	stop->user = user;
	stop->start = time;
	auparse_first_record(au);
	if (auparse_find_field(au, "vm-pid"))
		stop->pid = auparse_get_field_int(au);
	add_proof(stop, au);
	if (list_append(events, stop) == NULL) {
		free((void *)user);
		event_free(stop);
		return 1;
	}

	/* Update the correlated start event. */
	if (success) {
		start->end = time;
		add_proof(start, au);
	}
	return 0;
}

static int process_control_event(auparse_state_t *au)
{
	const char *op;

	if (filter_event(au))
		return 0;

	op = auparse_find_field(au, "op");
	if (op == NULL) {
		if (debug)
			fprintf(stderr, "Invalid op field.\n");
		return 0;
	}

	if (strcmp("start", op) == 0) {
		if (add_start_guest_event(au))
			return 1;
	} else if (strcmp("stop", op) == 0) {
		if (add_stop_guest_event(au))
			return 1;
	} else if (debug) {
		fprintf(stderr, "Unknown op: %s\n", op);
	}
	return 0;
}

static int is_resource(const char *res)
{
	if (res == NULL ||
	    res[0] == '\0' ||
	    strcmp("0", res) == 0 ||
	    strcmp("?", res) == 0)
		return 0;
	return 1;
}

static int add_resource(auparse_state_t *au, const char *uuid, const char *user,
		time_t time, const char *name, int success, const char *reason,
		const char *res_type, const char *res)
{
	if (!is_resource(res)) {
		free((void *)user);
		return 0;
	}

	struct event *event = event_alloc();
	if (event == NULL)
		return 1;
	event->type = ET_RES;
	event->uuid = copy_str(uuid);
	event->name = copy_str(name);
	event->success = success;
	event->reason = copy_str(reason);
	event->res_type = copy_str(res_type);
	event->res = copy_str(res);
	event->user = user;
	event->start = time;
	add_proof(event, au);

	/* Get cgroup specific fields. */
	if (strcmp("cgroup", res_type) == 0) {
		event->cgroup_class = copy_str(auparse_find_field(au, "class"));
		if (event->cgroup_class) {
			const char *detail = NULL;
			if (strcmp("path", event->cgroup_class) == 0) {
				if (auparse_find_field(au, "path"))
					detail = auparse_interpret_field(au);
			} else if (strcmp("major", event->cgroup_class) == 0) {
				detail = auparse_find_field(au, "category");
			}
			event->cgroup_detail = copy_str(detail);
		}
		event->cgroup_acl = copy_str(auparse_find_field(au, "acl"));
	}

	if (list_append(events, event) == NULL) {
		event_free(event);
		return 1;
	}
	return 0;
}

static int update_resource(auparse_state_t *au, const char *uuid, time_t time,
		int success, const char *res_type, const char *res)
{
	if (!is_resource(res) || !success)
		return 0;

	list_node_t *it;
	struct event *start = NULL;

	/* Find the last start event for the uuid */
	for (it = events->tail; it; it = it->prev) {
		start = it->data;
		if (start->type == ET_RES &&
		    start->success &&
		    start->uuid &&
		    strcmp(uuid, start->uuid) == 0 &&
		    strcmp(res_type, start->res_type) == 0 &&
		    strcmp(res, start->res) == 0)
			break;
	}
	if (it == NULL) {
		if (debug) {
			fprintf(stderr, "Couldn't find the correlated resource"
				" record to update for %s.\n", res_type);
		}
		return 0;
	}

	start->end = time;
	add_proof(start, au);
	return 0;
}

static int process_resource_event(auparse_state_t *au)
{
	time_t time;
	const char *res_type, *uuid, *name, *user = NULL;
	char field[64];
	const char *reason;
	int success;

	if (filter_event(au))
		return 0;

	/* Just skip this record if it failed to get some of the fields */
	if (extract_virt_fields(au, &uuid, &user, &time, &name, &success))
		return 0;

	/* Get the resource type */
	auparse_first_record(au);
	res_type = auparse_find_field(au, "resrc");
	reason = auparse_find_field(au, "reason");
	if (res_type == NULL) {
		if (debug)
			fprintf(stderr, "Invalid resrc field.\n");
		free((void *)user);
		return 0;
	}

	/* Resource records with these types have old and new values. New
	 * values indicate resources assignments and are added to the event
	 * list. Old values are used to update the end time of a resource
	 * assignment.
	 */
	int rc = 0;
	if (strcmp("disk", res_type) == 0 ||
	    strcmp("vcpu", res_type) == 0 ||
	    strcmp("mem", res_type) == 0 ||
	    strcmp("rng", res_type) == 0 ||
	    strcmp("net", res_type) == 0) {
		const char *res = NULL;
		/* Resource removed */
		snprintf(field, sizeof(field), "old-%s", res_type);
		if(auparse_find_field(au, field))
			res = auparse_interpret_field(au);
		if (res == NULL && debug) {
			fprintf(stderr, "Failed to get %s field.\n", field);
		} else {
			rc += update_resource(au, uuid, time, 
					success, res_type, res);
		}

		/* Resource added */
		res = NULL;
		snprintf(field, sizeof(field), "new-%s", res_type);
		if (auparse_find_field(au, field))
			res = auparse_interpret_field(au);
		if (res == NULL && debug) {
			fprintf(stderr, "Failed to get %s field.\n", field);
			free((void *)user);
		} else {
			rc += add_resource(au, uuid, user, time, name, success,
					reason, res_type, res);
		}
	} else if (strcmp("cgroup", res_type) == 0) {
		auparse_first_record(au);
		const char *cgroup = NULL;
		if (auparse_find_field(au, "cgroup"))
			cgroup = auparse_interpret_field(au);
		rc += add_resource(au, uuid, user, time, name, success, reason,
				res_type, cgroup);
	} else if (debug) {
		fprintf(stderr, "Found an unknown resource: %s.\n",
				res_type);
		free((void *)user);
	} else
		free((void *)user);
	return rc;
}

/* Search for the last machine_id record with the given seclevel */
static struct event *get_machine_id_by_seclevel(const char *seclevel)
{
	struct event *machine_id = NULL;
	list_node_t *it;

	for (it = events->tail; it; it = it->prev) {
		struct event *event = it->data;
		if (event->type == ET_MACHINE_ID &&
		    event->seclevel != NULL) {
			if (strcmp(event->seclevel, seclevel) == 0) {
				machine_id = event;
				break;
			}
		}
	}

	return machine_id;
}

/* AVC records are correlated to guest through the selinux context. */
static int process_avc_selinux(auparse_state_t *au)
{
	const char *seclevel, *user = NULL;
	struct event *machine_id, *avc;
	time_t time;

	seclevel = get_seclevel(auparse_find_field(au, "scontext"));
	if (seclevel == NULL) {
		if (debug) {
			fprintf(stderr, "Security context not found "
					"for AVC event.\n");
		}
		return 0;
	}

	if (extract_virt_fields(au, NULL, &user, &time, NULL, NULL))
		return 0;

	machine_id = get_machine_id_by_seclevel(seclevel);
	if (machine_id == NULL) {
		if (debug) {
			fprintf(stderr, "Couldn't get the machine id "
				"based on security level in AVC event.\n");
		}
		free((void *)user);
		return 0;
	}

	avc = event_alloc();
	if (avc == NULL) {
		free((void *)user);
		return 1;
	}
	avc->type = ET_AVC;

	/* Guest info */
	avc->uuid = copy_str(machine_id->uuid);
	avc->name = copy_str(machine_id->name);
	memcpy(avc->proof, machine_id->proof, sizeof(avc->proof));

	/* AVC info */
	avc->start = time;
	avc->user = user;
	avc->seclevel = copy_str(seclevel);
	auparse_first_record(au);
	avc->avc_result = copy_str(auparse_find_field(au, "seresult"));
	avc->avc_operation = copy_str(auparse_find_field(au, "seperms"));
	if (auparse_find_field(au, "comm"))
		avc->comm = copy_str(auparse_interpret_field(au));
	if (auparse_find_field(au, "name"))
		avc->target = copy_str(auparse_interpret_field(au));

	/* get the context related to the permission that was denied. */
	if (avc->avc_operation) {
		const char *ctx = NULL;
		if (strcmp("relabelfrom", avc->avc_operation) == 0) {
			ctx = auparse_find_field(au, "scontext");
		} else if (strcmp("relabelto", avc->avc_operation) == 0) {
			ctx = auparse_find_field(au, "tcontext");
		}
		avc->context = copy_str(ctx);
	}

	add_proof(avc, au);
	if (list_append(events, avc) == NULL) {
		event_free(avc);
		return 1;
	}
	return 0;
}

#ifdef WITH_APPARMOR
static int process_avc_apparmor_source(auparse_state_t *au)
{
	time_t time = 0;
	struct event *avc;
	const char *target, *user = NULL;

	/* Get the target object. */
	if (auparse_find_field(au, "name") == NULL) {
		if (debug) {
			auparse_first_record(au);
			fprintf(stderr, "Couldn't get the resource name from "
					"the AVC record: %s\n",
					auparse_get_record_text(au));
		}
		return 0;
	}
	target = auparse_interpret_field(au);

	/* Loop backwards to find a guest session with the target object
	 * assigned to. */
	struct list_node_t *it;
	struct event *res = NULL;
	for (it = events->tail; it; it = it->prev) {
		struct event *event = it->data;
		if (event->success) {
			if (event->type == ET_RES &&
			           event->end == 0 &&
			           event->res != NULL &&
		                   strcmp(target, event->res) == 0) {
				res = event;
				break;
			}
		}
	}

	/* Check if a resource event was found. */
	if (res == NULL) {
		if (debug) {
			fprintf(stderr, "Target object not found for AVC "
					"event.\n");
		}
		return 0;
	}

	if (extract_virt_fields(au, NULL, &user, &time, NULL, NULL))
		return 0;

	avc = event_alloc();
	if (avc == NULL) {
		free(user);
		return 1;
	}
	avc->type = ET_AVC;

	/* Guest info */
	avc->uuid = copy_str(res->uuid);
	avc->name = copy_str(res->name);
	memcpy(avc->proof, res->proof, sizeof(avc->proof));

	/* AVC info */
	avc->start = time;
	avc->user = user;
	auparse_first_record(au);
	if (auparse_find_field(au, "apparmor")) {
		int i;
		avc->avc_result = copy_str(auparse_interpret_field(au));
		for (i = 0; avc->avc_result && avc->avc_result[i]; i++) {
			avc->avc_result[i] = tolower(avc->avc_result[i]);
		}
	}
	if (auparse_find_field(au, "operation"))
		avc->avc_operation = copy_str(auparse_interpret_field(au));
	avc->target = copy_str(target);
	if (auparse_find_field(au, "comm"))
		avc->comm = copy_str(auparse_interpret_field(au));

	add_proof(avc, au);
	if (list_append(events, avc) == NULL) {
		event_free(avc);
		return 1;
	}
	return 0;
}

static int process_avc_apparmor_target(auparse_state_t *au)
{
	const char *user = NULL;
	time_t time;
	const char *profile;
	struct event *avc;

	/* Get profile associated with the AVC record */
	if (auparse_find_field(au, "profile") == NULL) {
		if (debug) {
			auparse_first_record(au);
			fprintf(stderr, "AppArmor profile not found for AVC "
					"record: %s\n",
					auparse_get_record_text(au));
		}
		return 0;
	}
	profile = auparse_interpret_field(au);

	/* Break path to get just the basename */
	const char *basename = profile + strlen(profile);
	while (basename != profile && *basename != '/')
		basename--;
	if (*basename == '/')
		basename++;

	/* Check if it is an apparmor profile generated by libvirt and get the
	 * guest UUID from it */
	const char *prefix = "libvirt-";
	if (strncmp(prefix, basename, strlen(prefix)) != 0) {
		if (debug) {
			fprintf(stderr, "Found a profile which is not "
					"generated by libvirt: %s\n", profile);
		}
		return 0;
	}

	/* Try to find a valid guest session */
	const char *uuid = basename + strlen(prefix);
	struct list_node_t *it;
	struct event *machine_id = NULL;
	for (it = events->tail; it; it = it->prev) {
		struct event *event = it->data;
		if (event->success) {
			if (event->uuid != NULL &&
			    strcmp(event->uuid, uuid) == 0) {
				/* machine_id is used here instead of the start
				 * event because it is generated before any
				 * other event when a guest is started. So,
				 * it's possible to correlate AVC events that
				 * occurs during a guest start.
				 */
				if (event->type == ET_MACHINE_ID) {
					machine_id = event;
					break;
				} else if (event->type == ET_STOP) {
					break;
				}
			}
		}
	}
	if (machine_id == NULL) {
		if (debug) {
			fprintf(stderr, "Found an AVC record for an unknown "
					"guest.\n");
		}
		return 0;
	}

	if (extract_virt_fields(au, NULL, &user, &time, NULL, NULL))
		return 0;

	avc = event_alloc();
	if (avc == NULL) {
		free(user);
		return 1;
	}
	avc->type = ET_AVC;

	/* Guest info */
	avc->uuid = copy_str(machine_id->uuid);
	avc->name = copy_str(machine_id->name);
	memcpy(avc->proof, machine_id->proof, sizeof(avc->proof));

	/* AVC info */
	avc->start = time;
	avc->user = user;
	auparse_first_record(au);
	if (auparse_find_field(au, "apparmor")) {
		int i;
		avc->avc_result = copy_str(auparse_interpret_field(au));
		for (i = 0; avc->avc_result && avc->avc_result[i]; i++) {
			avc->avc_result[i] = tolower(avc->avc_result[i]);
		}
	}
	if (auparse_find_field(au, "operation"))
		avc->avc_operation = copy_str(auparse_interpret_field(au));
	if (auparse_find_field(au, "name"))
		avc->target = copy_str(auparse_interpret_field(au));
	if (auparse_find_field(au, "comm"))
		avc->comm = copy_str(auparse_interpret_field(au));

	add_proof(avc, au);
	if (list_append(events, avc) == NULL) {
		event_free(avc);
		return 1;
	}
	return 0;
}

/* AVC records are correlated to guest through the apparmor path name. */
static int process_avc_apparmor(auparse_state_t *au)
{
	if (process_avc_apparmor_target(au))
		return 1;
	auparse_first_record(au);
	return process_avc_apparmor_source(au);
}
#endif

static int process_avc(auparse_state_t *au)
{
	if (filter_event(au))
		return 0;

	/* Check if it is a SELinux AVC record */
	if (auparse_find_field(au, "scontext")) {
		return process_avc_selinux(au);
	}

#ifdef WITH_APPARMOR
	/* Check if it is an AppArmor AVC record */
	auparse_first_record(au);
	if (auparse_find_field(au, "apparmor")) {
		auparse_first_record(au);
		return process_avc_apparmor(au);
	}
#endif
	return 0;
}

/* This function tries to correlate an anomaly record to a guest using the qemu
 * pid or the selinux context. */
static int process_anom(auparse_state_t *au)
{
	const char *user = NULL;
	time_t time;
	pid_t pid = -1;
	list_node_t *it;
	struct event *anom, *start = NULL;

	if (filter_event(au))
		return 0;

	/* An anomaly record is correlated to a guest by the process id */
	if (auparse_find_field(au, "pid")) {
		pid = auparse_get_field_int(au);
	} else {
		if (debug) {
			fprintf(stderr, "Found an anomaly record "
					"without pid.\n");
		}
	}

	/* Loop backwards to find a running guest with the same pid. */
	if (pid >= 0) {
		for (it = events->tail; it; it = it->next) {
			struct event *event = it->data;
			if (event->pid == pid && event->success) {
				if (event->type == ET_STOP) {
					break;
				} else if (event->type == ET_START) {
					if (event->end == 0)
						start = event;
					break;
				}
			}
		}
	}

	/* Try to match using selinux context */
	if (start == NULL) {
		const char *seclevel;
		struct event *machine_id;

		seclevel = get_seclevel(auparse_find_field(au, "subj"));
		if (seclevel == NULL) {
			if (debug) {
				auparse_first_record(au);
				const char *text = auparse_get_record_text(au);
				fprintf(stderr, "Security context not found "
						"for anomaly event: %s\n",
						text ? text : "");
			}
			return 0;
		}
		machine_id = get_machine_id_by_seclevel(seclevel);
		if (machine_id == NULL) {
			if (debug) {
				fprintf(stderr, "Couldn't get the security "
					"level from the anomaly event.\n");
			}
			return 0;
		}

		for (it = events->tail; it; it = it->next) {
			struct event *event = it->data;
			if (event->success && machine_id->uuid && event->uuid &&
			    strcmp(machine_id->uuid, event->uuid) == 0) {
				if (event->type == ET_STOP) {
					break;
				} else if (event->type == ET_START) {
					if (event->end == 0)
						start = event;
					break;
				}
			}
		}
	}

	if (start == NULL) {
		if (debug) {
			const char *text = auparse_get_record_text(au);
			fprintf(stderr, "Guest not found for "
					"anomaly record: %s.\n",
					text ? text : "");
		}
		return 0;
	}

	if (extract_virt_fields(au, NULL, &user, &time, NULL, NULL))
		return 0;

	anom = event_alloc();
	if (anom == NULL) {
		free((void *)user);
		return 1;
	}
	anom->type = ET_ANOM;
	anom->uuid = copy_str(start->uuid);
	anom->name = copy_str(start->name);
	anom->user = user;
	anom->start = time;
	anom->pid = pid;
	memcpy(anom->proof, start->proof, sizeof(anom->proof));
	add_proof(anom, au);
	if (list_append(events, anom) == NULL) {
		event_free(anom);
		return 1;
	}
	return 0;
}

/* Convert record type to a string */
static const char *get_rec_type(struct event *e)
{
	static char buf[16];
	if (e == NULL)
		return "";

	switch (e->type) {
	case ET_START:
		return "start";
	case ET_STOP:
		return "stop";
	case ET_RES:
		return "resrc";
	case ET_AVC:
		return "avc";
	case ET_ANOM:
		return "anom";
	default:
		break;
	}

	snprintf(buf, sizeof(buf), "%d", e->type);
	return buf;
}

/* Convert a time period to string */
static const char *get_time_period(struct event *event)
{
	size_t i = 0;
	static char buf[128];

	i += sprintf(buf + i, "%-16.16s", ctime(&event->start));
	if (event->end) {
		time_t secs = event->end - event->start;
		int mins, hours, days;
		i += sprintf(buf + i, " - %-7.5s", ctime(&event->end) + 11);
		mins  = (secs / 60) % 60;
		hours = (secs / 3600) % 24;
		days  = secs / 86400;
		if (days) {
			sprintf(buf + i, "(%d+%02d:%02d)", days, hours,
					mins);
		} else {
			sprintf(buf + i, "(%02d:%02d)", hours, mins);
		}
	} else {
		if (!event->success &&
		    event->type != ET_AVC &&
		    event->type != ET_ANOM) {
			sprintf(buf + i, " - failed");
		}
	}
	return buf;
}

static void print_event(struct event *event)
{
	/* Auxiliary macro to convert NULL to "" */
	#define N(str) ((str) ? str : "")

	/* machine id records are used just to get information about
	 * the guests. */
	if (event->type == ET_MACHINE_ID)
		return;
	/* If "--all-events" is not given, only the start event is shown. */
	if (!all_events_flag && event->type != ET_START)
		return;
	/* The type of event is shown only when all records are shown */
	if (all_events_flag)
		printf("%-6.5s ", get_rec_type(event));

	/* Print common fields */
	printf("%-22.22s", N(event->name));
	if (uuid_flag)
		printf("\t%-36.36s", N(event->uuid));
	printf("\t%-11.11s\t%-32.32s", N(event->user),
			get_time_period(event));

	/* Print type specific fields */
	if (event->type == ET_RES) {
		printf("\t%-12.12s", N(event->res_type));
		printf("\t%-10.10s", N(event->reason));
		if (strcmp("cgroup", event->res_type) != 0) {
			printf("\t%s", N(event->res));
		} else {
			printf("\t%s\t%s\t%s", N(event->cgroup_class),
					N(event->cgroup_acl),
					N(event->cgroup_detail));
		}
	} else if (event->type == ET_MACHINE_ID) {
		printf("\t%s", N(event->seclevel));
	} else if (event->type == ET_AVC) {
		printf("\t%-12.12s", N(event->avc_operation));
		printf("\t%-10.10s", N(event->avc_result));
		printf("\t%s\t%s\t%s", N(event->comm), N(event->target),
				N(event->context));
	}
	printf("\n");

	/* Print proof */
	if (proof_flag) {
		int first = 1;
		int i, len = sizeof(event->proof)/sizeof(event->proof[0]);
		printf("    Proof:");
		for (i = 0; i < len; i++) {
			if (event->proof[i].time) {
				//printf("%s %ld.%03u:%lu",
				printf("%s%lu",
					(first) ? "" : ",",
//					event->proof[i].time,
//					event->proof[i].milli,
					event->proof[i].serial);
				first = 0;
			}
		}
		printf("\n\n");
	}
}

/* Print all events */
static void print_events(void)
{
	list_node_t *it;
	for (it = events->head; it; it = it->next) {
		struct event *event = it->data;
		if (event)
			print_event(event);
	}
}

/* Count and print summary */
static void print_summary(void)
{
	/* Summary numbers */
	time_t stime = 0, etime = 0;
	long start = 0, stop = 0, res = 0, avc = 0, anom = 0, failure = 0;
	char start_buf[32], end_buf[32];

	/* Calculate summary */
	list_node_t *it;
	for (it = events->head; it; it = it->next) {
		struct event *event = it->data;
		if (event->success == 0 &&
		    (event->type == ET_START ||
		     event->type == ET_STOP  ||
		     event->type == ET_RES)) {
			failure++;
		} else {
			switch (event->type) {
			case ET_START:
				start++;
				break;
			case ET_STOP:
				stop++;
				break;
			case ET_RES:
				res++;
				break;
			case ET_AVC:
				avc++;
				break;
			case ET_ANOM:
				anom++;
				break;
			default:
				break;
			}
		}

		/* Calculate time range */
		if (event->start) {
			if (stime == 0 || event->start < stime) {
				stime = event->start;
			}
			if (etime == 0 || event->start > etime) {
				etime = event->start;
			}
		}
		if (event->end) {
			if (stime == 0 || event->end < stime) {
				stime = event->end;
			}
			if (etime == 0 || event->end > etime) {
				etime = event->end;
			}
		}

	}

	if (stime)
		ctime_r(&stime, start_buf);
	else
		strcpy(start_buf, "undef");
	if (etime)
		ctime_r(&etime, end_buf);
	else
		strcpy(end_buf, "undef");

	/* Print summary */
	printf("Range of time for report:       %-.16s - %-.16s\n",
			start_buf, end_buf);
	printf("Number of guest starts:         %ld\n", start);
	printf("Number of guest stops:          %ld\n", stop);
	printf("Number of resource assignments: %ld\n", res);
	printf("Number of related AVCs:         %ld\n", avc);
	printf("Number of related anomalies:    %ld\n", anom);
	printf("Number of failed operations:    %ld\n", failure);
}

int main(int argc, char **argv)
{
	int rc = 0;
	auparse_state_t *au = NULL;

	setlocale(LC_ALL, "");
	if (parse_args(argc, argv))
		goto error;
	if (help_flag) {
		usage(stdout);
		goto exit;
	}

	/* Initialize event list*/
	events = list_new((list_free_data_fn*) event_free);
	if (events == NULL)
		goto unexpected_error;

	/* Initialize auparse */
	au = init_auparse();
	if (au == NULL)
		goto error;

	while (auparse_next_event(au) > 0) {
		int err = 0;

		switch(auparse_get_type(au)) {
		case AUDIT_VIRT_MACHINE_ID:
			err = process_machine_id_event(au);
			break;
		case AUDIT_VIRT_CONTROL:
			err = process_control_event(au);
			break;
		case AUDIT_VIRT_RESOURCE:
			err = process_resource_event(au);
			break;
		case AUDIT_AVC:
			if (all_events_flag || summary_flag)
				err = process_avc(au);
			break;
		case AUDIT_FIRST_ANOM_MSG ... AUDIT_LAST_ANOM_MSG:
		case AUDIT_FIRST_KERN_ANOM_MSG ... AUDIT_LAST_KERN_ANOM_MSG:
			if (all_events_flag || summary_flag)
				err = process_anom(au);
			break;
		default:
			break;
		}
		if (err) {
			goto unexpected_error;
		}
	}

	/* Show results */
	if (summary_flag) {
		print_summary();
	} else {
		print_events();
	}

	/* success */
	goto exit;

unexpected_error:
	fprintf(stderr, "Unexpected error\n");
error:
	rc = 1;
exit:
	if (au)
		auparse_destroy(au);
	list_free(events);
	if (debug)
		fprintf(stdout, "Exit code: %d\n", rc);
	return rc;
}

