> 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 <[email protected]>
---
 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 <[email protected]>
+ */
+
+#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 <[email protected]>
+ */
+
+#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 <[email protected]>
+ */
+
+#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 <[email protected]>
+ */
+
+#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

--
Linux-audit mailing list
[email protected]
https://www.redhat.com/mailman/listinfo/linux-audit

Reply via email to