[PATCH] mapping of reactions

Juraj Hlista juro.hlista at gmail.com
Fri Apr 2 12:15:19 UTC 2010


> Hello,
> the code looks reasonable, some minor comments are below.  I'll let Steve and others comment on the high-level design (just to point out a question, is it OK that auditctl will depend on sqlite?).
>     Mirek
Changes were applied according to the comments.

> I didn't look in detail, this does not match my understanding of "reset_vars()"; reset_vars() is supposed to reinitialize everything for a next command, not free everything.  (The free(rule_new) call you moved from reset_vars() to free_vars() was at the beginning of reset_vars(), not at the end.)
I renamed "reset_vars()" to "init_vars()" so it could be clear enough.

Signed-off-by: Juraj Hlista <juro.hlista at gmail.com>
---
 audit.spec              |    2 +
 lib/Makefile.am         |    4 +-
 lib/errormsg.h          |    7 +-
 lib/fieldtab.h          |    2 +-
 lib/libaudit.c          |   26 ++++
 lib/libaudit.h          |    4 +
 lib/msg_typetab.h       |    1 +
 lib/reactarray.c        |   77 +++++++++++
 lib/reactarray.h        |   41 ++++++
 src/Makefile.am         |    7 +-
 src/auditctl-reactsql.c |  332 +++++++++++++++++++++++++++++++++++++++++++++++
 src/auditctl-reactsql.h |   55 ++++++++
 src/auditctl.c          |  240 +++++++++++++++++++++++++++++++---
 src/mt/Makefile.am      |    4 +-
 14 files changed, 776 insertions(+), 26 deletions(-)
 create mode 100644 lib/reactarray.c
 create mode 100644 lib/reactarray.h
 create mode 100644 src/auditctl-reactsql.c
 create mode 100644 src/auditctl-reactsql.h

diff --git a/audit.spec b/audit.spec
index a7a94c4..af3ee43 100644
--- a/audit.spec
+++ b/audit.spec
@@ -82,6 +82,7 @@ mkdir -p $RPM_BUILD_ROOT/%{_mandir}/{man5,man8}
 mkdir -p $RPM_BUILD_ROOT/%{_lib}
 mkdir -p $RPM_BUILD_ROOT/%{_libdir}/audit
 mkdir -p $RPM_BUILD_ROOT/%{_var}/log/audit
+mkdir -p $RPM_BUILD_ROOT/%{_var}/run/auditctl
 make DESTDIR=$RPM_BUILD_ROOT install
 
 mkdir -p $RPM_BUILD_ROOT/%{_libdir}
@@ -187,6 +188,7 @@ fi
 %attr(755,root,root) %{_bindir}/ausyscall
 %attr(755,root,root) /etc/rc.d/init.d/auditd
 %attr(750,root,root) %{_var}/log/audit
+%attr(750,root,root) %{_var}/run/auditctl
 %attr(750,root,root) %dir /etc/audit
 %attr(750,root,root) %dir /etc/audisp
 %attr(750,root,root) %dir /etc/audisp/plugins.d
diff --git a/lib/Makefile.am b/lib/Makefile.am
index c5952f9..998215c 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -30,8 +30,8 @@ INCLUDES = -I. -I${top_srcdir} -I${top_srcdir}/auparse
 lib_LTLIBRARIES = libaudit.la
 include_HEADERS = libaudit.h
 libaudit_la_SOURCES = libaudit.c message.c netlink.c \
-	lookup_table.c audit_logging.c deprecated.c \
-	private.h errormsg.h
+	lookup_table.c audit_logging.c deprecated.c reactarray.c \
+	reactarray.h private.h errormsg.h
 libaudit_la_LIBADD =
 libaudit_la_DEPENDENCIES = $(libaudit_la_SOURCES) ../config.h
 libaudit_la_LDFLAGS = -Wl,-z,relro -version-info $(VERSION_INFO)
diff --git a/lib/errormsg.h b/lib/errormsg.h
index 625611b..e6d78a9 100644
--- a/lib/errormsg.h
+++ b/lib/errormsg.h
@@ -54,5 +54,10 @@ static const struct msg_tab err_msgtab[] = {
     { -19,    0,    "Key field needs a watch or syscall given prior to it" },
     { -20,    2,    "-F missing value after operation for" },
     { -21,    2,    "-F value should be number for" },
-    { -22,    2,    "-F missing field name before operator for" }
+    { -22,    2,    "-F missing field name before operator for" },
+    { -23,    0,    "Too many reactions" },
+    { -24,    0,    "Out of memory adding reaction" },
+    { -25,    0,    "React field needs a watch or syscall given prior to it" },
+    { -26,    0,    "Bad operation used with react field" },
+    { -27,    0,    "Failed converting react string to number" }
 };
diff --git a/lib/fieldtab.h b/lib/fieldtab.h
index ad95814..a973734 100644
--- a/lib/fieldtab.h
+++ b/lib/fieldtab.h
@@ -62,4 +62,4 @@ _S(AUDIT_ARG2,         "a2"           )
 _S(AUDIT_ARG3,         "a3"           )
 
 _S(AUDIT_FILTERKEY,    "key"          )
-
+_S(AUDIT_REACTION,     "react"	      )
diff --git a/lib/libaudit.c b/lib/libaudit.c
index 337d1d2..ec18f5e 100644
--- a/lib/libaudit.c
+++ b/lib/libaudit.c
@@ -41,6 +41,7 @@
 #include "libaudit.h"
 #include "private.h"
 #include "errormsg.h"
+#include "reactarray.h"
 
 /* #defines for the audit failure query  */
 #define CONFIG_FILE "/etc/libaudit.conf"
@@ -80,6 +81,7 @@ static const struct nv_list failure_actions[] =
 int audit_permadded hidden = 0;
 int audit_archadded hidden = 0;
 int audit_syscalladded hidden = 0;
+struct react_array ra hidden;
 unsigned int audit_elf hidden = 0U;
 static struct libaudit_conf config;
 
@@ -791,6 +793,7 @@ int audit_rule_fieldpair_data(struct audit_rule_data **rulep, const char *pair,
 	int        vlen;
 	int        offset;
 	struct audit_rule_data *rule = *rulep;
+	uint32_t react_num;
 
 	if (f == NULL)
 		return -1;
@@ -845,6 +848,21 @@ int audit_rule_fieldpair_data(struct audit_rule_data **rulep, const char *pair,
 	/* Exclude filter can be used only with MSGTYPE field */
 	if (flags == AUDIT_FILTER_EXCLUDE && field != AUDIT_MSGTYPE)
 		return -12; 
+	/* reaction string identifiers are stored in an array at first */
+	if (field == AUDIT_REACTION && !ra.add_to_rule) {
+		if (!audit_syscalladded && !audit_permadded)
+			return -25;
+		if (op != AUDIT_EQUAL)
+			return -26;
+		vlen = strlen(v);
+		if (vlen > AUDIT_MAX_KEY_LEN)
+			return -11;
+		if (ra.count >= AUDIT_MAX_REACTS)
+			return -23;
+		if (react_array_insert(&ra, v))
+			return -24;
+		return 0;
+	}
 
 	rule->fields[rule->field_count] = field;
 	rule->fieldflags[rule->field_count] = op;
@@ -965,6 +983,14 @@ int audit_rule_fieldpair_data(struct audit_rule_data **rulep, const char *pair,
 			strncpy(&rule->buf[offset], v, vlen);
 
 			break;
+		case AUDIT_REACTION:
+			/* string identifiers were converted to numbers */
+			if (isdigit((unsigned char)*(v)))
+				react_num = (uint32_t)strtoul(v, NULL, 0);
+			else
+				return -27;
+			rule->values[rule->field_count] = react_num;
+			break;
 		case AUDIT_ARCH:
 			if (audit_syscalladded) 
 				return -3;
diff --git a/lib/libaudit.h b/lib/libaudit.h
index e0a1510..f3ff84c 100644
--- a/lib/libaudit.h
+++ b/lib/libaudit.h
@@ -203,6 +203,10 @@ extern "C" {
 /* This is related to the filterkey patch */
 #define AUDIT_KEY_SEPARATOR 0x01
 
+#define AUDIT_MAX_REACTS	8
+#define AUDIT_REACTION		220
+#define AUDIT_REACT_RULE	1323
+
 /* These are used in filter control */
 #define AUDIT_FILTER_EXCLUDE	AUDIT_FILTER_TYPE
 #define AUDIT_FILTER_MASK	0x07	/* Mask to get actual filter */
diff --git a/lib/msg_typetab.h b/lib/msg_typetab.h
index 017bb27..dcbc8da 100644
--- a/lib/msg_typetab.h
+++ b/lib/msg_typetab.h
@@ -102,6 +102,7 @@ _S(AUDIT_TTY,                        "TTY"                           )
 _S(AUDIT_EOE,                        "EOE"                           )
 _S(AUDIT_BPRM_FCAPS,                 "BPRM_FCAPS"                    )
 _S(AUDIT_CAPSET,                     "CAPSET"                        )
+_S(AUDIT_REACT_RULE,		     "REACT_RULE"		     )
 _S(AUDIT_AVC,                        "AVC"                           )
 _S(AUDIT_SELINUX_ERR,                "SELINUX_ERR"                   )
 _S(AUDIT_AVC_PATH,                   "AVC_PATH"                      )
diff --git a/lib/reactarray.c b/lib/reactarray.c
new file mode 100644
index 0000000..98fd6ba
--- /dev/null
+++ b/lib/reactarray.c
@@ -0,0 +1,77 @@
+/* reactarray.c
+ * Copyright 2010 Juraj Hlista
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Authors:
+ *     Juraj Hlista <juro.hlista at gmail.com>
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "reactarray.h"
+
+/*
+ * Allocation and initialiazation of the array for
+ * reaction identifiers
+ */
+int react_array_init(struct react_array *a, const unsigned int size)
+{
+	int i;
+
+	a->add_to_rule = 0;
+	a->processed = 0;
+	a->count = 0;
+	a->size = size;
+	a->str = calloc(size, sizeof(char *));
+	if (!a->str)
+		return 1;
+
+	return 0;
+}
+
+/*
+ * Free identifiers
+ */
+void react_array_free(struct react_array *a)
+{
+	int i;
+
+	if (!a->str)
+		return;
+
+	for (i = 0; i < a->count; i++)
+		free(a->str[i]);
+
+	free(a->str);
+}
+
+/*
+ * Insert a string identifier into the array
+ */
+int react_array_insert(struct react_array *a, const char *s)
+{
+	/* error code reaturned in libaudit.c */
+	if (a->count >= a->size)
+		return 0;
+
+	a->str[a->count] = strdup(s);
+	if (!a->str[a->count])
+		return 1;
+	a->count++;
+
+	return 0;
+}
+
diff --git a/lib/reactarray.h b/lib/reactarray.h
new file mode 100644
index 0000000..904be95
--- /dev/null
+++ b/lib/reactarray.h
@@ -0,0 +1,41 @@
+/* reactarray.h
+ * Copyright 2010 Juraj Hlista
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Authors:
+ *     Juraj Hlista <juro.hlista at gmail.com>
+ */
+
+#ifndef _REACTARRAY_H_
+#define _REACTARRAY_H_
+
+struct react_array {
+	int add_to_rule;    /* if 0 - identifiers are stored in the array */
+	int processed;      /* number of reactions per 1 rule stored in database */
+	unsigned int count; /* number of reactions kept in this structure */
+	unsigned int size;  /* max number of reactions per 1 rule */
+	char **str;         /* identifiers */
+};
+
+
+int react_array_init(struct react_array *a, unsigned int size);
+
+void react_array_free(struct react_array *a);
+
+int react_array_insert(struct react_array *a, const char *s);
+
+#endif
+
diff --git a/src/Makefile.am b/src/Makefile.am
index 124b77e..2bc1ff4 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -28,7 +28,7 @@ sbin_PROGRAMS = auditd auditctl aureport ausearch autrace
 LIBS = -Lmt -lauditmt
 LDADD = -lpthread
 AM_CFLAGS = -D_REENTRANT -D_GNU_SOURCE
-noinst_HEADERS = auditd-config.h auditd-event.h auditd-listen.h ausearch-llist.h ausearch-options.h auditctl-llist.h aureport-options.h ausearch-parse.h aureport-scan.h ausearch-lookup.h ausearch-int.h auditd-dispatch.h ausearch-string.h ausearch-nvpair.h ausearch-common.h ausearch-avc.h ausearch-time.h ausearch-lol.h
+noinst_HEADERS = auditd-config.h auditd-event.h auditd-listen.h ausearch-llist.h ausearch-options.h auditctl-llist.h auditctl-reactsql.h aureport-options.h ausearch-parse.h aureport-scan.h ausearch-lookup.h ausearch-int.h auditd-dispatch.h ausearch-string.h ausearch-nvpair.h ausearch-common.h ausearch-avc.h ausearch-time.h ausearch-lol.h
 
 auditd_SOURCES = auditd.c auditd-event.c auditd-config.c auditd-reconfig.c auditd-sendmail.c auditd-dispatch.c auditd-listen.c
 auditd_CFLAGS = -fPIE -DPIE -g -D_REENTRANT -D_GNU_SOURCE -fno-strict-aliasing 
@@ -36,8 +36,9 @@ auditd_LDFLAGS = -pie -Wl,-z,relro
 auditd_DEPENDENCIES = mt/libauditmt.a libev/libev.a
 auditd_LDADD = @LIBWRAP_LIBS@ @libev_LIBS@ -Llibev -lev -lrt -lpthread -lm $(gss_libs)
 
-auditctl_SOURCES = auditctl.c auditctl-llist.c delete_all.c
-auditctl_DEPENDENCIES = mt/libauditmt.a 
+auditctl_SOURCES = auditctl.c auditctl-llist.c delete_all.c auditctl-reactsql.c
+auditctl_DEPENDENCIES = mt/libauditmt.a
+auditctl_LDADD = -lsqlite3
 
 aureport_SOURCES = aureport.c auditd-config.c ausearch-llist.c aureport-options.c ausearch-string.c ausearch-parse.c aureport-scan.c aureport-output.c ausearch-lookup.c ausearch-int.c ausearch-time.c ausearch-nvpair.c ausearch-avc.c ausearch-lol.c
 aureport_DEPENDENCIES = mt/libauditmt.a
diff --git a/src/auditctl-reactsql.c b/src/auditctl-reactsql.c
new file mode 100644
index 0000000..f7e921e
--- /dev/null
+++ b/src/auditctl-reactsql.c
@@ -0,0 +1,332 @@
+/* auditctl-reactsql.c
+ * Copyright 2010 Juraj Hlista
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Authors:
+ *     Juraj Hlista <juro.hlista at gmail.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "auditctl-reactsql.h"
+
+#define SQL_OFFSET 10000
+
+const char *sql_errmsg[] = {
+	"", "",
+	"SQL query suppossed to return a value",
+	"Out of memory allocating reaction string",
+	"React number reached maximal value"
+};
+
+static int sql_table_check(sqlite3 *c);
+
+/*
+ * Print an error
+ */
+void sql_print_error(sqlite3 *c, int err)
+{
+	if (err == -SQL_ERROR)
+		fprintf(stderr, "SQLite error: %s\n", sqlite3_errmsg(c));
+	else
+		fprintf(stderr, "SQLite error: %s\n", sql_errmsg[-err]);
+}
+
+/*
+ * Open a database file and check if table exists - if not, create it
+ */
+int sql_open_database(sqlite3 **c, const char *db)
+{
+	if (sqlite3_open(db, c) || sql_table_check(*c))
+		return -SQL_ERROR;
+
+	return 0;
+}
+
+/*
+ * Close database
+ */
+int sql_close_database(sqlite3 *c)
+{
+	if (sqlite3_close(c))
+		return -SQL_ERROR;
+
+	return 0;
+}
+
+/*
+ * Get reaction string
+ */
+int sql_number_to_reaction(sqlite3 *c, const int num, char **str)
+{
+	const char *reaction = NULL;
+	sqlite3_stmt *find_str;
+	const char *query = "SELECT string FROM reaction WHERE number = ?";
+
+	if (sqlite3_prepare(c, query, -1, &find_str, NULL))
+		return -SQL_ERROR;
+
+	if (sqlite3_bind_int(find_str, 1, num))
+		return -SQL_ERROR;
+
+	if (sqlite3_step(find_str) != SQLITE_ROW) {
+		sqlite3_finalize(find_str);
+		return -SQL_NO_VALUE;
+	}
+
+	reaction = (const char *)sqlite3_column_text(find_str, 0);
+	*str = strdup(reaction);
+	if (*str == NULL) {
+		sqlite3_finalize(find_str);
+		return -SQL_NO_MEMORY;
+	}
+
+	if (sqlite3_finalize(find_str))
+		return -SQL_ERROR;
+
+	return 0;
+}
+
+/*
+ * Get reaction number
+ */
+int sql_reaction_to_number(sqlite3 *c, const char *str, int *num)
+{
+	sqlite3_stmt *find_num;
+	const char *query = "SELECT number FROM reaction WHERE string = ?";
+
+	if (sqlite3_prepare(c, query, -1, &find_num, NULL))
+		return -SQL_ERROR;
+
+	if (sqlite3_bind_text(find_num, 1, str, -1, NULL))
+		return -SQL_ERROR;
+
+	if (sqlite3_step(find_num) != SQLITE_ROW) {
+		sqlite3_finalize(find_num);
+		return -SQL_NO_VALUE;
+	}
+
+	*num = sqlite3_column_int(find_num, 0);
+
+	if (sqlite3_finalize(find_num))
+		return -SQL_ERROR;
+
+	return 0;
+}
+
+/*
+ * Add a reaction to the database - if num->action is UPDATE,
+ * a reaction identifier (string) is already in the database and only
+ * 'used' is incremented. If there is not such a reaction string, a new
+ * one is inserted into the database and 'used' is set to 1.
+ */
+int sql_add_reaction(sqlite3 *c, const struct react_number *num, const char *str)
+{
+	sqlite3_stmt *change;
+
+	/* update table */
+	if (num->action == UPDATE) {
+		const char *query = "UPDATE reaction SET "
+				    "string = ?, used = used + 1 "
+				    "WHERE number = ?";
+
+		if (sqlite3_prepare(c, query, -1, &change, NULL))
+			return -SQL_ERROR;
+
+		if (sqlite3_bind_text(change, 1, str, -1, NULL))
+			return -SQL_ERROR;
+
+		if (sqlite3_bind_int(change, 2, num->number))
+			return -SQL_ERROR;
+	}
+	/* insert into table */
+	else if (num->action == INSERT) {
+		const char *query = "INSERT INTO reaction VALUES(?, ?, 1)";
+
+		if (sqlite3_prepare(c, query, -1, &change, NULL))
+			return -SQL_ERROR;
+
+		if (sqlite3_bind_int(change, 1, num->number))
+			return -SQL_ERROR;
+
+		if (sqlite3_bind_text(change, 2, str, -1, NULL))
+			return -SQL_ERROR;
+	}
+
+	sqlite3_step(change);
+
+	if (sqlite3_finalize(change))
+		return -SQL_ERROR;
+
+	return 0;
+}
+
+/*
+ * Delete reaction by decreasing of used
+ */
+int sql_del_reaction(sqlite3 *c, const char *str)
+{
+	sqlite3_stmt *change;
+	const char *query = "UPDATE reaction SET used = used - 1 "
+			    "WHERE string = ?";
+
+	if (sqlite3_prepare(c, query, -1, &change, NULL))
+		return -SQL_ERROR;
+
+	if (sqlite3_bind_text(change, 1, str, -1, NULL))
+		return -SQL_ERROR;
+
+	sqlite3_step(change);
+
+	if (sqlite3_finalize(change))
+		return -SQL_ERROR;
+
+	return 0;
+}
+
+/*
+ * Drop table
+ */
+int sql_del_reaction_all(sqlite3 *c)
+{
+	sqlite3_stmt *drop;
+	const char *query = "DROP TABLE reaction";
+
+	if (sqlite3_prepare(c, query, -1, &drop, NULL))
+		return -SQL_ERROR;
+
+	sqlite3_step(drop);
+
+	if (sqlite3_finalize(drop))
+		return -SQL_ERROR;
+
+	return 0;
+}
+
+/*
+ * This function must be called before adding reactions to the database.
+ */
+struct react_number sql_get_next_number(sqlite3 *c, const char *str)
+{
+	int x;
+	struct react_number result = {ERROR, 0};
+	sqlite3_stmt *get_num;
+	/* if table is empty, return 1
+	 * if a reaction 'string' is in the table, return the string's 'number'
+	 * if 'used' is 0, return 'number' in this row
+	 * return max 'number' + 1 otherwise
+	 * number 10000 must be the same as SQL_OFFSET
+	 */
+	const char *query = "SELECT COALESCE "
+			    "(CASE WHEN A.cnt = 0 THEN 1 END, "
+			    "B.num + 10000, C.num + 10000, D.num) "
+			    "FROM "
+			    "(SELECT COUNT(*) AS cnt FROM reaction) AS A, "
+			    "(SELECT MAX(number) AS num "
+			    "FROM reaction WHERE string = ?) AS B, "
+			    "(SELECT MIN(number) AS num "
+			    "FROM reaction WHERE used = 0) AS C, "
+			    "(SELECT (MAX(number) + 1) AS num "
+			    "FROM reaction) AS D";
+
+	if (sqlite3_prepare(c, query, -1, &get_num, NULL)) {
+		result.number -SQL_ERROR;
+		return result;
+	}
+
+	if (sqlite3_bind_text(get_num, 1, str, -1, NULL)) {
+		result.number = -SQL_ERROR;
+		return result;
+	}
+
+	if (sqlite3_step(get_num) != SQLITE_ROW) {
+		sqlite3_finalize(get_num);
+		result.number = -SQL_NO_VALUE;
+		return result;
+	}
+
+	result.number = sqlite3_column_int(get_num, 0);
+
+	if (sqlite3_finalize(get_num)) {
+		result.number = -SQL_ERROR;
+		return result;
+	}
+
+	x = result.number - SQL_OFFSET;
+	/* there are SQL_OFFSET - 1 numbers to be used with reaction strings */
+	if (!x || x >= SQL_OFFSET) {
+		result.number = -SQL_MAX_NUMBER;
+		return result;
+	} else {
+		if (x < 0) {
+			result.action = INSERT;
+		} else {
+			result.action = UPDATE;
+			result.number = x;
+		}
+	}
+
+	return result;
+}
+
+/*
+ * Check if table exists, if not, a new one is created.
+ */
+static int sql_table_check(sqlite3 *c)
+{
+	int rc;
+	sqlite3_stmt *check, *create;
+
+	const char *query1 = "SELECT name "
+			     "FROM sqlite_master "
+			     "WHERE type='table' AND name='reaction'";
+
+	/* check if table exists */
+	if (sqlite3_prepare(c, query1, -1, &check, NULL))
+		return -SQL_ERROR;
+
+	rc = sqlite3_step(check);
+
+	if (sqlite3_finalize(check))
+		return -SQL_ERROR;
+
+	/* table doesn't exist, create table */
+	if (rc != SQLITE_ROW) {
+		/* number - the value that is in the kernel 
+		 * string - reaction identifier
+		 * used - how many times is the reaction used with rules
+		 */
+		const char *query2 = "CREATE TABLE reaction "
+				     "(number INTEGER NOT NULL "
+				     "CHECK (number > 0), "
+				     "string VARCHAR(255) NOT NULL, "
+				     "used INTEGER CHECK (used >= 0), "
+				     "UNIQUE(number), "
+				     "UNIQUE(string))";
+
+		if (sqlite3_prepare(c, query2, -1, &create, NULL))
+			return -SQL_ERROR;
+
+		sqlite3_step(create);
+
+		if (sqlite3_finalize(create))
+			return -SQL_ERROR;
+	}
+
+	return 0;
+}
+
diff --git a/src/auditctl-reactsql.h b/src/auditctl-reactsql.h
new file mode 100644
index 0000000..8c70a31
--- /dev/null
+++ b/src/auditctl-reactsql.h
@@ -0,0 +1,55 @@
+/* auditctl-reactsql.h
+ * Copyright 2010 Juraj Hlista
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Authors:
+ *     Juraj Hlista <juro.hlista at gmail.com>
+ */
+
+#ifndef CTLREACTSQL_HEADER
+#define CTLREACTSQL_HEADER
+
+#include <sqlite3.h>
+
+struct react_number {
+	enum {
+		ERROR,
+		INSERT,
+		UPDATE
+	} action;
+	int number;
+};
+
+enum {
+	SQL_ERROR = 1,
+	SQL_NO_VALUE,
+	SQL_NO_MEMORY,
+	SQL_MAX_NUMBER
+};
+
+
+int sql_open_database(sqlite3 **c, const char *db);
+
+int sql_close_database(sqlite3 *c);
+
+int sql_add_reaction(sqlite3 *c, const struct react_number *num, const char *str);
+
+int sql_del_reaction(sqlite3 *c, const char *str);
+
+struct react_number sql_get_next_number(sqlite3 *c, const char *str);
+
+#endif
+
diff --git a/src/auditctl.c b/src/auditctl.c
index 03cac39..def078d 100644
--- a/src/auditctl.c
+++ b/src/auditctl.c
@@ -37,6 +37,8 @@
 #include <limits.h>	/* PATH_MAX */
 #include "libaudit.h"
 #include "private.h"
+#include "auditctl-reactsql.h"
+#include "reactarray.h"
 
 /* This define controls how many rule options we will allow when
  * reading a rule from a file. 64 fields are allowed by the kernel, so I
@@ -50,6 +52,8 @@
  */
 #define LINE_SIZE 1600
 
+/* Database file where mapping of reaction strings to numbers is stored */
+#define REACT_DB "/var/run/auditctl/react.db"
 
 /* Global functions */
 static int handle_request(int status);
@@ -73,14 +77,15 @@ static const char key_sep[2] = { AUDIT_KEY_SEPARATOR, 0 };
 /* External vars */
 extern int audit_archadded;
 extern int audit_syscalladded;
+extern struct react_array ra;
 extern unsigned int audit_elf;
 extern int audit_permadded;
 
 /*
- * This function will reset everything used for each loop when loading 
- * a ruleset from a file.
+ * This function will init everything used for each loop when loading
+ * rules
  */
-static int reset_vars(void)
+static int init_vars(void)
 {
 	list_requested = 0;
 	audit_syscalladded = 0;
@@ -92,19 +97,32 @@ static int reset_vars(void)
 	action = -1;
 	exclude = 0;
 	multiple = 0;
-
-	free(rule_new);
 	rule_new = malloc(sizeof(struct audit_rule_data));
+	if (!rule_new) {
+		fprintf(stderr, "Out of memory allocating new rule\n");
+		return 1;
+	}
 	memset(rule_new, 0, sizeof(struct audit_rule_data));
+	if (react_array_init(&ra, AUDIT_MAX_REACTS)) {
+		fprintf(stderr, "Out of memory allocating reaction array\n");
+		return 1;
+	}
 	if (fd < 0) {
 		if ((fd = audit_open()) < 0) {
 			fprintf(stderr, "Cannot open netlink audit socket\n");
 			return 1;
 		}
 	}
+
 	return 0;
 }
 
+static void free_vars(void)
+{
+	react_array_free(&ra);
+	free(rule_new);
+}
+
 static void usage(void)
 {
     printf(
@@ -463,6 +481,28 @@ void check_rule_mismatch(int lineno, const char *option)
 	}
 }
 
+/*
+ * Remove reactions from database
+ */
+static int db_del_reacts(struct react_array *arr)
+{
+	int rc, i;
+	sqlite3 *conn;
+
+	if (sql_open_database(&conn, REACT_DB) < 0)
+		return 1;
+	for (i = 0; i < arr->count; i++) {
+		if (sql_del_reaction(conn, arr->str[i]) < 0) {
+			sql_close_database(conn);
+			return 1;
+		}
+	}
+	sql_close_database(conn);
+
+	return 0;
+}
+
+
 // FIXME: Change these to enums
 /*
  * returns: -3 deprecated, -2 success - no reply, -1 error - noreply,
@@ -731,8 +771,8 @@ static int setopt(int count, int lineno, char *vars[])
 			audit_number_to_errmsg(rc, optarg);
 			retval = -1;
 		} else {
-			if (rule_new->fields[rule_new->field_count-1] ==
-						AUDIT_PERM)
+			if (rule_new->field_count > 0 &&
+			    rule_new->fields[rule_new->field_count - 1] == AUDIT_PERM)
 				audit_permadded = 1;
 		}
 
@@ -772,6 +812,21 @@ static int setopt(int count, int lineno, char *vars[])
 		}
 		retval = delete_all_rules(fd);
 		if (retval == 0) {
+			sqlite3 *conn;
+			rc = sql_open_database(&conn, REACT_DB);
+			if (rc < 0) {
+				sql_print_error(conn, rc);
+				retval = -1;
+				break;
+			}
+			rc = sql_del_reaction_all(conn);
+			if (rc < 0) {
+				sql_print_error(conn, rc);
+				sql_close_database(conn);
+				retval = -1;
+				break;
+			}
+			sql_close_database(conn);
 			audit_request_rule_list(fd);
 			key[0] = 0;
 			retval = -2;
@@ -917,6 +972,93 @@ static int setopt(int count, int lineno, char *vars[])
 		retval = -1;
 	}
     }
+
+    /* If there are any react fields, reaction string(s) is/are stored in the
+     * array and need to be converted to numbers. Mapping string <-> number is
+     * kept in a SQLite database file. Every insert/update is dependent on the
+     * previous insert/update.
+     */
+    if (ra.count && retval >= 0) {
+	int i;
+	char *cmd = NULL;
+	int flags = 0;
+        sqlite3 *conn;
+
+        if (add != AUDIT_FILTER_UNSET)
+		flags = add & AUDIT_FILTER_MASK;
+        else if (del != AUDIT_FILTER_UNSET)
+		flags = del & AUDIT_FILTER_MASK;
+
+        rc = sql_open_database(&conn, REACT_DB);
+        if (rc < 0) {
+		sql_print_error(conn, rc);
+		return -4;
+        }
+
+        ra.add_to_rule = 1;
+        for (i = 0; i < ra.count; i++) {
+		/* add rule */
+		if (add != AUDIT_FILTER_UNSET) {
+			struct react_number num;
+			/* get a number for the reaction string */
+			num = sql_get_next_number(conn, ra.str[i]);
+			if (num.action == ERROR) {
+				sql_print_error(conn, num.number);
+				sql_close_database(conn);
+				return -4;
+			}
+			asprintf(&cmd, "react=%u", num.number);
+			if (!cmd) {
+				fprintf(stderr,
+				        "Out of memory adding reaction\n");
+				sql_close_database(conn);
+				return -4;
+			}
+			rc = audit_rule_fieldpair_data(&rule_new, cmd, flags);
+			free(cmd);
+			if (rc < 0) {
+				audit_number_to_errmsg(rc, NULL);
+				sql_close_database(conn);
+				return -4;
+			}
+			rc = sql_add_reaction(conn, &num, ra.str[i]);
+			if (rc < 0) {
+				sql_print_error(conn, rc);
+				sql_close_database(conn);
+				return -4;
+			}
+			/* In case an error occurs, keep the number of
+			 * successfully inserted/updated reactions,
+			 * so that these changes can be rolled back.
+			 */
+			ra.processed++;
+		/* delete rule */
+		} else if (del != AUDIT_FILTER_UNSET) {
+			int del_num;
+			rc = sql_reaction_to_number(conn, ra.str[i], &del_num);
+			if (rc < 0) {
+				sql_print_error(conn, rc);
+				sql_close_database(conn);
+				return -4;
+			}
+			asprintf(&cmd, "react=%u", del_num);
+			if (!cmd) {
+				fprintf(stderr,
+				        "Out of memory adding reaction\n");
+				sql_close_database(conn);
+				return -4;
+			}
+			rc = audit_rule_fieldpair_data(&rule_new, cmd, flags);
+			if (rc < 0) {
+				audit_number_to_errmsg(rc, NULL);
+				sql_close_database(conn);
+				return -4;
+			}
+		}
+	}
+        sql_close_database(conn);
+    }
+
     if (retval == -1 && errno == ECONNREFUSED)
 		fprintf(stderr,	"The audit system is disabled\n");
     return retval;
@@ -1021,7 +1163,8 @@ static int fileopt(const char *file)
 		options[i] = NULL;
 
 		/* Parse it */
-		if (reset_vars()) {
+		if (init_vars()) {
+			free_vars();
 			fclose(f);
 			return -1;
 		}
@@ -1045,6 +1188,8 @@ static int fileopt(const char *file)
 					return -1;
 				}
 			}
+		} else {
+			free_vars();
 		}
 		lineno++;
 	}
@@ -1085,13 +1230,13 @@ int main(int argc, char *argv[])
 		else
 			return 0;
 	} else {
-		if (reset_vars()) {
-			free(rule_new);
+		if (init_vars()) {
+			free_vars();
 			return 1;
 		}
 		retval = setopt(argc, 0, argv);
 		if (retval == -3) {
-			free(rule_new);
+			free_vars();
 			return 0;
 		}
 	}
@@ -1102,11 +1247,11 @@ int main(int argc, char *argv[])
 			fprintf(stderr,
 				"The audit system is in immutable "
 				"mode, no rules loaded\n");
-			free(rule_new);
+			free_vars();
 			return 0;
 		} else if (errno == ECONNREFUSED) {
 			fprintf(stderr, "The audit system is disabled\n");
-			free(rule_new);
+			free_vars();
 			return 0;
 		}
 	}
@@ -1132,7 +1277,7 @@ static int handle_request(int status)
 	} else if (status == -2)
 		status = 0;  // report success 
 	else if (status > 0) {
-		int rc;
+		int rc, i;
 		if (add != AUDIT_FILTER_UNSET) {
 			// if !task add syscall any if not specified
 			if ((add & AUDIT_FILTER_MASK) != AUDIT_FILTER_TASK && 
@@ -1155,6 +1300,14 @@ static int handle_request(int status)
 				"Error sending add rule data request (%s)\n",
 					errno == EEXIST ?
 					"Rule exists" : strerror(-rc));
+					/* undo changes in database */
+					if (ra.count)
+						/* Error - database must
+						 * contain the same values
+						 * as it had before adding
+						 * the rule
+                                                 */
+						db_del_reacts(&ra);
 				}
 			}
 		}
@@ -1175,25 +1328,54 @@ static int handle_request(int status)
 					rule_new->fields[0] = AUDIT_WATCH;
 					rc = audit_delete_rule_data(fd,rule_new,
 								del, action);
+					if (rc >= 0 && ra.count)
+						/* success - delete reactions */
+						db_del_reacts(&ra);
 				} else {
 					fprintf(stderr,
 			       "Error sending delete rule data request (%s)\n",
 					errno == EEXIST ?
 					"Rule exists" : strerror(-rc));
 				}
+			} else if (ra.count){
+				db_del_reacts(&ra);
 			}
 		} else {
         		usage();
-	    		audit_close(fd);
+			audit_close(fd);
+			free_vars();
 			exit(1);
 	    	}
 		if (rc <= 0) 
 			status = -1;
 		else
 			status = 0;
-	} else 
+	/* There was an error working with database */
+	} else if (status == -4) {
+		if (ra.processed) {
+			int rc, i;
+			sqlite3 *conn;
+
+			rc = sql_open_database(&conn, REACT_DB);
+			if (rc < 0)
+				status = -1;
+
+			if (status != -1) {
+				/* some reactions were inserted/updated successfully */
+				for (i = 0; i < ra.processed; i++) {
+					rc = sql_del_reaction(conn, ra.str[i]);
+					if (rc < 0)
+						break;
+				}
+				sql_close_database(conn);
+			}
+		}
 		status = -1;
 
+	} else
+		status = -1;
+
+	free_vars();
 	audit_close(fd);
 	fd = -1;
 	return status;
@@ -1278,6 +1460,7 @@ int key_match(struct audit_reply *rep)
  */
 static int audit_print_reply(struct audit_reply *rep)
 {
+	int rc;
 	unsigned int i;
 	int first;
 	int sparse;
@@ -1382,6 +1565,28 @@ static int audit_print_reply(struct audit_reply *rep)
 								key_sep);
 						}
 						free(rkey);
+					} else if (field == AUDIT_REACTION) {
+						sqlite3 *conn;
+						char *str_react = NULL;
+						rc = sql_open_database(&conn,
+						  REACT_DB);
+						if (rc < 0) {
+							sql_print_error(conn, rc);
+							return -1;
+						}
+						rc = sql_number_to_reaction(conn,
+						  rep->ruledata->values[i],
+						  &str_react);
+						if (rc < 0) {
+							/* print only number */
+							printf(" react=%u\n",
+							rep->ruledata->values[i]);
+							sql_print_error(conn, rc);
+							return -1;
+						}
+						printf(" react=%s", str_react);
+						free(str_react);
+						sql_close_database(conn);
 					} else if (field == AUDIT_PERM) {
 						char perms[5];
 						int val=rep->ruledata->values[i];
@@ -1419,7 +1624,8 @@ static int audit_print_reply(struct audit_reply *rep)
 						 field > AUDIT_SUBJ_CLR) &&
 						field != AUDIT_WATCH &&
 						field != AUDIT_FILTERKEY &&
-						field != AUDIT_PERM)
+						field != AUDIT_PERM &&
+						field != AUDIT_REACTION)
 					printf(" (0x%x)", rep->ruledata->values[i]);
 			}
 			if (show_syscall &&
diff --git a/src/mt/Makefile.am b/src/mt/Makefile.am
index 5f1ebc0..8827b2c 100644
--- a/src/mt/Makefile.am
+++ b/src/mt/Makefile.am
@@ -32,9 +32,9 @@ noinst_LIBRARIES = libauditmt.a
 libauditmt_a_SOURCES = ${top_srcdir}/lib/libaudit.c \
 	${top_srcdir}/lib/message.c ${top_srcdir}/lib/netlink.c \
 	${top_srcdir}/lib/lookup_table.c ${top_srcdir}/lib/audit_logging.c \
-	${top_srcdir}/lib/deprecated.c
+	${top_srcdir}/lib/deprecated.c ${top_srcdir}/lib/reactarray.c
 libauditmt_a_HEADERS: ${top_builddir}/config.h ${top_srcdir}/lib/libaudit.h \
-	${top_srcdir}/lib/private.h
+	${top_srcdir}/lib/private.h ${top_srcdir}/lib/reactarray.h
 libauditmt_a_DEPENDENCIES = $(libaudit_a_SOURCES) ${top_builddir}/config.h \
 	${top_srcdir}/lib/gen_tables.h ${top_builddir}/lib/i386_tables.h \
 	${top_builddir}/lib/ia64_tables.h ${top_builddir}/lib/ppc_tables.h \
-- 
1.6.4.4




More information about the Linux-audit mailing list