Date: Sun, 25 Jul 2010 11:08:49 +0200 (CEST) From: juro.hlista@gmail.com To: linux-audit@redhat.com cc: sgrubb@redhat.com, mitr@redhat.com, juro.hlista@gmail.com Subject: [PATCH] Reactive audit Hello, this patch consists of two audit plugins. The first one, "reactive", extends functionality of the current audit. It is possible to react to some audit events by adding/deleting audit rules and triggering commands. Reactions are defined in a config file (audisp/plugins/reactive/audisp-reactive.conf). Each reaction has two parts: 1. condition, 2. action For example: react: get(syscall) == 21 && get(success) == "yes" { addw get(apath) + " -p w"; } The first line is a condition and the rest is an action. If the condition is fulfilled, the action is performed. The function "get()" is used to get values from audit fields of the current audit event. The "addw" command in the action part stands for "auditctl -w ". This reaction should add a new audit rule when a mount syscall occurs (which is number 21 in the example). The second plugin, "stats", is able to store some audit events with their timestamps in a database. The reactive plugin can access the database and make decisions according to the information stored in the database. You can find more about the reactive audit here: http://www.stud.fit.vutbr.cz/~xhlist00/dip.pdf --- trunk/audisp/audispd.c | 6 +- trunk/audisp/plugins/Makefile.am | 2 +- trunk/audisp/plugins/reactive/Makefile.am | 38 + trunk/audisp/plugins/reactive/au-reactive.conf | 8 + trunk/audisp/plugins/reactive/audisp-reactive.c | 609 +++++++++++ trunk/audisp/plugins/reactive/audisp-reactive.conf | 143 +++ trunk/audisp/plugins/reactive/reactive-ast.c | 1123 ++++++++++++++++++++ trunk/audisp/plugins/reactive/reactive-ast.h | 167 +++ trunk/audisp/plugins/reactive/reactive-aststack.c | 107 ++ trunk/audisp/plugins/reactive/reactive-aststack.h | 49 + trunk/audisp/plugins/reactive/reactive-astvalue.h | 41 + trunk/audisp/plugins/reactive/reactive-config.c | 98 ++ trunk/audisp/plugins/reactive/reactive-config.h | 48 + trunk/audisp/plugins/reactive/reactive-error.c | 68 ++ trunk/audisp/plugins/reactive/reactive-error.h | 64 ++ trunk/audisp/plugins/reactive/reactive-fieldtab.c | 163 +++ trunk/audisp/plugins/reactive/reactive-fieldtab.h | 71 ++ trunk/audisp/plugins/reactive/reactive-parser.y | 1089 +++++++++++++++++++ trunk/audisp/plugins/reactive/reactive-reactarr.c | 106 ++ trunk/audisp/plugins/reactive/reactive-reactarr.h | 58 + trunk/audisp/plugins/reactive/reactive-scanner.h | 32 + trunk/audisp/plugins/reactive/reactive-scanner.l | 185 ++++ trunk/audisp/plugins/reactive/reactive-symtab.c | 155 +++ trunk/audisp/plugins/reactive/reactive-symtab.h | 64 ++ trunk/audisp/plugins/stats/Makefile.am | 39 + trunk/audisp/plugins/stats/au-stats.conf | 8 + trunk/audisp/plugins/stats/audisp-stats-clear.c | 208 ++++ trunk/audisp/plugins/stats/audisp-stats.c | 665 ++++++++++++ trunk/audisp/plugins/stats/audisp-stats.conf | 34 + trunk/audisp/plugins/stats/stats-config.c | 336 ++++++ trunk/audisp/plugins/stats/stats-config.h | 46 + trunk/audisp/plugins/stats/stats-eventarr.c | 106 ++ trunk/audisp/plugins/stats/stats-eventarr.h | 44 + trunk/audisp/plugins/stats/stats-fieldarr.c | 124 +++ trunk/audisp/plugins/stats/stats-fieldarr.h | 56 + trunk/audisp/plugins/stats/stats-fieldtab.c | 163 +++ trunk/audisp/plugins/stats/stats-fieldtab.h | 69 ++ trunk/audisp/plugins/stats/stats-sql.c | 672 ++++++++++++ trunk/audisp/plugins/stats/stats-sql.h | 45 + trunk/configure.ac | 4 +- 40 files changed, 7109 insertions(+), 4 deletions(-) create mode 100644 trunk/audisp/plugins/reactive/Makefile.am create mode 100644 trunk/audisp/plugins/reactive/au-reactive.conf create mode 100644 trunk/audisp/plugins/reactive/audisp-reactive.c create mode 100644 trunk/audisp/plugins/reactive/audisp-reactive.conf create mode 100644 trunk/audisp/plugins/reactive/reactive-ast.c create mode 100644 trunk/audisp/plugins/reactive/reactive-ast.h create mode 100644 trunk/audisp/plugins/reactive/reactive-aststack.c create mode 100644 trunk/audisp/plugins/reactive/reactive-aststack.h create mode 100644 trunk/audisp/plugins/reactive/reactive-astvalue.h create mode 100644 trunk/audisp/plugins/reactive/reactive-config.c create mode 100644 trunk/audisp/plugins/reactive/reactive-config.h create mode 100644 trunk/audisp/plugins/reactive/reactive-error.c create mode 100644 trunk/audisp/plugins/reactive/reactive-error.h create mode 100644 trunk/audisp/plugins/reactive/reactive-fieldtab.c create mode 100644 trunk/audisp/plugins/reactive/reactive-fieldtab.h create mode 100644 trunk/audisp/plugins/reactive/reactive-parser.y create mode 100644 trunk/audisp/plugins/reactive/reactive-reactarr.c create mode 100644 trunk/audisp/plugins/reactive/reactive-reactarr.h create mode 100644 trunk/audisp/plugins/reactive/reactive-scanner.h create mode 100644 trunk/audisp/plugins/reactive/reactive-scanner.l create mode 100644 trunk/audisp/plugins/reactive/reactive-symtab.c create mode 100644 trunk/audisp/plugins/reactive/reactive-symtab.h create mode 100644 trunk/audisp/plugins/stats/Makefile.am create mode 100644 trunk/audisp/plugins/stats/au-stats.conf create mode 100644 trunk/audisp/plugins/stats/audisp-stats-clear.c create mode 100644 trunk/audisp/plugins/stats/audisp-stats.c create mode 100644 trunk/audisp/plugins/stats/audisp-stats.conf create mode 100644 trunk/audisp/plugins/stats/stats-config.c create mode 100644 trunk/audisp/plugins/stats/stats-config.h create mode 100644 trunk/audisp/plugins/stats/stats-eventarr.c create mode 100644 trunk/audisp/plugins/stats/stats-eventarr.h create mode 100644 trunk/audisp/plugins/stats/stats-fieldarr.c create mode 100644 trunk/audisp/plugins/stats/stats-fieldarr.h create mode 100644 trunk/audisp/plugins/stats/stats-fieldtab.c create mode 100644 trunk/audisp/plugins/stats/stats-fieldtab.h create mode 100644 trunk/audisp/plugins/stats/stats-sql.c create mode 100644 trunk/audisp/plugins/stats/stats-sql.h diff --git a/trunk/audisp/audispd.c b/trunk/audisp/audispd.c index 0d41601..31d8502 100644 --- a/trunk/audisp/audispd.c +++ b/trunk/audisp/audispd.c @@ -53,6 +53,7 @@ static int audit_fd; static pthread_t inbound_thread; static const char *config_file = "/etc/audisp/audispd.conf"; static const char *plugin_dir = "/etc/audisp/plugins.d/"; +static char **env_var; /* Local function prototypes */ static void signal_plugins(int sig); @@ -290,11 +291,12 @@ static void reconfigure(void) plist_clear(&tmp_plugin); } -int main(int argc, char *argv[]) +int main(int argc, char *argv[], char *envp[]) { lnode *conf; struct sigaction sa; int i; + env_var = envp; #ifndef DEBUG /* Make sure we are root */ @@ -457,7 +459,7 @@ static int safe_exec(plugin_conf_t *conf) for (i=1; i<(MAX_PLUGIN_ARGS+1); i++) argv[i] = conf->args[i]; argv[i] = NULL; - execve(conf->path, argv, NULL); + execve(conf->path, argv, env_var); exit(1); /* Failed to exec */ } diff --git a/trunk/audisp/plugins/Makefile.am b/trunk/audisp/plugins/Makefile.am index b0fa60a..cf8fcdd 100644 --- a/trunk/audisp/plugins/Makefile.am +++ b/trunk/audisp/plugins/Makefile.am @@ -22,7 +22,7 @@ CONFIG_CLEAN_FILES = *.loT *.rej *.orig -SUBDIRS = builtins zos-remote remote +SUBDIRS = builtins zos-remote remote stats reactive #SUBDIRS = builtins zos-remote if HAVE_PRELUDE SUBDIRS += prelude diff --git a/trunk/audisp/plugins/reactive/Makefile.am b/trunk/audisp/plugins/reactive/Makefile.am new file mode 100644 index 0000000..ebb3ab9 --- /dev/null +++ b/trunk/audisp/plugins/reactive/Makefile.am @@ -0,0 +1,38 @@ + +CONFIG_CLEAN_FILES = *.rej *.orig +EXTRA_DIST = au-reactive.conf audisp-reactive.conf +AUTOMAKE_OPTIONS = no-dependencies +INCLUDES = -I${top_srcdir} -I${top_srcdir}/lib -I${top_srcdir}/auparse +LDADD = ${top_builddir}/auparse/libauparse.la +#LIBS = -L${top_srcdir}/auparse/.libs -lauparse +prog_confdir = $(sysconfdir)/audisp +prog_conf = audisp-reactive.conf +plugin_confdir=$(prog_confdir)/plugins.d +plugin_conf = au-reactive.conf +sbin_PROGRAMS = audisp-reactive +noinst_HEADERS = reactive-config.h reactive-ast.h reactive-aststack.h \ + reactive-error.h reactive-reactarr.h reactive-fieldtab.h \ + reactive-symtab.h reactive-astvalue.h +#dist_man_MANS = + +BUILT_SOURCES = reactive-parser.h +AM_YFLAGS = -d +AM_LFLAGS = -o$(LEX_OUTPUT_ROOT).c + +audisp_reactive_SOURCES = audisp-reactive.c reactive-config.c reactive-ast.c \ + reactive-aststack.c reactive-error.c reactive-reactarr.c \ + reactive-fieldtab.c reactive-symtab.c reactive-scanner.l \ + reactive-parser.y +audisp_reactive_CFLAGS = -fPIE -DPIE -g -D_REENTRANT -D_GNU_SOURCE -Wundef +audisp_reactive_LDFLAGS = -pie -Wl,-z,relro @LEXLIB@ +audisp_reactive_LDADD = ../stats/libstats_sql.la $(LDADD) + +install-data-hook: + mkdir -p -m 0750 ${DESTDIR}${plugin_confdir} + $(INSTALL_DATA) -D -m 640 ${srcdir}/$(plugin_conf) ${DESTDIR}${plugin_confdir} + $(INSTALL_DATA) -D -m 640 ${srcdir}/$(prog_conf) ${DESTDIR}${prog_confdir} + +uninstall-hook: + rm ${DESTDIR}${plugin_confdir}/$(plugin_conf) + rm ${DESTDIR}${prog_confdir}/$(prog_conf) + diff --git a/trunk/audisp/plugins/reactive/au-reactive.conf b/trunk/audisp/plugins/reactive/au-reactive.conf new file mode 100644 index 0000000..1f070d8 --- /dev/null +++ b/trunk/audisp/plugins/reactive/au-reactive.conf @@ -0,0 +1,8 @@ + +active = yes +direction = out +path = /sbin/audisp-reactive +type = always +#args = +format = string + diff --git a/trunk/audisp/plugins/reactive/audisp-reactive.c b/trunk/audisp/plugins/reactive/audisp-reactive.c new file mode 100644 index 0000000..00b9c2b --- /dev/null +++ b/trunk/audisp/plugins/reactive/audisp-reactive.c @@ -0,0 +1,609 @@ + +/* audisp-reactive.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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "libaudit.h" +#include "auparse.h" +#include "reactive-config.h" +#include "reactive-error.h" + +#define BUFLEN 1024 +#define FIELDTAB_SIZE 37 /* size of hash table for storing audit fields */ +#define CONFIG "/etc/audisp/audisp-reactive.conf" + + +static volatile sig_atomic_t stop = 0; +static volatile sig_atomic_t hup = 1; +static struct react_data *rd; +static struct fieldtab *ft; + +static void term_handler(int sig); + +static void hup_handler(int sig); + +static FILE *file_open(const char *s); + +static void file_close(FILE *f); + +static void handle_event(auparse_state_t *au, + auparse_cb_event_t cb_event_type, void *user_data); + +static void extract_first_record(auparse_state_t *au, struct fieldtab *ft); + +static void extract_apath(auparse_state_t *au, char **cwd, char **path); + +static void insert_apath(struct fieldtab *ft, const char *cwd, const char *path); + +static struct ast_value *field_value(auparse_state_t *au, const char *field, + int *err); + +static struct ast_value *apath_value(const char *cwd, const char *path); + +/* + * Main + */ +int main(int argc, char *argv[]) +{ + int rc; + FILE *conf; + auparse_state_t *au = NULL; + struct sigaction sa; + int len; + char buf[MAX_AUDIT_MESSAGE_LENGTH + 1]; + fd_set rfds; + struct timeval tv; + + /* Register sighandlers */ + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + /* Set handler for the ones we care about */ + sa.sa_handler = term_handler; + sigaction(SIGTERM, &sa, NULL); + sa.sa_handler = hup_handler; + sigaction(SIGHUP, &sa, NULL); + + /* Initialize the auparse library */ + au = auparse_init(AUSOURCE_FEED, 0); + if (!au) { + syslog(LOG_ERR, "Exiting due to auparse init errors"); + return -1; + } + rd = config_init(); + if (!rd) { + syslog(LOG_ERR, "Exiting due to reaction data init errors"); + auparse_destroy(au); + return -1; + } + ft = fieldtab_init(FIELDTAB_SIZE); + if (!ft) { + syslog(LOG_ERR, "Exiting due to field table init errors"); + auparse_destroy(au); + config_destroy(rd); + return -1; + } + auparse_add_callback(au, handle_event, NULL, NULL); + do { + /* Load configuration */ + if (hup) { + int line; + conf = file_open(CONFIG); + if (!conf) + break; + rc = config_read(rd, conf, &line); + if (rc) { + file_close(conf); + config_error(rc, line); + break; + } + rc = ast_eval(rd->root, ft, EVAL_TEST, &line); + if (rc < 0) { + file_close(conf); + config_error(rc, line); + break; + } + rc = ast_reset(rd->root); + if (rc < 0) { + file_close(conf); + config_error(rc, 0); + break; + } + file_close(conf); + hup = 0; + } + FD_ZERO(&rfds); + FD_SET(0, &rfds); + tv.tv_sec = 1; + tv.tv_usec = 0; + rc = select(1, &rfds, NULL, NULL, &tv); + if (rc == -1) { + if (errno == EINTR) { + continue; + } else { + syslog(LOG_ERR, + "Error while monitoring input: %s. Aborting", + strerror(errno)); + stop = 1; + } + } else if (rc) { + len = read(0, buf, BUFLEN); + if (len > 0) { + auparse_feed(au, buf, len); + } else if (len == 0) { + stop = 1; + } else { + /* ignore interrupted call or empty pipe */ + if (errno != EINTR && errno != EAGAIN) { + syslog(LOG_ERR, + "Error while reading input: %s. Aborting", + strerror(errno)); + stop = 1; + } + } + } + } while (!stop); + + fieldtab_destroy(ft); + config_destroy(rd); + auparse_flush_feed(au); + auparse_destroy(au); + + return 0; +} + + +/* + * SIGTERM handler + */ +static void term_handler(int sig) +{ + stop = 1; +} + +/* + * SIGHUP handler: re-read config + */ +static void hup_handler(int sig) +{ + hup = 1; +} + +/* + * Open configuration file + */ +static FILE *file_open(const char *s) +{ + int fd, rc; + struct stat st; + FILE *f; + + /* open the file */ + rc = open(s, O_RDONLY|O_NOFOLLOW); + if (rc < 0) { + if (errno != ENOENT) { + syslog(LOG_ERR, "Error opening %s (%s)", s, + strerror(errno)); + return NULL; + } + syslog(LOG_WARNING, + "Config file %s doesn't exist, skipping", s); + return NULL; + } + fd = rc; + + /* check the file's permissions: owned by root, not world writable */ + if (fstat(fd, &st) < 0) { + syslog(LOG_ERR, "Error fstat'ing config file (%s)", + strerror(errno)); + close(fd); + return NULL; + } + if (st.st_uid != 0) { + syslog(LOG_ERR, "Error - %s isn't owned by root", s); + close(fd); + return NULL; + } + if ((st.st_mode & S_IWOTH) == S_IWOTH) { + syslog(LOG_ERR, "Error - %s is world writable", s); + close(fd); + return NULL; + } + if (!S_ISREG(st.st_mode)) { + syslog(LOG_ERR, "Error - %s is not a regular file", s); + close(fd); + return NULL; + } + f = fdopen(fd, "r"); + if (!f) { + syslog(LOG_ERR, "Error - fdopen failed (%s)", strerror(errno)); + close(fd); + return NULL; + } + + return f; +} + +/* + * Close configuration file + */ +static void file_close(FILE *f) +{ + fclose(f); +} + +/* + * Process audit event + */ +static void handle_event(auparse_state_t *au, + auparse_cb_event_t cb_event_type, void *user_data) +{ + int rc, line, record = 0; + unsigned int i, react_cnt; + struct reaction *react; + char *cwd = NULL, *path = NULL; + + if (cb_event_type != AUPARSE_CB_EVENT_READY) + return; + + /* Config changed is skipped */ + if (auparse_get_type(au) == AUDIT_CONFIG_CHANGE) + return; + + while (auparse_goto_record_num(au, record) > 0) { + /* parse fields from the first record */ + if (record == 0) { + do { + /* parse first record */ + extract_first_record(au, ft); + } while (auparse_next_field(au) > 0); + /* parse CWD and PATH records to get the absolute path */ + } else { + /* get absolute path */ + extract_apath(au, &cwd, &path); + } + record++; + } + /* save apath (absolute path) */ + insert_apath(ft, cwd, path); + free(cwd); + free(path); + + /* number of reactions */ + react_cnt = rd->rarr->count; + for (i = 0; i < react_cnt; i++) { + react = rd->rarr->data[i]; + + rc = ast_eval(react->cond, ft, EVAL_COND, &line); + if (rc < 0) + config_error(rc, line); + /* more field values per one field name */ + while (!react->cond->value.n && rc == AST_NEXT) { + ast_reset(react->cond); + rc = ast_eval(react->cond, ft, EVAL_COND_NEXT, &line); + if (rc < 0) + config_error(rc, line); + } + /* condition is true - trigger action */ + if (react->cond->value.n) { + rc = ast_eval(react->action, ft, EVAL_ACTION, &line); + if (rc < 0) + config_error(rc, line); + + ast_reset(react->action); + } + ast_reset(react->cond); + } + /* prepare fieldtab for the following audit event */ + fieldtab_clear(ft); +} + +/* + * Parse and save data from the first record + */ +static void extract_first_record(auparse_state_t *au, struct fieldtab *ft) +{ + char *fname; + struct ast_value *fvalue; + int error; + + do { + /* get field name */ + if (!auparse_get_field_name(au)) { + syslog(LOG_ERR, "Error getting field name"); + return; + } + fname = strdup(auparse_get_field_name(au)); + if (!fname) { + syslog(LOG_ERR, + "Out of memory allocating field name"); + return; + } + /* get field value */ + fvalue = field_value(au, fname, &error); + if (!fvalue) { + if (error) { + syslog(LOG_ERR, + "Error getting value of field %s", fname); + } + /* if not error, then key=(null) or key=stats */ + free(fname); + return; + } + if (fieldtab_insert(ft, fname, fvalue)) { + syslog(LOG_ERR, + "Error inserting values into field table"); + free(fname); + free(fvalue); + } + } while (auparse_next_field(au) > 0); +} + +/* + * Get absolute path + */ +static void extract_apath(auparse_state_t *au, char **cwd, char **path) +{ + int type; + char *path2; + + type = auparse_get_type(au); + /* CWD record */ + if (type == AUDIT_CWD && auparse_find_field(au, "cwd")) { + if (!auparse_interpret_field(au)) { + syslog(LOG_ERR, + "Error getting \"cwd\" field"); + } + if (!(*cwd)) { + *cwd = strdup(auparse_interpret_field(au)); + if (!(*cwd)) { + syslog(LOG_ERR, + "Error getting value of \"cwd\" field"); + return; + } + } + /* PATH record */ + } else if (type == AUDIT_PATH && auparse_find_field(au, "name")) { + if (!auparse_interpret_field(au)) { + syslog(LOG_ERR, + "Error getting \"name\" field"); + } + if (!(*path)) { + *path = strdup(auparse_interpret_field(au)); + if (!(*path)) { + syslog(LOG_ERR, + "Error getting value of \"name\" field"); + return; + } + } else { + /* get the longest path */ + path2 = strdup(auparse_interpret_field(au)); + if (!path2) { + syslog(LOG_ERR, + "Error getting value of \"name\" field"); + return; + } + /* path2 is not (null) */ + if (strcmp(path2, "(null)")) { + if (strlen(path2) > strlen(*path)) { + free(*path); + *path = path2; + } + } else { + free(path2); + } + } + } +} + +/* + * Insert absolute path into the field table + */ +static void insert_apath(struct fieldtab *ft, const char *cwd, const char *path) +{ + char *fname; + struct ast_value *fvalue; + + fname = strdup("apath"); + if (!fname) { + syslog(LOG_ERR, "Out of memory allocating field name\n"); + return; + } + fvalue = apath_value(cwd, path); + if (!fvalue) { + free(fname); + return; + } + if (fieldtab_insert(ft, fname , fvalue)) { + syslog(LOG_ERR, + "Error inserting values into field table"); + free(fname); + free(fvalue); + return; + } +} + +/* + * Field values that will be sent to the field table + */ +static struct ast_value *field_value(auparse_state_t *au, const char *field, + int *err) +{ + struct ast_value *result; + int val_type = auparse_get_field_type(au); + + *err = 0; + + result = malloc(sizeof(struct ast_value)); + if (!result) + return NULL; + + switch (val_type) { + case AUPARSE_TYPE_UNCLASSIFIED: /* 0 */ + /* number */ + if (!strcmp(field, "pid") || + !strcmp(field, "ppid") || + !strcmp(field, "audit_enabled") || + !strcmp(field, "old") || + !strcmp(field, "items")) { + result->n = (long)auparse_get_field_int(au); + result->type = T_NUM; + } else { + /* string */ + if (!auparse_interpret_field(au)) + { + free(result); + *err = 1; + return NULL; + } + result->s = strdup(auparse_interpret_field(au)); + if (!result->s) { + free(result); + *err = 1; + return NULL; + } + result->type = T_STR; + } + break; + /* numbers */ + case AUPARSE_TYPE_UID: /* 1 */ + case AUPARSE_TYPE_GID: /* 2 */ + case AUPARSE_TYPE_SYSCALL: /* 3 */ + case AUPARSE_TYPE_ARCH: /* 4 */ + case AUPARSE_TYPE_EXIT: /* 5 */ + case AUPARSE_TYPE_PERM: /* 7 */ + case AUPARSE_TYPE_MODE: /* 8 */ + case AUPARSE_TYPE_SESSION: /* 20 */ + case AUPARSE_TYPE_FLAGS: /* 10 */ + case AUPARSE_TYPE_LIST: /* 18 */ + case AUPARSE_TYPE_A0: /* 14 */ + case AUPARSE_TYPE_A1: /* 15 */ + case AUPARSE_TYPE_A2: /* 16 */ + result->n = (long)auparse_get_field_int(au); + result->type = T_NUM; + break; + /* strings */ + case AUPARSE_TYPE_ESCAPED: /* 6 (key, exe, comm) */ + case AUPARSE_TYPE_SUCCESS: /* 13 (ret=success) */ + /* */ + case AUPARSE_TYPE_SOCKADDR: /* 9 */ + case AUPARSE_TYPE_PROMISC: /* 11 */ + case AUPARSE_TYPE_CAPABILITY: /* 12 */ + case AUPARSE_TYPE_SIGNAL: /* 17 */ + case AUPARSE_TYPE_TTY_DATA: /* 19 */ + case AUPARSE_TYPE_CAP_BITMAP: /* 21 */ + /* */ + if (!auparse_interpret_field(au)) { + free(result); + *err = 1; + return NULL; + } + result->s = strdup(auparse_interpret_field(au)); + if (!result->s) { + free(result); + *err = 1; + return NULL; + } + result->type = T_STR; + + if (!strcmp(field, "key") && + !strcmp(result->s, "(null)")) { + free(result->s); + free(result); + return NULL; + } + break; + default: + break; + } + + return result; +} + +/* + * Get apath from 'cwd' and 'path' + */ +static struct ast_value *apath_value(const char *cwd, const char *path) +{ + struct ast_value *result; + char *tmp_path; + int i = 0, end = 0, offset = 0; + + result = malloc(sizeof(struct ast_value)); + if (!result) + return NULL; + + if (!path) { + free(result); + return NULL; + } + + while (!end) { + if (path[i] == '.') { + offset++; + } else if (path[i] == '/') { + if (path[i + 1] == '.') + offset++; + else + end = 1; + } else { + end = 1; + } + i++; + } + + if (!offset && path[0] == '/') { + /* realpath() causes generation of audit events + since it utilizes lstat() syscall -> can cause failure */ + /* result->s = realpath(path, NULL); */ + result->s = strdup(path); + result->type = T_STR; + return result; + } + + if (!cwd) { + free(result); + return NULL; + } + + tmp_path = malloc((strlen(cwd) + strlen(path) - offset + 1) * sizeof(char)); + if (!tmp_path) { + free(result); + return NULL; + } + strcpy(tmp_path, cwd); + strcat(tmp_path, path + offset); + + result->s = tmp_path; + result->type = T_STR; +/* + result->s = realpath(tmp_path, NULL); + free(tmp_path); +*/ + return result; +} + diff --git a/trunk/audisp/plugins/reactive/audisp-reactive.conf b/trunk/audisp/plugins/reactive/audisp-reactive.conf new file mode 100644 index 0000000..f823b17 --- /dev/null +++ b/trunk/audisp/plugins/reactive/audisp-reactive.conf @@ -0,0 +1,143 @@ + + +########################################################### + +# auditctl -a exit,always -S mount +# auditctl -a exit,always -S mount + +# +# USB stick mount +# +react: get(syscall) == 21 { + addw get(apath) + " -p w"; +} + +# +# USB stick umount +# +react: get(syscall) == 22 { + delw get(apath) + " -p w"; +} + +########################################################### + +# auditctl -w /etc/shadow -p wa -k warning + +# +# Warning +# +react: get(key) == "warning" { + exec "echo WARNING"; +} + +########################################################### + +# +# Different users with specific audit rules +# + +var login_cnt_user1 = 0; +var login_cnt_user2 = 0; +var login_cnt_user3 = 0; + +# Logins +react: get(type) == "USER_START" && get(res) == "success" { + var acct = get(acct); + + if (acct == "user1") { + if (login_cnt_user1 == 0) { + add "exit,always -F path=/tmp/u1 -S open"; + } + login_cnt_user1 = login_cnt_user1 + 1; + } else if (acct == "user2") { + if (login_cnt_user2 == 0) { + add "exit,always -F path=/tmp/u2 -S open"; + } + login_cnt_user2 = login_cnt_user2 + 1; + } else if (acct == "user3") { + if (login_cnt_user3 == 0) { + add "exit,always -F path=/tmp/u3 -S open"; + } + login_cnt_user3 = login_cnt_user3 + 1; + } +} + +# Logouts +react: get(type) == "USER_END" && get(res) == "success" { + var acct = get(acct); + + if (acct == "user1") { + login_cnt_user1 = login_cnt_user1 - 1; + if (login_cnt_user1 == 0) { + del "exit,always -F path=/tmp/u1 -S open"; + } + } else if (acct == "user2") { + login_cnt_user2 = login_cnt_user2 - 1; + if (login_cnt_user2 == 0) { + del "exit,always -F path=/tmp/u2 -S open"; + } + } else if (acct == "user3") { + login_cnt_user3 = login_cnt_user3 - 1; + if (login_cnt_user3 == 0) { + del "exit,always -F path=/tmp/u3 -S open"; + } + } +} + +########################################################### + +# +# Login attempts +# + +var msg_sent = 0; + +react: get(type) == "USER_LOGIN" && get(exe) == "/usr/sbin/sshd" && + get(res) == "failed" { + + const query = "type='USER_LOGIN' AND exe='/usr/sbin/sshd' AND res='failed'"; + + const login_cnt = stats(query, 1 min, now); + + if (login_cnt > 5) { + if (!msg_sent) { + exec "echo LOGINS WARNING"; + msg_sent = 1; + } + } else if (login_cnt < 2) { + if (msg_sent) + msg_sent = 0; + } +} + +########################################################### + +# +# Anomaly +# + +var msg_sent2 = 0; + +react: get(type) == "USER_LOGIN" && get(exe) == "/usr/sbin/sshd" && + get(res) == "failed" { + + const query = "type='USER_LOGIN' AND exe='/usr/sbin/sshd' AND res='failed'" + + " AND acct=" + getq(acct); + + const login_cnt_current = stats(query, 1 min, now); + const login_cnt_old = stats(query, 2 min, 1 min); + + const difference = login_cnt_current - login_cnt_old; + + if (difference > 5) { + if (!msg_sent2) { + exec "echo ANOMALY DETECTED"; + msg_sent2 = 1; + } else if (difference < 2) { + if (msg_sent2) + msg_sent2 = 0; + } + } + +} + diff --git a/trunk/audisp/plugins/reactive/reactive-ast.c b/trunk/audisp/plugins/reactive/reactive-ast.c new file mode 100644 index 0000000..3b0e862 --- /dev/null +++ b/trunk/audisp/plugins/reactive/reactive-ast.c @@ -0,0 +1,1123 @@ + +/* reactive-ast.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 + */ + +#include +#include +#include +#include +#include +#include "reactive-aststack.h" +#include "reactive-error.h" + +#define AST_STACK_EMPTY 2 +#define AST_FIELD_UNSET 3 +#define STRLEN_CMD_AUDITCTL 12 +#define STRLEN_CMD_BGPROCESS 2 +#define STRLEN_CHAR_QUOTE 2 /* two quotes */ +#define CMD_AUDITCTL_ADD "auditctl -a " +#define CMD_AUDITCTL_DEL "auditctl -d " +#define CMD_AUDITCTL_ADDW "auditctl -w " +#define CMD_AUDITCTL_DELW "auditctl -W " +#define CMD_BGPROCESS " &" /* commands in background */ +#define CHAR_QUOTE "'" +#define STR_TEST "test" +#define NUM_TEST 0 + +#define STRLEN_LONG ((CHAR_BIT * sizeof(long) + 2) / 3 + 1) + + +static int ast_shift(struct aststack *as, struct ast *a); + +static int ast_reduce(struct aststack *as, struct ast **a, struct fieldtab *ft, + enum eval_type et, int *line); + +static void ast_get_value(struct ast *n, struct ast *t); + +static char *ast_quote_value(struct ast_value *v); + +static int ast_exec(const int t, const char *s); + +static void ast_replace_quote(char *s); + + +/* + * Create a new non-terminal node of type t + */ +struct ast *ast_node(const int t, struct ast *left, struct ast *right, + const int line) +{ + struct ast *a = malloc(sizeof(struct ast)); + if(!a) + return NULL; + + a->line = line; + a->processed = 0; + a->type = t; + a->value.type = T_UNDEF; + a->value.n = 0; + a->value.s = NULL; + a->x = left; + a->u.node.y = right; + + return a; +} + +/* + * Create a new terminal node that stores number + */ +struct ast *ast_number(long n, const int line) +{ + struct ast *a = malloc(sizeof(struct ast)); + if (!a) + return NULL; + + a->line = line; + a->processed = 0; + a->type = AST_VAL_NUM; + a->value.type = T_NUM; + a->value.n = n; + a->value.s = NULL; + a->x = NULL; + + return a; +} + +/* + * Create a new terminal node that stores string + */ +struct ast *ast_string(char *s, const int line) +{ + struct ast *a = malloc(sizeof(struct ast)); + if (!a) + return NULL; + + a->line = line; + a->processed = 0; + a->type = AST_VAL_STR; + a->value.type = T_STR; + a->value.n = 0; + a->value.s = s; + a->x = NULL; + + return a; +} + +/* + * Create a new terminal node that stores symbol reference + */ +struct ast *ast_ref(struct symbol *id, const int line) +{ + struct ast *a = malloc(sizeof(struct ast)); + if(!a) + return NULL; + + a->line = line; + a->processed = 0; + a->type = AST_REF; + a->value.type = T_UNDEF; + a->value.n = 0; + a->value.s = NULL; + a->x = NULL; + a->u.ref.sym = id; + + return a; +} + +/* + * Create a new terminal node that stores unary operator types + * or command types + */ +struct ast *ast_type(const int t, const int line) +{ + struct ast *a = malloc(sizeof(struct ast)); + if (!a) + return NULL; + + a->line = line; + a->processed = 0; + a->type = t; + a->value.type = T_UNDEF; + a->value.n = 0; + a->value.s = NULL; + a->x = NULL; + + return a; +} + +/* + * Terminal node that stores + */ +struct ast *ast_field(char *field, const int quote, const int vtype, + const int line) +{ + struct ast *a = malloc(sizeof(struct ast)); + if (!a) + return NULL; + + a->line = line; + a->processed = 0; + a->type = AST_FIELD; + a->value.type = vtype; + a->value.n = 0; + a->value.s = NULL; + a->x = NULL; + a->u.field.f = field; + a->u.field.q = quote; + + return a; +} + +/* + * Create a new non-terminal node that represents comparison of type t + */ +struct ast *ast_cmp(const int t, struct ast *left, struct ast *right, + const int line) +{ + struct ast *a = malloc(sizeof(struct ast)); + if(!a) + return NULL; + + a->line = line; + a->processed = 0; + a->type = t; + a->value.type = T_UNDEF; + a->value.n = 0; + a->value.s = NULL; + a->x = left; + a->u.node.y = right; + + return a; +} + +/* + * Create a new non-terminal node that represents assignment operator + */ +struct ast *ast_assign(struct ast *id, struct ast *left, const int line) +{ + struct ast *a = malloc(sizeof(struct ast)); + if(!a) + return NULL; + + a->line = line; + a->processed = 0; + a->type = AST_OP_ASGN; + a->value.type = T_UNDEF; + a->value.n = 0; + a->value.s = NULL; + a->x = left; + a->u.node.y = id; + + return a; +} + +/* + * Create a new non-terminal node that represents 'if else' statement + * according to the result of condition it continues to the left branch + * or to the right branch + */ +struct ast *ast_flow(const int t, struct ast *cond, struct ast *left, + struct ast *right, const int line) +{ + struct ast *a = malloc(sizeof(struct ast)); + if(!a) + return NULL; + + a->line = line; + a->processed = 0; + a->type = t; + a->value.type = T_UNDEF; + a->value.n = 0; + a->value.s = NULL; + a->x = cond; + a->u.flow.y = left; + a->u.flow.z = right; + + return a; +} + +/* + * Create a new non-terminal node that represents reaction + */ +struct ast *ast_react(struct ast *cond, struct ast *left, const int line) +{ + struct ast *a = malloc(sizeof(struct ast)); + if (!a) + return NULL; + + a->line = line; + a->processed = 0; + a->type = AST_REACT; + a->value.type = T_UNDEF; + a->value.n = 0; + a->value.s = NULL; + a->x = left; + a->u.node.y = cond; + + return a; +} + +/* + * Create a new non-terminal that represents a command + * command are: add, del, addw, delw, exec + */ +struct ast *ast_cmd(struct ast *id, struct ast *left, const int line) +{ + struct ast *a = malloc(sizeof(struct ast)); + if(!a) + return NULL; + + a->line = line; + a->processed = 0; + a->type = AST_CMD; + a->value.type = T_UNDEF; + a->value.n = 0; + a->value.s = NULL; + a->x = left; + a->u.node.y = id; + + return a; +} + +/* + * Create a new non-terminal that represents calling of stats() function + */ +struct ast *ast_stats(struct ast *left, struct ast *period, const int line) +{ + struct ast *a = malloc(sizeof(struct ast)); + if (!a) + return NULL; + + a->line = line; + a->processed = 0; + a->type = AST_STATS; + a->value.type = T_UNDEF; + a->value.n = 0; + a->value.s = NULL; + a->x = left; + a->u.node.y = period; + + return a; +} + +/* + * Create a new terminal that represents time period used in connection + * with stats() function + */ +struct ast *ast_period(const unsigned int start, const unsigned int stop, + const int line) +{ + struct ast *a = malloc(sizeof(struct ast)); + if (!a) + return NULL; + + a->line = line; + a->processed = 0; + a->type = AST_PERIOD; + a->value.type = T_UNDEF; + a->value.n = 0; + a->value.s = NULL; + a->x = NULL; + a->u.period.start = start; + a->u.period.stop = stop; + + return a; +} + +/* + * Evaluate AST + */ +int ast_eval(struct ast *a, struct fieldtab *ft, enum eval_type et, int *line) +{ + int rc; + int retval = AST_FINISH; + struct ast *root = a; + struct ast *result = NULL; + struct aststack *astack; + + if (!root) + return retval;; + + /* stack for keeping nodes visited and not yet evaluated nodes */ + astack = aststack_init(ALLOC_SIZE_ASTACK); + if (!astack) + return -ERR_ASTACK_MEM; + + if (ast_shift(astack, root)) + return -ERR_ASTACK_MEM; + + while (!aststack_empty(astack)) { + rc = ast_reduce(astack, &result, ft, et, line); + if (rc < 0) { + aststack_free(astack); + return rc; + } else if (rc == AST_STACK_EMPTY || rc == AST_FIELD_UNSET) { + break; + } else if (rc == AST_NEXT) { + if (retval != AST_NEXT) + retval = AST_NEXT; + } + switch (result->type) { + /* only non-terminals */ + case AST_OP_ADD: + case AST_OP_SUB: + case AST_OP_MUL: + case AST_OP_DIV: + case AST_OP_MOD: + case AST_OP_OR: + case AST_OP_AND: + case AST_CMP_LT: + case AST_CMP_LE: + case AST_CMP_GT: + case AST_CMP_GE: + case AST_CMP_EQ: + case AST_CMP_NE: + case AST_OP_UNARY: + case AST_LIST: + case AST_OP_ASGN: + case AST_REACT: + case AST_CMD: + case AST_STATS: + root = result->u.node.y; + break; + case AST_STMT_IF: + if (result->value.n) { + root = result->u.flow.y; + if (result->u.flow.z) + result->u.flow.z->processed = 1; + } else { + root = result->u.flow.z; + result->u.flow.y->processed = 1; + } + break; + default: + break; + } + if (root && root->processed) + continue; + if (ast_shift(astack, root)) + return -ERR_ASTACK_MEM; + } + aststack_free(astack); + + return retval; +} + +/* + * The same reaction can be triggered several time so after + * every evaluation, the tree must be reset + */ +int ast_reset(struct ast *a) +{ + struct ast *root = a; + struct ast *result; + struct aststack *astack; + int reset, cont; + + if (!root) + return 0; + + astack = aststack_init(ALLOC_SIZE_ASTACK); + if (!astack) + return -ERR_ASTACK_MEM; + + if (ast_shift(astack, root)) + return -ERR_ASTACK_MEM; + + while (!aststack_empty(astack)) { + reset = 0; + cont = 0; + result = aststack_pop(astack); + switch (result->type) { + case AST_OP_ADD: + case AST_OP_SUB: + case AST_OP_MUL: + case AST_OP_DIV: + case AST_OP_MOD: + case AST_OP_OR: + case AST_OP_AND: + case AST_CMP_LT: + case AST_CMP_LE: + case AST_CMP_GT: + case AST_CMP_GE: + case AST_CMP_EQ: + case AST_CMP_NE: + case AST_OP_UNARY: + case AST_LIST: + case AST_OP_ASGN: + case AST_REACT: + case AST_CMD: + case AST_STATS: + root = result->u.node.y; + reset = 1; + break; + case AST_STMT_IF: + if (result->value.n) { + root = result->u.flow.y; + if (result->u.flow.z) + result->u.flow.z->processed = 0; + } else { + root = result->u.flow.z; + result->u.flow.y->processed = 0; + } + reset = 1; + break; + case AST_REF: + /* local constant */ + if (result->u.ref.sym->type && + result->u.ref.sym->constant) + --result->u.ref.sym->constant; + case AST_FIELD: + reset = 1; + cont = 1; + break; + case AST_OP_UMINUS: + case AST_OP_NEG: + case AST_CMD_ADD: + case AST_CMD_DEL: + case AST_CMD_ADDW: + case AST_CMD_DELW: + case AST_CMD_EXEC: + case AST_VAL_NUM: + case AST_VAL_STR: + case AST_PERIOD: + result->processed = 0; + cont = 1; + break; + default: + break; + } + if (reset) { + result->processed = 0; + /* temporary string */ + if (result->value.type == T_STR_ALLOC) + free(result->value.s); + result->value.type = T_UNDEF; + result->value.n = 0; + result->value.s = NULL; + } + if (root && (!root->processed || cont)) + continue; + + if (ast_shift(astack, root)) + return -ERR_ASTACK_MEM; + } + aststack_free(astack); + + return 0; +} + +/* + * Free the AST tree + */ +int ast_free(struct ast *a) +{ + struct ast *root = a; + struct ast *result; + struct aststack *astack; + int cont = 0; + + if (!root) + return 0; + + astack = aststack_init(ALLOC_SIZE_ASTACK); + if (!astack) + return -ERR_ASTACK_MEM; + + if (ast_shift(astack, root)) + return -ERR_ASTACK_MEM; + + while (!aststack_empty(astack)) { + cont = 0; + result = aststack_pop(astack); + switch (result->type) { + case AST_OP_ADD: + case AST_OP_SUB: + case AST_OP_MUL: + case AST_OP_DIV: + case AST_OP_MOD: + case AST_OP_OR: + case AST_OP_AND: + case AST_CMP_LT: + case AST_CMP_LE: + case AST_CMP_GT: + case AST_CMP_GE: + case AST_CMP_EQ: + case AST_CMP_NE: + case AST_OP_UNARY: + case AST_LIST: + case AST_OP_ASGN: + case AST_REACT: + case AST_CMD: + case AST_STATS: + root = result->u.node.y; + break; + case AST_STMT_IF: + root = result->u.flow.y; + if (ast_shift(astack, result->u.flow.z)) + return -ERR_ASTACK_MEM; + break; + case AST_OP_UMINUS: + case AST_OP_NEG: + case AST_CMD_ADD: + case AST_CMD_DEL: + case AST_CMD_ADDW: + case AST_CMD_DELW: + case AST_CMD_EXEC: + case AST_VAL_NUM: + case AST_VAL_STR: + case AST_PERIOD: + root = NULL; + break; + case AST_REF: + root = NULL; + cont = 1; + break; + case AST_FIELD: + root = NULL; + free(result->u.field.f); + cont = 1; /* free in fieldtab */ + break; + default: + break; + } + if (!cont && result->value.type == T_STR) + free(result->value.s); + free(result); + + if (ast_shift(astack, root)) + return -ERR_ASTACK_MEM; + } + aststack_free(astack); + + return 0; + +} + +/* + * Push nodes onto the stack when going down the tree + * on the left branch + */ +static int ast_shift(struct aststack *as, struct ast *a) +{ + struct ast *ptr = a; + + while (ptr) { + if (aststack_push(as, ptr)) + return 1; + ptr = ptr->x; + } + + return 0; +} + +/* + * Left branch is evaluated at first, the result is stored in the current + * non-terminal node, then the right branch is evaluated + */ +static int ast_reduce(struct aststack *as, struct ast **a, struct fieldtab *ft, + enum eval_type et, int *line) +{ + int result = AST_FINISH; + /* get the top of the stack - considered as terminal node */ + struct ast *t = aststack_pop(as); + int t_type; + struct ast *n; + int n_type; + char *buf; + /* value list is used only with fields that + can represent more values - the 'key' field */ + struct field_value_list *vlist; + + /* the stack is empty */ + if (as->top < 0) + return AST_STACK_EMPTY; + + *a = n = as->data[as->top]; /* non-terminal */ + *line = n->line; /* line number in config file */ + n_type = as->data[as->top]->value.type; + + if (t->type == AST_REF) { + /* get value from symbol table */ + t->value = t->u.ref.sym->value; + } else if (t->type == AST_FIELD) { + if (et == EVAL_TEST) { + if (t->value.type == T_NUM) + t->value.n = NUM_TEST; + else if (t->value.type == T_STR) + t->value.s = STR_TEST; + } else { + if (!fieldtab_search(ft, &vlist, t->u.field.f)) { + if (et == EVAL_COND || et == EVAL_COND_NEXT) + return AST_FIELD_UNSET; + else if (et == EVAL_ACTION) + return -ERR_AST_FIELD_FOUND; + } + if (!(vlist->which)) + return -ERR_AST_FIELD_VALUE; + + if (et == EVAL_COND) { + if (vlist->which->next_val) + result = AST_NEXT; + t->value = *(vlist->which->value); + } else if (et == EVAL_COND_NEXT) { + /* if one field has more values */ + if (vlist->which->next_val) { + result = AST_NEXT; + vlist->which = vlist->which->next_val; + } + t->value = *(vlist->which->value); + } else if (et == EVAL_ACTION) { + if (t->u.field.q) { + t->value.s = + ast_quote_value(vlist->which->value); + if (!(t->value.s)) + return -ERR_FIELD_QUOTE_MEM; + + t->value.type = T_STR_ALLOC; + } else { + t->value = *(vlist->which->value); + } + } + } + } + t_type = t->value.type; + + switch (n->type) { + case AST_OP_ADD: + if (n_type == T_UNDEF) { + ast_get_value(n, t); + } else { + if (n_type == T_NUM) { + if (t_type == T_NUM) { + n->value.n += t->value.n; + } else { + /* number + string */ + int len = strlen(t->value.s); + buf = malloc((STRLEN_LONG + len) * + sizeof(char)); + if (!buf) + return -ERR_AST_OP_ADD_MEM; + + snprintf(buf, STRLEN_LONG, + "%ld", n->value.n); + strcat(buf, t->value.s); + n->value.type = T_STR_ALLOC; + n->value.s = buf; + } + } else { + if (t_type == T_NUM) { + /* string + number */ + int offset = strlen(n->value.s); + buf = malloc((STRLEN_LONG + offset) * + sizeof(char)); + if (!buf) + return -ERR_AST_OP_ADD_MEM; + + strcpy(buf, n->value.s); + snprintf(buf + offset, STRLEN_LONG, + "%ld", t->value.n); + n->value.type = T_STR_ALLOC; + n->value.s = buf; + } else { + /* string + string */ + buf = malloc((strlen(n->value.s) + + strlen(t->value.s) + 1) * + sizeof(char)); + if (!buf) + return -ERR_AST_OP_ADD_MEM; + + strcpy(buf, n->value.s); + strcat(buf, t->value.s); + + n->value.type = T_STR_ALLOC; + n->value.s = buf; + } + } + } + break; + case AST_OP_SUB: + if (n_type == T_UNDEF) { + ast_get_value(n, t); + } else { + if (n_type != T_NUM || t_type != T_NUM) + return -ERR_AST_OP_SUB; + else + n->value.n -= t->value.n; + } + break; + case AST_OP_MUL: + if (n_type == T_UNDEF) { + ast_get_value(n, t); + } else { + if (n_type != T_NUM || t_type != T_NUM) + return -ERR_AST_OP_MUL; + else + n->value.n *= t->value.n; + } + break; + case AST_OP_DIV: + if (n_type == T_UNDEF) { + ast_get_value(n, t); + } else { + if (n_type != T_NUM || t_type != T_NUM) { + return -ERR_AST_OP_DIV; + } else { + if (t->value.n == 0) + return -ERR_AST_OP_DIV_ZERO; + + n->value.n /= t->value.n; + } + } + break; + case AST_OP_MOD: + if (n_type == T_UNDEF) { + ast_get_value(n, t); + } else { + if (n_type != T_NUM || t_type != T_NUM) { + return -ERR_AST_OP_MOD; + } else { + if (t->value.n == 0) + return -ERR_AST_OP_MOD_ZERO; + + n->value.n %= t->value.n; + } + } + break; + case AST_OP_AND: + if (n_type == T_UNDEF) { + ast_get_value(n, t); + } else { + if (n_type != T_NUM || t_type != T_NUM) + return -ERR_AST_OP_AND; + else + n->value.n = + n->value.n && t->value.n; + } + break; + case AST_OP_OR: + if (n_type == T_UNDEF) { + ast_get_value(n, t); + } else { + if (n_type != T_NUM || t_type != T_NUM) + return -ERR_AST_OP_OR; + else + n->value.n = + n->value.n || t->value.n; + } + break; + case AST_CMP_LT: + if (n_type == T_UNDEF) { + ast_get_value(n, t); + } else { + if (n_type != T_NUM || t_type != T_NUM) + return -ERR_AST_CMP_LT; + else + n->value.n = + (n->value.n < t->value.n)? 1 : 0; + } + break; + case AST_CMP_LE: + if (n_type == T_UNDEF) { + ast_get_value(n, t); + } else { + if (n_type != T_NUM || t_type != T_NUM) + return -ERR_AST_CMP_LE; + else + n->value.n = + (n->value.n <= t->value.n)? 1 : 0; + } + break; + case AST_CMP_GT: + if (n_type == T_UNDEF) { + ast_get_value(n, t); + } else { + if (n_type != T_NUM || t_type != T_NUM) + return -ERR_AST_CMP_GT; + + n->value.n = (n->value.n > t->value.n)? 1 : 0; + } + break; + case AST_CMP_GE: + if (n_type == T_UNDEF) { + ast_get_value(n, t); + } else { + if (n_type != T_NUM || t_type != T_NUM) + return -ERR_AST_CMP_GE; + else + n->value.n = + (n->value.n >= t->value.n)? 1 : 0; + } + break; + case AST_CMP_EQ: + if (n_type == T_UNDEF) { + ast_get_value(n, t); + } else { + if (n_type != t_type) + return -ERR_AST_CMP_EQ; + + if (n_type == T_NUM && t_type == T_NUM) + n->value.n = + (n->value.n == t->value.n)? 1 : 0; + else if (n_type == T_STR && t_type == T_STR) + n->value.n = + !strcmp(n->value.s, t->value.s); + n->value.type = T_NUM; + } + break; + case AST_CMP_NE: + if (n_type == T_UNDEF) { + ast_get_value(n, t); + } else { + if (n_type != t_type) + return -ERR_AST_CMP_NE; + if (n_type == T_NUM && t_type == T_NUM) + n->value.n = + (n->value.n != t->value.n)? 1 : 0; + else if (n_type == T_STR && t_type == T_STR) + n->value.n = + strcmp(n->value.s, t->value.s); + n->value.type = T_NUM; + } + break; + case AST_OP_UNARY: + if (n_type == T_UNDEF) { + ast_get_value(n, t); + } else { + if (n_type != T_NUM) + return -ERR_AST_OP_UNARY; + else + if (t->type == AST_OP_UMINUS) + n->value.n = + -n->value.n; + else if (t->type == AST_OP_NEG) + n->value.n = + !n->value.n; + } + break; + case AST_OP_ASGN: + if (n_type == T_UNDEF) { + ast_get_value(n, t); + } else { + struct symbol *symref; + if (t->type != AST_REF) + return -ERR_AST_OP_ASGN_LOP; + + symref = t->u.ref.sym; + /* do not assign any values + to global variables in reaction + definition when testing */ + if (et == EVAL_TEST && !symref->constant && + !symref->type && symref->value.type != T_UNDEF) + break; + + if (symref->constant == 1) + ++symref->constant; + else if (symref->constant > 1) + return -ERR_AST_CONSTANT; + + if (symref->value.type == T_STR) + free(symref->value.s); + if (n->value.type == T_STR) { + symref->value.s = strdup(n->value.s); + if (!symref->value.s) + return -ERR_AST_OP_ASGN_MEM; + + symref->value.type = T_STR; + } else { + symref->value = n->value; + n->value.n = 1; + } + } + break; + case AST_STMT_IF: + if (n_type == T_UNDEF) + ast_get_value(n, t); + break; + case AST_CMD: + if (n_type == T_UNDEF) { + ast_get_value(n, t); + } else { + int rc; + /* do not execute command when testing */ + if (et == EVAL_TEST) + break; + if (n_type == T_STR) { + rc = ast_exec(t->type, n->value.s); + if (rc < 0) + return rc; + } else if (n_type == T_NUM) { + buf = malloc(STRLEN_LONG * sizeof(char)); + if (!buf) + return -ERR_AST_CMD_MEM; + + snprintf(buf, STRLEN_LONG, "%ld", + n->value.n); + rc = ast_exec(t->type, buf); + free(buf); + if (rc < 0) + return rc; + } + } + break; + case AST_STATS: + if (n_type == T_UNDEF) { + ast_get_value(n, t); + } else { + int start, stop; + int current; + + if (et == EVAL_TEST) { + if (n_type == T_STR_ALLOC) + free(n->value.s); + n->value.n = NUM_TEST; + n->value.type = T_NUM; + break; + } + + current = (int)time(NULL); + start = current - t->u.period.start; + stop = current - t->u.period.stop; + n->value.n = (long)sql_get_stats(n->value.s, + start, stop); + if (n_type == T_STR_ALLOC) + free(n->value.s); + n->value.type = T_NUM; + } + break; + case AST_LIST: + case AST_REACT: + break; + default: + break; + } + t->processed = 1; + + return result; +} + +/* * T_STR_ALLOC is changed to T_STR when result is passed + * up the tree + */ +static void ast_get_value(struct ast *n, struct ast *t) +{ + if (t->value.type == T_STR_ALLOC) { + n->value.type = T_STR; + n->value.s = t->value.s; + } else { + n->value = t->value; + } +} + +/* + * Get field value from the first record of an audit event + */ +static char *ast_quote_value(struct ast_value *v) +{ + int len; + char *str = NULL; + + if (v->type == T_NUM) { + len = STRLEN_LONG + STRLEN_CHAR_QUOTE + 1; + str = malloc(len * sizeof(char)); + if (!str) + return NULL; + + strcpy(str, CHAR_QUOTE); + snprintf(str + 1, STRLEN_LONG, "%ld", v->n); + strcat(str, CHAR_QUOTE); + } else if (v->type == T_STR) { + len = strlen(v->s) + STRLEN_CHAR_QUOTE + 1; + str = malloc(len * sizeof(char)); + if (!str) + return NULL; + + strcpy(str, CHAR_QUOTE); + strcat(str, v->s); + /* replace quotes with white space in the original string */ + ast_replace_quote(str + 1); + strcat(str, CHAR_QUOTE); + } + + return str; +} + +/* + * Prepare a command string and execute an command + */ +static int ast_exec(const int t, const char *s) +{ + char *cmd = NULL; + + if (t >= AST_CMD_ADD && t <= AST_CMD_DELW) { + cmd = malloc((STRLEN_CMD_AUDITCTL + STRLEN_CMD_BGPROCESS + + strlen(s) + 1) * sizeof(char)); + if (!cmd) + return -ERR_AST_CMD_MEM; + switch (t) { + case AST_CMD_ADD: + strcpy(cmd, CMD_AUDITCTL_ADD); + break; + case AST_CMD_DEL: + strcpy(cmd, CMD_AUDITCTL_DEL); + break; + case AST_CMD_ADDW: + strcpy(cmd, CMD_AUDITCTL_ADDW); + break; + case AST_CMD_DELW: + strcpy(cmd, CMD_AUDITCTL_DELW); + break; + default: + break; + } + strcat(cmd, s); + strcat(cmd, CMD_BGPROCESS); + } else if (t == AST_CMD_EXEC) { + cmd = malloc((STRLEN_CMD_BGPROCESS + strlen(s) + 1) * + sizeof(char)); + if (!cmd) + return -ERR_AST_CMD_MEM; + + strcpy(cmd, s); + strcat(cmd, CMD_BGPROCESS); + } + if (system(cmd)) + return -ERR_AST_CMD_EXEC; + + free(cmd); + + return 0; +} + +/* + * Replace "'" with " " + */ +static void ast_replace_quote(char *s) +{ + int i = 0; + + while (s[i]) { + if (s[i] == 0x27) + s[i] = 0x20; + i++; + } +} + diff --git a/trunk/audisp/plugins/reactive/reactive-ast.h b/trunk/audisp/plugins/reactive/reactive-ast.h new file mode 100644 index 0000000..b92a0df --- /dev/null +++ b/trunk/audisp/plugins/reactive/reactive-ast.h @@ -0,0 +1,167 @@ + +/* reactive-ast.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 + */ + +#ifndef REACTIVE_ASTNODE_H +#define REACTIVE_ASTNODE_H + +#include "reactive-symtab.h" +#include "reactive-fieldtab.h" + +/* + * Node types of abstract syntax tree + */ +enum node_type +{ + AST_OP_ADD = 1, + AST_OP_SUB, + AST_OP_MUL, + AST_OP_DIV, + AST_OP_MOD, + AST_OP_OR, + AST_OP_AND, + + AST_CMP_LT, + AST_CMP_LE, + AST_CMP_GT, + AST_CMP_GE, + AST_CMP_EQ, + AST_CMP_NE, + + AST_OP_UNARY, + AST_LIST, + + AST_OP_UMINUS, /* unary minus */ + AST_OP_NEG, + + AST_OP_ASGN, + + AST_STMT_IF, + + AST_REACT, + + AST_CMD, + AST_CMD_ADD, + AST_CMD_DEL, + AST_CMD_ADDW, + AST_CMD_DELW, + AST_CMD_EXEC, + + AST_REF, + AST_VAL_NUM, + AST_VAL_STR, + AST_FIELD, + AST_STATS, + AST_PERIOD +}; + +/* + * node of AST + */ +struct ast +{ + int line; /* line number in config file */ + int processed; /* states if the node was evaluated */ + enum node_type type; /* node type */ + struct ast_value value; /* node value */ + struct ast *x; /* pointer to left-side node */ + union { + struct { + struct ast *y; /* pointer to right-side node */ + } node; + struct { + struct symbol *sym; /* symbol reference */ + } ref; + struct { + char *f; + int q; /* quotes */ + } field; + struct { + struct ast *y; /* 'true' branch of if statement */ + struct ast *z; /* 'false' branch */ + } flow; + struct { + unsigned int start; + unsigned int stop; + } period; + } u; +}; + +/* + * type of evaluation + */ +enum eval_type { + EVAL_TEST, /* test the AST without triggering any commands */ + EVAL_COND, /* evaluate condition */ + EVAL_COND_NEXT, + EVAL_ACTION /* evaluate action */ +}; + +/* + * if no error occurs during ast_eval() + * it returns either EVAL_FINISHED or EVAL_NEXT + */ +enum eval_result { + AST_FINISH, + AST_NEXT /* there are more field values to be evaluated */ +}; + +struct ast *ast_node(const int t, struct ast *left, struct ast *right, + const int line); + +struct ast *ast_number(long n, const int line); + +struct ast *ast_string(char *s, const int line); + +struct ast *ast_ref(struct symbol *id, const int line); + +struct ast *ast_type(const int t, const int line); + +struct ast *ast_field(char *field, const int quote, const int vtype, + const int line); + +struct ast *ast_cmp(const int t, struct ast *left, struct ast *right, + const int line); + +struct ast *ast_assign(struct ast *id, struct ast *left, + const int line); + +struct ast *ast_flow(const int t, struct ast *cond, struct ast *left, + struct ast *right, const int line); + +struct ast *ast_react(struct ast *cond, struct ast *left, + const int line); + +struct ast *ast_cmd(struct ast *id, struct ast *left, const int line); + +struct ast *ast_stats(struct ast *left, struct ast *period, const int line); + +struct ast *ast_period(const unsigned int start, const unsigned int stop, + const int line); + +int ast_eval(struct ast *a, struct fieldtab *ft, enum eval_type et, int *line); + +int ast_reset(struct ast *a); + +int ast_free(struct ast *a); + +#endif + diff --git a/trunk/audisp/plugins/reactive/reactive-aststack.c b/trunk/audisp/plugins/reactive/reactive-aststack.c new file mode 100644 index 0000000..042d94b --- /dev/null +++ b/trunk/audisp/plugins/reactive/reactive-aststack.c @@ -0,0 +1,107 @@ + +/* reactive-aststack.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 + */ + +#include +#include "reactive-aststack.h" + +static inline int aststack_realloc(struct aststack *as); + +/* + * Init stack for storing AST nodes + */ +struct aststack *aststack_init(int size) +{ + struct aststack *as = malloc(sizeof(struct aststack)); + if (!as) + return NULL; + + as->size = size; + as->top = -1; + as->data = malloc(size * sizeof(struct ast *)); + if (!as->data) { + free(as); + return NULL; + } + return as; +} + +/* + * Free the stack + */ +void aststack_free(struct aststack *as) +{ + if (!as) + return; + + free(as->data); + free(as); +} + +/* + * Check if the stack is empty + */ +int aststack_empty(struct aststack *as) +{ + return (as->top == -1)? 1 : 0; +} + +/* + * Push a new node onto the stack + */ +int aststack_push(struct aststack *as, struct ast *a) +{ + if (as->top == as->size - 1) { + if (aststack_realloc(as)) + return -1; + } + ++as->top; + as->data[as->top] = a; + + return 0; +} + +/* + * Return a node and remove it from the stack + */ +struct ast *aststack_pop(struct aststack *as) +{ + return as->data[as->top--]; +} + +/* + * Realloc the stack + */ +static inline int aststack_realloc(struct aststack *as) +{ + int new_size = as->size + ALLOC_SIZE_ASTACK; + + as->data = realloc(as->data, new_size * sizeof(struct ast *)); + if (!as->data) { + free(as); + return -1; + } + as->size = new_size; + + return 0; +} + + diff --git a/trunk/audisp/plugins/reactive/reactive-aststack.h b/trunk/audisp/plugins/reactive/reactive-aststack.h new file mode 100644 index 0000000..a0e5401 --- /dev/null +++ b/trunk/audisp/plugins/reactive/reactive-aststack.h @@ -0,0 +1,49 @@ + +/* reactive-aststack.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 + */ + +#ifndef REACTIVE_ASTSTACK_H +#define REACTIVE_ASTSTACK_H + +#include "reactive-ast.h" + +#define ALLOC_SIZE_ASTACK 32 + +struct aststack +{ + int size; + int top; + struct ast **data; +}; + + +struct aststack *aststack_init(int size); + +void aststack_free(struct aststack *as); + +int aststack_empty(struct aststack *as); + +int aststack_push(struct aststack *as, struct ast *a); + +struct ast *aststack_pop(struct aststack *as); + +#endif + diff --git a/trunk/audisp/plugins/reactive/reactive-astvalue.h b/trunk/audisp/plugins/reactive/reactive-astvalue.h new file mode 100644 index 0000000..ae5aa2b --- /dev/null +++ b/trunk/audisp/plugins/reactive/reactive-astvalue.h @@ -0,0 +1,41 @@ + +/* reactive-astvalue.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 + */ + +#ifndef REACTIVE_ASTVALUE_H +#define REACTIVE_ASTVALUE_H + +struct ast_value +{ + enum { + T_UNDEF, + T_NUM, + T_STR, + /* new string might be allocated + during evaluation of AST */ + T_STR_ALLOC + } type; + long n; + char *s; +}; + +#endif + diff --git a/trunk/audisp/plugins/reactive/reactive-config.c b/trunk/audisp/plugins/reactive/reactive-config.c new file mode 100644 index 0000000..e85d210 --- /dev/null +++ b/trunk/audisp/plugins/reactive/reactive-config.c @@ -0,0 +1,98 @@ + +/* reactive-config.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 + */ + +#include +#include +#include "reactive-scanner.h" +#include "reactive-config.h" +#include "reactive-error.h" + +/* size of hash table for storing symbols */ +#define SIZE_SYMTAB 103 + +/* + * Init reaction array, symbol table and buffer length + */ +struct react_data *config_init(void) +{ + struct react_data *rdata = malloc(sizeof(struct react_data)); + if (!rdata) + return NULL; + + rdata->rarr = reactarr_init(ALLOC_SIZE_REACTARR); + if (!(rdata->rarr)) { + free(rdata); + return NULL; + } + rdata->stab = symtab_init(SIZE_SYMTAB); + if (!(rdata->stab)) { + reactarr_free(rdata->rarr); + free(rdata); + return NULL; + } + rdata->root = NULL; + + return rdata; +} + +/* + * Parse file with reactions and create AST + */ +int config_read(struct react_data *rdata, FILE *f, int *line) +{ + int rc; + + scanner_set_input(f); + rc = yyparse(rdata); + *line = scanner_get_lineno(); + if (rc) + return rc; + + return rc; +} + +/* + * Free the whole AST, reaction and symbol table + */ +void config_destroy(struct react_data *rdata) +{ + reactarr_free(rdata->rarr); + ast_free(rdata->root); + symtab_free(rdata->stab); + free(rdata); +} + +/* + * Print error + */ +void config_error(const int err, const int line) +{ + int err_code = -err; + /* err is negative value */ + if ((err_code >= ERR_REACTARR_MEM) && + (err_code <= ERR_AST_CMD_MEM)) + syslog(LOG_ERR, "%s\n", error_get(err)); + else if ((err_code >= ERR_SYMTAB_REDECLAR) && + (err_code <= ERR_AST_FIELD_VALUE)) + syslog(LOG_ERR, "Line: %d error: %s\n", line, error_get(err)); +} + diff --git a/trunk/audisp/plugins/reactive/reactive-config.h b/trunk/audisp/plugins/reactive/reactive-config.h new file mode 100644 index 0000000..d7eace9 --- /dev/null +++ b/trunk/audisp/plugins/reactive/reactive-config.h @@ -0,0 +1,48 @@ + +/* reactive-config.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 + */ + +#ifndef REACTIVE_CONFIG_H +#define REACTIVE_CONFIG_H + +#include +#include "reactive-reactarr.h" + + +struct react_data +{ + struct reactarr *rarr; + struct symtab *stab; + struct ast *root; +}; + +struct react_data *config_init(void); + +int config_read(struct react_data *rdata, FILE *f, int *line); + +void config_destroy(struct react_data *rdata); + +void config_error(const int err, const int line); + +int yyparse(struct react_data *rdata); + +#endif + diff --git a/trunk/audisp/plugins/reactive/reactive-error.c b/trunk/audisp/plugins/reactive/reactive-error.c new file mode 100644 index 0000000..368d63f --- /dev/null +++ b/trunk/audisp/plugins/reactive/reactive-error.c @@ -0,0 +1,68 @@ + +/* reactive-error.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 + */ + +#include "reactive-error.h" + +const char *rerror[] = +{ + "", + "Out of memory - reaction array", + "Out of memory - symbol table", + "Out of memory - abstract syntax tree", + "Out of memory - AST stack", + "Out of memory - string concatenation", + "Out of memory - assignment", + "Out of memory - quote field value", + "Out of memory - command string", + + "symbol already declared", + "symbol undeclared", + "subtraction of incompatible types", + "multiplication of incompatible types", + "division of incompatible types", + "division by zero", + "modulo of incompatible types", + "modulo by zero", + "'or' - incompatible types", + "'and' - incompatible type", + "'<' - incompatible types", + "'<=' - incompatible types", + "'>' - incompatible types", + "'>=' - incompatible types", + "'==' - incompatible types", + "'!=' - incompatible types", + "unary operator cannot be used with string", + "wrong left side operand", + "constant value cannot be changed", + "couldn't execute command", + "couldn't get field value", + "field without any value" +}; + +/* + * Get an error string + */ +const char *error_get(const int err) +{ + return rerror[-err]; +} + diff --git a/trunk/audisp/plugins/reactive/reactive-error.h b/trunk/audisp/plugins/reactive/reactive-error.h new file mode 100644 index 0000000..90741bd --- /dev/null +++ b/trunk/audisp/plugins/reactive/reactive-error.h @@ -0,0 +1,64 @@ + +/* reactive-error.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 + */ + +#ifndef REACTIVE_ERROR_H +#define REACTIVE_ERROR_H + +enum rerror_code { + /* do no print line with these errors */ + ERR_REACTARR_MEM = 1, + ERR_SYMTAB_MEM, + ERR_AST_MEM, + ERR_ASTACK_MEM, + ERR_AST_OP_ADD_MEM, + ERR_AST_OP_ASGN_MEM, + ERR_FIELD_QUOTE_MEM, + ERR_AST_CMD_MEM, + /* print line*/ + ERR_SYMTAB_REDECLAR, + ERR_SYMTAB_UNDECLAR, + ERR_AST_OP_SUB, + ERR_AST_OP_MUL, + ERR_AST_OP_DIV, + ERR_AST_OP_DIV_ZERO, + ERR_AST_OP_MOD, + ERR_AST_OP_MOD_ZERO, + ERR_AST_OP_OR, + ERR_AST_OP_AND, + ERR_AST_CMP_LT, + ERR_AST_CMP_LE, + ERR_AST_CMP_GT, + ERR_AST_CMP_GE, + ERR_AST_CMP_EQ, + ERR_AST_CMP_NE, + ERR_AST_OP_UNARY, + ERR_AST_OP_ASGN_LOP, + ERR_AST_CONSTANT, + ERR_AST_CMD_EXEC, + ERR_AST_FIELD_FOUND, + ERR_AST_FIELD_VALUE +}; + +const char *error_get(const int err); + +#endif + diff --git a/trunk/audisp/plugins/reactive/reactive-fieldtab.c b/trunk/audisp/plugins/reactive/reactive-fieldtab.c new file mode 100644 index 0000000..8de60d3 --- /dev/null +++ b/trunk/audisp/plugins/reactive/reactive-fieldtab.c @@ -0,0 +1,163 @@ + +/* reactive-fieldtab.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 + */ + +#include +#include +#include "reactive-fieldtab.h" + + +static unsigned int fieldtab_hash(const char *s, unsigned int n); + +/* + * Init hash table for storing fields + */ +struct fieldtab *fieldtab_init(const unsigned int size) +{ + struct fieldtab *ft = malloc(sizeof(struct fieldtab)); + if(!ft) + return NULL; + + ft->size = size; + ft->data = calloc(size, sizeof(struct field_item *)); + if(!(ft->data)) { + free(ft); + return NULL; + } + + return ft; +} + +/* + * Free the hash table + */ +void fieldtab_destroy(struct fieldtab *ft) +{ + fieldtab_clear(ft); + free(ft->data); + free(ft); +} + +/* + * Insert new field value - if a name of the field doesn't exist, it is created + * A field name can contain more than value ('key' field) - these values are + * stored in a list + */ +int fieldtab_insert(struct fieldtab *ft, char *name, struct ast_value *value) +{ + int found = 0; + struct field_item *p; + struct field_value *v; + unsigned int k = fieldtab_hash(name, ft->size); + + for (p = ft->data[k]; p != NULL; p = p->next) { + if (!strcmp(p->id, name)) { + free(name); + found = 1; + break; + } + } + v = malloc(sizeof(struct field_value)); + if (!v) + return -1; + + v->value = value; + if (!found) { + p = malloc(sizeof(struct field_item)); + if (!p) { + free(v); + return -1; + } + p->id = name; + p->fval_list.val_count = 0; + p->fval_list.first_val = NULL; + p->fval_list.which = NULL; + p->next = ft->data[k]; + ft->data[k] = p; + } + v->next_val = p->fval_list.first_val; + p->fval_list.first_val = v; + p->fval_list.which = v; + ++p->fval_list.val_count; + + return 0; +} + +/* + * Search field name and return corresponding value list + */ +int fieldtab_search(struct fieldtab *ft, struct field_value_list **l, + const char *s) +{ + struct field_item *p; + unsigned int k = fieldtab_hash(s, ft->size); + + for (p = ft->data[k]; p != NULL; p = p->next) { + if (!strcmp(p->id, s)) { + *l = &(p->fval_list); + return 1; + } + } + + return 0; +} + +/* + * Clear the table + */ +void fieldtab_clear(struct fieldtab *ft) +{ + unsigned int i; + struct field_item *p; + struct field_value *v; + + for (i = 0; i < ft->size; i++) { + while (ft->data[i]) { + p = ft->data[i]; + while(p->fval_list.first_val) { + v = p->fval_list.first_val; + p->fval_list.first_val = v->next_val; + if (v->value->type == T_STR) + free(v->value->s); + free(v->value); + free(v); + } + ft->data[i] = p->next; + free(p->id); + free(p); + } + } +} + +/* + * Hash function + */ +static unsigned int fieldtab_hash(const char *s, unsigned int n) +{ + unsigned int h = 0; + unsigned char *p; + + for(p = (unsigned char *)s; *p != '\0'; p++) + h = 31 * h + *p; + + return h % n; +} + diff --git a/trunk/audisp/plugins/reactive/reactive-fieldtab.h b/trunk/audisp/plugins/reactive/reactive-fieldtab.h new file mode 100644 index 0000000..c7c76ff --- /dev/null +++ b/trunk/audisp/plugins/reactive/reactive-fieldtab.h @@ -0,0 +1,71 @@ + +/* reactive-fieldtab.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 + */ + +#ifndef REACTIVE_FIELDTAB_H +#define REACTIVE_FIELDTAB_H + +#include "reactive-astvalue.h" + + +struct field_value +{ + struct ast_value *value; + struct field_value *next_val; +}; + +struct field_value_list +{ + unsigned int val_count; + /* if there are multiple values per one field, + the pointer selects one of the values to be used when + reaction action is triggered. */ + struct field_value *which; + struct field_value *first_val; +}; + +struct field_item +{ + char *id; /* field name */ + struct field_value_list fval_list; /* values */ + struct field_item *next; +}; + +struct fieldtab +{ + unsigned int size; + struct field_item **data; +}; + + +struct fieldtab *fieldtab_init(const unsigned int size); + +void fieldtab_destroy(struct fieldtab *ft); + +int fieldtab_insert(struct fieldtab *ft, char *name, struct ast_value *value); + +int fieldtab_search(struct fieldtab *ft, struct field_value_list **l, + const char *s); + +void fieldtab_clear(struct fieldtab *ft); + +#endif + diff --git a/trunk/audisp/plugins/reactive/reactive-parser.y b/trunk/audisp/plugins/reactive/reactive-parser.y new file mode 100644 index 0000000..095cb24 --- /dev/null +++ b/trunk/audisp/plugins/reactive/reactive-parser.y @@ -0,0 +1,1089 @@ + +/* reactive-parser.y + * 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 + */ + +%{ +#include +#include +#include +#include "reactive-scanner.h" +#include "reactive-config.h" +#include "reactive-error.h" + +/* + * Flex function that return tokens + */ +extern int yylex(); + +static int get_field_type(const char *s); + +static int constant, reaction = 1; + +void yyerror(struct react_data *rd, const char *s) +{ + syslog(LOG_ERR, "Line: %d error: %s\n", scanner_get_lineno(), s); +} + +int yywrap(void) +{ + return 1; +} +%} + +/* + * terminals and non-terminals can be only of these types + */ +%union { + long num; + char *str; + struct ast *a; +} + +/* + * Parameter for the parse funciton + */ +%parse-param { struct react_data *rd } + +/* + * Terminal nodes + */ +%token VAR CONST IF ELSE NOT AND OR GET GETQ STATS +%token CMD_ADD CMD_DEL CMD_ADDW CMD_DELW CMD_EXEC +%token CMP_GT CMP_GE CMP_LT CMP_LE CMP_EQ CMP_NE +%token REACTION +%token NOW SEC MIN HOUR DAY WEEK MONTH +%token ERR +%token IDENTIFIER +%token NUMBER +%token STRING + +/* Non-terminal nodes */ +%type time_specifier time_interval +%type + parser_start + external_declaration + global_declaration + local_declaration + global_init_declarator_list + local_init_declarator_list + global_init_declarator + local_init_declarator + global_declarator + local_declarator + initializer + reaction_definition + block_item_list + block_item + statement + reaction_statement + compound_statement + selection_statement + expression_statement + command_statement + command + command_parameters + expression + reaction_expression + assignment_expression + variable_or_constant + conditional_expression + logical_or_expression + logical_and_expression + equality_expression + relational_expression + additive_expression + multiplicative_expression + unary_expression + primary_expression + unary_operator + condition_conditional_expression + condition_unary_expression + condition_primary_expression + condition_unary_operator + field_get + field_get_quote + stats_get + condition_logical_or_expression + condition_logical_and_expression + condition_equality_expression + condition_relational_expression + time_period + +%expect 1 /* 'if' 'else' conflict */ + +%start parser_start + +%% + +parser_start + : external_declaration + { + /* root of the whole configuration */ + rd->root = $1; + $$ = $1; + } + | parser_start external_declaration + { + struct ast *node = ast_node(AST_LIST, $1, $2, + scanner_get_lineno()); + if (!node) + return 1; + + rd->root = node; + $$ = node; + } + ; + +external_declaration + : global_declaration + { + $$ = $1; + } + | reaction_definition + { + reaction++; + $$ = $1; + } + ; + +global_declaration + : declaration_specifier global_init_declarator_list ';' + { + $$ = $2; + } + ; + +declaration_specifier + : VAR + { + constant = 0; + } + | CONST + { + constant = 1; + } + ; + +global_init_declarator_list + : global_init_declarator + { + $$ = $1; + } + | global_init_declarator_list ',' global_init_declarator + { + struct ast *node = ast_node(AST_LIST, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + + +global_init_declarator + : global_declarator '=' initializer + { + struct ast *node = ast_assign($1, $3, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +global_declarator + : IDENTIFIER + { + struct ast *node; + struct symbol *sym; + /* store global symbol */ + int rc = symtab_insert(rd->stab, &sym, $1, 0, constant); + if (rc > 0) + return -ERR_SYMTAB_REDECLAR; + else if (rc < 0) + return -ERR_SYMTAB_MEM; + + node = ast_ref(sym, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +initializer + : assignment_expression + { + $$ = $1; + } + ; + +reaction_definition + : reaction_statement compound_statement + { + int rc; + /* new reaction */ + struct ast *node = ast_react($1, $2, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + /* reaction condition and reaction action */ + rc = reactarr_insert(rd->rarr, $1, $2); + if (rc) + return -ERR_REACTARR_MEM; + + $$ = node; + } + ; + +compound_statement + : '{' '}' + { + $$ = NULL; + } + | '{' block_item_list '}' + { + $$ = $2; + } + ; + +block_item_list + : block_item + { + $$ = $1; + } + | block_item_list block_item + { + struct ast *node = ast_node(AST_LIST, $1, $2, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +block_item + : local_declaration + { + $$ = $1; + } + | statement + { + $$ = $1; + } + ; + +local_declaration + : declaration_specifier local_init_declarator_list ';' + { + $$ = $2; + } + ; + +local_init_declarator_list + : local_init_declarator + { + $$ = $1; + } + | local_init_declarator_list ',' local_init_declarator + { + struct ast *node = ast_node(AST_LIST, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + + +local_init_declarator + : local_declarator '=' initializer + { + /* assignment */ + struct ast *node = ast_assign($1, $3, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +local_declarator + : IDENTIFIER + { + struct ast *node; + struct symbol *sym; + /* store local symbol */ + int rc = symtab_insert(rd->stab, &sym, $1, reaction, constant); + if (rc > 0) + return -ERR_SYMTAB_REDECLAR; + else if (rc < 0) + return -ERR_SYMTAB_MEM; + /* reference */ + node = ast_ref(sym, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +statement + : compound_statement + { + $$ = $1; + } + | selection_statement + { + $$ = $1; + } + | expression_statement + { + $$ = $1; + } + | command_statement + { + $$ = $1; + } + ; + +selection_statement + : IF '(' expression ')' statement + { + /* "if-then" statement */ + struct ast *node = ast_flow(AST_STMT_IF, $3, $5, NULL, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + | IF '(' expression ')' statement ELSE statement + { + /*if-then-else statement */ + struct ast *node = ast_flow(AST_STMT_IF, $3, $5, $7, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +expression_statement + : ';' + { + $$ = NULL; + } + | expression ';' + { + $$ = $1; + } + ; + +command_statement + : command command_parameters ';' + { + struct ast *node = ast_cmd($1, $2, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +command + : CMD_ADD + { + struct ast *node = ast_type(AST_CMD_ADD, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + | CMD_DEL + { + struct ast *node = ast_type(AST_CMD_DEL, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + | CMD_ADDW + { + struct ast *node = ast_type(AST_CMD_ADDW, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + | CMD_DELW + { + struct ast *node = ast_type(AST_CMD_DELW, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + | CMD_EXEC + { + struct ast *node = ast_type(AST_CMD_EXEC, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +command_parameters + : assignment_expression + { + $$ = $1; + } + ; + +expression + : assignment_expression + { + $$ = $1; + } + | expression ',' assignment_expression + { + struct ast *node = ast_node(AST_LIST, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +assignment_expression + : conditional_expression + { + $$ = $1; + } + | variable_or_constant '=' assignment_expression + { + struct ast *node = ast_assign($1, $3, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +variable_or_constant + : IDENTIFIER + { + struct ast *node; + struct symbol *sym = NULL; + if (!symtab_search(rd->stab, &sym, $1, reaction)) + return -ERR_SYMTAB_UNDECLAR; + + free($1); + node = ast_ref(sym, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + + +conditional_expression + : logical_or_expression + { + $$ = $1; + } + ; + +logical_or_expression + : logical_and_expression + { + $$ = $1; + } + | logical_or_expression OR logical_and_expression + { + struct ast *node = ast_node(AST_OP_OR, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +logical_and_expression + : equality_expression + { + $$ = $1; + } + | logical_and_expression AND equality_expression + { + struct ast *node = ast_node(AST_OP_AND, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +equality_expression + : relational_expression + { + $$ = $1; + } + | equality_expression CMP_EQ relational_expression + { + struct ast *node = ast_node(AST_CMP_EQ, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + | equality_expression CMP_NE relational_expression + { + struct ast *node = ast_node(AST_CMP_NE, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +relational_expression + : additive_expression + { + $$ = $1; + } + | relational_expression CMP_LT additive_expression + { + struct ast *node = ast_node(AST_CMP_LT, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + | relational_expression CMP_LE additive_expression + { + struct ast *node = ast_node(AST_CMP_LE, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + + } + | relational_expression CMP_GT additive_expression + { + struct ast *node = ast_node(AST_CMP_GT, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + + } + | relational_expression CMP_GE additive_expression + { + struct ast *node = ast_node(AST_CMP_GE, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + + } + ; + +additive_expression + : multiplicative_expression + { + $$ = $1; + } + | additive_expression '+' multiplicative_expression + { + struct ast *node = ast_node(AST_OP_ADD, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + | additive_expression '-' multiplicative_expression + { + struct ast *node = ast_node(AST_OP_SUB, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + + multiplicative_expression + : unary_expression + { + $$ = $1; + } + | multiplicative_expression '*' unary_expression + { + struct ast *node = ast_node(AST_OP_MUL, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + | multiplicative_expression '/' unary_expression + { + struct ast *node = ast_node(AST_OP_DIV, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + | multiplicative_expression '%' unary_expression + { + struct ast *node = ast_node(AST_OP_MOD, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +unary_expression + : primary_expression + { + $$ = $1; + } + | unary_operator primary_expression + { + struct ast *node = ast_node(AST_OP_UNARY, $2, $1, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +primary_expression + : variable_or_constant + { + $$ = $1; + } + | NUMBER + { + struct ast *node = ast_number($1, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + | STRING + { + struct ast *node = ast_string($1, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + | '(' expression ')' + { + $$ = $2; + } + | field_get + { + $$ = $1; + } + | field_get_quote + { + $$ = $1; + } + | stats_get + { + $$ = $1; + } + ; + +unary_operator + : '-' + { + struct ast *node = ast_type(AST_OP_UMINUS, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + | NOT + { + struct ast *node = ast_type(AST_OP_NEG, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +reaction_statement + : REACTION reaction_expression + { + $$ = $2; + } + ; + +reaction_expression + : condition_conditional_expression + { + $$ = $1; + } + ; + +condition_conditional_expression + : condition_logical_or_expression + { + $$ = $1; + } + ; + +condition_logical_or_expression + : condition_logical_and_expression + { + $$ = $1; + } + | condition_logical_or_expression OR condition_logical_and_expression + { + struct ast *node = ast_node(AST_OP_OR, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +condition_logical_and_expression + : condition_equality_expression + { + $$ = $1; + } + | condition_logical_and_expression AND condition_equality_expression + { + struct ast *node = ast_node(AST_OP_AND, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +condition_equality_expression + : condition_relational_expression + { + $$ = $1; + } + | condition_equality_expression CMP_EQ condition_relational_expression + { + struct ast *node = ast_node(AST_CMP_EQ, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + | condition_equality_expression CMP_NE condition_relational_expression + { + struct ast *node = ast_node(AST_CMP_NE, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +condition_relational_expression + : condition_unary_expression + { + $$ = $1; + } + | condition_relational_expression CMP_LT condition_unary_expression + { + struct ast *node = ast_node(AST_CMP_LT, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + | condition_relational_expression CMP_LE condition_unary_expression + { + struct ast *node = ast_node(AST_CMP_LE, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + | condition_relational_expression CMP_GT condition_unary_expression + { + struct ast *node = ast_node(AST_CMP_GT, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + | condition_relational_expression CMP_GE condition_unary_expression + { + struct ast *node = ast_node(AST_CMP_GE, $1, $3, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +condition_unary_expression + : condition_primary_expression + { + $$ = $1; + } + | condition_unary_operator condition_primary_expression + { + struct ast *node = ast_node(AST_OP_UNARY, $2, $1, + scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +condition_unary_operator + : NOT + { + struct ast *node = ast_type(AST_OP_NEG, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +condition_primary_expression + : IDENTIFIER + { + struct ast *node; + struct symbol *sym = NULL; + /* only global symbols allowed */ + if (!symtab_search(rd->stab, &sym, $1, 0)) + return -ERR_SYMTAB_UNDECLAR; + + free($1); + node = ast_ref(sym, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + | NUMBER + { + struct ast *node = ast_number($1, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + | STRING + { + struct ast *node = ast_string($1, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + | field_get + { + $$ = $1; + } + | '(' reaction_expression ')' + { + $$ = $2; + } + +field_get + : GET '(' IDENTIFIER ')' + { + struct ast *node; + int type = get_field_type($3); + node = ast_field($3, 0, type, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +field_get_quote + : GETQ '(' IDENTIFIER ')' + { + struct ast *node; + int type = get_field_type($3); + node = ast_field($3, 1, type, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +stats_get + : STATS '(' assignment_expression ',' time_period ')' + { + struct ast *node; + node = ast_stats($3, $5, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +time_period + : time_specifier ',' time_specifier + { + struct ast *node; + node = ast_period($1, $3, scanner_get_lineno()); + if (!node) + return -ERR_AST_MEM; + + $$ = node; + } + ; + +time_specifier + : NUMBER time_interval + { + $$ = $1 * $2; + } + | NOW + { + $$ = 0; + } + ; + +time_interval + : SEC + { + $$ = 1; + } + | MIN + { + $$ = 60; + } + | HOUR + { + $$ = 3600; + } + | DAY + { + $$ = 86400; + } + | WEEK + { + $$ = 604800; + } + | MONTH /* 30 days */ + { + $$ = 18144000; + } + ; + +%% + +/* + * http://people.redhat.com/sgrubb/audit/audit-parse.txt + */ +static int get_field_type(const char *s) +{ + /* numbers */ + if (!strcmp(s, "arch") || + !strcmp(s, "audit_backlog_limit") || + !strcmp(s, "audit_enabled") || + !strcmp(s, "audit_failure") || + !strcmp(s, "auid") || + /* TODO: "dev" */ + !strcmp(s, "egid") || + !strcmp(s, "euid") || + !strcmp(s, "exit") || + !strcmp(s, "flags") || + !strcmp(s, "fsgid") || + !strcmp(s, "fsuid") || + !strcmp(s, "gid") || + !strcmp(s, "id") || + !strcmp(s, "inode") || + !strcmp(s, "inode_gid") || + !strcmp(s, "inode_uid") || + !strcmp(s, "item") || + !strcmp(s, "items") || + !strcmp(s, "list") || + !strcmp(s, "mode") || + !strcmp(s, "nargs") || + !strcmp(s, "ogid") || + !strcmp(s, "old") || + !strcmp(s, "old_prom") || + !strcmp(s, "ouid") || + !strcmp(s, "parent") || + !strcmp(s, "perm") || + !strcmp(s, "perm_mask") || + !strcmp(s, "pid") || + !strcmp(s, "prom") || + !strcmp(s, "qbytes") || + !strcmp(s, "rdev") || + !strcmp(s, "sauid") || + !strcmp(s, "sgid") || + !strcmp(s, "spid") || + !strcmp(s, "suid") || + !strcmp(s, "syscall") || + !strcmp(s, "uid") || + !strcmp(s, "ver")) { + return T_NUM; + /* the rest -> strings */ + } else { + return T_STR; + } +} + diff --git a/trunk/audisp/plugins/reactive/reactive-reactarr.c b/trunk/audisp/plugins/reactive/reactive-reactarr.c new file mode 100644 index 0000000..86a5d13 --- /dev/null +++ b/trunk/audisp/plugins/reactive/reactive-reactarr.c @@ -0,0 +1,106 @@ + +/* reactive-reactarr.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 + */ + +#include +#include "reactive-reactarr.h" + +static int reactarr_realloc(struct reactarr *ra); + +/* + * Init array for storing reaction conditions and actions + */ +struct reactarr *reactarr_init(const unsigned int size) +{ + struct reactarr *rarr = malloc(sizeof(struct reactarr)); + if (!rarr) + return NULL; + + rarr->size = size; + rarr->count = 0; + rarr->data = malloc(size * sizeof(struct reaction *)); + if (!(rarr->data)) { + free(rarr); + return NULL; + } + + return rarr; +} + +/* + * Free the array + */ +void reactarr_free(struct reactarr *ra) +{ + unsigned int i; + + if (!ra) + return; + if (!(ra->data)) { + free(ra); + return; + } + for (i = 0; i < ra->count; i++) + free(ra->data[i]); + free(ra->data); + free(ra); +} + +/* + * Insert a new condition which is tied with its action + */ +int reactarr_insert(struct reactarr *ra, struct ast *cond, struct ast *action) +{ + unsigned int i = ra->count; + struct reaction *r; + + if (i == ra->size) { + if (reactarr_realloc(ra)) + return -1; + } + r = malloc(sizeof(struct reaction)); + if (!r) + return -1; + + r->cond = cond; + r->action = action; + ra->data[i] = r; + ra->count++; + + return 0; +} + +/* + * Realloc array + */ +static int reactarr_realloc(struct reactarr *ra) +{ + unsigned int new_size = ra->size + ALLOC_SIZE_REACTARR; + ra->data = realloc(ra->data, new_size * sizeof(struct reaction *)); + if (!ra->data) { + free(ra); + return -1; + } + ra->size = new_size; + + return 0; +} + diff --git a/trunk/audisp/plugins/reactive/reactive-reactarr.h b/trunk/audisp/plugins/reactive/reactive-reactarr.h new file mode 100644 index 0000000..980aadd --- /dev/null +++ b/trunk/audisp/plugins/reactive/reactive-reactarr.h @@ -0,0 +1,58 @@ + +/* reactive-reactarr.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 + */ + +#ifndef REACTIVE_REACTARR_H +#define REACTIVE_REACTARR_H + +#include "reactive-ast.h" + +#define ALLOC_SIZE_REACTARR 8 + +/* + * Every reaction definition can be split into + * two ASTs: condition and action + */ +struct reaction +{ + struct ast *cond; + struct ast *action; +}; + +/* + * Reaction array stores conditions and ations of every reaction + */ +struct reactarr +{ + unsigned int size; + unsigned int count; + struct reaction **data; +}; + + +struct reactarr *reactarr_init(const unsigned int size); + +void reactarr_free(struct reactarr *ra); + +int reactarr_insert(struct reactarr *ra, struct ast *cond, struct ast *action); + +#endif + diff --git a/trunk/audisp/plugins/reactive/reactive-scanner.h b/trunk/audisp/plugins/reactive/reactive-scanner.h new file mode 100644 index 0000000..0849697 --- /dev/null +++ b/trunk/audisp/plugins/reactive/reactive-scanner.h @@ -0,0 +1,32 @@ + +/* reactive-scanner.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 + */ + +#ifndef REACTIVE_SCANNER_H +#define REACTIVE_SCANNER_H + +#include + +void scanner_set_input(FILE *f); + +int scanner_get_lineno(void); + +#endif diff --git a/trunk/audisp/plugins/reactive/reactive-scanner.l b/trunk/audisp/plugins/reactive/reactive-scanner.l new file mode 100644 index 0000000..925278e --- /dev/null +++ b/trunk/audisp/plugins/reactive/reactive-scanner.l @@ -0,0 +1,185 @@ + +/* reactive-scanner.l + * 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 + */ + +%option noyywrap nodefault yylineno + +%{ +#include +#include +#include +#include "reactive-scanner.h" +#include "reactive-parser.h" + +#define BASE_10 10 + +extern void yyerror(const char *s); + +enum { + ERR_LEX_MEM, + ERR_LEX_NUM, + ERR_LEX_CHAR +}; + +static const char *lex_error[] = +{ + "out of memory allocating string", + "couldn't convert string to number", + "unrecognized character" +}; + +static int get_number(const char *s, long *r); + +static int get_string(const char *s, char **r, const int t); +%} + +%x COMMENT + +%% + +"+" | +"-" | +"*" | +"/" | +"%" | +"{" | +"}" | +"(" | +")" | +"=" | +";" | +"," { return yytext[0]; } + +"#" { BEGIN(COMMENT); } +[^\n]* { /* ignore */ } +\n { BEGIN(INITIAL); } + +"!" { return NOT; } +"&&" { return AND; } +"||" { return OR; } + +"<" { return CMP_LT; } +"<=" { return CMP_LE; } +">" { return CMP_GT; } +">=" { return CMP_GE; } +"==" { return CMP_EQ; } +"!=" { return CMP_NE; } + +"var" { return VAR; } +"const" { return CONST; } +"if" { return IF; } +"else" { return ELSE; } +"get" { return GET; } +"getq" { return GETQ; } +"stats" { return STATS; } +"react:" { return REACTION; } + +"add" { return CMD_ADD; } +"del" { return CMD_DEL; } +"addw" { return CMD_ADDW; } +"delw" { return CMD_DELW; } +"exec" { return CMD_EXEC; } + +"now" { return NOW; } +"sec" { return SEC; } +"min" { return MIN; } +"hour" { return HOUR; } +"day" { return DAY; } +"week" { return WEEK; } +"month" { return MONTH; } + +[a-zA-Z_][a-zA-Z0-9_]* { if (get_string(yytext, &yylval.str, 0)) { + yyerror(lex_error[ERR_LEX_MEM]); + return ERR; + } + return IDENTIFIER; + } +\"(\\.|[^"])*\" { if (get_string(yytext, &yylval.str, 1)) { + yyerror(lex_error[ERR_LEX_MEM]); + return ERR; + } + return STRING; } +[0-9]+ { if (get_number(yytext, &yylval.num)) { + yyerror(lex_error[ERR_LEX_NUM]); + return ERR; + } + return NUMBER; + } +[ \t\n] { /* ignore */ } +. { yyerror(lex_error[ERR_LEX_CHAR]); + return ERR; + } + +%% + +/* + * Set input file + */ +void scanner_set_input(FILE *f) +{ + yyin = f; +} + +/* + * Get current line + */ +int scanner_get_lineno(void) +{ + return yylineno; +} + +/* + * Convert string to number + */ +static int get_number(const char *s, long *r) +{ + errno = 0; + *r = strtol(s, NULL, BASE_10); + + if (errno) + return 1; + + return 0; +} + +/* + * Get string value + */ +static int get_string(const char *s, char **r, const int t) +{ + if (t) { + /* " are removed (-2) and (+1) for '\0' */ + int len = strlen(s) - 1; + *r = calloc(len, sizeof(char)); + if (!(*r)) + return 1; + /* skip the first and the last " character */ + strncpy(*r, s + 1, len - 1); + /* string not between " characters - identifier */ + } else { + *r = strdup(s); + if (!(*r)) + return 1; + } + + return 0; +} + diff --git a/trunk/audisp/plugins/reactive/reactive-symtab.c b/trunk/audisp/plugins/reactive/reactive-symtab.c new file mode 100644 index 0000000..e7a6f15 --- /dev/null +++ b/trunk/audisp/plugins/reactive/reactive-symtab.c @@ -0,0 +1,155 @@ + +/* reactive-symtab.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 + */ + +#include +#include +#include "reactive-symtab.h" + +static unsigned int symtab_hash(const char *s, unsigned int n); + +static void symtab_clear(struct symtab *st); + +/* + * Init hash table for storing symbols + */ +struct symtab *symtab_init(const unsigned int size) +{ + struct symtab *st = malloc(sizeof(struct symtab)); + if(!st) + return NULL; + + st->size = size; + st->data = calloc(size, sizeof(struct symbol_item *)); + if(!(st->data)) { + free(st); + return NULL; + } + + return st; +} + +/* + * Free hash table + */ +void symtab_free(struct symtab *st) +{ + symtab_clear(st); + free(st->data); + free(st); +} + +/* + * Find symbol in the table, prefer local symbol to global + * if there are symbol with the same name + */ +int symtab_search(struct symtab *st, struct symbol **x, const char *s, + const int t) +{ + struct symbol_item *p; + unsigned int k = symtab_hash(s, st->size); + + for(p = st->data[k]; p != NULL; p = p->next) { + if (!strcmp(p->sym.id, s)) { + /* preffered (local) variable */ + if (p->sym.type == t) { + *x = &(p->sym); + return 1; + /* global variable */ + } else if (!p->sym.type) { + *x = &(p->sym); + continue; + } + } + } + if (*x) + return 1; + + return 0; +} + +/* + * Insert symbol into the table, global symbol has t == 0 + */ +int symtab_insert(struct symtab *st, struct symbol **x, char *s, + const int t, const int c) +{ + struct symbol_item *p; + unsigned int k = symtab_hash(s, st->size); + + for(p = st->data[k]; p != NULL; p = p->next) { + /* symbol already declared */ + if (!strcmp(p->sym.id, s) && p->sym.type == t) + return 1; + } + p = malloc(sizeof(struct symbol_item)); + if (!p) { + symtab_free(st); + return -1; + } + p->sym.type = t; + p->sym.id = s; + p->sym.constant = c; + p->sym.value.type = T_UNDEF; + p->sym.value.n = 0; + p->sym.value.s = NULL; + p->next = st->data[k]; + st->data[k] = p; + *x = &(p->sym); + + return 0; +} + +/* + * Hash function for converting string to index + */ +static unsigned int symtab_hash(const char *s, unsigned int n) +{ + unsigned int h = 0; + unsigned char *p; + + for(p = (unsigned char *)s; *p != '\0'; p++) + h = 31 * h + *p; + + return h % n; +} + +/* + * Free table items + */ +static void symtab_clear(struct symtab *st) +{ + unsigned int i; + struct symbol_item *p; + + for(i = 0; i < st->size; i++) { + while(st->data[i]) { + p = st->data[i]; + st->data[i] = st->data[i]->next; + /* symbol names are freed here, not in AST */ + if (p->sym.value.type == T_STR) + free(p->sym.value.s); + free(p->sym.id); + free(p); + } + } +} + diff --git a/trunk/audisp/plugins/reactive/reactive-symtab.h b/trunk/audisp/plugins/reactive/reactive-symtab.h new file mode 100644 index 0000000..36fb13e --- /dev/null +++ b/trunk/audisp/plugins/reactive/reactive-symtab.h @@ -0,0 +1,64 @@ + +/* reactive-symtab.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 + */ + +#ifndef REACTIVE_SYMTAB_H +#define REACTIVE_SYMTAB_H + +#include "reactive-astvalue.h" + +/* + * symbols are stored in symbol table (hash table) + * references to symbols are stored in AST + */ +struct symbol +{ + int type; /* global/local */ + char *id; /* symbol identifier */ + int constant; /* constant/variable */ + struct ast_value value; /* value used in AST */ +}; + +struct symbol_item +{ + struct symbol sym; + struct symbol_item *next; +}; + +struct symtab +{ + unsigned int size; + struct symbol_item **data; +}; + + +struct symtab *symtab_init(unsigned int s); + +void symtab_free(struct symtab *st); + +int symtab_search(struct symtab *st, struct symbol **x, const char *s, + const int t); + +int symtab_insert(struct symtab *st, struct symbol **x, char *s, + const int t, const int c); + +#endif + diff --git a/trunk/audisp/plugins/stats/Makefile.am b/trunk/audisp/plugins/stats/Makefile.am new file mode 100644 index 0000000..57f2ff9 --- /dev/null +++ b/trunk/audisp/plugins/stats/Makefile.am @@ -0,0 +1,39 @@ + +CONFIG_CLEAN_FILES = *.rej *.orig +EXTRA_DIST = au-stats.conf audisp-stats.conf +AUTOMAKE_OPTIONS = no-dependencies +INCLUDES = -I${top_srcdir} -I${top_srcdir}/lib -I${top_srcdir}/auparse +LDADD = ${top_builddir}/auparse/libauparse.la +prog_confdir = $(sysconfdir)/audisp +prog_conf = audisp-stats.conf +plugin_confdir=$(prog_confdir)/plugins.d +plugin_conf = au-stats.conf +sbin_PROGRAMS = audisp-stats audisp-stats-clear +noinst_HEADERS = stats-config.h stats-fieldtab.h stats-sql.h \ + stats-fieldarr.h stats-eventarr.h +#dist_man_MANS = + +noinst_LTLIBRARIES = libstats_sql.la +libstats_sql_la_SOURCES = stats-sql.c +libstats_sql_la_LIBADD = -lsqlite3 + +audisp_stats_SOURCES = audisp-stats.c stats-config.c stats-fieldtab.c \ + stats-fieldarr.c stats-eventarr.c +audisp_stats_CFLAGS = -fPIE -DPIE -g -D_REENTRANT -D_GNU_SOURCE -Wundef +audisp_stats_LDFLAGS = -pie -Wl,-z,relro +audisp_stats_LDADD = libstats_sql.la $(LDADD) + +audisp_stats_clear_SOURCER = audisp-stats-clear.c +audisp_stats_clear_CFLAGS = -D_GNU_SOURCE -Wundef +audisp_stats_clear_LDADD = -lsqlite3 $(LDADD) + +install-data-hook: + mkdir -p -m 0750 ${DESTDIR}${plugin_confdir} + mkdir -p -m 0750 ${DESTDIR}${localstatedir}/lib/audisp-stats + $(INSTALL_DATA) -D -m 640 ${srcdir}/$(plugin_conf) ${DESTDIR}${plugin_confdir} + $(INSTALL_DATA) -D -m 640 ${srcdir}/$(prog_conf) ${DESTDIR}${prog_confdir} + +uninstall-hook: + rm ${DESTDIR}${plugin_confdir}/$(plugin_conf) + rm ${DESTDIR}${prog_confdir}/$(prog_conf) + diff --git a/trunk/audisp/plugins/stats/au-stats.conf b/trunk/audisp/plugins/stats/au-stats.conf new file mode 100644 index 0000000..241e05c --- /dev/null +++ b/trunk/audisp/plugins/stats/au-stats.conf @@ -0,0 +1,8 @@ + +active = yes +direction = out +path = /sbin/audisp-stats +type = always +#args = +format = string + diff --git a/trunk/audisp/plugins/stats/audisp-stats-clear.c b/trunk/audisp/plugins/stats/audisp-stats-clear.c new file mode 100644 index 0000000..22dbeba --- /dev/null +++ b/trunk/audisp/plugins/stats/audisp-stats-clear.c @@ -0,0 +1,208 @@ + +/* audisp-stats-clear.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 + */ + +#include +#include +#include +#include + +#define DB_FILE "/var/lib/audisp-stats/stats.db" + +#define TIME_NOW "now" +#define TIME_SEC "sec" +#define TIME_MIN "min" +#define TIME_HOUR "hour" +#define TIME_DAY "day" +#define TIME_WEEK "week" +#define TIME_MONTH "month" + +#define QUERY_TRANSACTION_BEGIN "BEGIN TRANSACTION" +#define QUERY_TRANSACTION_COMMIT "COMMIT" +#define QUERY_TRANSACTION_ROLLBACK "ROLLBACK" +#define QUERY_TIMESTAMP "DELETE FROM timestamp WHERE time <= ?" +#define QUERY_FIELD "DELETE FROM field WHERE pk NOT IN (SELECT fk FROM timestamp)" +#define QUERY_KEYFIELD "DELETE FROM keyfield WHERE fk NOT IN (SELECT pk FROM field)" + + +static int sql_exec(sqlite3 *conn, const char *q); + +static int get_param(int n, char *s[]); + +static void usage(void); + +/* + * Main + */ +int main(int argc, char *argv[]) +{ + int time1, time2; + sqlite3 *conn; + sqlite3_stmt *stmt; + + time1 = (int)time(NULL); + + time2 = get_param(argc, argv); + if (time2 < 0) { + usage(); + return 1; + } + + if (sqlite3_open(DB_FILE, &conn)) { + fprintf(stderr, "Error opening database\n"); + return 1; + } + if (sqlite3_busy_timeout(conn, 10000)) { + fprintf(stderr, "Busy timeout error\n"); + return 1; + } + /* transaction begin */ + if (sql_exec(conn, QUERY_TRANSACTION_BEGIN)) { + fprintf(stderr, "Error executing query (transaction begin)\n"); + sqlite3_close(conn); + return 1; + } + /* delete timestamps */ + if (sqlite3_prepare(conn, QUERY_TIMESTAMP, -1, &stmt, NULL)) { + fprintf(stderr, "Error preparing statement (%s)\n", sqlite3_errmsg(conn)); + sql_exec(conn, QUERY_TRANSACTION_ROLLBACK); + sqlite3_close(conn); + return 1; + } + /* bind time */ + if (sqlite3_bind_int(stmt, 1, time1 - time2)) { + fprintf(stderr, "Error binding parameter\n"); + sql_exec(conn, QUERY_TRANSACTION_ROLLBACK); + sqlite3_close(conn); + return 1; + } + sqlite3_step(stmt); + if (sqlite3_finalize(stmt)) { + fprintf(stderr, "Error executing query (delete timestamp)"); + sql_exec(conn, QUERY_TRANSACTION_ROLLBACK); + sqlite3_close(conn); + return 1; + } + /* delete fields */ + if (sql_exec(conn, QUERY_FIELD)) { + fprintf(stderr, "Error executing query (delete field)"); + sql_exec(conn, QUERY_TRANSACTION_ROLLBACK); + sqlite3_close(conn); + return 1; + } + /* delete keys */ + if (sql_exec(conn, QUERY_KEYFIELD)) { + fprintf(stderr, "Error executing query (delete keyfield)"); + sql_exec(conn, QUERY_TRANSACTION_ROLLBACK); + sqlite3_close(conn); + return 1; + } + /* commit */ + if (sql_exec(conn, QUERY_TRANSACTION_COMMIT)) { + fprintf(stderr, "Error executing query (commit)"); + sql_exec(conn, QUERY_TRANSACTION_ROLLBACK); + sqlite3_close(conn); + return 1; + } + if (sqlite3_close(conn)) { + fprintf(stderr, "Error closing database\n"); + return 1; + } + + return 0; +} + +/* + * Get time interval from parameters + */ +static int get_param(int n, char *s[]) +{ + int num; + + if (n == 2) { + if (!strcmp(s[1], TIME_NOW)) { + return 0; + } else { + return -1; + } + } else if (n == 3) { + errno = 0; + char *end; + num = (int)strtol(s[1], &end, 10); + if (*end) { + return -1; + } + if (!strcmp(s[2], TIME_SEC)) { + return num * 1; + } else if (!strcmp(s[2], TIME_MIN)) { + return num * 60; + } else if (!strcmp(s[2], TIME_HOUR)) { + return num * 3600; + } else if (!strcmp(s[2], TIME_DAY)) { + return num * 86400; + } else if (!strcmp(s[2], TIME_WEEK)) { + return num * 604800; + } else if (!strcmp(s[2], TIME_MONTH)) { + return num * 18144000; + } else { + return -1; + } + } + + return -1; +} + +/* + * Print usage + */ +static void usage(void) +{ + fprintf(stderr, "Usage: audisp-stats-clear [[number time_keyword] | [now]]\n" + "number - number of \"time_keyword\"\n" + "time_keyword - express time period, possibilities:\n" + "\tsec\n" + "\tmin\n" + "\thour\n" + "\tday\n" + "\tweek\n" + "\tmonth\n"); +} + +/* + * Execute SQL query + */ +static int sql_exec(sqlite3 *conn, const char *q) +{ + sqlite3_stmt *stmt; + + if (!q || !conn) + return -2; + + if (sqlite3_prepare(conn, q, -1, &stmt, NULL)) + return -1; + + sqlite3_step(stmt); + if (sqlite3_finalize(stmt)) + return -1; + + return 0; +} + diff --git a/trunk/audisp/plugins/stats/audisp-stats.c b/trunk/audisp/plugins/stats/audisp-stats.c new file mode 100644 index 0000000..107c16e --- /dev/null +++ b/trunk/audisp/plugins/stats/audisp-stats.c @@ -0,0 +1,665 @@ + +/* audisp-stats.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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include "libaudit.h" +#include "auparse.h" +#include "stats-sql.h" + + +#define BUFLEN 1024 +#define FIELDTAB_SIZE 37 +#define CONFIG "/etc/audisp/audisp-stats.conf" + + +/* Global Data */ +static volatile sig_atomic_t stop = 0; +static volatile sig_atomic_t hup = 1; +static struct fieldtab *ft; +static struct config *conf; + +static void term_handler(int sig); + +static void hup_handler(int sig); + +static FILE *file_open(const char *s); + +static void file_close(FILE *f); + +static int check_stats_key(auparse_state_t *au); + +static void extract_first_record(auparse_state_t *au, struct fieldtab *ft); + +static void extract_apath(auparse_state_t *au, char **cwd, char **path); + +static void insert_apath(struct fieldtab *ft, const char *cwd, const char *path); + +static char *field_value(auparse_state_t *au, const char *field, int *err); + +static char *apath_value(const char *cwd, const char *path); + +static void field_replace_quote(char *s, const int len); + +static void handle_event(auparse_state_t *au, + auparse_cb_event_t cb_event_type, void *user_data); + +/* + * Main + */ +int main(int argc, char *argv[]) +{ + int rc; + FILE *f; + auparse_state_t *au = NULL; + struct sigaction sa; + int len; + char buf[MAX_AUDIT_MESSAGE_LENGTH + 1]; + fd_set rfds; + struct timeval tv; + + /* Register sighandlers */ + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + /* Set handler for the ones we care about */ + sa.sa_handler = term_handler; + sigaction(SIGTERM, &sa, NULL); + sa.sa_handler = hup_handler; + sigaction(SIGHUP, &sa, NULL); + + /* Initialize the auparse library */ + au = auparse_init(AUSOURCE_FEED, 0); + if (!au) { + syslog(LOG_ERR, "Exiting due to auparse init errors"); + return -1; + } + ft = fieldtab_init(FIELDTAB_SIZE); + if (!ft) { + syslog(LOG_ERR, "Exiting due to field table init errors"); + auparse_destroy(au); + return -1; + } + conf = config_init(); + if (!conf) { + syslog(LOG_ERR, "Exiting due to config init errors"); + auparse_destroy(au); + fieldtab_destroy(ft); + return -1; + } + auparse_add_callback(au, handle_event, NULL, NULL); + + do { + /* Load configuration */ + if (hup) { + sqlite3 *conn; + f = file_open(CONFIG); + if (!f) + break; + rc = config_read(conf, f); + if (rc) { + file_close(f); + break; + } + file_close(f); + + rc = sql_open(&conn, DB_FILE); + if (rc) { + syslog(LOG_ERR, "Error opening database"); + break; + } + rc = sql_init(conn, conf); + if (rc) { + syslog(LOG_ERR, "Exiting due to database init errors"); + sql_close(conn); + break; + } + rc = sql_close(conn); + if (rc) { + syslog(LOG_ERR, "Error closing database"); + break; + } + hup = 0; + } + FD_ZERO(&rfds); + FD_SET(0, &rfds); + tv.tv_sec = 1; + tv.tv_usec = 0; + rc = select(1, &rfds, NULL, NULL, &tv); + if (rc == -1) { + if (errno == EINTR) { + continue; + } else { + syslog(LOG_ERR, + "Error while monitoring input: %s. Aborting", + strerror(errno)); + stop = 1; + } + } else if (rc) { + len = read(0, buf, BUFLEN); + if (len > 0) { + auparse_feed(au, buf, len); + } else if (len == 0) { + stop = 1; + } else { + /* ignore interrupted call or empty pipe */ + if (errno != EINTR && errno != EAGAIN) { + syslog(LOG_ERR, + "Error while reading input: %s. Aborting", + strerror(errno)); + stop = 1; + } + } + } + } while (!stop); + + config_destroy(conf); + fieldtab_destroy(ft); + auparse_flush_feed(au); + auparse_destroy(au); + + return 0; +} + + +/* + * SIGTERM handler + */ +static void term_handler(int sig) +{ + stop = 1; +} + +/* + * SIGHUP handler: re-read config + */ +static void hup_handler(int sig) +{ + hup = 1; +} + +/* + * Open config file + */ +static FILE *file_open(const char *s) +{ + int fd, rc; + struct stat st; + FILE *f; + + /* open the file */ + rc = open(s, O_RDONLY|O_NOFOLLOW); + if (rc < 0) { + if (errno != ENOENT) { + syslog(LOG_ERR, "Error opening %s (%s)", s, + strerror(errno)); + return NULL; + } + syslog(LOG_WARNING, + "Config file %s doesn't exist, skipping", s); + return 0; + } + fd = rc; + + /* check the file's permissions: owned by root, not world writable */ + if (fstat(fd, &st) < 0) { + syslog(LOG_ERR, "Error fstat'ing config file (%s)", + strerror(errno)); + close(fd); + return NULL; + } + if (st.st_uid != 0) { + syslog(LOG_ERR, "Error - %s isn't owned by root", s); + close(fd); + return NULL; + } + if ((st.st_mode & S_IWOTH) == S_IWOTH) { + syslog(LOG_ERR, "Error - %s is world writable", s); + close(fd); + return NULL; + } + if (!S_ISREG(st.st_mode)) { + syslog(LOG_ERR, "Error - %s is not a regular file", s); + close(fd); + return NULL; + } + f = fdopen(fd, "r"); + if (!f) { + syslog(LOG_ERR, "Error - fdopen failed (%s)", strerror(errno)); + close(fd); + return NULL; + } + + return f; +} + +/* + * Close config file + */ +static void file_close(FILE *f) +{ + fclose(f); +} + + +/* + * Process received audit event + */ +static void handle_event(auparse_state_t *au, + auparse_cb_event_t cb_event_type, void *user_data) +{ + int event_type, save = 0, record = 0; + char *cwd = NULL, *path = NULL; + unsigned int time; + sqlite3 *conn; + const au_event_t *e = auparse_get_timestamp(au); + + if (!e) { + syslog(LOG_ERR, "Error getting timestamp"); + return; + } + + if (cb_event_type != AUPARSE_CB_EVENT_READY) + return; + + /* check the type of user space generated event */ + event_type = auparse_get_type(au); + + /* TODO */ + if (event_type == AUDIT_CONFIG_CHANGE) + return; + + if (event_type >= AUDIT_FIRST_USER_MSG && + event_type <= AUDIT_LAST_USER_MSG) { + if (config_search_eventtype(conf->earr, event_type)) + save = 1; + } + /* TODO: add support for other event types */ + + /* check the "stats" key field */ + if (!save && (check_stats_key(au) > 0)) + save = 1; + + /* user space event type does not match, no "stats" was found */ + if (!save) + return; + + /* timestamp */ + time = (unsigned int)e->sec; + + auparse_first_record(au); + auparse_first_field(au); + + while (auparse_goto_record_num(au, record) > 0) { + /* parse fields from the first record */ + if (record == 0) { + do { + extract_first_record(au, ft); + } while (auparse_next_field(au) > 0); + /* parse CWD and PATH records to get the absolute path */ + } else { + extract_apath(au, &cwd, &path); + } + record++; + } + insert_apath(ft, cwd, path); + free(cwd); + free(path); + + if (sql_open(&conn, DB_FILE)) { + syslog(LOG_ERR, "Error opening database\n"); + return; + } + + if (sql_insert(conn, conf, ft, time)) + syslog(LOG_ERR, "Error inserting data into database"); + + if (sql_close(conn)) + syslog(LOG_ERR, "Error closing database"); + + fieldtab_clear(ft); +} + +/* + * Check if "stats" key is present + */ +static int check_stats_key(auparse_state_t *au) +{ + if (auparse_find_field(au, "key")) { + if (!auparse_interpret_field(au)) { + syslog(LOG_ERR, "Error getting 'key' field"); + return -1; + } + if (!strcmp(auparse_interpret_field(au), "stats")) + return 1; + + while (auparse_find_field_next(au)) { + if (!auparse_interpret_field(au)) { + syslog(LOG_ERR, "Error getting 'key' field"); + return -1; + } + if (!strcmp(auparse_interpret_field(au), "stats")) + return 1; + } + } + + return 0; +} + +/* + * Parse and save data from the first record + */ +static void extract_first_record(auparse_state_t *au, struct fieldtab *ft) +{ + char *fname; + char *fvalue; + int error; + + do { + /* get field name */ + if (!auparse_get_field_name(au)) { + syslog(LOG_ERR, "Error getting field name"); + return; + } + fname = strdup(auparse_get_field_name(au)); + if (!fname) { + syslog(LOG_ERR, + "Out of memory allocating field name"); + return; + } + /* get field value */ + fvalue = field_value(au, fname, &error); + if (!fvalue) { + if (error) { + syslog(LOG_ERR, + "Error getting value of field %s", fname); + } + /* if not error, then key=(null) or key=stats */ + free(fname); + return; + } + if (fieldtab_insert(ft, fname, fvalue)) { + syslog(LOG_ERR, + "Error inserting values into field table"); + free(fname); + free(fvalue); + } + } while (auparse_next_field(au) > 0); +} + +/* + * Get absolute path + */ +static void extract_apath(auparse_state_t *au, char **cwd, char **path) +{ + int type; + char *path2; + + type = auparse_get_type(au); + /* CWD record */ + if (type == AUDIT_CWD && auparse_find_field(au, "cwd")) { + if (!auparse_interpret_field(au)) { + syslog(LOG_ERR, + "Error getting \"cwd\" field"); + } + if (!(*cwd)) { + *cwd = strdup(auparse_interpret_field(au)); + if (!(*cwd)) { + syslog(LOG_ERR, + "Error getting value of \"cwd\" field"); + return; + } + } + /* PATH record */ + } else if (type == AUDIT_PATH && auparse_find_field(au, "name")) { + if (!auparse_interpret_field(au)) { + syslog(LOG_ERR, + "Error getting \"name\" field"); + } + if (!(*path)) { + *path = strdup(auparse_interpret_field(au)); + if (!(*path)) { + syslog(LOG_ERR, + "Error getting value of \"name\" field"); + return; + } + } else { + /* get the longest path */ + path2 = strdup(auparse_interpret_field(au)); + if (!path2) { + syslog(LOG_ERR, + "Error getting value of \"name\" field"); + return; + } + /* path2 is not (null) */ + if (strcmp(path2, "(null)")) { + if (strlen(path2) > strlen(*path)) { + free(*path); + *path = path2; + } + } else { + free(path2); + } + } + } +} + +/* + * Insert absolute path into the field table + */ +static void insert_apath(struct fieldtab *ft, const char *cwd, const char *path) +{ + char *fname; + char *fvalue; + + fname = strdup("apath"); + if (!fname) { + syslog(LOG_ERR, "Out of memory allocating field name\n"); + return; + } + fvalue = apath_value(cwd, path); + if (!fvalue) { + free(fname); + return; + } + if (fieldtab_insert(ft, fname , fvalue)) { + syslog(LOG_ERR, + "Error inserting values into field table"); + free(fname); + free(fvalue); + return; + } +} + +/* + * Extra field value + */ +static char *field_value(auparse_state_t *au, const char *field, int *err) +{ + char *result; + char buf[STRLEN_INT + 2]; + int len; + int val_type = auparse_get_field_type(au); + + *err = 0; + + /* TODO: some fields are strings instead of numbers! */ + switch (val_type) { + case AUPARSE_TYPE_UNCLASSIFIED: /* 0 */ + /* number */ + if (!strcmp(field, "pid") || + !strcmp(field, "ppid") || + !strcmp(field, "audit_enabled") || + !strcmp(field, "old") || + !strcmp(field, "items")) { + sprintf(buf, "'%d'", auparse_get_field_int(au)); + result = strdup(buf); + if (!result) { + *err = 1; + return NULL; + } + } else { + /* string */ + if (!auparse_interpret_field(au)) { + *err = 1; + return NULL; + } + len = strlen(auparse_interpret_field(au)) + 3; + result = malloc(len * sizeof(char)); + if (!result) { + *err = 1; + return NULL; + } + sprintf(result, "'%s'", auparse_interpret_field(au)); + field_replace_quote(result, len); + } + break; + /* numbers */ + case AUPARSE_TYPE_UID: /* 1 */ + case AUPARSE_TYPE_GID: /* 2 */ + case AUPARSE_TYPE_SYSCALL: /* 3 */ + case AUPARSE_TYPE_ARCH: /* 4 */ + case AUPARSE_TYPE_EXIT: /* 5 */ + case AUPARSE_TYPE_PERM: /* 7 */ + case AUPARSE_TYPE_MODE: /* 8 */ + case AUPARSE_TYPE_SESSION: /* 20 */ + case AUPARSE_TYPE_FLAGS: /* 10 */ + case AUPARSE_TYPE_LIST: /* 18 */ + case AUPARSE_TYPE_A0: /* 14 */ + case AUPARSE_TYPE_A1: /* 15 */ + case AUPARSE_TYPE_A2: /* 16 */ + sprintf(buf, "'%d'", auparse_get_field_int(au)); + result = strdup(buf); + if (!result) { + *err = 1; + return NULL; + } + break; + /* strings */ + case AUPARSE_TYPE_ESCAPED: /* 6 (key, exe, comm) */ + case AUPARSE_TYPE_SUCCESS: /* 13 (ret=success) */ + /* */ + case AUPARSE_TYPE_SOCKADDR: /* 9 */ + case AUPARSE_TYPE_PROMISC: /* 11 */ + case AUPARSE_TYPE_CAPABILITY: /* 12 */ + case AUPARSE_TYPE_SIGNAL: /* 17 */ + case AUPARSE_TYPE_TTY_DATA: /* 19 */ + case AUPARSE_TYPE_CAP_BITMAP: /* 21 */ + /* */ + if (!auparse_interpret_field(au)) { + *err = 1; + return NULL; + } + len = strlen(auparse_interpret_field(au)) + 3; + result = malloc(len * sizeof(char)); + if (!result) { + *err = 1; + return NULL; + } + sprintf(result, "'%s'", auparse_interpret_field(au)); + field_replace_quote(result, len); + + if (!strcmp(field, "key") && + (!strcmp(result, "'(null)'") || + !strcmp(result, "'stats'"))) { + free(result); + return NULL; + } + break; + default: + break; + } + + return result; +} + + +/* + * Get absolute path from 'cwd' and 'path' values + */ +static char *apath_value(const char *cwd, const char *path) +{ + char *result; + int offset = 0; + int len; + + if (!path) + return NULL; + + while (path[offset] == '.') + offset++; + + if (!offset && path[0] == '/') { + /* realpath() causes generation of audit events + since it utilizes lstat() syscall -> can cause loop (failure) */ + /* result->s = realpath(path, NULL); */ + len = strlen(path) + 3; + result = malloc(len * sizeof(char)); + if (!result) + return NULL; + strcpy(result, "'"); + strcat(result, path); + strcat(result, "'"); + field_replace_quote(result, len); + + return result; + } + + if (!cwd) + return NULL; + + len = strlen(cwd) + strlen(path) - offset + 3; + result = malloc(len * sizeof(char)); + if (!result) + return NULL; + + strcpy(result, "'"); + strcat(result, cwd); + strcat(result, path + offset); + strcat(result, "'"); + + field_replace_quote(result, len); + +/* + result->s = realpath(tmp_path, NULL); + free(tmp_path); +*/ + return result; +} + +/* + * Replace "'" with ' ' + */ +static void field_replace_quote(char *s, const int len) +{ + int i, value_len = len - 2; + + for(i = 1; i < value_len; i++) { + if (s[i] == 0x27) + s[i] = 0x20; + } +} + diff --git a/trunk/audisp/plugins/stats/audisp-stats.conf b/trunk/audisp/plugins/stats/audisp-stats.conf new file mode 100644 index 0000000..1fa622e --- /dev/null +++ b/trunk/audisp/plugins/stats/audisp-stats.conf @@ -0,0 +1,34 @@ + +# +# user space generated messages that do not include "stats" key +# +# defined in libaudit.h +# +user_start +user_end +user_login +user_logout +user_auth + +# +# field name and value type +# each change in this section will cause that the database tables +# will be dropped (all data will be lost) and generated new ones +# +type = string +uid = int +euid = int +suid = int +fsuid = int +gid = int +egid = int +sgid = int +fsgid = int +syscall = int +exe = string +exit = int +acct = string +apath = string +res = string +key = string + diff --git a/trunk/audisp/plugins/stats/stats-config.c b/trunk/audisp/plugins/stats/stats-config.c new file mode 100644 index 0000000..85129a2 --- /dev/null +++ b/trunk/audisp/plugins/stats/stats-config.c @@ -0,0 +1,336 @@ + +/* stats-config.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 + */ + +#include +#include +#include +#include +#include +#include "libaudit.h" +#include "stats-config.h" + +#define STR_STRING "string" +#define STR_NUMBER "int" +#define LENGHT_LINE 128 + + +enum { + LINE_EMPTY = 1, + LINE_COMMENT, + LINE_ERR_MEM, + LINE_ERR_ARGS, + LINE_ERR_ASGN, + LINE_ERR_TYPE, + LINE_VALUE_EVENT, + LINE_VALUE_FIELD +}; + + +static int get_line(FILE *f, char *l, const int limit); + +static int get_value(struct field_pair *fp, char *s); + + +/* + * Configuration init + */ +struct config *config_init(void) +{ + struct config *conf = calloc(1, sizeof(struct config)); + if (!conf) + return NULL; + + conf->earr = eventarr_init(ALLOC_SIZE_EVENTARR); + if (!conf->earr) { + free(conf); + return NULL; + } + + conf->farr = fieldarr_init(ALLOC_SIZE_FIELDARR); + if (!conf->farr) { + eventarr_free(conf->earr); + free(conf); + return NULL; + } + + return conf; +} + +/* + * Read config file and store configuration + */ +int config_read(struct config *conf, FILE *f) +{ + int rc, err, line = 1; + unsigned int i; + char buf[LENGHT_LINE]; + struct field_pair *fp; + + if (!f) { + syslog(LOG_ERR, "Could not read configuration"); + return 1; + } + + while (get_line(f, buf, LENGHT_LINE)) { + err = 0; + fp = calloc(1, sizeof(struct field_pair)); + if (!fp) { + syslog(LOG_ERR, "Out of memory reading configuration"); + return -1; + } + rc = get_value(fp, buf); + switch (rc) { + case LINE_EMPTY: + case LINE_COMMENT: + free(fp); + break; + case LINE_ERR_MEM: + syslog(LOG_ERR, "Out of memory reading configuration"); + err = 1; + break; + case LINE_ERR_ARGS: + syslog(LOG_ERR, "Line %d: Wrong number of arguments " + "or wrong event identifier", line); + err = 1; + break; + case LINE_ERR_ASGN: + syslog(LOG_ERR, "Line %d: '=' expected", line); + err = 1; + break; + case LINE_ERR_TYPE: + syslog(LOG_ERR, "Line %d: Wrong field type", line); + err = 1; + break; + case LINE_VALUE_EVENT: + rc = eventarr_search(conf->earr, fp->type); + if (rc) { + syslog(LOG_ERR, "Line %d: Audit event " + "already declared", line); + err = 1; + } else { + rc = eventarr_insert(conf->earr, fp->type); + if (rc) { + syslog(LOG_ERR, "Out of memory " + "storing event in configuration"); + err = 1; + } else { + free(fp->name); + free(fp); + } + } + break; + case LINE_VALUE_FIELD: + rc = fieldarr_search(conf->farr, fp->name); + if (rc) { + syslog(LOG_ERR, "Line %d: Field already declared", line); + err = 1; + } else { + if (!strcmp(fp->name, "key")) { + if (fp->type != T_STR) { + syslog(LOG_ERR, "Line %d: " + "'key' must be string", line); + err = 1; + } else { + conf->key = 1; + free(fp->name); + free(fp); + } + } else { + rc = fieldarr_insert(conf->farr, fp); + if (rc) { + syslog(LOG_ERR, "Out of memory " + "storing field in configuration"); + err = 1; + } + } + } + break; + default: + syslog(LOG_ERR, "Line %d: Unknown error", line); + break; + } + if (err) { + if (fp) + free(fp->name); + free(fp); + return 1; + } + line++; + } + + /* total strlen of all field names */ + for (i = 0; i < conf->farr->count; i++) + conf->farr->names_len += strlen(conf->farr->data[i]->name); + + return 0; +} + +/* + * Find event type in the event array + */ +int config_search_eventtype(struct eventarr *ea, const int type) +{ + unsigned int i, cnt = ea->count; + + for (i = 0; i < cnt; i++) { + if (ea->data[i] == type) + return 1; + } + + return 0; +} + + +/* + * Free configuration + */ +void config_destroy(struct config *conf) +{ + eventarr_free(conf->earr); + fieldarr_free(conf->farr); + free(conf); +} + +/* + * Read one line from config + */ +static int get_line(FILE *f, char *l, const int limit) +{ + char *ptr; + + if (fgets(l, limit, f)) { + ptr = strchr(l, '\n'); + if (ptr) + *ptr = '\0'; + return 1; + } + + return 0; +} + +/* + * Get field pair + */ +static int get_value(struct field_pair *fp, char *s) +{ + char *ptr; + + ptr = strtok(s, " "); + if (!ptr) + return LINE_EMPTY; + if (ptr[0] == '#') + return LINE_COMMENT; + + fp->name = strdup(ptr); + if (!fp->name) + return LINE_ERR_MEM; + + ptr = strtok(NULL, " "); + if (!ptr) { + /* messages generated in the user + space that do not have 'key' field + TODO: add support for other messages */ + if (!strcmp(fp->name, "user_auth")) { + fp->type = AUDIT_USER_AUTH; + } else if (!strcmp(fp->name, "user_acct")) { + fp->type = AUDIT_USER_ACCT; + } else if (!strcmp(fp->name, "user_mgmt")) { + fp->type = AUDIT_USER_MGMT; + } else if (!strcmp(fp->name, "cred_acq")) { + fp->type = AUDIT_CRED_ACQ; + } else if (!strcmp(fp->name, "cred_disp")) { + fp->type = AUDIT_CRED_DISP; + } else if (!strcmp(fp->name, "user_start")) { + fp->type = AUDIT_USER_START; + } else if (!strcmp(fp->name, "user_end")) { + fp->type = AUDIT_USER_END; + } else if (!strcmp(fp->name, "user_avc")) { + fp->type = AUDIT_USER_AVC; + } else if (!strcmp(fp->name, "user_chauthtok")) { + fp->type = AUDIT_USER_CHAUTHTOK; + } else if (!strcmp(fp->name, "user_err")) { + fp->type = AUDIT_USER_ERR; + } else if (!strcmp(fp->name, "cred_refr")) { + fp->type = AUDIT_CRED_REFR; + } else if (!strcmp(fp->name, "usys_config")) { + fp->type = AUDIT_USYS_CONFIG; + } else if (!strcmp(fp->name, "user_login")) { + fp->type = AUDIT_USER_LOGIN; + } else if (!strcmp(fp->name, "user_logout")) { + fp->type = AUDIT_USER_LOGOUT; + } else if (!strcmp(fp->name, "add_user")) { + fp->type = AUDIT_ADD_USER; + } else if (!strcmp(fp->name, "del_user")) { + fp->type = AUDIT_DEL_USER; + } else if (!strcmp(fp->name, "add_group")) { + fp->type = AUDIT_ADD_GROUP; + } else if (!strcmp(fp->name, "del_group")) { + fp->type = AUDIT_DEL_GROUP; + } else if (!strcmp(fp->name, "dac_check")) { + fp->type = AUDIT_DAC_CHECK; + } else if (!strcmp(fp->name, "chgrp_id")) { + fp->type = AUDIT_CHGRP_ID; + } else if (!strcmp(fp->name, "test")) { + fp->type = AUDIT_TEST; + } else if (!strcmp(fp->name, "trusted_app")) { + fp->type = AUDIT_TRUSTED_APP; + } else if (!strcmp(fp->name, "user_selinux_err")) { + fp->type = AUDIT_USER_SELINUX_ERR; + } else if (!strcmp(fp->name, "user_cmd")) { + fp->type = AUDIT_USER_CMD; + } else if (!strcmp(fp->name, "user_tty")) { + fp->type = AUDIT_USER_TTY; + } else if (!strcmp(fp->name, "chuser_id")) { + fp->type = AUDIT_CHUSER_ID; + } else if (!strcmp(fp->name, "grp_auth")) { + fp->type = AUDIT_GRP_AUTH; + } else if (!strcmp(fp->name, "system_boot")) { + fp->type = AUDIT_SYSTEM_BOOT; + } else if (!strcmp(fp->name, "system_shutdown")) { + fp->type = AUDIT_SYSTEM_SHUTDOWN; + } else if (!strcmp(fp->name, "system_runlevel")) { + fp->type = AUDIT_SYSTEM_RUNLEVEL; + } + + if (!fp->type) + return LINE_ERR_ARGS; + else + return LINE_VALUE_EVENT; + } + + if (strcmp(ptr, "=")) + return LINE_ERR_ASGN; + + ptr = strtok(NULL, " "); + if (!ptr) + return LINE_ERR_ARGS; + + if (!strcmp(STR_NUMBER, ptr)) + fp->type = T_INT; + else if (!strcmp(STR_STRING, ptr)) + fp->type = T_STR; + else + return LINE_ERR_TYPE; + + return LINE_VALUE_FIELD; +} + diff --git a/trunk/audisp/plugins/stats/stats-config.h b/trunk/audisp/plugins/stats/stats-config.h new file mode 100644 index 0000000..af7a74c --- /dev/null +++ b/trunk/audisp/plugins/stats/stats-config.h @@ -0,0 +1,46 @@ + +/* stats-config.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 + */ + +#ifndef STATS_CONFIG_H +#define STATS_CONFIG_H + +#include +#include "stats-eventarr.h" +#include "stats-fieldarr.h" + +struct config +{ + struct eventarr *earr; /* originating events from user space */ + struct fieldarr *farr; /* interesting fields */ + int key; /* 0 - no key */ +}; + +struct config *config_init(void); + +int config_read(struct config *conf, FILE *f); + +int config_search_eventtype(struct eventarr *earr, const int type); + +void config_destroy(struct config *conf); + +#endif + diff --git a/trunk/audisp/plugins/stats/stats-eventarr.c b/trunk/audisp/plugins/stats/stats-eventarr.c new file mode 100644 index 0000000..a143af0 --- /dev/null +++ b/trunk/audisp/plugins/stats/stats-eventarr.c @@ -0,0 +1,106 @@ + +/* stats-eventarr.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 + */ + +#include +#include "stats-eventarr.h" + +static int eventarr_realloc(struct eventarr *ea); + +/* + * Init event array to store types of audit events to be stored + * - user space event taht cannot have 'key' field + */ +struct eventarr *eventarr_init(const unsigned int size) +{ + struct eventarr *earr = malloc(sizeof(struct eventarr)); + if (!earr) + return NULL; + + earr->size = size; + earr->count = 0; + earr->data = calloc(size, sizeof(int)); + if (!(earr->data)) { + free(earr); + return NULL; + } + + return earr; +} + +/* + * Free the array + */ +void eventarr_free(struct eventarr *ea) +{ + if (!ea) + return; + free(ea->data); + free(ea); +} + +/* + * Insert new event + */ +int eventarr_insert(struct eventarr *ea, int event) +{ + if (ea->count == ea->size) { + if (eventarr_realloc(ea)) + return -1; + } + + ea->data[ea->count] = event; + ea->count++; + + return 0; +} + +/* + * Search event type in the array + */ +int eventarr_search(struct eventarr *ea, const int event) +{ + unsigned int i; + + for (i = 0; i < ea->count; i++) { + if (ea->data[i] == event) + return 1; + } + + return 0; +} + +/* + * Realloc array + */ +static int eventarr_realloc(struct eventarr *ea) +{ + unsigned int new_size = ea->size + ALLOC_SIZE_EVENTARR; + ea->data = realloc(ea->data, new_size * sizeof(int)); + if (!ea->data) { + free(ea); + return -1; + } + ea->size = new_size; + + return 0; +} + diff --git a/trunk/audisp/plugins/stats/stats-eventarr.h b/trunk/audisp/plugins/stats/stats-eventarr.h new file mode 100644 index 0000000..9fe75fd --- /dev/null +++ b/trunk/audisp/plugins/stats/stats-eventarr.h @@ -0,0 +1,44 @@ + +/* stats-eventarr.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 + */ + +#ifndef STATS_EVENTARR_H +#define STATS_EVENTARR_H + +#define ALLOC_SIZE_EVENTARR 8 + +struct eventarr +{ + unsigned int size; + unsigned int count; + int *data; +}; + +struct eventarr *eventarr_init(const unsigned int size); + +void eventarr_free(struct eventarr *ea); + +int eventarr_insert(struct eventarr *ea, int event); + +int eventarr_search(struct eventarr *ea, const int event); + + +#endif diff --git a/trunk/audisp/plugins/stats/stats-fieldarr.c b/trunk/audisp/plugins/stats/stats-fieldarr.c new file mode 100644 index 0000000..723c610 --- /dev/null +++ b/trunk/audisp/plugins/stats/stats-fieldarr.c @@ -0,0 +1,124 @@ + +/* stats-fieldarr.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 + */ + + +#include +#include +#include "stats-fieldarr.h" + +static int fieldarr_realloc(struct fieldarr *fa); + +/* + * Initialize field array + */ +struct fieldarr *fieldarr_init(const unsigned int size) +{ + struct fieldarr *farr = malloc(sizeof(struct fieldarr)); + if (!farr) + return NULL; + + farr->size = size; + farr->count = 0; + farr->names_len = 0; + farr->data = calloc(size, sizeof(struct field_pair *)); + if (!(farr->data)) { + free(farr); + return NULL; + } + + return farr; +} + +/* + * Free array + */ +void fieldarr_free(struct fieldarr *fa) +{ + unsigned int i; + + if (!fa) + return; + if (!(fa->data)) { + free(fa); + return; + } + for (i = 0; i < fa->count; i++) { + free(fa->data[i]->name); + free(fa->data[i]); + } + free(fa->data); + free(fa); +} + +/* + * Insert field pair into to field array + */ +int fieldarr_insert(struct fieldarr *fa, struct field_pair *p) +{ + unsigned int i = fa->count; + unsigned int j; + + if (i == fa->size) { + if (fieldarr_realloc(fa)) + return -1; + } + for (j = 0; j < fa->count; j++) { + if (!strcmp(fa->data[j]->name, p->name)) + return -2; + } + fa->data[i] = p; + fa->count++; + + return 0; +} + +/* + * Search field name + */ +int fieldarr_search(struct fieldarr *fa, const char *s) +{ + unsigned int i; + + for (i = 0; i < fa->count; i++) { + if (!strcmp(fa->data[i]->name, s)) + return 1; + } + + return 0; +} + +/* + * Realloc array + */ +static int fieldarr_realloc(struct fieldarr *fa) +{ + unsigned int new_size = fa->size + ALLOC_SIZE_FIELDARR; + fa->data = realloc(fa->data, new_size * sizeof(struct field_pair *)); + if (!fa->data) { + free(fa); + return -1; + } + fa->size = new_size; + + return 0; +} + diff --git a/trunk/audisp/plugins/stats/stats-fieldarr.h b/trunk/audisp/plugins/stats/stats-fieldarr.h new file mode 100644 index 0000000..ed4711f --- /dev/null +++ b/trunk/audisp/plugins/stats/stats-fieldarr.h @@ -0,0 +1,56 @@ + +/* stats-fieldarr.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 + */ + +#ifndef STATS_FIELARR_H +#define STATS_FIELDARR_H + +#define ALLOC_SIZE_FIELDARR 8 + +enum value_type +{ + T_INT = 1, + T_STR +}; + +struct field_pair +{ + char *name; + int type; +}; + +struct fieldarr +{ + unsigned int size; + unsigned int count; + unsigned int names_len; + struct field_pair **data; +}; + +struct fieldarr *fieldarr_init(const unsigned int size); + +int fieldarr_insert(struct fieldarr *fa, struct field_pair *p); + +int fieldarr_search(struct fieldarr *fa, const char *s); + +void fieldarr_free(struct fieldarr *fa); + +#endif diff --git a/trunk/audisp/plugins/stats/stats-fieldtab.c b/trunk/audisp/plugins/stats/stats-fieldtab.c new file mode 100644 index 0000000..1b7b731 --- /dev/null +++ b/trunk/audisp/plugins/stats/stats-fieldtab.c @@ -0,0 +1,163 @@ + +/* stats-fieldtab.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 + */ + +#include +#include +#include "stats-fieldtab.h" + + +static unsigned int fieldtab_hash(const char *s, unsigned int n); + + +/* + * Init hash table for storing fields + */ +struct fieldtab *fieldtab_init(const unsigned int size) +{ + struct fieldtab *ft = malloc(sizeof(struct fieldtab)); + if(!ft) + return NULL; + + ft->size = size; + ft->data = calloc(size, sizeof(struct field_item *)); + if(!(ft->data)) { + free(ft); + return NULL; + } + + return ft; +} + +/* + * Free the hash table + */ +void fieldtab_destroy(struct fieldtab *ft) +{ + fieldtab_clear(ft); + free(ft->data); + free(ft); +} + +/* + * Insert new field value + * A field name can contain more than value ('key' field) - these values are + * stored in a list + */ +int fieldtab_insert(struct fieldtab *ft, char *name, char *value) +{ + int found = 0; + /*unsigned int val_count; */ + struct field_item *p; + struct field_value *v; + unsigned int k = fieldtab_hash(name, ft->size); + + for (p = ft->data[k]; p != NULL; p = p->next) { + if (!strcmp(p->id, name)) { + free(name); + found = 1; + break; + } + } + v = malloc(sizeof(struct field_value)); + if (!v) + return -1; + + v->value = value; + if (!found) { + p = malloc(sizeof(struct field_item)); + if (!p) { + free(v); + return -1; + } + p->id = name; + p->fval_list.val_count = 0; + p->fval_list.first_val = NULL; + p->fval_list.which = NULL; + p->next = ft->data[k]; + ft->data[k] = p; + } + v->next_val = p->fval_list.first_val; + p->fval_list.first_val = v; + p->fval_list.which = v; + ++p->fval_list.val_count; + + return 0; +} + +/* + * Search a field name a return a value list + */ +int fieldtab_search(struct fieldtab *ft, struct field_value_list **l, + const char *s) +{ + struct field_item *p; + unsigned int k = fieldtab_hash(s, ft->size); + + for (p = ft->data[k]; p != NULL; p = p->next) { + if (!strcmp(p->id, s)) { + *l = &(p->fval_list); + return 1; + } + } + + return 0; +} + +/* + * Clear the table + */ +void fieldtab_clear(struct fieldtab *ft) +{ + unsigned int i; + struct field_item *p; + struct field_value *v; + + for (i = 0; i < ft->size; i++) { + while (ft->data[i]) { + p = ft->data[i]; + while(p->fval_list.first_val) { + v = p->fval_list.first_val; + p->fval_list.first_val = v->next_val; + free(v->value); + free(v); + } + ft->data[i] = p->next; + free(p->id); + free(p); + } + } +} + +/* + * Hash function + */ +static unsigned int fieldtab_hash(const char *s, unsigned int n) +{ + unsigned int h = 0; + unsigned char *p; + + for(p = (unsigned char *)s; *p != '\0'; p++) + h = 31 * h + *p; + + return h % n; +} + diff --git a/trunk/audisp/plugins/stats/stats-fieldtab.h b/trunk/audisp/plugins/stats/stats-fieldtab.h new file mode 100644 index 0000000..bc14cf8 --- /dev/null +++ b/trunk/audisp/plugins/stats/stats-fieldtab.h @@ -0,0 +1,69 @@ + +/* stats-fieldtab.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 + */ + +#ifndef STATS_FIELDTAB_H +#define STATS_FIELDTAB_H + +struct field_value +{ + char *value; + struct field_value *next_val; +}; + +struct field_value_list +{ + unsigned int val_count; + /* if there are multiple values per one field, + the pointer selects one of the values to be used when + reaction action is triggered. */ + struct field_value *which; + struct field_value *first_val; +}; + +struct field_item +{ + char *id; /* field name */ + struct field_value_list fval_list; /* values */ + struct field_item *next; +}; + +struct fieldtab +{ + unsigned int size; + unsigned int time; + struct field_item **data; +}; + + +struct fieldtab *fieldtab_init(const unsigned int size); + +void fieldtab_destroy(struct fieldtab *ft); + +int fieldtab_insert(struct fieldtab *ft, char *name, char *value); + +int fieldtab_search(struct fieldtab *ft, struct field_value_list **l, + const char *s); + +void fieldtab_clear(struct fieldtab *ft); + +#endif + diff --git a/trunk/audisp/plugins/stats/stats-sql.c b/trunk/audisp/plugins/stats/stats-sql.c new file mode 100644 index 0000000..ea0f9ca --- /dev/null +++ b/trunk/audisp/plugins/stats/stats-sql.c @@ -0,0 +1,672 @@ + +/* stats-sql.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 + */ + + +#include +#include +#include +#include "stats-sql.h" + +#define STR_STRING "string" +#define STR_NUMBER "int" + +#define STRLEN_AND 5 +#define STRLEN_ISNULL 8 +#define STRLEN_NULL 4 +#define STRLEN_TYPE 8 +#define STR_AND " AND " +#define STR_NULL "NULL" +#define STR_ISNULL " IS NULL" +#define STR_INTEGER " INTEGER" +#define STR_VARCHAR " VARCHAR" +#define SQL_NULL_KEY "''" +#define SQL_TRANSACTION_BEGIN "BEGIN TRANSACTION" +#define SQL_TRANSACTION_COMMIT "COMMIT" +#define SQL_TRANSACTION_ROLLBACK "ROLLBACK" + + +/* + * this is used to associate the field values + * in field table with field names in configuration + */ +struct valuearr { + unsigned int value_count; + unsigned int key_count; + unsigned int values_len; + unsigned int keys_len; + unsigned int values_unset; + char **value; + char **key; +}; + + +static int sql_exec(sqlite3 *conn, const char *q); + +static int sql_check_fieldtab(sqlite3 *conn, const char *q); + +static char *sql_query_insert_key(const char *id, const char *key); + +static char *sql_query_create_fieldtab(const char *qstart, struct fieldarr *fa); + +static char *sql_query_insert_search(struct fieldarr *fa, struct valuearr *va); + +static char *sql_query_insert_field(struct fieldarr *fa, struct valuearr *va); + +static char *sql_query_insert_time(const char *id, const char *t); + + +static struct valuearr *valuearr_init(const unsigned int size); + +static int valuearr_insert(struct valuearr *va, struct config *c, + struct fieldtab *ft); + +static void valuearr_free(struct valuearr *va); + + +/* + * Open database + */ +int sql_open(sqlite3 **conn, const char *db) +{ + if (sqlite3_open(db, conn)) + return -1; + + return 0; +} + +/* + * Close database + */ +int sql_close(sqlite3 *conn) +{ + if (sqlite3_close(conn)) + return -1; + + return 0; +} + +/* + * Create database tables if do not exist + * If configuration is changed, tables are dropped and new + * table are created + */ +int sql_init(sqlite3 *conn, struct config *conf) +{ + int rc; + char *query; + const char *qstart1 = "CREATE TABLE field (" + "pk INTEGER PRIMARY KEY"; + const char *qstart2 = "CREATE TABLE IF NOT EXISTS field (" + "pk INTEGER PRIMARY KEY"; + + /* turn on foreign key support */ + query = "PRAGMA foreign_keys = ON"; + rc = sql_exec(conn, query); + if (rc) + return rc; + + /* create field table */ + query = sql_query_create_fieldtab(qstart1, conf->farr); + /* If configuration of what fields should be stored is changed, + all table are dropped!!! + */ + rc = sql_check_fieldtab(conn, query); + if (rc < 0) { + free(query); + return -1; + } else if (rc > 0) { + rc = sql_exec(conn, query); + free(query); + if (rc) + return rc; + } else { + free(query); + } + + query = sql_query_create_fieldtab(qstart2, conf->farr); + rc = sql_exec(conn, query); + free(query); + if (rc) + return rc; + + /* create keyfield table */ + query = "CREATE TABLE IF NOT EXISTS keyfield (" + "fk INTEGER NOT NULL," + "key VARCHAR," + "FOREIGN KEY (fk) REFERENCES field(pk))"; + rc = sql_exec(conn, query); + if (rc) + return rc; + + /* create timestamp table */ + query = "CREATE TABLE IF NOT EXISTS timestamp (" + "fk INTEGER NOT NULL," + "time DATE," + "FOREIGN KEY (fk) REFERENCES field(pk))"; + rc = sql_exec(conn, query); + if (rc) + return rc; + + return 0; +} + +/* + * Insert new data into the database + */ +int sql_insert(sqlite3 *conn, struct config *conf, struct fieldtab *ft, + unsigned int time) +{ + unsigned int i; + int rc, insert = 0; + char *query; + char str_row_id[STRLEN_INT]; + char str_time[STRLEN_INT]; + sqlite3_stmt *stmt; + struct valuearr *varr; + + + varr = valuearr_init(conf->farr->count); + if (!varr) + return -1; + + rc = valuearr_insert(varr, conf, ft); + if (rc) { + valuearr_free(varr); + return -1; + } + + /* find out if the data is saved */ + query = sql_query_insert_search(conf->farr, varr); + rc = sqlite3_prepare(conn, query, -1, &stmt, NULL); + free(query); + if (rc) { + valuearr_free(varr); + return -1; + } + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) + sprintf(str_row_id, "%d", (unsigned int)sqlite3_column_int(stmt, 0)); + else + insert = 1; + + rc = (sqlite3_finalize(stmt)); + if (rc) { + valuearr_free(varr); + return -1; + } + + /* insertion to the tables is a part of a transaction */ + rc = sql_exec(conn, SQL_TRANSACTION_BEGIN); + if (rc) { + valuearr_free(varr); + return rc; + } + + if (insert) { + query = sql_query_insert_field(conf->farr, varr); + rc = sql_exec(conn, query); + free(query); + if (rc){ + valuearr_free(varr); + sql_exec(conn, SQL_TRANSACTION_ROLLBACK); + return rc; + } + sprintf(str_row_id, "%d", + (unsigned int)sqlite3_last_insert_rowid(conn)); + /* insert keys */ + if (conf->key) { + for (i = 0; i < varr->key_count; i++) { + query = sql_query_insert_key(str_row_id, + varr->key[i]); + rc = sql_exec(conn, query); + free(query); + if (rc) { + valuearr_free(varr); + sql_exec(conn, SQL_TRANSACTION_ROLLBACK); + return rc; + } + } + if (!varr->key_count) { + query = sql_query_insert_key(str_row_id, SQL_NULL_KEY); + rc = sql_exec(conn, query); + free(query); + if (rc) { + valuearr_free(varr); + sql_exec(conn, SQL_TRANSACTION_ROLLBACK); + return rc; + } + } + } + } + valuearr_free(varr); + sprintf(str_time, "%d", time); + + query = sql_query_insert_time(str_row_id, str_time); + rc = sql_exec(conn, query); + free(query); + if (rc) { + sql_exec(conn, SQL_TRANSACTION_ROLLBACK); + return rc; + } + rc = sql_exec(conn, SQL_TRANSACTION_COMMIT); + if (rc) + return rc; + + return 0; +} + +/* + * Get statistics from database, this function can be called + * in audisp-reactive + */ +int sql_get_stats(const char *cond, unsigned int start, + unsigned int stop) +{ + const char *qstart = "SELECT COUNT(*) FROM " + "(SELECT DISTINCT pk FROM field LEFT JOIN " + "keyfield ON field.pk=keyfield.fk WHERE "; + const char *qcond = ") AS data JOIN timestamp " + "ON data.pk=timestamp.fk " + "WHERE time>="; + const char *qtime = " AND time<"; + int rc, len, result; + sqlite3 *conn; + sqlite3_stmt *stmt; + char *query; + char str_start[STRLEN_INT]; + char str_stop[STRLEN_INT]; + + sprintf(str_start, "%d", start); + sprintf(str_stop, "%d", stop); + + len = strlen(qstart) + strlen(qcond) + strlen(qtime) + 1; + len += strlen(cond) + strlen(str_start) + strlen(str_stop); + + query = malloc(len * sizeof(char)); + if (!query) + return -1; + strcpy(query, qstart); + strcat(query, cond); + strcat(query, qcond); + strcat(query, str_start); + strcat(query, qtime); + strcat(query, str_stop); + + rc = sqlite3_open(DB_FILE, &conn); + if (rc) { + free(query); + return -1; + } + /* TODO: */ + rc = sqlite3_busy_timeout(conn, 50); + if (rc) { + sqlite3_close(conn); + return -1; + } + + rc = sqlite3_prepare(conn, query, -1, &stmt, NULL); + free(query); + if (rc) { + sqlite3_close(conn); + return -1; + } + + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) + result = sqlite3_column_int(stmt, 0); + else + result = 0; + + if (sqlite3_finalize(stmt)) { + sqlite3_close(conn); + return -1; + } + + sqlite3_close(conn); + + return result; +} + +/* + * Execute SQL query + */ +static int sql_exec(sqlite3 *conn, const char *q) +{ + sqlite3_stmt *stmt; + + if (!q || !conn) + return -2; + + if (sqlite3_prepare(conn, q, -1, &stmt, NULL)) + return -1; + + sqlite3_step(stmt); + if (sqlite3_finalize(stmt)) + return -1; + + return 0; +} + + +/* + * Check field table according to the configuration + * If changed - drop tables + */ +static int sql_check_fieldtab(sqlite3 *conn, const char *q) +{ + sqlite3_stmt *stmt; + int rc, drop = 0; + const char *query = "SELECT sql FROM sqlite_master WHERE name='field'"; + + rc = sqlite3_prepare(conn, query, -1, &stmt, NULL); + if (rc) + return -1; + + rc = sqlite3_step(stmt); + + if (rc == SQLITE_ROW) { + if (strcmp(q, (char *)sqlite3_column_text(stmt, 0))) + drop = 1; + } + + rc = sqlite3_finalize(stmt); + if (rc) + return -1; + + if (drop) { + rc = sql_exec(conn, SQL_TRANSACTION_BEGIN); + if (rc) + return -1; + rc = sql_exec(conn, "DROP TABLE IF EXISTS keyfield"); + if (rc) { + sql_exec(conn, SQL_TRANSACTION_ROLLBACK); + return -1; + } + rc = sql_exec(conn, "DROP TABLE IF EXISTS timestamp"); + if (rc) { + sql_exec(conn, SQL_TRANSACTION_ROLLBACK); + return -1; + } + rc = sql_exec(conn, "DROP TABLE IF EXISTS field"); + if (rc) { + sql_exec(conn, SQL_TRANSACTION_ROLLBACK); + return -1; + } + rc = sql_exec(conn, SQL_TRANSACTION_COMMIT); + if (rc) + return -1; + } + + return drop; +} + +/* + * Get query for creating fieldtab + */ +static char *sql_query_create_fieldtab(const char *qstart, struct fieldarr *fa) +{ + const char *qstop = ")"; + + unsigned int i; + char *result; + int len = 0; + + len = strlen(qstart) + 2; /* )\0 */ + len += fa->names_len; /* field names*/ + len += fa->count; /* ',' */ + len += fa->count * STRLEN_TYPE; /* INTEGER/VARCHAR */ + result = malloc(len * sizeof(char)); + if (!result) + return NULL; + + strcpy(result, qstart); + for (i = 0; i < fa->count; i++) { + strcat(result, ","); + strcat(result, fa->data[i]->name); + if (fa->data[i]->type == T_INT) + strcat(result, STR_INTEGER); + else if (fa->data[i]->type == T_STR) + strcat(result, STR_VARCHAR); + } + strcat(result, qstop); + + return result; +} + +/* + * Search if data is present before insert + */ +static char *sql_query_insert_search(struct fieldarr *fa, struct valuearr *va) +{ + const char *qstart = "SELECT pk FROM field WHERE "; + const char *qkey = " AND (SELECT COUNT(*) FROM keyfield WHERE fk=pk AND " + "key IN ("; + const char *qstop = "))="; + unsigned int i, len; + char str_count[STRLEN_INT]; + unsigned int field_cnt = fa->count; + unsigned int key_cnt = va->key_count; + char *result; + + if (key_cnt == 0) + strcpy(str_count, "1"); + else + sprintf(str_count, "%d", va->key_count); + + len = strlen(qkey) + strlen(qstop) + strlen(qstart) + strlen(str_count) + 1; + len += field_cnt - va->values_unset; /* '=' */ + len += va->values_unset * STRLEN_ISNULL; /* ' IS NULL ' */ + len += (field_cnt - 1) * STRLEN_AND; /* ' AND ' */ + len += key_cnt; /* ',' */ + len += fa->names_len + va->values_len; + if (va->key_count) + len += va->keys_len; + else + len += 2; + + result = malloc(len * sizeof(char)); + if (!result) + return NULL; + + strcpy(result, qstart); + for (i = 0; i < field_cnt; i++) { + strcat(result, fa->data[i]->name); + if (!va->value[i]) { + strcat(result, STR_ISNULL); + } else { + strcat(result, "="); + strcat(result, va->value[i]); + } + if (i < field_cnt - 1) + strcat(result, STR_AND); + } + strcat(result, qkey); + for (i = 0; i < key_cnt; i++) { + strcat(result, va->key[i]); + if (i < key_cnt - 1) + strcat(result, ","); + } + if (!key_cnt) + strcat(result, "''"); + strcat(result, qstop); + strcat(result, str_count); + + return result; +} + +/* + * Insert key field value + */ +static char *sql_query_insert_key(const char *id, const char *key) +{ + const char *qstart = "INSERT INTO keyfield VALUES("; + const char *qstop = ")"; + int len; + char *result; + + len = strlen(qstart) + strlen(id); + len += strlen(key); + len += 3; /* ',' )\0 */ + + result = malloc(len * sizeof(char)); + if (!result) + return NULL; + + strcpy(result, qstart); + strcat(result, id); + strcat(result, ","); + strcat(result, key); + strcat(result, qstop); + + return result; +} + +/* + * Insert new fields + */ +static char *sql_query_insert_field(struct fieldarr *fa, struct valuearr *va) +{ + const char *qstart = "INSERT INTO field VALUES(NULL"; + unsigned int i, len; + unsigned int field_cnt = fa->count; + char *result; + + len = strlen(qstart) + 2; /* ')\0' */ + len += field_cnt; /* ',' */ + len += va->values_len; + len += va->values_unset * STRLEN_NULL; + + result = malloc(len * sizeof(char)); + if (!result) + return NULL; + + strcpy(result, qstart); + for (i = 0; i < field_cnt; i++) { + strcat(result, ","); + if (!va->value[i]) + strcat(result, STR_NULL); + else + strcat(result, va->value[i]); + } + strcat(result, ")"); + + return result; +} + +/* + * Insert timestamp + */ +static char *sql_query_insert_time(const char *id, const char *t) +{ + const char *qstart = "INSERT INTO timestamp VALUES("; + const char *qstop = ")"; + unsigned int len; + char *result; + + + len = strlen(qstart) + 3; /* )\0 ',' */ + len += strlen(id) + strlen(t); + + result = malloc(len * sizeof(char)); + if (!result) + return NULL; + strcpy(result, qstart); + strcat(result, id); + strcat(result, ","); + strcat(result, t); + strcat(result, qstop); + + return result; +} + +/* + * Value array init + */ +static struct valuearr *valuearr_init(const unsigned int size) +{ + struct valuearr *result = calloc(1, sizeof(struct valuearr)); + + if (!result) + return NULL; + + result->value = calloc(size, sizeof(char *)); + if (!result->value) { + free(result); + return NULL; + } + result->value_count = size; + + return result; +} + +/* + * Insert field value pointers and key pointers + */ +static int valuearr_insert(struct valuearr *va, struct config *c, + struct fieldtab *ft) +{ + unsigned int i, rc; + struct field_value_list *list; + struct fieldarr *fa = c->farr; + struct field_value *key_val; + + for (i = 0; i < va->value_count; i++) { + rc = fieldtab_search(ft, &list, fa->data[i]->name); + if (rc) { + va->value[i] = list->which->value; + va->values_len += strlen(va->value[i]); + } else { + ++va->values_unset; + } + } + + if (c->key) { + rc = fieldtab_search(ft, &list, "key"); + if (rc) { + va->key_count = list->val_count; + va->key = calloc(va->key_count, sizeof(char *)); + if (!va->key) + return 1; + key_val = list->first_val; + i = 0; + while (key_val && (i < va->key_count)) { + va->key[i] = key_val->value; + va->keys_len += strlen(va->key[i]); + key_val = key_val->next_val; + i++; + } + } + } + + return 0; +} + +/* + * Free + */ +static void valuearr_free(struct valuearr *va) +{ + free(va->value); + free(va->key); + free(va); +} + diff --git a/trunk/audisp/plugins/stats/stats-sql.h b/trunk/audisp/plugins/stats/stats-sql.h new file mode 100644 index 0000000..1bbdc38 --- /dev/null +++ b/trunk/audisp/plugins/stats/stats-sql.h @@ -0,0 +1,45 @@ + +/* stats-sql.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 + */ + +#ifndef STATS_SQL_H +#define STATS_SQL_H + +#include +#include +#include "stats-config.h" +#include "stats-fieldtab.h" + +#define DB_FILE "/var/lib/audisp-stats/stats.db" + +#define STRLEN_INT ((CHAR_BIT * sizeof(int) + 2) / 3 + 1) + + +int sql_open(sqlite3 **conn, const char *db); + +int sql_close(sqlite3 *conn); + +int sql_init(sqlite3 *conn, struct config *conf); + +int sql_insert(sqlite3 *conn, struct config *conf, struct fieldtab *ft, + unsigned int time); + +#endif diff --git a/trunk/configure.ac b/trunk/configure.ac index 6567119..3bcbe4b 100644 --- a/trunk/configure.ac +++ b/trunk/configure.ac @@ -38,6 +38,8 @@ echo Configuring auditd $VERSION AC_CONFIG_MACRO_DIR([m4]) AC_CANONICAL_TARGET AM_INIT_AUTOMAKE +AM_PROG_LEX +AC_PROG_YACC AM_PROG_LIBTOOL AC_SUBST(LIBTOOL_DEPS) AM_PATH_PYTHON @@ -231,7 +233,7 @@ AC_SUBST(libev_LIBS) AC_SUBST(LIBPRELUDE_CFLAGS) AC_SUBST(LIBPRELUDE_LDFLAGS) -AC_OUTPUT(Makefile lib/Makefile lib/test/Makefile auparse/Makefile auparse/test/Makefile src/Makefile src/mt/Makefile src/libev/Makefile src/test/Makefile swig/Makefile docs/Makefile init.d/Makefile audisp/Makefile audisp/plugins/Makefile audisp/plugins/builtins/Makefile audisp/plugins/prelude/Makefile audisp/plugins/remote/Makefile audisp/plugins/zos-remote/Makefile bindings/Makefile bindings/python/Makefile tools/Makefile tools/aulast/Makefile tools/aulastlog/Makefile tools/ausyscall/Makefile) +AC_OUTPUT(Makefile lib/Makefile lib/test/Makefile auparse/Makefile auparse/test/Makefile src/Makefile src/mt/Makefile src/libev/Makefile src/test/Makefile swig/Makefile docs/Makefile init.d/Makefile audisp/Makefile audisp/plugins/Makefile audisp/plugins/builtins/Makefile audisp/plugins/prelude/Makefile audisp/plugins/remote/Makefile audisp/plugins/zos-remote/Makefile audisp/plugins/reactive/Makefile audisp/plugins/stats/Makefile bindings/Makefile bindings/python/Makefile tools/Makefile tools/aulast/Makefile tools/aulastlog/Makefile tools/ausyscall/Makefile) echo . echo " -- 1.6.6.1