public inbox for linux-audit@redhat.com
 help / color / mirror / Atom feed
* [PATCH] mapping of reactions
@ 2010-03-30 22:24 Juraj Hlista
  0 siblings, 0 replies; 7+ messages in thread
From: Juraj Hlista @ 2010-03-30 22:24 UTC (permalink / raw)
  To: sgrubb, mitr; +Cc: linux-audit

From: Juraj Hlista <juro.hlista@gmail.com>

Reactions are identified by numbers in the kernel, but user space requires string identifiers of reactions. That's why there must be some mapping from numbers to strings and vice versa. This is implemented using SQLite. A table where the reaction strings and numbers are kept has the following columns:  number, string, used. 'number' is the value used in the kernel, 'string' is used in the user space. 'used' means how many times is the reaction used in audit rules.

When a new reactive rule is added, the database determines what number will be sent to the kernel for each reaction:
1 if the table is empty
if string is already in the table, its number will be sent to the kernel and 'used' incremented
if there isn't such a string in the table, but there is some row with 'used' == 0, then string in this row will be rewritten by the new one and 'used' set to 1
otherwise max(number) + 1 will be used

When a reactive rule is removed, 'used' is just decremented.

More reactions per one rule are supported, they are defined by react field (-F react=reaction). This patch includes only mapping, audit plugin for triggering reactions is being developed.

Signed-off-by: Juraj Hlista <juro.hlista@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        |   84 ++++++++++++
 lib/reactarray.h        |   41 ++++++
 src/Makefile.am         |    7 +-
 src/auditctl-reactsql.c |  325 +++++++++++++++++++++++++++++++++++++++++++++++
 src/auditctl-reactsql.h |   48 +++++++
 src/auditctl.c          |  231 +++++++++++++++++++++++++++++++--
 src/mt/Makefile.am      |    4 +-
 14 files changed, 765 insertions(+), 21 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..9823e31 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((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..03b8a65
--- /dev/null
+++ b/lib/reactarray.c
@@ -0,0 +1,84 @@
+/* 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@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, unsigned int size)
+{
+	int i;
+
+	a->add_to_rule = 0;
+	a->processed = 0;
+	a->count = 0;
+	a->size = size;
+	a->str = (char **)malloc(size * sizeof(char *));
+	if (!a->str)
+		return 1;
+
+	for (i = 0; i < size; i++)
+		a->str[i] = NULL;
+
+	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++) {
+		if (a->str[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] = (char *)malloc((strlen(s) + 1) * sizeof(char));
+	if (!a->str[a->count])
+		return 1;
+
+	strcpy(a->str[a->count], s);
+	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@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..0a70658
--- /dev/null
+++ b/src/auditctl-reactsql.c
@@ -0,0 +1,325 @@
+/* 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@gmail.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "auditctl-reactsql.h"
+
+const char *sql_errmsg[] = {
+	"SQL query suppossed to return a value",
+	"Out of memory allocating reaction string",
+	"React number reached maximal value"
+};
+
+enum {
+	SQL_CHECK_DB = 0,
+	SQL_CREATE_TABLE,
+	SQL_GET_NEXT_NUMBER,
+	SQL_NUMBER_TO_STRING,
+	SQL_STRING_TO_NUMBER,
+	SQL_ADD_UPDATE,
+	SQL_ADD_INSERT,
+	SQL_DEL_UPDATE,
+	SQL_DEL_ALL
+};
+
+static const char *query[] = {
+	"SELECT name \
+	FROM sqlite_master \
+	WHERE type='table' AND name='reaction'",
+
+	/* number - the value that is in the kernel 
+	 * string - reaction identifier
+	 * used - how many times is the reaction used with rules
+	 */
+	"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 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
+	 */
+	"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",
+
+	"SELECT string FROM reaction WHERE number = ?",
+
+	"SELECT number FROM reaction WHERE string = ?",
+
+	"UPDATE reaction SET string = ?, used = used + 1 WHERE number = ?",
+
+	"INSERT INTO reaction VALUES(?, ?, 1)",
+
+	"UPDATE reaction SET used = used - 1 WHERE string = ?",
+
+	"DROP TABLE reaction"
+};
+
+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 - 2]);
+}
+
+/*
+ * 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;
+
+	if (sqlite3_prepare(c, query[SQL_NUMBER_TO_STRING], -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 = malloc((strlen(reaction) + 1) * sizeof(char));
+	if (*str == NULL) {
+		sqlite3_finalize(find_str);
+		return -SQL_NO_MEMORY;
+	}
+	strcpy(*str, reaction);
+
+	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;
+
+	if (sqlite3_prepare(c, query[SQL_STRING_TO_NUMBER], -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' is greater than SQL_OFFSET,
+ * 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 int num, const char *str)
+{
+	sqlite3_stmt *change;
+
+	/* update table */
+	if (num > SQL_OFFSET) {
+		if (sqlite3_prepare(c, query[SQL_ADD_UPDATE], -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 - SQL_OFFSET))
+			return -SQL_ERROR;
+	}
+	/* insert into table */
+	else {
+		if (sqlite3_prepare(c, query[SQL_ADD_INSERT], -1, &change, NULL))
+			return -SQL_ERROR;
+
+		if (sqlite3_bind_int(change, 1, num))
+			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;
+
+	if (sqlite3_prepare(c, query[SQL_DEL_UPDATE], -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;
+
+	if (sqlite3_prepare(c, query[SQL_DEL_ALL], -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.
+ */
+int sql_get_next_number(sqlite3 *c, const char *str)
+{
+	int result;
+	int x;
+	sqlite3_stmt *get_num;
+
+	if (sqlite3_prepare(c, query[SQL_GET_NEXT_NUMBER], -1, &get_num, NULL))
+		return -SQL_ERROR;
+
+	if (sqlite3_bind_text(get_num, 1, str, -1, NULL))
+		return -SQL_ERROR;
+
+	if (sqlite3_step(get_num) != SQLITE_ROW) {
+		sqlite3_finalize(get_num);
+		return -SQL_NO_VALUE;
+	}
+
+	result = sqlite3_column_int(get_num, 0);
+
+	if (sqlite3_finalize(get_num))
+		return -SQL_ERROR;
+
+	x = result - SQL_OFFSET;
+	/* there are SQL_OFFSET - 1 numbers to be used with reaction strings */
+	if (!x || x >= SQL_OFFSET)
+		return -SQL_MAX_NUMBER;
+
+	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;
+
+	/* check if table exists */
+	if (sqlite3_prepare(c, query[SQL_CHECK_DB], -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) {
+		if (sqlite3_prepare(c, query[SQL_CREATE_TABLE], -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..1e0ea97
--- /dev/null
+++ b/src/auditctl-reactsql.h
@@ -0,0 +1,48 @@
+/* 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@gmail.com>
+ */
+
+#ifndef CTLREACTSQL_HEADER
+#define CTLREACTSQL_HEADER
+
+#include <sqlite3.h>
+
+#define SQL_OFFSET 10000
+
+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 int num, const char *str);
+
+int sql_del_reaction(sqlite3 *c, const char *str);
+
+int sql_get_next_number(sqlite3 *c, const char *str);
+
+#endif
+
diff --git a/src/auditctl.c b/src/auditctl.c
index 03cac39..832ea31 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,6 +77,7 @@ 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;
 
@@ -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,97 @@ 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, num;
+	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) {
+			/* get a number for the reaction string */
+			num = sql_get_next_number(conn, ra.str[i]);
+			if (num < 0) {
+				sql_print_error(conn, num);
+				sql_close_database(conn);
+				return -4;
+			}
+			if (num > SQL_OFFSET)
+				asprintf(&cmd, "react=%u", num - SQL_OFFSET);
+			else
+				asprintf(&cmd, "react=%u", num);
+			if (cmd) {
+				rc = audit_rule_fieldpair_data(&rule_new,
+								cmd, flags);
+				if (rc < 0) {
+					audit_number_to_errmsg(rc, NULL);
+					sql_close_database(conn);
+					free(cmd);
+					return -4;
+				}
+				free(cmd);
+			} else {
+				fprintf(stderr,
+				        "Out of memory adding reaction\n");
+				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) {
+			rc = sql_reaction_to_number(conn, ra.str[i], &num);
+			if (rc < 0) {
+				sql_print_error(conn, rc);
+				sql_close_database(conn);
+				return -4;
+			}
+
+			asprintf(&cmd, "react=%u", num);
+			if (cmd) {
+				rc = audit_rule_fieldpair_data(&rule_new,
+								cmd, flags);
+				if (rc < 0) {
+					audit_number_to_errmsg(rc, NULL);
+					sql_close_database(conn);
+					free(cmd);
+					return -4;
+				}
+				free(cmd);
+			}
+		}
+	}
+        sql_close_database(conn);
+    }
+
     if (retval == -1 && errno == ECONNREFUSED)
 		fprintf(stderr,	"The audit system is disabled\n");
     return retval;
@@ -1022,6 +1168,7 @@ static int fileopt(const char *file)
 
 		/* Parse it */
 		if (reset_vars()) {
+			free_vars();
 			fclose(f);
 			return -1;
 		}
@@ -1045,6 +1192,8 @@ static int fileopt(const char *file)
 					return -1;
 				}
 			}
+		} else {
+			free_vars();
 		}
 		lineno++;
 	}
@@ -1086,12 +1235,12 @@ int main(int argc, char *argv[])
 			return 0;
 	} else {
 		if (reset_vars()) {
-			free(rule_new);
+			free_vars();
 			return 1;
 		}
 		retval = setopt(argc, 0, argv);
 		if (retval == -3) {
-			free(rule_new);
+			free_vars();
 			return 0;
 		}
 	}
@@ -1102,11 +1251,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 +1281,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 +1304,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 +1332,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 +1464,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 +1569,25 @@ 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) {
+							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 +1625,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

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH] mapping of reactions
       [not found] <1888242976.297641270059855277.JavaMail.root@zmail07.collab.prod.int.phx2.redhat.com>
@ 2010-03-31 18:36 ` Miloslav Trmac
  2010-03-31 19:12   ` Steve Grubb
  2010-04-02 12:15   ` Juraj Hlista
  0 siblings, 2 replies; 7+ messages in thread
From: Miloslav Trmac @ 2010-03-31 18:36 UTC (permalink / raw)
  To: Juraj Hlista; +Cc: linux-audit

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

----- "Juraj Hlista" <juro.hlista@gmail.com> wrote:
> diff --git a/lib/libaudit.c b/lib/libaudit.c
> @@ -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((char)*(v)))
Nitpick: the isdigit argument should be cast to (unsigned char).

> diff --git a/lib/reactarray.c b/lib/reactarray.c
<snip>
> +int react_array_init(struct react_array *a, unsigned int size)
<snip>
> +	a->str = (char **)malloc(size * sizeof(char *));
The return value of malloc() is not usually manually cast in C.
> +	if (!a->str)
> +		return 1;
> +
> +	for (i = 0; i < size; i++)
> +		a->str[i] = NULL;
You can just use calloc() to initialize a->str.
<snip>
> +void react_array_free(struct react_array *a)
<snip>
> +	for (i = 0; i < a->count; i++) {
> +		if (a->str[i])
> +			free(a->str[i]);
free(NULL) is OK, so the if ( ) is not necessary.

> +int react_array_insert(struct react_array *a, const char *s)
> +{
<snip>
> +	a->str[a->count] = (char *)malloc((strlen(s) + 1) * sizeof(char));
> +	if (!a->str[a->count])
> +		return 1;
> +
> +	strcpy(a->str[a->count], s);
Using strdup() would be simpler.


> diff --git a/src/auditctl-reactsql.c b/src/auditctl-reactsql.c
<snip>
> +enum {
> +	SQL_CHECK_DB = 0,
Just use string constants in the code directly, this indirection is difficult to follow.
<snip>
> +void sql_print_error(sqlite3 *c, int err)
<snip>
> +		fprintf(stderr, "SQLite error: %s\n", sql_errmsg[-err - 2]);
The "-2" is a bit difficult to follow... I'd just sacrifice the two additional empty entries in sql_errmsg.
<snip>
> +int sql_number_to_reaction(sqlite3 *c, const int num, char **str)
<snip>
> +	*str = malloc((strlen(reaction) + 1) * sizeof(char));
> +	if (*str == NULL) {
> +		sqlite3_finalize(find_str);
> +		return -SQL_NO_MEMORY;
> +	}
> +	strcpy(*str, reaction);
Use strdup ().

<snip>
> +/*
> + * Add a reaction to the database - if 'num' is greater than SQL_OFFSET,
> + * 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.
> + */
Using a separate variable for "new/existing" would be much cleaner than the magic SQL_OFFSET.  Especially see how this implementation detail leaks into auditctl.c.
<snip>
> +int sql_get_next_number(sqlite3 *c, const char *str)
Here as well.


> diff --git a/src/auditctl.c b/src/auditctl.c
> @@ -917,6 +972,97 @@ static int setopt(int count, int lineno, char
<snip>
> +			if (num > SQL_OFFSET)
> +				asprintf(&cmd, "react=%u", num - SQL_OFFSET);
> +			else
> +				asprintf(&cmd, "react=%u", num);
> +			if (cmd) {
(...)
> +			} else {
> +				fprintf(stderr,
> +				        "Out of memory adding reaction\n");
> +				sql_close_database(conn);
> +				return -4;
> +			}
If you reverse the if (cmd) here, the else {} branch becomes the default control flow, resulting in a bit simpler code.

> @@ -1022,6 +1168,7 @@ static int fileopt(const char *file)
>  
>  		/* Parse it */
>  		if (reset_vars()) {
> +			free_vars();
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.)

> @@ -1382,6 +1569,25 @@ static int audit_print_reply(struct audit_reply
<snip>
> +						rc = sql_number_to_reaction(conn,
> +						  rep->ruledata->values[i],
> +						  &str_react);
> +						if (rc < 0) {
I think it's prefereble to print the number if the lookup fails, so that the admin can see at least something from the rule.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] mapping of reactions
  2010-03-31 18:36 ` [PATCH] mapping of reactions Miloslav Trmac
@ 2010-03-31 19:12   ` Steve Grubb
  2010-04-02 12:15   ` Juraj Hlista
  1 sibling, 0 replies; 7+ messages in thread
From: Steve Grubb @ 2010-03-31 19:12 UTC (permalink / raw)
  To: Miloslav Trmac; +Cc: linux-audit

On Wednesday 31 March 2010 02:36:29 pm Miloslav Trmac wrote:
>  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?).

Quite honestly, I had never considered the audit system depending on sqlite.

-Steve

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] mapping of reactions
  2010-03-31 18:36 ` [PATCH] mapping of reactions Miloslav Trmac
  2010-03-31 19:12   ` Steve Grubb
@ 2010-04-02 12:15   ` Juraj Hlista
  2010-04-05 13:38     ` LC Bruzenak
  1 sibling, 1 reply; 7+ messages in thread
From: Juraj Hlista @ 2010-04-02 12:15 UTC (permalink / raw)
  To: Miloslav Trmac; +Cc: linux-audit

> 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@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@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@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@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@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

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH] mapping of reactions
  2010-04-02 12:15   ` Juraj Hlista
@ 2010-04-05 13:38     ` LC Bruzenak
  2010-04-06  9:13       ` Juraj Hlista
  0 siblings, 1 reply; 7+ messages in thread
From: LC Bruzenak @ 2010-04-05 13:38 UTC (permalink / raw)
  To: Juraj Hlista; +Cc: linux-audit

Now that you appear to have all supporting patches in place, would you
mind sending out a sample of how to use this? It looks very interesting.

Thx,
LCB.
-- 
LC (Lenny) Bruzenak
lenny@magitekltd.com

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] mapping of reactions
  2010-04-05 13:38     ` LC Bruzenak
@ 2010-04-06  9:13       ` Juraj Hlista
  2010-04-06 13:53         ` Steve Grubb
  0 siblings, 1 reply; 7+ messages in thread
From: Juraj Hlista @ 2010-04-06  9:13 UTC (permalink / raw)
  To: LC Bruzenak; +Cc: linux-audit

On Mon, Apr 5, 2010 at 3:38 PM, LC Bruzenak <lenny@magitekltd.com> wrote:
> Now that you appear to have all supporting patches in place, would you
> mind sending out a sample of how to use this? It looks very interesting.
>
> Thx,
> LCB.
> --
> LC (Lenny) Bruzenak
> lenny@magitekltd.com
>
>
The patches were denied, because it can be implemented without
touching the kernel (in the audit plugin, which I'm working on now)

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] mapping of reactions
  2010-04-06  9:13       ` Juraj Hlista
@ 2010-04-06 13:53         ` Steve Grubb
  0 siblings, 0 replies; 7+ messages in thread
From: Steve Grubb @ 2010-04-06 13:53 UTC (permalink / raw)
  To: linux-audit

On Tuesday 06 April 2010 05:13:49 am Juraj Hlista wrote:
> The patches were denied, because it can be implemented without
> touching the kernel (in the audit plugin, which I'm working on now)

Yes. It should be possible to set a list of parameters to match against and 
then run auditctl when a match is found. Auditctl can delete by key, so if you 
have a set of rules for a specific reaction, then you can add a key to the 
rules. Then if another rules is matched that would want to delete the rules, 
you can do that. For example, mount might require adding rules, unmount would 
probably delete any watches, but you can make sure everything is gone with a 
second match. Same thing with logon/logoff of a specific user.

-Steve

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2010-04-06 13:53 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <1888242976.297641270059855277.JavaMail.root@zmail07.collab.prod.int.phx2.redhat.com>
2010-03-31 18:36 ` [PATCH] mapping of reactions Miloslav Trmac
2010-03-31 19:12   ` Steve Grubb
2010-04-02 12:15   ` Juraj Hlista
2010-04-05 13:38     ` LC Bruzenak
2010-04-06  9:13       ` Juraj Hlista
2010-04-06 13:53         ` Steve Grubb
2010-03-30 22:24 Juraj Hlista

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox