[PATCH] auvirt: a new tool for reporting events related to virtual machines

Marcelo Cerri mhcerri at linux.vnet.ibm.com
Wed Feb 1 17:16:35 UTC 2012


This patch adds a new tool to extract information related to virtual machines
from the audit log files.

This tool is based on the proposal sent in the RFC:

https://www.redhat.com/archives/linux-audit/2011-November/msg00014.html
---
 audit.spec                 |    2 +
 configure.ac               |    2 +-
 tools/Makefile.am          |    2 +-
 tools/auvirt/Makefile.am   |   40 ++
 tools/auvirt/auvirt-list.c |  105 ++++
 tools/auvirt/auvirt-list.h |   31 +
 tools/auvirt/auvirt.8      |  121 ++++
 tools/auvirt/auvirt.c      | 1308 ++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 1609 insertions(+), 2 deletions(-)
 create mode 100644 tools/auvirt/Makefile.am
 create mode 100644 tools/auvirt/auvirt-list.c
 create mode 100644 tools/auvirt/auvirt-list.h
 create mode 100644 tools/auvirt/auvirt.8
 create mode 100644 tools/auvirt/auvirt.c

diff --git a/audit.spec b/audit.spec
index 383bed1..a9940b4 100644
--- a/audit.spec
+++ b/audit.spec
@@ -172,6 +172,7 @@ fi
 %attr(644,root,root) %{_mandir}/man8/autrace.8.gz
 %attr(644,root,root) %{_mandir}/man8/aulast.8.gz
 %attr(644,root,root) %{_mandir}/man8/aulastlog.8.gz
+%attr(644,root,root) %{_mandir}/man8/auvirt.8.gz
 %attr(644,root,root) %{_mandir}/man8/ausyscall.8.gz
 %attr(644,root,root) %{_mandir}/man7/audit.rules.7.gz
 %attr(644,root,root) %{_mandir}/man5/auditd.conf.5.gz
@@ -186,6 +187,7 @@ fi
 %attr(755,root,root) %{_bindir}/aulast
 %attr(755,root,root) %{_bindir}/aulastlog
 %attr(755,root,root) %{_bindir}/ausyscall
+%attr(755,root,root) %{_bindir}/auvirt
 %attr(755,root,root) /etc/rc.d/init.d/auditd
 %attr(750,root,root) %dir %{_var}/log/audit
 %attr(750,root,root) %dir /etc/audit
diff --git a/configure.ac b/configure.ac
index c1cf96f..b7f7fcd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -255,7 +255,7 @@ AC_SUBST(libev_LIBS)
 AC_SUBST(LIBPRELUDE_CFLAGS)
 AC_SUBST(LIBPRELUDE_LDFLAGS)
 
-AC_OUTPUT(Makefile lib/Makefile lib/test/Makefile auparse/Makefile auparse/test/Makefile src/Makefile src/mt/Makefile src/libev/Makefile src/test/Makefile swig/Makefile docs/Makefile init.d/Makefile audisp/Makefile audisp/plugins/Makefile audisp/plugins/builtins/Makefile audisp/plugins/prelude/Makefile audisp/plugins/remote/Makefile audisp/plugins/zos-remote/Makefile bindings/Makefile bindings/python/Makefile tools/Makefile tools/aulast/Makefile tools/aulastlog/Makefile tools/ausyscall/Makefile)
+AC_OUTPUT(Makefile lib/Makefile lib/test/Makefile auparse/Makefile auparse/test/Makefile src/Makefile src/mt/Makefile src/libev/Makefile src/test/Makefile swig/Makefile docs/Makefile init.d/Makefile audisp/Makefile audisp/plugins/Makefile audisp/plugins/builtins/Makefile audisp/plugins/prelude/Makefile audisp/plugins/remote/Makefile audisp/plugins/zos-remote/Makefile bindings/Makefile bindings/python/Makefile tools/Makefile tools/aulast/Makefile tools/aulastlog/Makefile tools/ausyscall/Makefile tools/auvirt/Makefile)
 
 echo .
 echo "
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 3b7acfe..15ba254 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -22,5 +22,5 @@
 
 CONFIG_CLEAN_FILES = *.loT *.rej *.orig
 
-SUBDIRS = aulast aulastlog ausyscall
+SUBDIRS = aulast aulastlog ausyscall auvirt
 
diff --git a/tools/auvirt/Makefile.am b/tools/auvirt/Makefile.am
new file mode 100644
index 0000000..0e9ec5b
--- /dev/null
+++ b/tools/auvirt/Makefile.am
@@ -0,0 +1,40 @@
+#
+# Makefile.am --
+# 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# Authors:
+#   Marcelo Henrique Cerri <mhcerri at br.ibm.com>
+#
+ 
+CONFIG_CLEAN_FILES = *.loT *.rej *.orig
+AUTOMAKE_OPTIONS = no-dependencies
+EXTRA_DIST = $(man_MANS)
+INCLUDES = -I${top_srcdir} \
+		   -I${top_srcdir}/lib \
+		   -I${top_srcdir}/auparse \
+		   -I${top_srcdir}/src
+LIBS = -L${top_builddir}/auparse \
+	   -lauparse
+AM_CFLAGS = -D_GNU_SOURCE
+bin_PROGRAMS = auvirt
+noinst_HEADERS = auvirt-list.h
+man_MANS = auvirt.8
+
+auvirt_SOURCES = auvirt.c \
+				 auvirt-list.c \
+				 ${top_srcdir}/src/ausearch-time.c
diff --git a/tools/auvirt/auvirt-list.c b/tools/auvirt/auvirt-list.c
new file mode 100644
index 0000000..7502188
--- /dev/null
+++ b/tools/auvirt/auvirt-list.c
@@ -0,0 +1,105 @@
+#include "auvirt-list.h"
+#include <stdlib.h>
+
+list_t *list_init(list_t *list, list_free_data_fn *free_data_fn)
+{
+	if (list == NULL)
+		return NULL;
+	list->head = list->tail = NULL;
+	list->free_data_fn = free_data_fn;
+	return list;
+}
+
+list_t *list_new(list_free_data_fn *free_data_fn)
+{
+	return list_init(malloc(sizeof(list_t)), free_data_fn);
+}
+
+void list_free_node(list_node_t *node, list_free_data_fn *free_data_fn)
+{
+	if (node) {
+		if (free_data_fn)
+			free_data_fn(node->data);
+		free(node);
+	}
+}
+
+void list_free_(list_t *list, list_free_data_fn *free_data_fn)
+{
+	if (list != NULL) {
+		list_node_t *it = list->head;
+		while (it && it->next) {
+			it = it->next;
+			list_free_node(it->prev, free_data_fn);
+		}
+		list_free_node(it, free_data_fn);
+		free(list);
+	}
+}
+
+void list_free(list_t *list)
+{
+	if (list)
+		list_free_(list, list->free_data_fn);
+}
+
+list_node_t *list_insert_after(list_t *list, list_node_t *it,
+		void *data)
+{
+	list_node_t *node = NULL;
+	if (list == NULL)
+		return NULL;
+
+	/* allocate node */
+	node = malloc(sizeof(list_node_t));
+	if (node == NULL)
+		return NULL;
+	node->data = data;
+
+	/* insert the new node after it */
+	node->prev = it;
+	if (it) {
+		node->next = it->next;
+		it->next = node;
+	}
+	else
+		node->next = list->head;
+	if (node->next)
+		node->next->prev = node;
+
+	/* update list's head and tail */
+	if (it == list->tail)
+		list->tail = node;
+	if (it == NULL)
+		list->head = node;
+
+	return node;
+}
+
+list_node_t *list_append(list_t *list, void *data)
+{
+	return list_insert_after(list, list->tail, data);
+}
+
+int list_remove_(list_t *list, list_node_t *it,
+		list_free_data_fn *free_data_fn)
+{
+	if (list == NULL || it == NULL)
+		return 1;
+	if (list->head == it)
+		list->head = it->next;
+	if (list->tail == it)
+		list->tail = it->prev;
+	if (it->next)
+		it->next->prev = it->prev;
+	if (it->prev)
+		it->prev->next = it->next;
+	list_free_node(it, free_data_fn);
+	return 0;
+}
+
+int list_remove(list_t *list, list_node_t *it)
+{
+	return list_remove_(list, it, list->free_data_fn);
+}
+
diff --git a/tools/auvirt/auvirt-list.h b/tools/auvirt/auvirt-list.h
new file mode 100644
index 0000000..fb58746
--- /dev/null
+++ b/tools/auvirt/auvirt-list.h
@@ -0,0 +1,31 @@
+
+#ifndef AUVIRT_LIST_HEADER
+#define AUVIRT_LIST_HEADER
+
+typedef void (list_free_data_fn)(void *data);
+
+typedef struct list_node_t {
+	void *data;
+	struct list_node_t *prev;
+	struct list_node_t *next;
+} list_node_t;
+
+typedef struct list_t {
+	list_node_t *head;
+	list_node_t *tail;
+	list_free_data_fn *free_data_fn;
+} list_t;
+
+list_t *list_init(list_t *list, list_free_data_fn *free_data_fn);
+list_t *list_new(list_free_data_fn *free_data_fn);
+void list_free_(list_t *list, list_free_data_fn *free_data_fn);
+void list_free(list_t *list);
+list_node_t *list_insert_after(list_t *list, list_node_t *it,
+		void *data);
+list_node_t *list_append(list_t *list, void *data);
+int list_remove_(list_t *list, list_node_t *it,
+		list_free_data_fn *free_data_fn);
+int list_remove(list_t *list, list_node_t *it);
+
+#endif
+
diff --git a/tools/auvirt/auvirt.8 b/tools/auvirt/auvirt.8
new file mode 100644
index 0000000..86266f7
--- /dev/null
+++ b/tools/auvirt/auvirt.8
@@ -0,0 +1,121 @@
+.TH AUVIRT 8 "Dec 2011" "IBM Corp" "System Administration Utilities"
+.SH NAME
+auvirt - a program that shows data related to virtual machines
+
+.SH SYNOPSIS
+.B auvirt
+[ \fIOPTIONS\fP ]
+
+.SH DESCRIPTION
+\fBauvirt\fP shows a list of guest sessions found in the audit logs. If a guest
+is specified, only the events related to that guest is considered. To specify a
+guest, both UUID or VM name can be given.
+
+For each guest session the tool prints a record with the domain name, the user
+that started the guest, the time when the guest was started and the time when
+the guest was stoped.
+
+If the option "--all-events" is given a more detailed output is shown. In this
+mode other records are shown for guest's stops, resource
+assignments, host shutdowns and AVC and anomaly events. The first field
+indicates the event type and can have the following values: start, stop,
+res, avc, anom and down (for host shutdowns).
+
+Resource assignments have the additional fields: resource type, reason and
+resource. And AVC records have the following additional fields: operation,
+result, command and target.
+
+By default, auvirt reads records from the system audit log file. But
+\fB--stdin\fP and \fB--file\fP options can be specified to change this
+behavior.
+
+.SH OPTIONS
+.TP
+\fB--all-events\fP
+Show records for all virtualization related events.
+.TP
+\fB--debug\fP
+Print debug messages to standard output.
+.TP
+\fB-f\fP, \fB--file\fP \fIfile\fP
+Read records from the given \fIfile\fP instead from the system audit log file.
+.TP
+\fB-h\fP, \fB--help\fP
+Print help message and exit.
+.TP
+\fB--proof\fP
+Add after each event a line containing all the identifiers of the audit records
+used to calculate the event. Each identifier consists of unix time,
+milliseconds and serial number.
+.TP
+\fB--show-uuid\fP
+Add the guest's UUID to each record.
+.TP
+\fB--stdin\fP
+Read records from the standard input instead from the system audit log file.
+This option cannot be specified with \fB--file\fP.
+.TP
+\fB--summary\fP
+Print a summary with information about the events found. The summary contains
+the considered range of time, the number of guest starts and stops, the number
+of resource assignments, the number of AVC and anomaly events, the number of
+host shutdowns and the number of failed operations.
+.TP
+.BR \-te ,\  \-\-end \ [\fIend-date\fP]\ [\fIend-time\fP]
+Search for events with time stamps equal to or before the given end time. The
+format of end time depends on your locale. If the date is omitted,
+.B today
+is assumed. If the time is omitted,
+.B now
+is assumed. Use 24 hour clock time rather than AM or PM to specify time.
+An example date using the en_US.utf8 locale is 09/03/2009. An example of time
+is 18:00:00. The date format accepted is influenced by the LC_TIME
+environmental variable.
+
+You may also use the word: \fBnow\fP, \fBrecent\fP, \fBtoday\fP,
+\fByesterday\fP, \fBthis\-week\fP, \fBweek\-ago\fP, \fBthis\-month\fP,
+\fBthis\-year\fP. \fBToday\fP means starting now. \fBRecent\fP is 10 minutes
+ago. \fBYesterday\fP is 1 second after midnight the previous day.
+\fBThis\-week\fP means starting 1 second after midnight on day 0 of the week
+determined by your locale (see \fBlocaltime\fP). \fBThis\-month\fP means 1
+second after midnight on day 1 of the month. \fBThis\-year\fP means the 1
+second after midnight on the first day of the first month.
+.TP
+.BR \-ts ,\  \-\-start \ [\fIstart-date\fP]\ [\fIstart-time\fP]
+Search for events with time stamps equal to or after the given end time. The
+format of end time depends on your locale. If the date is omitted,
+.B today
+is assumed. If the time is omitted,
+.B midnight
+is assumed. Use 24 hour clock time rather than AM or PM to specify time. An
+example date using the en_US.utf8 locale is 09/03/2009. An example of time is
+18:00:00. The date format accepted is influenced by the LC_TIME environmental
+variable.
+
+You may also use the word: \fBnow\fP, \fBrecent\fP, \fBtoday\fP,
+\fByesterday\fP, \fBthis\-week\fP, \fBthis\-month\fP, \fBthis\-year\fP.
+\fBToday\fP means starting at 1 second after midnight. \fBRecent\fP is 10
+minutes ago. \fBYesterday\fP is 1 second after midnight the previous day.
+\fBThis\-week\fP means starting 1 second after midnight on day 0 of the week
+determined by your locale (see \fBlocaltime\fP). \fBThis\-month\fP means 1
+second after midnight on day 1 of the month. \fBThis\-year\fP means the 1
+second after midnight on the first day of the first month.
+.TP
+\fB-u\fP, \fB--uuid\fP \ \fIUUID\fP
+Only show events related to the guest with the given UUID.
+.TP
+\fB-v\fP, \fB--vm\fP \ \fIname\fP
+Only show events related to the guest with the given name.
+
+.SH EXAMPLES
+To see all the records in this month for a guest
+
+\fBauvirt --start this-month --vm GuestVmName --all-events\fP
+
+.SH SEE ALSO
+.BR aulast (8),
+.BR ausearch (8),
+.BR aureport (8).
+
+.SH AUTHOR
+Marcelo Cerri
diff --git a/tools/auvirt/auvirt.c b/tools/auvirt/auvirt.c
new file mode 100644
index 0000000..c04780a
--- /dev/null
+++ b/tools/auvirt/auvirt.c
@@ -0,0 +1,1308 @@
+/*
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors:
+ *   Marcelo Henrique Cerri <mhcerri at br.ibm.com>
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <locale.h>
+#include <string.h>
+#include <regex.h>
+#include <time.h>
+#include <sys/types.h>
+#include <pwd.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,
+	ET_DOWN
+};
+struct record_id {
+	time_t time;
+	unsigned int milli;
+	unsigned long serial;
+};
+struct event {
+	enum event_type type;
+	time_t start;
+	time_t end;
+	uid_t uid;
+	char *uuid;
+	char *name;
+	int success;
+	pid_t pid;
+	/* Fields specific for resource events: */
+	char *reason;
+	char *res_type;
+	char *res;
+	/* Fields specific for machine id events: */
+	char *seclevel;
+	/* Fields specific for avc events: */
+	char *target;
+	char *comm;
+	char *seresult;
+	char *seperms;
+	/* Fields to print proof information: */
+	struct record_id proof[4];
+};
+list_t *events = NULL;
+
+
+/* Auxiliary functions to allocate and to free events. */
+struct event *event_alloc()
+{
+	struct event *event = malloc(sizeof(struct event));
+	if (event) {
+		/* The new event is initialized with values that represents
+		 * unset values: -1 for uid 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->uid = -1;
+		event->pid = -1;
+	}
+	return event;
+}
+
+void event_free(struct event *event)
+{
+	if (event) {
+		free(event->uuid);
+		free(event->name);
+		free(event->reason);
+		free(event->res_type);
+		free(event->res);
+		free(event->seclevel);
+		free(event->target);
+		free(event->comm);
+		free(event->seresult);
+		free(event->seperms);
+		free(event);
+	}
+}
+
+inline char *copy_str(const char *str)
+{
+	return (str) ? strdup(str) : NULL;
+}
+
+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] "
+			"[--uuid uuid] [--vm vm-name]\n");
+}
+
+/* Parse and check command line arguments */
+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];
+			/* 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];
+			/* 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. */
+auparse_state_t *init_auparse()
+{
+	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 {
+		if (getuid()) {
+			fprintf(stderr, "You probably need to be root for "
+					"this to work\n");
+		}
+		au = auparse_init(AUSOURCE_LOGS, NULL);
+	}
+	if (au == NULL) {
+		fprintf(stderr, "Error: %s\n", strerror(errno));
+	}
+	return au;
+}
+
+/* Create a criteria to search for the virtualization related records */
+int create_search_criteria(auparse_state_t *au)
+{
+	char *error = NULL;
+	char expr[1024];
+	snprintf(expr, sizeof(expr),
+		"(\\record_type >= %d && \\record_type <= %d)",
+		AUDIT_FIRST_VIRT_MSG, AUDIT_LAST_VIRT_MSG);
+	if (ausearch_add_expression(au, expr, &error, AUSEARCH_RULE_CLEAR)) {
+		fprintf(stderr, "Criteria error: %s\n", error);
+		free(error);
+		return 1;
+	}
+	if (uuid) {
+		if (ausearch_add_item(au, "uuid", "=", uuid,
+					AUSEARCH_RULE_AND)) {
+			fprintf(stderr, "Criteria error: uuid\n");
+			return 1;
+		}
+	}
+	if (vm) {
+		/*
+		 * If a field has its value quoted in the audit log, for
+		 * example:
+		 *	vm="guest-name"
+		 *
+		 * auparse will consider the field value with quotes when
+		 * matching a rule. For example, using the example above the
+		 * following rule will not match:
+		 *     ausearch_add_item(au, "vm", "=", "guest-name", how);
+		 *
+		 * But this rule will match:
+		 *     ausearch_add_item(au, "vm", "=", "\"guest-name\"", how);
+		 *
+		 * TODO use a better approach for this problem...
+		 */
+		snprintf(expr, sizeof(expr), "\"%s\"", vm);
+		if (ausearch_add_item(au, "vm", "=", expr,
+					AUSEARCH_RULE_AND)) {
+			fprintf(stderr, "Criteria error: id\n");
+			return 1;
+		}
+	}
+	if (all_events_flag || summary_flag) {
+		if (ausearch_add_item(au, "type", "=", "AVC",
+					AUSEARCH_RULE_OR)) {
+			fprintf(stderr, "Criteria error: AVC\n");
+			return 1;
+		}
+		if (ausearch_add_item(au, "type", "=", "SYSTEM_SHUTDOWN",
+					AUSEARCH_RULE_OR)) {
+			fprintf(stderr, "Criteria error: shutdown\n");
+			return 1;
+		}
+		snprintf(expr, sizeof(expr),
+			"(\\record_type >= %d && \\record_type <= %d) ||"
+			"(\\record_type >= %d && \\record_type <= %d)",
+			AUDIT_FIRST_ANOM_MSG, AUDIT_LAST_ANOM_MSG,
+			AUDIT_FIRST_KERN_ANOM_MSG, AUDIT_LAST_KERN_ANOM_MSG);
+		if (ausearch_add_expression(au, expr, &error,
+					AUSEARCH_RULE_OR)) {
+			fprintf(stderr, "Criteria error: %s\n", error);
+			free(error);
+			return 1;
+		}
+	}
+	if (start_time) {
+		if (ausearch_add_timestamp_item(au, ">=", start_time, 0,
+					AUSEARCH_RULE_AND)) {
+			fprintf(stderr, "Criteria error: start_time\n");
+			return 1;
+		}
+	}
+	if (end_time) {
+		if (ausearch_add_timestamp_item(au, "<=", end_time, 0,
+					AUSEARCH_RULE_AND)) {
+			fprintf(stderr, "Criteria error: end_time\n");
+			return 1;
+		}
+	}
+	return 0;
+}
+
+/* Extract the most common fields from virtualization-related records. */
+int extract_virt_fields(auparse_state_t *au, const char **p_uuid,
+		uid_t *p_uid, time_t *p_time, const char **p_name,
+		int *p_suc)
+{
+	const char *field;
+	auparse_first_record(au);
+	/* Order matters */
+	if (p_uid) {
+		if (!auparse_find_field(au, field = "uid"))
+			goto error;
+		*p_uid = auparse_get_field_int(au);
+	}
+	if (p_name) {
+		if (!auparse_find_field(au, field = "vm"))
+			goto error;
+		*p_name = auparse_get_field_str(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 ? field : "",
+				auparse_get_time(au),
+				auparse_get_milli(au),
+				auparse_get_serial(au));
+	}
+	return 1;
+}
+
+/* Return label and categories from a security context. */
+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;
+}
+
+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;
+}
+
+/*
+ * machine_id records are used to get the selinux context associated to a
+ * guest.
+ */
+int process_machine_id_event(auparse_state_t *au)
+{
+	uid_t uid;
+	time_t time;
+	const char *seclevel, *uuid, *name;
+	struct event *event;
+	int success;
+
+	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");
+	}
+
+	if (extract_virt_fields(au, &uuid, &uid, &time, &name, &success))
+		return 0;
+
+	event = event_alloc();
+	if (event == NULL)
+		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->uid = uid;
+	event->start = time;
+	add_proof(event, au);
+	if (list_append(events, event) == NULL) {
+		event_free(event);
+		return 1;
+	}
+	return 0;
+}
+
+int add_start_guest_event(auparse_state_t *au)
+{
+	struct event *start;
+	uid_t uid;
+	time_t time;
+	const char *uuid, *name;
+	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, &uid, &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)
+		return 1;
+	start->type = ET_START;
+	start->uuid = copy_str(uuid);
+	start->name = copy_str(name);
+	start->success = success;
+	start->uid = uid;
+	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;
+}
+
+int add_stop_guest_event(auparse_state_t *au)
+{
+	list_node_t *it;
+	struct event *stop, *start = NULL, *event = NULL;
+	uid_t uid;
+	time_t time;
+	const char *uuid, *name;
+	int success;
+
+	/* Just skip this record if it failed to get some of the fields */
+	if (extract_virt_fields(au, &uuid, &uid, &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 i"
+					"record to the stop event.\n");
+		}
+		return 0;
+	}
+
+	/* Create a new stop event */
+	stop = event_alloc();
+	if (stop == NULL)
+		return 1;
+	stop->type = ET_STOP;
+	stop->uuid = copy_str(uuid);
+	stop->name = copy_str(name);
+	stop->success = success;
+	stop->uid = uid;
+	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) {
+		event_free(stop);
+		return 1;
+	}
+
+	/* Update the correlated start event. */
+	if (success) {
+		start->end = time;
+		add_proof(start, au);
+	}
+	return 0;
+}
+
+int process_control_event(auparse_state_t *au)
+{
+	const char *op;
+
+	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;
+}
+
+inline int is_resource(const char *res)
+{
+	if (res == NULL ||
+	    res[0] == '\0' ||
+	    strcmp("0", res) == 0 ||
+	    strcmp("?", res) == 0)
+		return 0;
+	return 1;
+}
+
+int add_resource(auparse_state_t *au, const char *uuid, uid_t uid, time_t time,
+		const char *name, int success, const char *reason,
+		const char *res_type, const char *res)
+{
+	if (!is_resource(res))
+		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->uid = uid;
+	event->start = time;
+	add_proof(event, au);
+	if (list_append(events, event) == NULL) {
+		event_free(event);
+		return 1;
+	}
+	return 0;
+}
+
+int update_resource(auparse_state_t *au, const char *uuid, uid_t uid,
+		time_t time, const char *name, int success, const char *reason,
+		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.\n");
+		}
+		return 0;
+	}
+
+	start->end = time;
+	add_proof(start, au);
+	return 0;
+}
+
+int process_resource_event(auparse_state_t *au)
+{
+	uid_t uid;
+	time_t time;
+	const char *res_type, *uuid, *name;
+	char field[64];
+	const char *reason;
+	int success;
+
+	/* Just skip this record if it failed to get some of the fields */
+	if (extract_virt_fields(au, &uuid, &uid, &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");
+		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("net", res_type) == 0) {
+		const char *res;
+		/* Resource removed */
+		snprintf(field, sizeof(field), "old-%s", res_type);
+		res = auparse_find_field(au, field);
+		if (res == NULL && debug) {
+			fprintf(stderr, "Failed to get %s field.\n", field);
+		} else {
+			rc += update_resource(au, uuid, uid, time, name,
+					success, reason, res_type, res);
+		}
+
+		/* Resource added */
+		snprintf(field, sizeof(field), "new-%s", res_type);
+		res = auparse_find_field(au, field);
+		if (res == NULL && debug) {
+			fprintf(stderr, "Failed to get %s field.\n", field);
+		} else {
+			rc += add_resource(au, uuid, uid, time, name, success,
+					reason, res_type, res);
+		}
+	} else if (strcmp("cgroup", res_type) == 0) {
+		auparse_first_record(au);
+		const char *cgroup = auparse_find_field(au, "cgroup");
+		rc += add_resource(au, uuid, uid, time, name, success, reason,
+				res_type, cgroup);
+	} else if (debug) {
+		fprintf(stderr, "Found an unknown resource: %s.\n",
+				res_type);
+	}
+	return rc;
+}
+
+/* Search for the last machine_id record with the given seclevel */
+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 &&
+		    strcmp(event->seclevel, seclevel) == 0) {
+			machine_id = event;
+			break;
+		}
+	}
+
+	return machine_id;
+}
+
+/* AVC records are correlated to guest through the selinux context. */
+int process_avc(auparse_state_t *au)
+{
+	const char *target, *seclevel;
+	struct event *machine_id, *avc;
+	uid_t uid;
+	time_t time;
+
+	seclevel = get_seclevel(auparse_find_field(au, "tcontext"));
+	if (seclevel == NULL) {
+		if (debug) {
+			fprintf(stderr, "Security context not found for "
+					"AVC event.\n");
+		}
+		return 0;
+	}
+
+	if (extract_virt_fields(au, NULL, &uid, &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 security level from "
+					"the AVC event.\n");
+		}
+		return 0;
+	}
+
+	avc = event_alloc();
+	if (avc == NULL)
+		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->uid = uid;
+	avc->seclevel = copy_str(seclevel);
+	auparse_first_record(au);
+	avc->seresult = copy_str(auparse_find_field(au, "seresult"));
+	avc->seperms = copy_str(auparse_find_field(au, "seperms"));
+	avc->comm = copy_str(auparse_find_field(au, "comm"));
+	avc->target = copy_str(auparse_find_field(au, "name"));
+	add_proof(avc, au);
+	if (list_append(events, avc) == NULL) {
+		event_free(avc);
+		return 1;
+	}
+	return 0;
+}
+
+/* This function tries to correlate an anomaly record to a guest using the qemu
+ * pid or the selinux context. */
+int process_anom(auparse_state_t *au)
+{
+	uid_t uid;
+	time_t time;
+	pid_t pid = -1;
+	list_node_t *it;
+	struct event *anom, *start = NULL;
+
+	/* 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 &&
+			    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, &uid, &time, NULL, NULL))
+		return 0;
+
+	anom = event_alloc();
+	if (anom == NULL)
+		return 1;
+	anom->type = ET_ANOM;
+	anom->uuid = copy_str(start->uuid);
+	anom->name = copy_str(start->name);
+	anom->uid = uid;
+	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;
+}
+
+int process_shutdown(auparse_state_t *au)
+{
+	uid_t uid = -1;
+	time_t time = 0;
+	struct event *down;
+	list_node_t *it;
+	int success = 0;
+
+	if (extract_virt_fields(au, NULL, &uid, &time, NULL, &success))
+		return 0;
+
+	for (it = events->tail; it; it = it->prev) {
+		struct event *event = it->data;
+		if (event->success) {
+			if (event->type == ET_START || event->type == ET_RES) {
+				if (event->end == 0) {
+					event->end = time;
+					add_proof(event, au);
+				}
+			} else if (event->type == ET_DOWN) {
+				break;
+			}
+		}
+	}
+
+	down = event_alloc();
+	if (down == NULL)
+		return 1;
+	down->type = ET_DOWN;
+	down->uid = uid;
+	down->start = time;
+	down->success = success;
+	add_proof(down, au);
+	if (list_append(events, down) == NULL) {
+		event_free(down);
+		return 1;
+	}
+	return 0;
+}
+
+/* Convert record type to a string */
+const char *get_rec_type(struct event *e)
+{
+	static char buf[64];
+	if (e == NULL)
+		return "";
+
+	switch (e->type) {
+	case ET_START:
+		return "start";
+	case ET_STOP:
+		return "stop";
+	case ET_RES:
+		return "res";
+	case ET_AVC:
+		return "avc";
+	case ET_ANOM:
+		return "anom";
+	case ET_DOWN:
+		return "down";
+	}
+
+	snprintf(buf, sizeof(buf), "%d", e->type);
+	return buf;
+}
+
+/* Convert uid to a string */
+const char *get_username(struct event *e)
+{
+	static char s[256];
+	if (!e || e->uid < 0) {
+		s[0] = '?';
+		s[1] = '\0';
+	} else {
+		struct passwd *passwd = getpwuid(e->uid);
+		if (passwd == NULL || passwd->pw_name == NULL) {
+			snprintf(s, sizeof(s), "%d", e->uid);
+		} else {
+			snprintf(s, sizeof(s), "%s", passwd->pw_name);
+		}
+	}
+	return s;
+}
+
+/* Convert a time period to string */
+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) {
+			i += sprintf(buf + i, "(%d+%02d:%02d)", days, hours,
+					mins);
+		} else {
+			i += sprintf(buf + i, "(%02d:%02d)", hours, mins);
+		}
+	} else {
+		if (!event->success &&
+		    event->type != ET_AVC &&
+		    event->type != ET_ANOM) {
+			i += sprintf(buf + i, " - failed");
+		}
+	}
+	return buf;
+}
+
+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("%-5.5s ", get_rec_type(event));
+
+	/* Print common fields */
+	printf("%-25.25s", N(event->name));
+	if (uuid_flag)
+		printf("\t%-36.36s", N(event->uuid));
+	printf("\t%-11.11s\t%-35.35s", get_username(event),
+			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));
+		printf("\t%s", N(event->res));
+	} 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->seperms));
+		printf("\t%-10.10s", N(event->seresult));
+		printf("\t%s\t%s", N(event->comm), N(event->target));
+	}
+	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",
+					(first) ? "" : ",",
+					event->proof[i].time,
+					event->proof[i].milli,
+					event->proof[i].serial);
+				first = 0;
+			}
+		}
+		printf("\n\n");
+	}
+}
+
+/* Print all events */
+void print_events()
+{
+	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 */
+void print_summary()
+{
+	/* Summary numbers */
+	time_t start_time = 0, end_time = 0;
+	long start = 0, stop = 0, res = 0, avc = 0, anom = 0,
+	     shutdown = 0, failure = 0;
+
+	/* 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;
+			case ET_DOWN:
+				shutdown++;
+				break;
+			}
+		}
+
+		/* Calculate time range */
+		if (event->start) {
+			if (start_time == 0 || event->start < start_time) {
+				start_time = event->start;
+			}
+			if (end_time == 0 || event->start > end_time) {
+				end_time = event->start;
+			}
+		}
+		if (event->end) {
+			if (start_time == 0 || event->end < start_time) {
+				start_time = event->end;
+			}
+			if (end_time == 0 || event->end > end_time) {
+				end_time = event->end;
+			}
+		}
+
+	}
+
+	/* Print summary */
+	printf("Range of time for report:       %-.16s - %-.16s\n",
+			(start_time) ? ctime(&start_time) : "undef",
+			(end_time) ? ctime(&end_time) : "undef");
+	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 host shutdowns:       %ld\n", shutdown);
+	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;
+	if (create_search_criteria(au))
+		goto error;
+
+	while (ausearch_next_event(au) > 0) {
+		const char *op;
+		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:
+			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:
+			err = process_anom(au);
+			break;
+		case AUDIT_SYSTEM_SHUTDOWN:
+			err = process_shutdown(au);
+			break;
+		}
+		if (err) {
+			goto unexpected_error;
+		}
+		auparse_next_event(au);
+	}
+
+	/* 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;
+}
+
-- 
1.7.1




More information about the Linux-audit mailing list